Skip to content

morgan-coded/chrome-review

Repository files navigation

chrome-review

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.

Install

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-extension

All 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.

What it does

  • Parses manifest.json and 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 CommonJS require() calls. Relative paths are resolved and followed; bare module specifiers ('lodash', '@scope/pkg') are recorded as LOW-confidence unresolved (the analyzer does not read node_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).
  • 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. webRequest findings carry the MV3 webRequestBlocking deprecation note.
  • Remote-hosted-code detectors cover eight shapes: HTML <script src>, eval / Function, dynamic import (literal remote + template remote prefix), fetcheval / Worker-blob dataflow, setTimeout/setInterval with string arg, document.write with embedded remote script, and remote importScripts / 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 + scripting drops one tier; fully-dynamic URLs and chrome.scripting.executeScript call 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 loc is remapped back to the original source via @jridgewell/trace-mapping. When the map is missing / invalid / absent, the file is listed in partiallyAnalyzedFiles with 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; --json emits a structured AnalyzerReport validated against packages/cli/schema/report.schema.json (0.x — unstable).

What it does NOT do

  • 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).

Development setup (clean checkout)

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 test

Expected 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.

Use

# 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.

Test

npm run typecheck
npm test
npm run test:unit
npm run test:corpus
npm run test:scorecard
npm run test:schema

npm 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.

Repo layout

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)

Confidence tiers

  • 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.

Status

See ROADMAP.md for what's in scope and what is explicitly deferred.

About

TypeScript static analyzer for Chrome MV3 extensions

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors