This document describes Claudemeter's security posture and how to report issues.
Please email security reports to security@hyperi.io. Do not open public
GitHub issues for vulnerabilities. We aim to acknowledge within 72 hours
and to ship a fix or formal mitigation within two weeks for high-severity
issues.
The marketplace artifact dist/extension.js is built from extension.js
via node esbuild.js --production.
Claudemeter has zero runtime dependencies (package.json has no
dependencies key at all). It ships no browser, no Chromium, and no
scraping stack - usage is fetched with the platform fetch from the
first-party api.anthropic.com OAuth endpoints. The only third-party
packages are dev-only (esbuild, @vscode/vsce, eslint, vitest),
which never reach an installed extension.
(For historical context: earlier versions drove a browser to harvest a
claude.ai session cookie - first puppeteer-core (whose
@puppeteer/browsers → proxy-agent → pac-proxy-agent → get-uri → basic-ftp chain was a recurring advisory source, stripped via a
build-time stub), then playwright-core. Both are gone: the switch to
Claude Code's OAuth token removed the browser entirely, and with it the
whole runtime-dependency surface.)
We track every Dependabot / npm-audit advisory and dismiss with a reason rather than letting alerts pile up. Current accepted risks:
Reaches us via @vscode/vsce → @azure/identity → @azure/msal-node → uuid. Status: deferred - tolerable risk.
- Reach: dev-only.
@vscode/vsceis invoked bynpm run packageand the GitHub Actions release pipeline. It never reaches an installed extension at runtime. - Vulnerable code path:
uuid.v3 / v5 / v6with a user-controlledbufargument.@azure/msal-nodecallsuuid.v4()(random generation, no buffer argument), so the vulnerable branch is never reached on our call paths. - Why we can't fix forward:
@azure/msal-nodedeclares"uuid": "^8.3.0"as a hard dependency, so a top-leveloverridesto bump uuid past 8.x would break msal-node's own usage. - Re-evaluation trigger: we re-check whenever
@azure/msal-nodepublishes a new minor that bumps its uuid pin, or whenever@vscode/vsceswitches its auth backend off@azure/identity.
The 4 GitHub Dependabot alerts for this advisory (one per package in
the chain) have been dismissed with reason tolerable_risk and a
comment pointing to this section.
Policy: no advisory stays open without a decision. When Dependabot fires:
- If the audit says
npm audit fixresolves it, ship the lockfile fix and let GitHub auto-close the alert on next scan. - If the audit says
--force(semver-major downgrade or breaking change), open a draft change and weigh the regression risk against the actual reach. Don't accept the downgrade without a written justification in the commit body. - If neither (1) nor (2) applies - the chain is genuinely stuck on
upstream - dismiss the alert with one of the GitHub-provided
reasons (
tolerable_risk,not_used,inaccurate) and a comment that links here, so a future maintainer can find the rationale.
- Claudemeter reads Claude Code's existing OAuth token from the shared
store it writes - the macOS Keychain, or
~/.claude/.credentials.jsonon Linux/Windows. Both are resolved the way Claude Code resolves them, soCLAUDE_CONFIG_DIRmoves the file and scopes the Keychain item to that directory. Claudemeter reads only the item that directory maps to and never searches the Keychain. It reads the token fresh per fetch and never copies, persists, or writes it back - so it cannot disturb Claude Code's own login. - The token is used only as the
Authorization: Bearerheader tohttps://api.anthropic.com. Requests setredirect: 'error'so a Bearer-bearing request is never followed to another host. - Claudemeter never refreshes the token. Claude Code owns the token lifecycle, and Anthropic rotates refresh tokens. Claudemeter only tracks the current value, so it cannot invalidate Claude Code's session.
- Debug logs (rotated under the config dir, max 256 KB by default) and the Dump State report never include the token - only its presence, source, scopes, and expiry. If you find a log that leaks a token, please report per the section above.
The debug log and the Dump State report are built to be pasted into a public GitHub issue. Redaction is automatic. By design they carry:
- No token or credentials - only token presence, source, scopes, expiry.
- No account name and no email - the state dump reports account
presence plus org type (e.g.
Personal), nothing identifying. (src/oauthFetcher.js's CLI smoke-test also redacts name/email.) - No username in paths - every logged line goes through
scrubHome()(src/utils.js), which swaps the home directory for~, so absolute paths never carry the OS username.
What does appear, deliberately: plan/tier strings, usage percentages, token counts, timestamps, and your own project folder names. The gauge is project-scoped, so a folder name is often what pins down a "wrong project" report.
The redaction is automatic, so the output should be safe to post as-is. We do not warrant every environment though - read it before you attach it, and cut anything you would rather not share. On our side it is a maintained invariant. A change that adds identifying data to a log or the state dump is a defect - report it.