KeyDrift
Free scan
criticalClaude Code·GitHub token

GitHub token in the browser bundle after a Claude Code session

Repository access under the token’s scopes — for a classic PAT, usually every private repo the user can see.

Why Claude Code does this

A CLI agent working across the repo will follow the pattern it already sees. If one component reads a key from `import.meta.env`, the next feature it writes will too — the leak spreads by imitation rather than by a single mistake.

Confirm it first

Before rotating anything, check whether the key is actually being served. Paste your deployed URL — KeyDrift downloads the same JavaScript a visitor gets and tells you what is in it.

No account. Read-only — the scanner only ever issues GET requests, and never stores a key: findings carry a masked prefix and a fingerprint.

Rotate the key

Do this before changing any code. The key has been served to browsers, cached by CDNs and very likely scraped already — removing it from the source does not un-publish it.

  1. 1Revoke the token in Settings → Developer settings → Personal access tokens.
  2. 2Check the account’s security log for pushes, clones and workflow runs you did not make.
  3. 3Re-issue as a fine-grained token scoped to the one repository that needs it.
Open the revocation page

Move the call to a server

The replacement key must not follow the old one into the bundle, which means the code that uses it cannot live in the browser.

Before — shipped to the browser

// src/components/Chat.tsx
// Vite substitutes the literal value here at build time.
const key = import.meta.env.VITE_API_KEY;
const result = await callTheApi(key, body);

After — stays on a route handler or server action

// supabase/functions/proxy/index.ts  — runs on a route handler or server action
Deno.serve(async (request) => {
  const key = Deno.env.get('API_KEY')!; // never sent to the browser
  const result = await callTheApi(key, body);
  return Response.json(result);
});

// src/components/Chat.tsx
const result = await fetch('/functions/v1/proxy', { method: 'POST' }).then((r) => r.json());

How KeyDrift detects it

Matches the `ghp_`, `gho_`, `ghu_`, `ghs_` and `ghr_` prefixes. A classic personal access token usually carries every private repository the user can see.

It will happen again

A CLI agent working across the repo will follow the pattern it already sees. That has not changed because you fixed this one file — the next feature request produces the same shape of code. Continuous monitoring re-scans every deploy and tells you the moment a key comes back.