Pre-submission auditor for Chrome Web Store (Manifest V3) extensions. Static analysis only. No extension code is executed. No telemetry.
See BUILD.md for the full build spec and
ROADMAP.md for scope boundaries.
The CLI is prepared for npm publication. Until a public npm release is cut, run it from a cloned workspace:
git clone https://github.com/morgan-coded/Chrome-Review.git && cd Chrome-Review
npm install && npm run build
npm run cli -- ./path/to/extension
npm run cli -- https://github.com/<owner>/<repo>For local package smoke tests, use npm pack tarballs:
npm run build
PACK_DIR=$(mktemp -d)
npm pack --pack-destination "$PACK_DIR" \
--workspace @chrome-review/types \
--workspace @chrome-review/parser \
--workspace @chrome-review/rules \
--workspace chrome-review
SMOKE_DIR=$(mktemp -d)
cd "$SMOKE_DIR"
npm init -y
npm install \
"$PACK_DIR"/chrome-review-types-0.1.0.tgz \
"$PACK_DIR"/chrome-review-parser-0.1.0.tgz \
"$PACK_DIR"/chrome-review-rules-0.1.0.tgz \
"$PACK_DIR"/chrome-review-0.1.0.tgz
npx chrome-review /absolute/path/to/unpacked-extension
npx chrome-review --json /absolute/path/to/unpacked-extensionAll four tarballs are required for this local tarball install because the CLI imports the internal parser/rules/types packages by package name and the CLI package depends on those workspace packages.
Supported Node versions: 20 LTS or 22 LTS. See
packages/cli/README.md for the full CLI
reference, docs/release.md for the release
runbook, and CHANGELOG.md for what's new in each
version.
- Parses
manifest.jsonand resolves entry points (background / service worker, content scripts, popup HTML + its scripts, options page, side-panel page + its scripts, web-accessible resources where trivially reachable). - Walks ESM static imports, string-literal dynamic
import()calls, and CommonJSrequire()calls. Relative paths are resolved and followed; bare module specifiers ('lodash','@scope/pkg') are recorded as LOW-confidence unresolved (the analyzer does not readnode_modules). - Detects
chrome.*/browser.*API usage via AST walk and records file/line evidence. Supported forms:- direct member chains (
chrome.tabs.query), - scope-local aliases (
const c = chrome; c.tabs.query), - destructured bindings including nested (
const { tabs: { query } } = chrome), - cross-module re-exports (MEDIUM tier — the binding crossed a module boundary),
- escape-scope bindings (top-level
let/var, function-scoped const, block-scoped const — MEDIUM tier because the binding left the provably-immutable top-level-const model).
- direct member chains (
- Permission-to-usage diff with three confidence tiers
(
HIGH/MEDIUM/LOW). The 25 supported permission families span the MV3 surface; partial families (clipboardRead,clipboardWrite) and declarative-only (unlimitedStorage) are labeled honestly.webRequestfindings carry the MV3webRequestBlockingdeprecation note. - Remote-hosted-code detectors cover eight shapes: HTML
<script src>,eval/Function, dynamicimport(literal remote + template remote prefix),fetch→eval/ Worker-blob dataflow,setTimeout/setIntervalwith string arg,document.writewith embedded remote script, and remoteimportScripts/new Worker. HIGH requires a static dataflow link from a network source to an execution sink. - Host-permission overbreadth rule consumes
content_scripts+ DNR rule resources + network URL literals (fetch / XHR.open / WebSocket / EventSource / sendBeacon / importScripts). Tier is data-driven: ≥3 distinct observed hosts under a broad declaration → LOW, ≤2 → MEDIUM.activeTab + scriptingdrops one tier; fully-dynamic URLs andchrome.scripting.executeScriptcall sites produce honest LOW caveats rather than silent drops. - Source-map honesty for minified bundles. When a minified file ships
a valid source map whose sources resolve on disk, every finding's
locis remapped back to the original source via@jridgewell/trace-mapping. When the map is missing / invalid / absent, the file is listed inpartiallyAnalyzedFileswith a reason, and the rules engine caps any finding whose evidence originates there at MEDIUM with a rationale citing the partial- analysis origin. - Prints a legible report to stdout;
--jsonemits a structuredAnalyzerReportvalidated againstpackages/cli/schema/report.schema.json(0.x — unstable).
- Execute extension code.
- Phone home.
- Pretend certainty on bundled / minified code.
- Guarantee Chrome Web Store approval or guarantee rejection prevention.
- Anything outside the analyzer (no accounts, no dashboard, no upload flow, no payment, no email).
Supported Node versions: Node 20 LTS and Node 22 LTS. CI runs both.
git clone <repo> chrome-review && cd chrome-review
npm install
npm run build
npm run lint
npm run format:check
npm testExpected result on HEAD: npm run build, npm run typecheck,
npm run lint, npm run format:check, and npm test all pass. The
two real-network integration tests are skipped unless
NETWORK_TESTS=1 is set; CI does not set that variable by default.
The corpus scorecard currently lists 29 entries
(18 control_fixture, 4 derived_real, 6 real, 1 real_partial).
The breadth gate is GREEN and there are no deferred core shapes;
declared_unused is covered by the real real-semantic-search
fixture.
Pre-release, a maintainer additionally runs
NETWORK_TESTS=1 npm test to exercise the two real-network
integration tests. See docs/release.md.
If you see tsc: command not found, ensure your shell isn't filtering
devDependencies (e.g. NPM_CONFIG_PRODUCTION=true in the environment, or a
project-local .npmrc with production=true). The repo ships no
.npmrc; both should be unset.
# Analyze a local unpacked extension directory
npx chrome-review ./path/to/extension
# Or, in the cloned workspace, via the dist binary / workspace script
node packages/cli/dist/index.js ./path/to/extension
npm run cli -- ./path/to/extension
# Analyze a public GitHub repo
npx chrome-review https://github.com/<owner>/<repo>
# Specific branch, tag, or commit SHA
npx chrome-review https://github.com/<owner>/<repo>/tree/<ref>
# Specific ref + subpath as the extension root
npx chrome-review https://github.com/<owner>/<repo>/tree/<ref>/<subpath>The CLI reads manifest.json from the given directory. For GitHub URLs
the CLI fetches a codeload tarball, extracts to a tracked temp
directory, analyzes, and cleans up — including on SIGINT / SIGTERM and
on uncaught exception. The only outbound HTTP request the CLI ever
makes is the codeload tarball fetch against github.com. No
telemetry.
For private repos or to lift the unauthenticated rate limit, set
GITHUB_TOKEN in the environment; it is sent as Authorization: Bearer <token> and never logged. See
packages/cli/README.md for the full
exit-code matrix, every GitHub error class, and the JSON schema.
npm run typecheck
npm test
npm run test:unit
npm run test:corpus
npm run test:scorecard
npm run test:schemanpm run test:scorecard is green on HEAD. It prints per-shape coverage
for the core shape vocabulary and enumerates any deferred shapes from
corpus/catalog.json's deferred_shapes array. On HEAD, that array is
empty: every core shape is covered by at least one real or
real_partial entry that scored PASS|USEFUL, including
declared_unused via real-semantic-search.
chrome-review/
BUILD.md # build spec (source of truth)
ROADMAP.md # scope / non-goals
README.md
corpus/ # control fixtures + real / real_partial / derived_real entries
packages/
types/ # shared TS types
parser/ # manifest + AST parsing, entry-point resolution, API-usage extraction
rules/ # permission diff, remote-hosted-code rules, confidence assignment
cli/ # local path input, formatting, exit codes
tests/
corpus-expected/ # hand-reasoned expectation files
corpus/ # runner comparing analyzer output to expectations
unit/ # unit tests (build their own throwaway fixtures via tmpdir)
- HIGH — explicit API usage with file/line evidence.
- MEDIUM — inferred from manifest wiring or indirect but credible evidence.
- LOW — declared but no clear evidence found, or ambiguous because parsing was incomplete.
If the analyzer cannot parse a file (bundled/minified/unsupported syntax), it records that as an uncertainty and surfaces it in the report rather than silently guessing.
See ROADMAP.md for what's in scope and what is
explicitly deferred.