# Threat Model

KiCad MCP Pro runs local automation against KiCad projects on behalf of an MCP client
(an AI agent). This document maps the data flows, the trust boundaries, and — for each
attack surface — the control and the test that verifies it. Controls that are only
recommended (not yet implemented) are called out as residual risks.

## Data flow and trust boundaries

```
MCP client (agent)  --tool calls-->  KiCad MCP Pro
                                       |  |  |
        argv list (no shell) ---------+  |  +--- file I/O (workspace-confined)
        kicad-cli subprocess             |
                                         +--- KiCad IPC (kipy) to a running KiCad
```

- **The MCP client is not automatically trusted.** Tool arguments (paths, names,
  net specs) are attacker-controllable. Use the smallest practical profile and the
  least-privileged operating mode.
- **The active project directory is the trusted I/O scope.** Reads/writes are confined
  to it (or the configured workspace root).
- **`kicad-cli` and the KiCad IPC endpoint are trusted local components**, configured by
  the operator (`KICAD_MCP_KICAD_CLI`, socket env). Whoever sets those env vars is in the
  trust base.
- **HTTP/bridge transports are off by default** and local-only unless explicitly exposed.

## Surfaces, controls, and verification

### 1. `kicad-cli` subprocess (command/argument injection)

- **Threat:** a tool argument (an output path, layer, variant, or net name) is
  interpreted as a shell command or an injected CLI flag.
- **Control:** the server **never invokes a shell.** Every `kicad-cli` call is
  `subprocess.run([binary, *args], shell=False)` with the operator-configured binary, so
  arguments are passed as discrete argv elements and are never shell-interpreted. There is
  no `os.system`, `os.popen`, `subprocess.getoutput`, or `shell=True` anywhere in `src/`.
- **Verification:** `tests/unit/test_security_controls.py::test_no_shell_execution_anywhere_in_src`
  scans the whole source tree, and `::test_kicad_cli_is_invoked_as_argv_list` asserts an
  injection-looking argument (`"; rm -rf /"`) reaches the CLI as a single literal argv
  element with `shell=False`.

### 2. Path traversal

- **Threat:** a client supplies `../../etc/passwd` or an absolute/UNC path to read or
  write outside the project.
- **Control:** `path_safety.resolve_under` / `config.resolve_within_project` resolve every
  path under the project root and raise `UnsafePathError` if it escapes; `relative_subpath`
  rejects absolute paths and `..` for output subdirectories; `reject_foreign_windows_path`
  blocks Windows drive/UNC paths on POSIX hosts.
- **Verification:** `tests/unit/test_security_controls.py` (traversal/absolute rejection)
  and the broader `tests/unit/test_path_safety.py` corpus (UNC, symlinks, spaces/unicode,
  long paths).

### 3. HTTP transport (auth, CORS, origin)

- **Threat:** a local web page or a leaked token drives the tool surface over HTTP.
- **Control:** Streamable HTTP is opt-in. When enabled, `_DashboardAuthMiddleware` enforces
  the optional bearer token (`_StaticTokenVerifier`, `required_scopes=["mcp"]`),
  `_OriginValidationMiddleware` checks the `Origin`, and `CORSMiddleware` restricts origins
  to the configured allow-list. The legacy HTTP+SSE routes are disabled by default.
- **Verification:** web-route and server-startup tests under `tests/unit/` exercise the
  auth/CORS/origin paths; transport contract is checked by `test_mcp_protocol_contract.py`.

### 4. Bridge daemon (remote pairing)

- **Threat:** an attacker brute-forces the bridge pairing code or floods the daemon.
- **Control:** the bridge is opt-in, binds locally, and requires a per-session pairing
  code (`secrets.token_hex`). Inbound messages are now rate-limited by a token bucket
  (`TokenBucket` in `bridge.py`): a general per-daemon budget blunts floods, and a
  much stricter pairing budget (small burst, then one attempt every five seconds)
  makes brute-forcing the 24-bit code infeasible. Exceeding either budget returns a
  JSON-RPC rate-limit error (`-32004`) without doing any work. Covered by
  `tests/unit/test_bridge_rate_limit.py`.
- **Residual risk:** the pairing code is still short (24-bit). Rate-limiting makes
  online brute force impractical, but **use a longer explicit `--code` and do not
  expose the bridge port to untrusted networks** until a longer default code ships.

## Supply chain and release integrity

- Dependabot/Renovate, CodeQL, Gitleaks, Trivy, Hadolint, Bandit, and pip-audit cover the
  main automated scan layers.
- Release artifacts are built by GitHub Actions and accompanied by an SBOM, Sigstore
  signatures, checksums, and GitHub artifact attestations, so an artifact is traceable to
  its source and workflow.

## Reporting

Report vulnerabilities privately through GitHub Security Advisories.
