# Security Surface Audit

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

- Skill: `b4r7x/security-surface-audit` (Agent Skill)
- Install (CLI): `npx skillmds@latest add b4r7x/security-surface-audit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/b4r7x/security-surface-audit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: b4r7x (https://skillmd.com/u/b4r7x)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/b4r7x/security-surface-audit

---


# 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

1. **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.

2. **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.)

3. **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.

4. **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)?

5. **Severity-rate every finding** (scale in Output) and cite the CVE class where one exists — named prior art makes findings credible and non-hypothetical.

6. **Emit the report** in the exact Output shape. State the static/defensive scope line verbatim.

7. **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:

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

