Skip to main content
Glama

jsscan-mcp

An MCP server that lets an AI agent discover JavaScript in a web app and scan it for exposed secrets and known vulnerable libraries. Findings include verification evidence and a source URL, with a line and column when the scanner can locate the match in the source. A local dashboard groups findings by type and severity.

The MCP tools return compact structured results and finding IDs. Target requests use a host allowlist and an audit log.

Only scan systems you own or have written permission to test, or a deliberately vulnerable app such as OWASP Juice Shop or DVWA. The allowlist exists to make that rule enforceable, not to replace it.

Setup

Python 3.10 or newer.

git clone https://github.com/Saumya1404/jsscan-mcp && cd jsscan-mcp
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest

On Windows PowerShell, use:

py -3 -m venv .venv
$env:PYTHONUTF8 = "1"
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
.\.venv\Scripts\python.exe -m pytest

The scanner currently needs UTF-8 mode on Windows to read the bundled advisory database. Set PYTHONUTF8=1 in the MCP client's server environment too.

Scope is set by whoever launches the server. An empty allowlist refuses all scan targets.

export JSSCAN_ALLOWED_HOSTS="localhost:3000"     # comma separated
# or: export JSSCAN_SCOPE_FILE=scope.json        # {"allowed_hosts": ["localhost:3000"]}
jsscan-mcp                                       # stdio transport, web view on 127.0.0.1:8765

Register it with a client. For Claude Code:

claude mcp add jsscan -e JSSCAN_ALLOWED_HOSTS=localhost:3000 -- jsscan-mcp

For any client that takes a JSON config:

{
  "mcpServers": {
    "jsscan": {
      "command": "/path/to/.venv/bin/jsscan-mcp",
      "env": { "JSSCAN_ALLOWED_HOSTS": "localhost:3000" }
    }
  }
}

Other settings: JSSCAN_HOME (database and audit log, default ~/.jsscan), JSSCAN_WEB_PORT (default 8765), JSSCAN_NO_WEB=1 to disable the web view, JSSCAN_REQUEST_DELAY (seconds between requests).

Try the local demo

python demo/agent_demo.py --hold

On Windows, run .\.venv\Scripts\python.exe demo\agent_demo.py --hold with the UTF-8 setting above. The script generates a local fixture with fabricated credentials and sample library version banners, serves it on port 8011, and calls the MCP tools over stdio. Open the dashboard link it prints before pressing Enter. Add --real-libs to download the old library packages instead. That option requires npm and registry access.

To use an AI agent, generate the fixture separately with python fixtures/make_fixture.py fixtures/out and serve it with python -m http.server 8011 --bind 127.0.0.1 --directory fixtures/out. Use the virtual environment's Python for both commands. Register the scanner with the client using the configuration above, changing the allowlist to 127.0.0.1:8011. On Windows, set the command to the absolute path to .venv/Scripts/python.exe and add "args": ["-m", "jsscan.server"]. Ask the agent to call scan_target for http://127.0.0.1:8011/index.html, then use the returned scan ID with list_findings.

Related MCP server: vibecheck

Tools

Tool

Purpose

get_scope

Show the allowlist and limits. Read only.

check_scope

Dry run: is this URL allowed? Makes no request.

crawl_target

List the JavaScript a target loads, without scanning.

scan_target

Crawl, scan and verify in one call. Returns counts, the top five findings and a dashboard link.

list_findings

Filter by type, severity and confidence. Most severe first, paginated.

get_finding

Finding evidence, including location, verification checks, and advisory details where available.

verify_finding

Re-fetch the source and re-run verification. Updates the stored status.

list_scans, get_audit_log

History, and what was allowed or refused.

refresh_vuln_db

Fetch updated Retire.js data from GitHub. This request currently bypasses the target allowlist.

A finding looks like this, which is the shape an agent sees in lists:

{"id": "009afddd-001", "type": "secret", "severity": "critical", "status": "confirmed",
 "confidence": 95, "title": "GitHub token", "where": "http://127.0.0.1:8011/app.js:5:17",
 "preview": "ghp_…ql (40 chars)", "occurrences": 2}

How it works

The crawler fetches pages breadth first and collects external scripts, inline scripts, modulepreload links, and script references found inside already fetched scripts (webpack chunks, dynamic imports). Each script goes through two analyzers.

The secret analyzer applies 19 rules (cloud keys, GitHub, GitLab, Slack, Stripe, Google, SendGrid, OpenAI, Anthropic, npm, PEM private keys, JWTs, database URLs with credentials, and a generic credential assignment rule). The dependency analyzer uses the Retire.js repository format: version extractors for URL, filename, file content and file hash, matched against version ranges with advisories.

Verification

A regex hit is only a candidate. Each one runs through a verifier for its type and gets a status, a confidence from 0 to 100, and the list of checks that produced it.

  • confirmed: structure validated by something stronger than a pattern. A GitHub token whose CRC32 suffix matches, an AWS key id whose embedded account id decodes to a plausible account, a JWT whose header and payload decode, a PEM body that base64 decodes to key-sized DER.

  • likely: strong format, plausible entropy, nothing suspicious.

  • unverified: weak signal, kept for triage.

  • rejected: placeholder, template, repeated characters, low entropy, translation key, hash constant, default database password. Hidden by default but counted, and one click away in the web view.

Context changes severity as well as status. A JWT with role anon is info, with service_role critical, and an expired one low. A Stripe publishable key is info. A database URL pointing at localhost is low. Library findings take the highest advisory severity and report the version that fixes it.

Library confidence depends on how many independent signals agree. A hash match is strongest, a banner in the file plus the filename is next, a URL alone is weakest. Signals that disagree are flagged and the file content wins.

verify_finding goes back to the live source, reproduces the match by fingerprint, and refreshes the verdict. A finding that no longer reproduces is marked not_reproduced instead of silently kept.

Design choices

Target requests use a scoped fetcher. The crawler and finding rechecks use a fetcher that checks the allowlist before each request, including redirect hops. Entries without a port match the scheme's default port. Wildcards match subdomains, and only HTTP and HTTPS are allowed. The JSONL audit log records allowed requests and groups refusals by scan, host and purpose. The advisory refresh tool currently uses a separate HTTP client outside this scope enforcement. The local demo uses the bundled snapshot.

Out-of-scope scripts are not fetched, but their URLs are still read. A page that loads cdnjs.cloudflare.com/.../moment.js/2.18.1/... tells you the version without any request to the CDN. Those findings are capped at 55 confidence, marked unverified, and say plainly that the file was not fetched.

Secret findings use redacted previews. The secret analyzer stores a short fingerprint and redacts neighboring detected secrets in its snippets. Dependency snippets currently copy nearby source text, and scan records and audit logs can include raw URLs. Keep local scan data out of Git.

Verification is offline. Checking whether a key is live means sending it to a third party, which breaks the scope guarantee and may be unauthorized use of the credential. The checks here are structural and statistical, and the live-validation step is left to a human with the owner's consent.

Scanned content is treated as untrusted. Snippets come from the target and can contain instructions aimed at the agent. The server returns them as data and tells the agent to treat them as untrusted. The web view escapes finding content, uses a content security policy that blocks scripts and external loads, binds to localhost, and rejects foreign Host headers.

Retire.js data, with a safety net. A snapshot of the Retire.js repository ships in the package (Apache-2.0, see THIRD_PARTY_NOTICES.md) and refresh_vuln_db updates it. Its patterns use variable-width lookbehind, so the matcher uses the regex module with a per-search timeout against pathological backtracking. A small supplemental extractor table covers builds upstream cannot identify; minified lodash 4.17.15 was the case that surfaced it.

One process, one SQLite file. The MCP server and the web view share a database. Simple to run, easy to inspect. Scans run inside the tool call, bounded by page and file limits.

Results so far

On the generated demo site: all planted credential types found, the AWS docs example key, a localhost database URL with a default password, translation keys and a "your-secret-here" value rejected as false positives, the same GitHub token in two files merged into one finding with two occurrences, and four libraries flagged (jQuery, lodash, Bootstrap, and moment from a refused CDN host, URL evidence only).

An earlier local run against the OWASP Juice Shop v20.3.0 frontend bundle reported 18 scripts, 28 MB and zero findings in about 10 seconds. The result describes what the scanner detected in that run.

Limits

  • No source map resolution. Locations are in the bundled file, and minified bundles mean long lines, so the column matters.

  • No JavaScript execution. Scripts injected at run time, or chunk URLs built by string concatenation, are not discovered. Single page apps with hash routing expose one page to the crawler.

  • Unauthenticated crawling only.

  • Reverification updates the stored verdict, but a moved match can leave the stored location and snippet stale.

  • Libraries are found only if Retire.js (or the supplemental table) can identify them. Heavily bundled or renamed copies can slip through.

  • The GitHub checksum follows GitHub's published token format description and is tested for self consistency, not against a real token. A mismatch lowers confidence instead of rejecting the hit, so a wrong assumption cannot hide a real token.

  • Entropy and shape heuristics for generic credentials will still let some noise through and can miss low-entropy real passwords.

  • Not tested against any third-party site, by design.

Layout

src/jsscan/   config, scope (allowlist and audit), fetcher, crawler, secrets, verify, deps, store, scanner, server (MCP), web
tests/        scope and redirect enforcement, verifiers, version logic, end to end, secret leakage, web escaping
fixtures/     generator for the vulnerable demo site (credentials created at run time, never committed)
demo/         scripted agent client and recording script

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Enables AI agents to run Shipcheck on local JavaScript/TypeScript repositories, scanning for launch risks like exposed env vars, unsigned webhooks, and missing security guardrails.
      1
      70 npm
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Agent-native "safe to ship?" security gate for AI-generated code. Uses real parsers and inter-rocedural taint analysis (JS/TS, Python, Go) to flag the classes AI coding agents get wrong — secrets, SQL injection, SS, SSRF, path traversal, command injection, weak JWT/CORS — and ranks findings by confidence. Exposes a scan tool over MCP.
      1
      8 npm
      2
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to control a local Chromium browser via CDP for page snapshots, screenshots, human-like interactions, autonomous crawling, API/GraphQL reconnaissance, authenticated differential testing, and Burp-like intercept, repeater, and intruder workflows with scope enforcement and audit logging.
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables AI coding assistants to debug and analyze JavaScript in web pages by managing scripts, breakpoints, network and WebSocket traffic, browser state, and local file inputs/outputs for continuous reverse-engineering workflows.
      24
      1,064 npm
      Apache 2.0