Skip to main content

Build Readiness Audit

Five risks in a security scanner, ranked, with the order to fix them

An excerpt from an audit of ThynkQ's own open-source tool. The scanning core was sound. The risk sat in the parts that guard it.

Subject
github.com/Abanoub-Rodolf/mcp-scan
Commit
86d8ffe, v2.0.13, committed 2026-09-09
Audited
2026-10-09
Size
299 tracked files, about 9,500 lines of TypeScript in src/
Method
Source read, typecheck, lint, tests with coverage, npm audit, and four hands-on reproductions
Verdict
Sound core, five fixable gaps, about 1.1 weeks

Summary

Every ranked finding was fixed in mcp-scan 2.1.0.

Nothing here needs a rewrite. Typecheck and lint pass, and all 418 tests pass once the project is built. The five risks sit in the controls around the scanner: a log that can leak, a tamper check that can switch itself off, and a release path with no test gate.

Severity is rated for a local, single-user threat model. A scanner that runs on your own machine is not a server, so a denial of service on your own config file ranks low. Estimates assume one engineer who knows the codebase and include tests.

Architecture sketch

  1. CLIsrc/index.tsCommander entry point. 20 command files in src/commands, including scan, proxy, ci, sbom and watch.
  2. Detect and parsesrc/configFinds client config files in home and project directories, then reads JSON, JSONC and TOML.
  3. Scannerssrc/scannersSixteen modules run per server. Some reach the network: the npm registry and the GitHub API in a scan, OSV in the library path.
  4. Reportssrc/utilsTerminal, JSON, HTML, SARIF and SBOM output, plus an optional webhook post.
  • Local state~/.mcp-scanaudit.log, known-servers.json (config fingerprints) and proxy logs: the state it keeps between runs. The fix command also rewrites client configs and keeps backups.
  • Release path.github/workflowsci.yml tests on push. publish.yml publishes to npm with provenance when a v* tag lands.

What is already clean

Checks run on a fresh clone
CheckResult
Typechecktsc --noEmit exits 0
Linteslint exits 0
Tests418 of 418 pass after a build
Coverageabout 80% lines and 70% branches

One catch: on a fresh clone npm test fails 5 tests until you build (risk 05). The numbers above are from after a build.

Top five risks, ranked

01mediumproxy-log-exposure

The proxy writes raw tool traffic to a world-readable file

Where
  • src/commands/proxy.ts:129-131
  • src/commands/proxy.ts:112-116
  • src/commands/proxy.ts:43, 52-54
Observed
We piped one JSON-RPC message through mcp-scan proxy. The log landed in ~/.mcp-scan/logs with the default file mode and held the whole message. Masking is off unless a policy file turns it on. The catch block at line 52 writes the unmasked line when parsing fails; we read that path but did not trigger a mask failure.
Evidence
-rw-r--r-- proxy-<timestamp>.log
[CLIENT -> SERVER] {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"arguments":{"note":"sample-value"}}}
Why it matters
Tool arguments often carry tokens and file contents. On a shared machine, other accounts can read the log. On a single-user laptop the risk is low.
Fix
Create the log directory 0700 and the file 0600. In the catch path, log a placeholder, never the raw line. Decide whether masking should default to on.
02mediumtamper-detection-fails-open

Tamper detection turns itself off when its store is corrupt

Where
  • src/utils/audit-logger.ts:64-66
  • src/utils/audit-logger.ts:90-99
  • src/commands/scan.ts:278
Observed
We corrupted known-servers.json, then changed a server's package version in the scanned config twice. Neither run raised server-mutation, and the file was still corrupt afterwards. With a healthy store, the same edit raised it.
Evidence
MEDIUM     server-mutation
✓ All clear (1 server scanned in 8ms)
{ not json
Why it matters
The check that watches for edited server configs fails silently, and nothing repairs it. A truncated write or a full disk gets the same result as an attacker.
Fix
Warn once when the store cannot be read, quarantine the bad file, and rebuild a baseline on the next run.
03mediumrelease-and-dependency-gates

The publish workflow skips the tests, and the dependency gate cannot fail

Where
  • .github/workflows/publish.yml:23-28
  • .github/workflows/ci.yml:32-33
  • package.json:90
  • package.json:28-31
  • src/config/parser.ts:103
  • src/config/paths.ts:102
Observed
A v* tag runs npm ci, build and publish with no test step and no dependency on ci.yml. The CI audit step fails only on critical. npm audit on the runtime tree reports 5 high and 3 moderate packages. smol-toml is a direct dependency pinned at 1.6.1 by the lockfile, inside two advisory ranges (GHSA-7w5x-hrqm-74c2, GHSA-r4xh-jqrq-34v2), and it parses .codex/config.toml from the scanned directory. The lodash override pins 4.17.21, inside its advisory range. We did not reproduce an exploit; the impact is a slow or stuck scan, not code execution. In fairness, the GitLab CI pipeline ran lint, build, test and audit on tags, and the lodash advisory was a documented accepted risk.
Evidence
8 vulnerabilities (3 moderate, 5 high)
Why it matters
A scanner that people run on untrusted project folders should not hang on a hostile file, and a release should not depend on someone remembering to wait for CI.
Fix
Add a test job the publish job needs, fail the audit step at high for runtime dependencies, bump smol-toml (a fix is published), and put a review date on the accepted lodash risk. The braces advisory has no published fix yet, so record it as accepted with an owner and a review date.
04lowfingerprint-includes-version

Upstream releases look like tampering

Where
  • src/utils/audit-logger.ts:102-112
  • src/utils/audit-logger.ts:72
Observed
The config fingerprint includes the resolved package version. We scanned an unchanged config offline, then online, and four official servers raised a MEDIUM that says the change may indicate unauthorized modification.
Evidence
Server 'filesystem' configuration has changed since the last scan. This may indicate unauthorized modification.
0 critical    0 high    5 medium    0 low
Why it matters
Any unpinned npx server will trip this each time its publisher ships. Alarms that cry wolf get switched off, which feeds the risk above.
Fix
Fingerprint the config the user wrote. Report a changed resolved version as an INFO line, not a tamper warning.
05lowtests-need-a-build

npm test fails on a fresh clone until you build

Where
  • package.json:36
  • tests/commands/golden.test.ts:9
  • tests/commands/ci.test.ts:5
  • .github/workflows/ci.yml:28-29
Observed
Five CLI tests run dist/index.js, but the test script does not build it. CI hides this by running the build first.
Evidence
Tests  5 failed | 413 passed (418)
Tests  418 passed (418)
Why it matters
A new contributor sees red on a clean checkout and has to guess why.
Fix
Add a pretest script that builds, or have the five tests build once in a global setup.

Fix-first plan

Estimates in engineer-weeks
OrderFixClosesWeeksWhy this order
1Release and dependency gatesRisk 030.25Cheapest change with the widest reach: every install passes through the release path.
2Proxy log modes and fail-closed maskingRisk 010.25The only finding that can leak data.
3Corrupt-store warning and rebuildRisk 020.25Restores a feature that is silently off today.
4Fingerprint the config, not the resolved versionRisk 040.25Needs the store from step 3 to be dependable first.
5Build before test, quieter snapshot warningRisk 050.1Housekeeping once the above has landed.

Total: about 1.1 weeks for one engineer.

Also noted

  • The stale-snapshot warning prints once per server, and tells installed users to run an npm script that only exists in the repository (src/scanners/package-scanner.ts:419).

What you keep if you don't build with us: the full report and fix plan

The audit is a deliverable you pay for: $7,500, 1 week. It comes with:

  • A ranked list: what is broken, what it costs to fix, and in what order
  • Plain notes on the big technical choices, with the options we weighed
  • Security and performance problems found, with what to fix first
  • A short summary you can share with partners or investors

This excerpt shows the ranked risks with severity and file evidence, and the order to fix them. It leaves out the architecture decision notes and the executive summary; the architecture sketch above is orientation, not those notes. A web product also gets a performance pass; a command-line tool has no page to measure.

Our IP terms say deliverables transfer to you on receipt of full payment. The standard split is 50/50 on everything up to $59,999. So once the fee is paid, the report and the plan are yours to hand to your own team or another vendor.

The only thing that depends on building with us is the fee credit: 100% of the fee credited toward Launch Build or Product Build if signed within 60 days. Nothing in our published terms makes the report conditional on a build.