Security Surface Audit
Overview
Detect the project's actual threat model first, then audit only the matching attack surfaces against universal bars. A localhost CLI server, a public API, and a published library face different attackers — applying one checklist to all of them produces both noise and blind spots.
Scope is static and defensive: read code, config, and CI definitions. Never develop exploits, run PoCs, or probe live systems unless the human explicitly authorizes it — and say so in the report.
When to use / when NOT
Use when:
- Pre-release or pre-handoff audit of what a project exposes.
- A new surface appeared: embedded server, deploy pipeline, process-spawning command, public package.
- Someone asks "is this exposed / safe / hardened?" about existing code.
Do NOT use when:
- Someone is adding auth, input handling, secrets, or payment code and wants implementation guidance → see security-review.
- You need vulnerability definitions, root causes, or taxonomy → see top-100-web-vulnerabilities-reference.
- The ask is penetration testing or exploit development → out of scope without explicit authorization.
- The ask is only "scan my dependencies for CVEs" → use a scanner; this skill audits code/config posture, not advisory databases.
How it works
Detect surfaces. Read workspace manifests, server entry points, bind addresses/ports, CLI entry points, child_process/spawn usage, publish config (exports, files, registry), CI workflow files, Dockerfiles, deploy config. Classify every surface present:
| Surface class |
Detection signals |
| Public web app |
SPA/SSR framework, deployed to a public host, user sessions, DOM rendering of dynamic data |
| API service |
Route handlers, auth middleware, DB access, public deploy target |
| Local/loopback server |
HTTP/WS server started by a CLI or dev tool, bound to localhost, browser-facing UI (illustrative example: a Hono control server embedded in a CLI binary) |
| CLI spawning processes |
spawn/exec/execa, git/package-manager invocation, file installs (illustrative example: a shadcn-style "add component" installer CLI) |
| Library consumed by others |
Publishable package, exported functions receiving caller input |
| Static site + deploy pipeline |
Docs/marketing build, CI workflows that publish artifacts |
| Desktop-embedded server |
Electron/Tauri/desktop app with internal HTTP/WS/IPC listener (same class as loopback, plus IPC) |
A project can have several. Enumerate all of them; missing a surface is the worst failure mode of this skill.
Write a one-line threat model per surface: who attacks it, through what channel. (Loopback server → any website open in the user's browser. CLI installer → a malicious registry item. Library → any hostile caller input.)
Apply only the matching check blocks from the Quality bar below. Do not run public-web checks against a loopback server or vice versa — severity depends on the threat model.
Trace untrusted input flows (applies to nearly every surface): where does ingested content — repo files, external JSON, network responses, model/LLM output — reach a sink (prompt, terminal, DOM, shell, file path, object merge, URL fetch)?
Severity-rate every finding (scale in Output) and cite the CVE class where one exists — named prior art makes findings credible and non-hypothetical.
Emit the report in the exact Output shape. State the static/defensive scope line verbatim.
Scale: on large multi-surface repos, dispatch one agent per surface and converge — see convergence-loop.
Quality bar (2026 snapshot)
Distilled 2026-06 from a researched state-of-the-art bar (CVE list and framework defaults verified 2026-06-10). Refresh note: re-verify the named advisories, browser mitigation status, and middleware defaults when auditing after ~mid-2027 or when a referenced framework major-bumps.
Local/loopback servers — the class teams get wrong
| Check |
Bar |
Prior art (cite in findings) |
| Bind address |
127.0.0.1, never 0.0.0.0 |
"0.0.0.0-day": browsers historically let pages reach 0.0.0.0 listeners; Chromium blocking only rolled out ~v128–133 — don't rely on browser fixes |
| Host header |
Allowlist-validate on every request |
DNS rebinding defeats same-origin even on loopback: Ollama CVE-2024-28224 (rebinding → API access/file exfil); MCP python-sdk CVE-2025-66416 (rebinding protection off by default) |
| Origin / CORS |
Validate Origin; CORS as explicit allowlist, never * |
esbuild GHSA-67mh-4wv8-2f99 (dev server sent Access-Control-Allow-Origin: *; any site could read responses). Check the framework default — e.g. Hono's hono/cors defaults origin to * (illustrative; check whatever middleware the repo uses) |
| Auth token |
Per-session token required even locally on state-changing/control endpoints |
MCP Inspector CVE-2025-49596 (CVSS 9.4: no client↔proxy auth + rebinding → RCE; fixed with session token + origin validation). Jupyter's token model is the positive example |
| CSRF |
Applies on localhost: simple requests need no preflight, any website can fetch()/form-POST to loopback |
Require Origin / Sec-Fetch-Site checks (or token) on every mutation |
| File serving |
Normalize before deny-list checks; audit traversal and bypass tricks |
Vite fs.deny bypass family: CVE-2025-30208 (?raw??), CVE-2025-31125, CVE-2025-46565 (/.), CVE-2025-62522 (Windows \) |
Loopback binding alone is insufficient. That is the single sentence to carry into any finding on this surface.
Any server (public or local)
- Schema-validated input at every boundary: params, query, body, headers.
- Authn on every non-public route; per-resource authz (IDOR check on IDs in paths/bodies).
- Secrets at rest: restrictive file permissions (0600-class), OS keyring where available; never in logs, error responses, or crash output.
- Error messages don't leak stack traces, absolute paths, or internal versions to clients.
postMessage: check event.origin on receive; explicit targetOrigin (never *) when sending anything sensitive.
Untrusted ingested data (any surface that reads content it didn't author)
- Content reaching prompts → prompt injection; reaching terminals → ANSI/OSC escape abuse (title set, clipboard write, hyperlink spoofing) — strip/encode before echo; reaching DOM → XSS — encode at the sink.
- Object merges from external data must be prototype-pollution-safe (
__proto__, constructor.prototype keys).
- User-configurable URLs (webhooks, registries, model endpoints) → SSRF; verify auth headers/API keys do not follow redirects to attacker hosts.
CLI / process spawning
- Spawn with argument arrays, never interpolated shell strings carrying external data.
- External names (packages, refs, paths) used as args: guard argument injection (
--flag-shaped values) with -- separators or validation.
- Files the CLI writes: no predictable temp paths, no symlink following into privileged locations, sane permissions.
Library consumed by others
- All caller input is hostile: ReDoS in exported regexes, prototype pollution in options merging, path handling on caller-supplied paths.
- No
eval/new Function on input; no install scripts doing undisclosed network/exec.
Supply chain / deploy pipeline
- CI: minimal
permissions: per workflow/job; third-party actions pinned to SHA; no secrets reachable from PR-triggered contexts (pull_request_target); no untrusted input (PR titles, branch names) interpolated into run:.
- Lockfile committed; provenance/trusted publishing for public packages.
- Static deploy: no server/API tokens baked into client bundles; restrictive headers where the host allows; no directory listing; rate limits on any serverless endpoints.
Public web app
- XSS sinks (
dangerouslySetInnerHTML, innerHTML, equivalent) only with sanitized input; CSP present or its absence justified; auth tokens not in localStorage when httpOnly cookies are feasible; no open redirects from query params.
Output
Produce exactly this report shape:
# Security Surface Audit — <project>
Date: <YYYY-MM-DD>
Scope: static, defensive review of code/config/CI. No exploit development
or live probing performed (requires explicit authorization).
## Surfaces detected
| # | Surface | Evidence | Primary threat |
|---|---------|----------|----------------|
| 1 | Local loopback server | src/server.ts binds 127.0.0.1:4317 | any website open in the user's browser |
## Findings
### [SS-1] CRITICAL — Loopback server: no Host/Origin validation
- Where: src/server.ts:42
- What: <one factual sentence>
- Class: DNS rebinding (cf. CVE-2024-28224, CVE-2025-49596)
- Fix: <smallest correct change>
(repeat per finding, ordered by severity, IDs SS-1..SS-n)
## Exposure summary
| Surface | Crit | High | Med | Low | Verdict |
|---------|------|------|-----|-----|---------|
| Loopback server | 1 | 2 | 1 | 0 | FIX-FIRST |
Overall: <one line — worst surface drives the call>
Severity scale — rate against the detected threat model, not an imagined one:
- CRITICAL — reachable compromise (RCE, token theft, cross-origin data read) on a detected surface with no mitigating control.
- HIGH — reachable with one realistic precondition (user visits a hostile page, installs a hostile registry item).
- MEDIUM — defense-in-depth gap or requires unusual configuration.
- LOW — hardening/hygiene.
- INFO — observation; no action required.
Verdict per surface: SHIP (no Crit/High) · FIX-FIRST (any Crit/High) · HARDEN-SOON (Med-only debt worth scheduling).
Common mistakes
- Skipping loopback servers as "local = safe", or auditing them with a public-web checklist. The loopback class has its own CVE history; use its own block.
- Accepting CORS
* because "it's only a dev server" — that is exactly the esbuild advisory.
- Severity inflation: flagging a missing CSP on a loopback control UI as critical. Severity follows the surface's threat model.
- Hardcoding one stack's checks ("look for Hono middleware") instead of detecting the surface first; every named tool here is illustrative.
- Missing a surface entirely (the CI pipeline, the spawned git subprocess) because the audit fixated on the obvious server.
- Drifting into implementation tutorials — report findings and the smallest fix; point builders to security-review.
- Running exploit PoCs or live probes without explicit authorization.
- Substituting dependency-scanner output for the audit; advisories complement, not replace, posture review.
1---2name: security-surface-audit3description: Use when auditing what an existing project exposes to attackers — a loopback/localhost dev or control server, public web app or API, CLI that spawns processes, published library, static site with CI/CD deploy pipeline, or desktop-embedded server. Triggers include "security audit", "attack surface", "threat model", "is this safe to ship", DNS rebinding / CORS / CSRF / Host-header exposure questions, secrets-at-rest review, or supply-chain posture checks before release or handoff.4---56# Security Surface Audit78## Overview910Detect the project's actual threat model first, then audit only the matching attack surfaces against universal bars. A localhost CLI server, a public API, and a published library face different attackers — applying one checklist to all of them produces both noise and blind spots.1112**Scope is static and defensive**: read code, config, and CI definitions. Never develop exploits, run PoCs, or probe live systems unless the human explicitly authorizes it — and say so in the report.1314## When to use / when NOT1516**Use when:**17- Pre-release or pre-handoff audit of what a project exposes.18- A new surface appeared: embedded server, deploy pipeline, process-spawning command, public package.19- Someone asks "is this exposed / safe / hardened?" about existing code.2021**Do NOT use when:**22- Someone is *adding* auth, input handling, secrets, or payment code and wants implementation guidance → see **security-review**.23- You need vulnerability definitions, root causes, or taxonomy → see **top-100-web-vulnerabilities-reference**.24- The ask is penetration testing or exploit development → out of scope without explicit authorization.25- The ask is only "scan my dependencies for CVEs" → use a scanner; this skill audits code/config posture, not advisory databases.2627## How it works28291. **Detect surfaces.** Read workspace manifests, server entry points, bind addresses/ports, CLI entry points, `child_process`/spawn usage, publish config (`exports`, `files`, registry), CI workflow files, Dockerfiles, deploy config. Classify every surface present:3031 | Surface class | Detection signals |32 |---|---|33 | Public web app | SPA/SSR framework, deployed to a public host, user sessions, DOM rendering of dynamic data |34 | API service | Route handlers, auth middleware, DB access, public deploy target |35 | Local/loopback server | HTTP/WS server started by a CLI or dev tool, bound to localhost, browser-facing UI (*illustrative example: a Hono control server embedded in a CLI binary*) |36 | CLI spawning processes | `spawn`/`exec`/`execa`, git/package-manager invocation, file installs (*illustrative example: a shadcn-style "add component" installer CLI*) |37 | Library consumed by others | Publishable package, exported functions receiving caller input |38 | Static site + deploy pipeline | Docs/marketing build, CI workflows that publish artifacts |39 | Desktop-embedded server | Electron/Tauri/desktop app with internal HTTP/WS/IPC listener (same class as loopback, plus IPC) |4041 A project can have several. Enumerate all of them; missing a surface is the worst failure mode of this skill.42432. **Write a one-line threat model per surface**: who attacks it, through what channel. (Loopback server → any website open in the user's browser. CLI installer → a malicious registry item. Library → any hostile caller input.)44453. **Apply only the matching check blocks** from the Quality bar below. Do not run public-web checks against a loopback server or vice versa — severity depends on the threat model.46474. **Trace untrusted input flows** (applies to nearly every surface): where does ingested content — repo files, external JSON, network responses, model/LLM output — reach a sink (prompt, terminal, DOM, shell, file path, object merge, URL fetch)?48495. **Severity-rate every finding** (scale in Output) and cite the CVE class where one exists — named prior art makes findings credible and non-hypothetical.50516. **Emit the report** in the exact Output shape. State the static/defensive scope line verbatim.52537. **Scale**: on large multi-surface repos, dispatch one agent per surface and converge — see **convergence-loop**.5455## Quality bar (2026 snapshot)5657*Distilled 2026-06 from a researched state-of-the-art bar (CVE list and framework defaults verified 2026-06-10). Refresh note: re-verify the named advisories, browser mitigation status, and middleware defaults when auditing after ~mid-2027 or when a referenced framework major-bumps.*5859### Local/loopback servers — the class teams get wrong6061| Check | Bar | Prior art (cite in findings) |62|---|---|---|63| Bind address | `127.0.0.1`, never `0.0.0.0` | "0.0.0.0-day": browsers historically let pages reach `0.0.0.0` listeners; Chromium blocking only rolled out ~v128–133 — don't rely on browser fixes |64| Host header | Allowlist-validate on **every** request | DNS rebinding defeats same-origin even on loopback: Ollama CVE-2024-28224 (rebinding → API access/file exfil); MCP python-sdk CVE-2025-66416 (rebinding protection off by default) |65| Origin / CORS | Validate `Origin`; CORS as explicit allowlist, **never `*`** | esbuild GHSA-67mh-4wv8-2f99 (dev server sent `Access-Control-Allow-Origin: *`; any site could read responses). Check the framework default — e.g. Hono's `hono/cors` defaults `origin` to `*` (*illustrative; check whatever middleware the repo uses*) |66| Auth token | Per-session token required **even locally** on state-changing/control endpoints | MCP Inspector CVE-2025-49596 (CVSS 9.4: no client↔proxy auth + rebinding → RCE; fixed with session token + origin validation). Jupyter's token model is the positive example |67| CSRF | Applies on localhost: simple requests need no preflight, any website can `fetch()`/form-POST to loopback | Require Origin / `Sec-Fetch-Site` checks (or token) on every mutation |68| File serving | Normalize **before** deny-list checks; audit traversal and bypass tricks | Vite fs.deny bypass family: CVE-2025-30208 (`?raw??`), CVE-2025-31125, CVE-2025-46565 (`/.`), CVE-2025-62522 (Windows `\`) |6970**Loopback binding alone is insufficient.** That is the single sentence to carry into any finding on this surface.7172### Any server (public or local)7374- Schema-validated input at every boundary: params, query, body, headers.75- Authn on every non-public route; per-resource authz (IDOR check on IDs in paths/bodies).76- Secrets at rest: restrictive file permissions (0600-class), OS keyring where available; never in logs, error responses, or crash output.77- Error messages don't leak stack traces, absolute paths, or internal versions to clients.78- `postMessage`: check `event.origin` on receive; explicit `targetOrigin` (never `*`) when sending anything sensitive.7980### Untrusted ingested data (any surface that reads content it didn't author)8182- Content reaching **prompts** → prompt injection; reaching **terminals** → ANSI/OSC escape abuse (title set, clipboard write, hyperlink spoofing) — strip/encode before echo; reaching **DOM** → XSS — encode at the sink.83- Object merges from external data must be prototype-pollution-safe (`__proto__`, `constructor.prototype` keys).84- User-configurable URLs (webhooks, registries, model endpoints) → SSRF; verify auth headers/API keys do not follow redirects to attacker hosts.8586### CLI / process spawning8788- Spawn with argument arrays, never interpolated shell strings carrying external data.89- External names (packages, refs, paths) used as args: guard argument injection (`--flag`-shaped values) with `--` separators or validation.90- Files the CLI writes: no predictable temp paths, no symlink following into privileged locations, sane permissions.9192### Library consumed by others9394- All caller input is hostile: ReDoS in exported regexes, prototype pollution in options merging, path handling on caller-supplied paths.95- No `eval`/`new Function` on input; no install scripts doing undisclosed network/exec.9697### Supply chain / deploy pipeline9899- CI: minimal `permissions:` per workflow/job; third-party actions pinned to SHA; no secrets reachable from PR-triggered contexts (`pull_request_target`); no untrusted input (PR titles, branch names) interpolated into `run:`.100- Lockfile committed; provenance/trusted publishing for public packages.101- Static deploy: no server/API tokens baked into client bundles; restrictive headers where the host allows; no directory listing; rate limits on any serverless endpoints.102103### Public web app104105- XSS sinks (`dangerouslySetInnerHTML`, `innerHTML`, equivalent) only with sanitized input; CSP present or its absence justified; auth tokens not in `localStorage` when httpOnly cookies are feasible; no open redirects from query params.106107## Output108109Produce exactly this report shape:110111```markdown112# Security Surface Audit — <project>113Date: <YYYY-MM-DD>114Scope: static, defensive review of code/config/CI. No exploit development115or live probing performed (requires explicit authorization).116117## Surfaces detected118| # | Surface | Evidence | Primary threat |119|---|---------|----------|----------------|120| 1 | Local loopback server | src/server.ts binds 127.0.0.1:4317 | any website open in the user's browser |121122## Findings123### [SS-1] CRITICAL — Loopback server: no Host/Origin validation124- Where: src/server.ts:42125- What: <one factual sentence>126- Class: DNS rebinding (cf. CVE-2024-28224, CVE-2025-49596)127- Fix: <smallest correct change>128129(repeat per finding, ordered by severity, IDs SS-1..SS-n)130131## Exposure summary132| Surface | Crit | High | Med | Low | Verdict |133|---------|------|------|-----|-----|---------|134| Loopback server | 1 | 2 | 1 | 0 | FIX-FIRST |135136Overall: <one line — worst surface drives the call>137```138139**Severity scale** — rate against the *detected* threat model, not an imagined one:140- **CRITICAL** — reachable compromise (RCE, token theft, cross-origin data read) on a detected surface with no mitigating control.141- **HIGH** — reachable with one realistic precondition (user visits a hostile page, installs a hostile registry item).142- **MEDIUM** — defense-in-depth gap or requires unusual configuration.143- **LOW** — hardening/hygiene.144- **INFO** — observation; no action required.145146**Verdict per surface:** `SHIP` (no Crit/High) · `FIX-FIRST` (any Crit/High) · `HARDEN-SOON` (Med-only debt worth scheduling).147148## Common mistakes149150- Skipping loopback servers as "local = safe", or auditing them with a public-web checklist. The loopback class has its own CVE history; use its own block.151- Accepting CORS `*` because "it's only a dev server" — that is exactly the esbuild advisory.152- Severity inflation: flagging a missing CSP on a loopback control UI as critical. Severity follows the surface's threat model.153- Hardcoding one stack's checks ("look for Hono middleware") instead of detecting the surface first; every named tool here is illustrative.154- Missing a surface entirely (the CI pipeline, the spawned git subprocess) because the audit fixated on the obvious server.155- Drifting into implementation tutorials — report findings and the smallest fix; point builders to **security-review**.156- Running exploit PoCs or live probes without explicit authorization.157- Substituting dependency-scanner output for the audit; advisories complement, not replace, posture review.