fix(usage-limits): scope the Antigravity process scan to the current user - #138
Merged
Conversation
…user `detectAntigravityProcess` ran `/bin/ps -ax`, which walks every account on the box, and attached to whichever Antigravity language server matched first. Tracing what that could expose settles the question #132 left open: the CSRF token and the pid never reach an HTTP response. `processInfo` is read field by field and is never spread into a returned object — the pid goes to `listAntigravityPorts`, the token becomes a request header, and all four return shapes of `fetchAntigravityLimits` carry neither. What the token *fetches* does reach the response. `normalizeAntigravityResponse` returns `account_email` and `account_plan`, `finalize` spreads them into the result, and `getUsageLimits` serves that at `/functions/tokentracker-usage-limits`. On a shared host this displayed another person's email, plan and quota as the local user's own, and `writeAntigravityLimitsCache` persisted the address to disk, where the not-configured branch kept serving it after their process exited. So: not credential exposure, but cross-account PII — on multi-user hosts only, which is why no observed output could have caught it. The scan now uses the PS_BINARY / PS_ARGS already exported by process-list.js rather than its own inline argv. Sharing the constant is the point: #129 fixed the other scan, and two scans that must both stay own-user should not be able to drift apart. The regression test captures the literal argv from an injected commandRunner, because a real `ps` run on a single-user machine returns identical lines either way. Verified as a guard rather than a restatement: restoring `-ax` fails it. Anyone who has run this on a shared host may have another user's account_email cached in ~/.tokentracker/tracker/usage-limits-cache.json. It is a cache; deleting the file is the whole remedy. Closes #132
Two gaps a review found in the previous commit. The `-x` scoping was only ever demonstrated on macOS, where `ps` is BSD and `-x` plainly means "own user, tty restriction lifted". Linux `ps` is procps and parses dash-prefixed options as UNIX-style, where `-x` is not an option — and `isProcessListSupported` returns true for every platform except win32, so Linux runs this argv. Had procps rejected it, `listProcessLines` would have returned `process_list_failed` on every Linux host: a permanent non-advisory warn, pinning `degraded` for a whole platform, which is the failure #136 exists to remove. Checked on a real Debian 12 / procps-ng 4.0.2 host rather than reasoned from the manual: `ps -x -o pid=,command=` exits 0 and reports one user across 22 lines, while `-ax` on the same box reports seven users across 39. procps accepts it as the BSD `x`, so the argv is correct on both supported platforms and #129's shipped code is correct too. Recorded in the comment so nobody has to re-derive it from a manpage. The array is now frozen. Two modules share it, and the claim written in the previous commit — that the two scans "cannot drift apart" — was only true of editorial drift. Importing one constant does not stop `PS_ARGS.push("-a")`; freezing does, and the test asserts the mutation throws rather than asserting the flag alone. Refs #132
This was referenced Aug 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Goal (one sentence)
Scope the Antigravity
psscan to the current user, and settle the severity question #132 left open.Why
detectAntigravityProcessran/bin/ps -ax— every account on the box — and attached to whichever Antigravity language server matched first. #129 had already narrowed the otherpscall site and deliberately left this one alone, because nothing in that PR exercised this path.Step 1 of the issue: what can actually escape
The CSRF token and the pid never reach an HTTP response.
processInfois read field by field and is never spread into a returned object:pid→listAntigravityPorts(usage-limits.js:1815),csrfToken→ theX-Codeium-Csrf-Tokenrequest header (:1616),extensionPort→ fallback port choice (:1840). All four return shapes offetchAntigravityLimits(:1795-1863) carry neither, and every error string on that path is a literal — the token travels as a header, so no URL an error could echo contains it.What the token fetches does reach the response.
normalizeAntigravityResponse(:1763-1775) returnsaccount_emailandaccount_plan;finalize(:1804-1812) spreads them into the result;getUsageLimits(:1976) collects it;GET /functions/tokentracker-usage-limitsserves it. AndwriteAntigravityLimitsCache(:1470-1483) writesaccount_emailto~/.tokentracker/tracker/usage-limits-cache.json, where the not-configured branch (:1798) keeps serving it after the other user's process exits.Severity: not credential exposure — cross-account PII display and disk persistence, on multi-user hosts only. Single-user machines were never affected, which is exactly why observed output could not have caught this.
Scope
src/)dashboard/)TokenTrackerBar/)TokenTrackerWin/)Checklist
npm run ci:localpasses (exit 0, 1007 CLI tests, 41 dashboard test files, all validators, OpenWiki 0 findings)copy.csv— N/A, no user-facing stringsWhat changed
detectAntigravityProcessnow uses thePS_BINARY/PS_ARGSalready exported bysrc/lib/process-list.js(-x, own user) instead of its own inline["-ax", ...]. Sharing the constant is the point rather than a convenience: #129 fixed one of these two scans, and two scans that must both stay own-user should not be able to drift apart independently.The comment above the function records the trace above, so the next reader does not have to redo it to know whether the token escapes.
Codex Context
-ain the argv, ever; bothpscall sites read the same constant.commandRunner; the existingdetectAntigravityProcessparse test still passes unchanged, so the behaviour on matching output is untouched.node --test test/usage-limits.test.js→ 46/46.npm run ci:local→ exit 0, 1007 CLI tests.pscall sites exist today, perrg 'PS_BINARY|/bin/ps' src/. (Note the narrowerrg '/bin/ps' src/is no longer sufficient: after this changeusage-limits.jsimports the constant instead of spelling the path, so that grep misses the very call site this PR touches, and would miss any future caller too.) The cache artifact an affected host may already hold is usage-limits: a cache written before #138 may still hold another account's email #139, deliberately not folded in here.Risk Layer Trigger (if any)
Risk Layer Addendum
Rules / Invariants
pscall site may be invoked with an all-users flag. Both now readPS_ARGSfrom one module, so there is a single place to get this wrong./functions/tokentracker-usage-limitsanswers unauthenticated loopback GETs, so anything the fetch obtains is effectively served; the scope of the scan is therefore the control, not the shape of the response.Boundary Matrix (must list at least 3)
PS_ARGS=["-x", "-o", "pid=,command="];test/usage-limits.test.js"scans only the current user's processes" asserts the literal argv and the absence of-a--csrf_tokenand--extension_server_portprocessInfois never spread into a return value at any of the fourfetchAntigravityLimitsexitsaccount_email,account_plan, quota windowspscall sites → each otherPS_ARGSfromsrc/lib/process-list.js;test/process-list.test.jspins the same valueEvidence (tests or repro)
-axinprocess-list.jsmakes it fail, restoring-xmakes it pass. A livepsrun on a single-user machine is identical either way, so nothing observational could substitute.detects antigravity process info from ps outputtest passes unchanged — the injected runner ignores argv, so parsing behaviour is provably untouched by this change.-xchecked on Linux, not assumed. macOSpsis BSD, where-xplainly means "own user, tty restriction lifted". Linuxpsis procps and parses dash-prefixed options as UNIX-style, where-xis not an option — andisProcessListSupportedreturns true for every platform but win32, so Linux runs this argv. On a real Debian 12 / procps-ng 4.0.2 host:ps -x -o pid=,command=exits 0 and reports one user across 22 lines, while-axon the same box reports seven users across 39. procps accepts it as the BSDx. Had it not, every Linux host would have fallen intoprocess_list_failed— a permanent non-advisory warn, pinningdegradedfor a whole platform, which is the failure fix(doctor): count only non-advisory warns towarddegraded#136 was written to remove. This also confirms feat(doctor): notice when Claude usage cannot be recorded at all #129's already-merged argv.PS_ARGSis frozen. The claim that the two scans "cannot drift apart" was true of editorial drift only; sharing an unfrozen array does not stopPS_ARGS.push("-a")at runtime. The test asserts the mutation throws, not merely that the value is currently right.Public Exposure Checklist
Regression Test Gate
Most likely regression surface
Antigravity quota detection itself: a narrower scan finds fewer processes, so a legitimate setup could silently become "not configured".
Verification method (choose at least one)
node --test test/usage-limits.test.js→ 46/46, including the pre-existing detection and cache-persistence testsnpm run ci:local→ exit 0Uncovered scope
-xvs-axis now demonstrated on a real Linux host (see Evidence), but on a host whose second "users" are service accounts rather than a second human running Antigravity. The argv semantics are what that check establishes; an end-to-end cross-account fetch has not been staged.account_emailin~/.tokentracker/tracker/usage-limits-cache.json, and the not-configured branch will serve it precisely because detection now correctly finds nothing. This PR does not purge it — tracked as usage-limits: a cache written before #138 may still hold another account's email #139, which weighs the three remediation options rather than picking one here.Closes #132.