From 9d843c12b90150f3dec34c354ae5f7818f53a815 Mon Sep 17 00:00:00 2001 From: "itarun.p" Date: Wed, 5 Aug 2026 15:56:00 +0700 Subject: [PATCH] fix(usage-limits): invalidate legacy Antigravity cache --- src/lib/usage-limits.js | 6 ++++++ test/usage-limits.test.js | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/lib/usage-limits.js b/src/lib/usage-limits.js index 82b92231..53b8458a 100644 --- a/src/lib/usage-limits.js +++ b/src/lib/usage-limits.js @@ -32,6 +32,10 @@ const runUsageLimitsFetch = createSingleFlight(); const CACHE_TTL_MS = 2 * 60 * 1000; const DEFAULT_PROVIDER_TIMEOUT_MS = 15_000; const ANTIGRAVITY_LIMITS_CACHE_FILE = "usage-limits-cache.json"; +// Trust marker, not a user identity: only cache blocks written after the +// current-user process-scan fix may be served. Missing/unknown markers fail +// closed so a legacy multi-user cache cannot disclose another account. +const ANTIGRAVITY_CACHE_PROCESS_SCOPE = "current-user-v1"; const ANTIGRAVITY_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; const ANTIGRAVITY_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS = 12 * 60 * 60 * 1000; const CLAUDE_LIMITS_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000; @@ -1447,6 +1451,7 @@ function hasAntigravityWindow(limits) { } function normalizeAntigravityCachedLimits(raw, { nowMs = Date.now() } = {}) { + if (raw?.process_scope !== ANTIGRAVITY_CACHE_PROCESS_SCOPE) return null; const cachedAtMs = parseTimeMs(raw?.cached_at); if (!Number.isFinite(cachedAtMs)) return null; if (cachedAtMs > nowMs + 60_000) return null; @@ -1475,6 +1480,7 @@ function writeAntigravityLimitsCache(limits, { home, nowMs = Date.now() } = {}) if (!limits?.configured || limits.error || !hasAntigravityWindow(limits)) return; const payload = { antigravity: { + process_scope: ANTIGRAVITY_CACHE_PROCESS_SCOPE, account_email: limits.account_email || null, account_plan: limits.account_plan || null, primary_window: limits.primary_window || null, diff --git a/test/usage-limits.test.js b/test/usage-limits.test.js index 76b5dadf..6d3de9d4 100644 --- a/test/usage-limits.test.js +++ b/test/usage-limits.test.js @@ -1352,6 +1352,7 @@ lang 123 me 22u IPv4 0x123 0t0 TCP 127.0.0.1:51234 (LISTEN) const cachedPath = path.join(tmp, ".tokentracker", "tracker", "usage-limits-cache.json"); const cached = JSON.parse(fs.readFileSync(cachedPath, "utf8")); + assert.equal(cached.antigravity.process_scope, "current-user-v1"); assert.equal(cached.antigravity.primary_window.used_percent, 75); assert.equal(cached.antigravity.cached_at, "2026-05-21T00:00:00.000Z"); } finally { @@ -1368,6 +1369,7 @@ lang 123 me 22u IPv4 0x123 0t0 TCP 127.0.0.1:51234 (LISTEN) path.join(trackerDir, "usage-limits-cache.json"), JSON.stringify({ antigravity: { + process_scope: "current-user-v1", primary_window: { used_percent: 42, reset_at: "2026-05-22T00:00:00.000Z", @@ -1393,6 +1395,43 @@ lang 123 me 22u IPv4 0x123 0t0 TCP 127.0.0.1:51234 (LISTEN) } }); + it("does not serve Antigravity cache with missing or unknown current-user provenance", async () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "tokentracker-antigravity-cache-legacy-")); + try { + const trackerDir = path.join(tmp, ".tokentracker", "tracker"); + fs.mkdirSync(trackerDir, { recursive: true }); + const commandRunner = () => ({ stdout: "", stderr: "", status: 1 }); + + for (const processScope of [undefined, "current-user-v2"]) { + const antigravity = { + account_email: "another-user@example.com", + account_plan: "Pro", + primary_window: { + used_percent: 42, + reset_at: "2026-05-22T00:00:00.000Z", + }, + cached_at: "2026-05-21T00:00:00.000Z", + }; + if (processScope !== undefined) antigravity.process_scope = processScope; + fs.writeFileSync( + path.join(trackerDir, "usage-limits-cache.json"), + JSON.stringify({ antigravity }), + "utf8", + ); + + const result = await fetchAntigravityLimits({ + home: tmp, + commandRunner, + nowMs: Date.parse("2026-05-21T01:00:00.000Z"), + }); + + assert.deepEqual(result, { configured: false }); + } + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + it("does not use cached Antigravity quota after all cached windows reset", async () => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "tokentracker-antigravity-cache-expired-")); try { @@ -1402,6 +1441,7 @@ lang 123 me 22u IPv4 0x123 0t0 TCP 127.0.0.1:51234 (LISTEN) path.join(trackerDir, "usage-limits-cache.json"), JSON.stringify({ antigravity: { + process_scope: "current-user-v1", primary_window: { used_percent: 42, reset_at: "2026-05-21T00:00:00.000Z",