deleteOrg.childTest.ts read its leftover counts with db.get(sql...), a better-sqlite3 driver method. The pg config type-checks the same file with the pg driver's types, where .get() does not exist, so build-postgres failed on PR #3895's head (TS2339 at deleteOrg.childTest.ts:108) and main has carried the break since the merge landed 40 seconds before the job reported red. Read the counts through the query builder instead - the same .select({ count: count() }) shape deleteOrg.ts itself uses, which compiles under both configs - keeping the observations JSON the parent test parses unchanged.
Signed-off-by: Manas Choksi <12302040701105@mbit.edu.in>
Signed-off-by: choksi2212 <choksi2212@users.noreply.github.com>
deleteOrgById fetched the org's sites with .limit(1), so the per-site
cleanup loop ran for at most one site: the WireGuard deletePeer call,
the Newt terminate collection, and cleanupSiteAssociations were all
skipped for every other site in the org.
Drop the limit so every site is loaded, and mirror deleteSite.ts in the
loop: run cleanupSiteAssociations for newt sites so clients of other
orgs do not keep stale associations for the deleted sites.
Covered by a standalone regression test that seeds two wireguard and
two newt sites plus a stale association-cache row, runs deleteOrgById
against a stubbed exit node, and asserts all four sites were cleaned.
On the previous code the test fails with exactly the reported symptom:
one peer removal instead of two.
Fixes#3863
A leading '-' only matters when the value is not a plain number, so
-12 and -1.5 stay numeric while payloads like -2+3+cmd|'/c calc'!A0
still get the apostrophe prefix.
generateCSV escaped quotes only when a value happened to contain a comma and never neutralised formula prefixes, so every CSV log export could emit attacker controlled cells that execute when an admin opens the file. actor is a virtual API key name validated as nothing more than a non-empty string, and path, host, originalRequestURL, metadata and the AI session bodies come straight from the client request.
Each value now goes through a single encoder that collapses CR, LF and CRLF to a literal \n so a record can never be split across rows, prefixes a leading apostrophe when the first character would make a spreadsheet evaluate the cell as a formula, and quotes whenever the field contains a comma or a quote instead of only when it contains a comma. Headers use the same encoder so a column name cannot desynchronise the rows from the header row.
The five CSV export endpoints also declare charset=utf-8, matching the content type already used by the log streaming destination.
Fixes#3864
DELETE /v1/org/{orgId} on the Integration API always returned
500 "An error occurred..." and left the organization in place, even for a
root API key holding the deleteOrg action. The handler looked up the
caller's ownership with req.user!.userId, but Integration API requests are
authenticated by verifyApiKey, which sets req.apiKey and never req.user,
so the lookup threw "Cannot read properties of undefined (reading
'userId')".
#1376 was fixed in f37eda47 by dropping the user-only permission check;
79cf7c84 later added the owner check to the handler shared by both
routers, reintroducing the failure for API keys.
Keep the owner check for dashboard sessions. For Integration API keys the
route is already restricted by verifyApiKeyIsRoot and
verifyApiKeyHasAction(deleteOrg), so load the organization directly. Both
paths still refuse a billing organization and return the same 404 for an
unknown organization.
Verified against a local SQLite instance with a root key: before the
change, create returned 201 and delete returned 500 with the organization
still present; after it, delete returns 200 and a repeated delete or GET
returns 404. npx tsc --noEmit passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>