Skip to content

Repository files navigation

目付 Metsuke - Repo Interview Guard

目付 (Metsuke) is the Edo-era word for an official who watches for wrongdoing. The extension analyzes source files on GitHub / GitLab / Bitbucket the moment you open them, and warns when a file shows traits of a malicious "fake-interview" repository. It only warns - it never modifies or blocks the page.

守望而不阻擋。 ・ 警戒すれども、妨げず。

Available in English, 繁體中文, and 日本語 (follows the browser language).

Install: Chrome Web Store · Extension ID edecaphkcjlamaidljcpjcodiljgcajb

Structure

File Responsibility
manifest.json MV3; only the storage permission + named host permissions
detector.js Pure detection engine (no DOM / no network); rules RIG-001~026 and threshold constants
content.js URL parsing, text fetch (raw -> GitHub embedded JSON), idle analysis, Shadow DOM banner, reveal, SPA navigation detection
background.js Service worker: proxy the raw fetch (with cookies), set the badge
popup.html/css/js Verdict summary, trust list (allowlist), master switch
tests/suite.js Runtime-agnostic assertions (runSuite(detector)); shared verbatim by the Node runner and the Cloudflare Worker (single source of truth)
tests/run.js Node runner for the suite (node tests/run.js): positive fixtures + false-positive corpus (0 high-severity FP gate)
worker/, wrangler.jsonc Cloudflare Worker that runs the suite in workerd and serves GET /test (see worker/README.md) — not shipped in the extension
_locales/{en,zh_TW,ja} i18n message catalogs (English default)

Development

# Run the detection-engine tests (required whenever threshold constants change)
node tests/run.js

# Build a Web Store zip + unpacked dir
bash scripts/pack.sh

Load it locally: chrome://extensions -> Developer mode -> "Load unpacked" -> pick this directory.

Testing without local EDR interference

The fixtures are full of malware patterns (eval/atob/child_process, C2 IPs, wallet paths…). A local endpoint-security agent (EDR) flags some Node invocations as suspicious and kills them (exit 137), which silently distorts results. Know which is which:

Command Local? Note
node tests/run.js ✅ runs executing a real file is fine — this is the canonical local run
node --check <file> killed syntax-check parse trips the EDR; don't use it
node -e "<inline code with eval/atob/fetch/process.env/…>" killed inline malware-pattern strings trip the EDR

So, do not verify with node --check or node -e "<malware-ish inline>". Instead:

  • Quick probe → write it to a temp file (e.g. tests/_probe.js), run node tests/_probe.js, then delete it. Executing a file is not blocked.

  • High-fidelity / zero-local-Node verification → use the deployed Cloudflare Worker test endpoint, which runs the exact same tests/suite.js inside workerd (closer to the extension's content-script isolated world than Node, and entirely off your machine):

    npm install && npx wrangler login && npx wrangler deploy   # one-time
    curl -fsS https://metsuke-detector-tests.gesarlin0803.workers.dev/test

    Returns HTTP 200 + { "ok": true, "pass": …, "fail": 0 } when green, 500 when red. Full deploy/CI notes in worker/README.md.

The suite also reports coverage metrics — per-rule positive/negative cases, recall, specificity, and a gate that fails if any enabled rule lacks a positive or negative case. The full test inventory and how to add cases are in tests/README.md.

Two analysis modes

  • File pages (/blob, /-/blob, /src): real-time analysis of the single file you open.
  • Repo home / tree pages: a targeted scan of fixed high-value entry files (package.json, .vscode/tasks.json, .vscode/settings.json, .claude/settings*.json, .gemini/settings*.json, .cursorrules, .cursor/rules/setup.mdc, .github/copilot-instructions.md, CLAUDE.md, AGENTS.md, GEMINI.md, .husky/*, backend entry points (index.js/server.js/…), .env-class files, and build/config files PolinRider injects into (tailwind.config.js, postcss.config.mjs, eslint.config.mjs, …)) - so you are warned on the landing page without opening each file. Findings are tagged with their source file and are click-through.

Staged detection (time to first signal). Both modes surface a preliminary verdict fast, then upgrade it:

  • detector.analyze(text, ctx) runs a fast tier (cheap, high-signal rules) when ctx.tier === 'fast', deferring the expensive base64 decode / permutation / entropy work (RIG-003/005/014) to the default deep pass. A deep result is a strict superset of a fast one, so the caller replaces (not merges) the preliminary findings.
  • File mode: the instant GitHub-embedded pass runs the fast tier for a preliminary verdict; the raw fetch then runs the full deep analysis as the final basis.
  • Repo mode: entry files are fetched in two phases — SCAN_HOT (run-on-open/install configs + PolinRider's primary targets) first for a fast preliminary, then SCAN_TAIL (lower-prevalence entry points, .env-class, secondary configs).
  • The banner pops on the first signal and re-pops once if the combined level escalates to alarm (overriding a prior dismiss); lesser updates refresh in place.

Detection rules (23 enabled)

Trigger is what makes a rule applicable: content rules match any fetched text (file-primary - the malicious source usually lives in a file you open); path-gated rules only fire on a specific config/instruction file (repo-primary - those files are exactly what the repo-home scan fetches).

Rule Detects Trigger File Repo Primary
RIG-001 Off-screen hidden code content file
RIG-002 Whitespace pushes code off-screen content file
RIG-003 Spliced/reordered base64 C2 content file
RIG-004 Known C2 port 1224/1244 content file
RIG-005 base64 decodes to IP:port content file
RIG-006 eval()/Function() dynamic loader content file
RIG-007 atob -> eval execution chain content file
RIG-008 Accesses wallet/key paths content file
RIG-009 Accesses browser profile dir (med) content file
RIG-019 SSH authorized_keys backdoor write content file
RIG-021 Dead-drop resolver (decoded string fetched as URL) content file
RIG-022 Network response passed to eval/Function content file
RIG-023 Whole process.env exfiltrated to network content file
RIG-025 Obfuscated payload appended after a module export (PolinRider) content file
RIG-026 Anti-forensic git history rewrite (force-push + clock tamper) content file
RIG-010 Install script downloads/executes package.json repo
RIG-011 Install script connects to IP package.json repo
RIG-013 VS Code run-on-open setting .vscode/ repo
RIG-016 Agent hooks run on open (Claude/Gemini) .claude/·.gemini/ settings repo
RIG-017 AI instruction file hidden injection chars instruction/rules files repo
RIG-018 Git hook (husky) runs on open .husky/ repo
RIG-020 AI rules file instructs the agent to run a command .cursor/rules·.cursorrules repo
RIG-024 Lifecycle script runs an in-repo script package.json repo

Experimental (off by default):

Rule Detects Trigger
RIG-012 Dependency from a non-registry source package.json
RIG-014 Off-screen high-entropy string content

The full rule-to-intel mapping (severity / confidence / family, combination rules, and 2026 threat-report sources) lives in detector.js and the internal rules doc.

Alert tiers (false-positive reduction)

Every rule carries severity x confidence x family, and detector.assess() decides how to show it:

  • alarm - full coral banner: a high-confidence strong signal (known C2 port, wallet path, hidden injection chars...), or two or more different families together (multi-stage chain).
  • caution - smaller amber banner: a single easily-misjudged signal (a legitimate postinstall can also use curl) or medium risk only - low-key, not intrusive.

The banner pops on the first dangerous signal; while the scan is still running it shows a loading spinner, then updates when done.

Privacy

No collection, no transmission, no sale; all analysis runs locally. The only network request fetches the raw source of the file you are already viewing, from the same code host you are on. Only { enabled, allowlist } is stored, in chrome.storage.sync. See PRIVACY.md.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages