Skip to content

Commit 14cb288

Browse files
authored
feat(security): Spec 101 US1 signed TPA bundle format and downgrade-proof loader (#1701)
Related #938 Spec 101 (tpa-db) User Story 1: an external TPA signature bundle is a signed, sequence-versioned artifact, verified before it is parsed and protected against downgrade. - internal/security/bundlesig: four-key Ed25519 sidecar grammar with total rejection and byte-level fixtures (contracts/sidecar-format.md). - Verify-before-parse, source-neutral Candidate pipeline in FR-008 order with the FR-011 ceilings (8 MiB, 2000 rules, 4 KiB, 5 s), no-follow descriptor opens on POSIX and Windows. - Per-authority (publisher/operator), epoch-scoped watermark, signature ratchet, pin and deny-list in a new BBolt bucket written in one transaction with the activation history; content-addressed last-known-good files anchored to <datadir>/tpa-lkg. - Rollback by full SHA-256 only, confined to the bundle directory; pin and deny-list; unpin; on-demand reload. - coverageOK and async scan-mode admission degrade on exactly the four FR-007/009a/009b conditions. - Config: require_signed_bundle (default off), tpa_active_root, tpa_operator_signers, tpa_operator_baseline, tpa_operator_epoch. - Surfaces: /api/v1/security/bundle{,/history,/rollback,/unpin,/reload}, mcpproxy security bundle, cmd/tpa-db keygen|sign|verify, Review/Home Web UI status and degraded banner, telemetry schema 15. - The publisher key set ships empty; operator-signed bundles are the signed path until a release adds a publisher key. - Docs: docs/features/tpa-bundle-signing.md, quickstart, configuration.
1 parent 0ca01c1 commit 14cb288

138 files changed

Lines changed: 15137 additions & 253 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitattributes‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ internal/profile/testdata/contract/*.json text eol=lf
4343
internal/httpapi/testdata/sc006_activity_recordsets.json text eol=lf
4444
cmd/mcpproxy/testdata/cli108/* text eol=lf
4545
internal/server/testdata/profiles_v3/*.json text eol=lf
46+
47+
# Spec 101 sidecar conformance fixtures are byte-exact (signatures cover exact
48+
# bytes, the 4096/4097-byte ceilings are measured on the file, and one fixture
49+
# deliberately ends in CRLF). Never convert line endings on checkout.
50+
internal/security/bundlesig/testdata/* -text

‎CLAUDE.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ tail -f ~/Library/Logs/mcpproxy/main.log # main log (macOS; Linux: ~/.mcpproxy/
146146
- **Windows installer**: [docs/github-actions-windows-wix-research.md](docs/github-actions-windows-wix-research.md). **Prerelease** (`next` branch + `v*-rc.*` tags, opt-in, off stable channels): [docs/prerelease-builds.md](docs/prerelease-builds.md).
147147

148148
## Recent Changes
149+
- 101-tpa-db US1 (tpa-db-format): Go 1.26, Vue 3 Web UI — external TPA bundles are signed artifacts: `<bundle>.sig` four-key Ed25519 sidecar (`internal/security/bundlesig`, pure, contract `specs/101-tpa-db/contracts/sidecar-format.md`), verify-before-parse source-neutral `Candidate` pipeline (`scanner/tpa_candidate.go`, FR-008 order, ceilings 8 MiB / 2000 rules / 4 KiB / 5 s via `candidate_open_{unix,windows}.go` no-follow descriptor opens), per-authority (publisher/operator) epoch-scoped watermark + ratchet + pin/deny-list in BBolt `tpa_bundle_state` (one tx with the history row) and content-addressed LKG files `<datadir>/tpa-lkg/`, full-digest rollback, `coverageOK` + async scan-mode admission degrade on the four FR-007/009a/009b conditions. Config: `require_signed_bundle` (default off), `tpa_active_root`, `tpa_operator_signers`, `tpa_operator_baseline`, `tpa_operator_epoch`. Surfaces: `GET/POST /api/v1/security/bundle{,/history,/rollback,/unpin,/reload}`, `mcpproxy security bundle …`, `cmd/tpa-db keygen|sign|verify`, telemetry schema 15 bundle keys. Publisher key set ships EMPTY. Docs: `docs/features/tpa-bundle-signing.md`. **No new dependencies.**
149150
- 118-auto-rescan-on-change: Go 1.26, small Web UI change — after a tool definition changes (or a tool is added after the baseline) in `manual`/`scan` trust mode the in-process TPA scan of its current definition is recorded as hold evidence (`held_reason: awaiting_review` + `held_verdict: clean` for a clean verdict; findings/coverage as before) and a debounced informational server rescan (`scan_informational.go`, fed by `Runtime.AddToolDefinitionsChangedObserver`) brings the review scan line back to "current". Switch: `security.auto_rescan_on_change` (default on, env `MCPPROXY_AUTO_RESCAN_ON_CHANGE`) AND `auto_baseline_scan`. RT-02 untouched: a clean verdict never approves a change. Docs: `docs/features/security-quarantine.md` "Automatic informational baseline scan". **No new dependencies.**
150151
- quarantine-approval-pilot: Go 1.26, tests + CI only — independent policy model `internal/qapilot` (no production imports), deterministic scenario inventory S01–S26 + 896-cell dispatch table + REST authz table + counting-upstream dispatch check, check ledger (`QAPILOT_REPORT_DIR`) gated by `go run ./cmd/qapilot report`, versioned mutation set `tests/qapilot/mutations.json` run by `qapilot mutate` in disposable worktrees; workflow `.github/workflows/quarantine-pilot.yml` (3 PR checks + scheduled rapid exploration). Docs: `docs/development/quarantine-approval-pilot.md`. **No new dependencies.**
151152
- 117-review-bound-approvals PR-3 (#1565): Go 1.26, backend-only — every upstream `tools/list` carries a `managed.InventoryCapture{Seq, Epoch, Client}` (process-global sequence stamped by the flight leader; `ListToolsCaptured`/`ListToolsFreshCaptured`; `Client.Retire`/`CaptureIsCurrent`; the manager retires replaced/removed clients; `Manager.DiscoverToolsReportCaptured` gives the sweep one capture per server). The runtime admits each inventory under the server's approval lock (`admitInventoryHeld`, per-server `applied`/`definitions` in `toolApprovalServerLock`): an older, retired or reconnected capture writes nothing (records, index, last-good snapshot + its capture, StateView); a lenient empty result is an observation fence; the approval reindex/sweep fallback re-apply the snapshot only while its capture is current, else re-list/skip. Docs: `docs/features/security-quarantine.md` "Tool lists are applied in the order they were captured". **No new dependencies.**

‎ROADMAP.md‎

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ graph LR
8383
classDef in_review fill:#9a6700,stroke:#5c3d00,color:#ffffff;
8484
classDef todo fill:#6e7781,stroke:#3d4248,color:#ffffff;
8585
class sandbox_isolation,scanner_v2,analytics_dashboard,scanner_simplification,schema_deferred,agent_scope_hardening,upstream_resilience,mcp_credential_lifecycle,config_commit_consistency,review_bound_approvals,quarantine_approval_pilot done;
86-
class ux_audit,action_log_transparency,token_bench,telemetry_identity,telemetry_v7_churn,profiles_v3 in_progress;
86+
class ux_audit,action_log_transparency,tpa_db,token_bench,telemetry_identity,telemetry_v7_churn,profiles_v3 in_progress;
8787
class pending_call_outcome in_review;
88-
class tpa_db,remote_access_tunnel,auto_routing_mode,tool_graph todo;
88+
class remote_access_tunnel,auto_routing_mode,tool_graph todo;
8989
```
9090

9191
**Independent epics** (16) — no cross-epic prerequisites; each stands alone:
@@ -220,6 +220,36 @@ graph LR
220220

221221
</details>
222222

223+
<details>
224+
<summary>🔵 tpa-db: versioned TPA signature database for the offline scanner — In progress · P1</summary>
225+
226+
> Vision pillar 'feel protected': the deterministic detect engine (Spec 076/077) ships with built-in checks but no updatable knowledge of in-the-wild Tool Poisoning Attacks. Build a versioned, offline-first signature/pattern database (known TPA campaigns, malicious phrase corpora, IoC hashes) that the engine consumes — bundled with the binary, refreshable out-of-band, community-contributable, and guarded by the existing scan-eval recall/FP CI gate. SPEC STAGE: specs/101-tpa-db MERGED as PR #1028 on 2026-08-27 (kept in this note, not in pr:, because pr: is implementation evidence and a docs(specs) merge is not that); 2026-10-20: US1 (tpa-db-format: signed four-key Ed25519 sidecar, verify-before-parse loader, per-authority anti-downgrade state, full-digest rollback, coverage degrade) shipped as #1701; US2 corpus in a sibling session; US3 refresh waits on Spec 087.
227+
228+
Spec: [101-tpa-db](./specs/101-tpa-db/)
229+
230+
```mermaid
231+
graph LR
232+
tpa_db_format["Signature DB format + loader (versioned, sign…"]
233+
tpa_db_corpus["Seed corpus: catalog known public TPA campaig…"]
234+
tpa_db_refresh["Out-of-band refresh (offline-friendly: manual…"]
235+
236+
tpa_db_format --> tpa_db_corpus
237+
tpa_db_format --> tpa_db_refresh
238+
239+
classDef done fill:#1f7a1f,stroke:#0d3d0d,color:#ffffff;
240+
classDef todo fill:#6e7781,stroke:#3d4248,color:#ffffff;
241+
class tpa_db_format done;
242+
class tpa_db_corpus,tpa_db_refresh todo;
243+
```
244+
245+
| Task | Status | Refs |
246+
| --- | --- | --- |
247+
| Signature DB format + loader (versioned, signed, bundled default) | 🟢 Done | #1701 |
248+
| Seed corpus: catalog known public TPA campaigns/patterns into the DB | ⚪ Todo | — |
249+
| Out-of-band refresh (offline-friendly: manual file drop + optional fetch), eval-gated | ⚪ Todo | — |
250+
251+
</details>
252+
223253
<details>
224254
<summary>🔵 Token-efficiency benchmark: measured savings, published results — In progress · P1</summary>
225255

@@ -445,34 +475,6 @@ PR: #1536
445475

446476
</details>
447477

448-
<details>
449-
<summary>⚪ tpa-db: versioned TPA signature database for the offline scanner — Todo · P1</summary>
450-
451-
> Vision pillar 'feel protected': the deterministic detect engine (Spec 076/077) ships with built-in checks but no updatable knowledge of in-the-wild Tool Poisoning Attacks. Build a versioned, offline-first signature/pattern database (known TPA campaigns, malicious phrase corpora, IoC hashes) that the engine consumes — bundled with the binary, refreshable out-of-band, community-contributable, and guarded by the existing scan-eval recall/FP CI gate. SPEC STAGE: specs/101-tpa-db MERGED as PR #1028 on 2026-08-27 (kept in this note, not in pr:, because pr: is implementation evidence and a docs(specs) merge is not that); no implementation has started, so the epic and every child task stay todo (the pr: link is the spec, not the build).
452-
453-
Spec: [101-tpa-db](./specs/101-tpa-db/)
454-
455-
```mermaid
456-
graph LR
457-
tpa_db_format["Signature DB format + loader (versioned, sign…"]
458-
tpa_db_corpus["Seed corpus: catalog known public TPA campaig…"]
459-
tpa_db_refresh["Out-of-band refresh (offline-friendly: manual…"]
460-
461-
tpa_db_format --> tpa_db_corpus
462-
tpa_db_format --> tpa_db_refresh
463-
464-
classDef todo fill:#6e7781,stroke:#3d4248,color:#ffffff;
465-
class tpa_db_format,tpa_db_corpus,tpa_db_refresh todo;
466-
```
467-
468-
| Task | Status | Refs |
469-
| --- | --- | --- |
470-
| Signature DB format + loader (versioned, signed, bundled default) | ⚪ Todo | — |
471-
| Seed corpus: catalog known public TPA campaigns/patterns into the DB | ⚪ Todo | — |
472-
| Out-of-band refresh (offline-friendly: manual file drop + optional fetch), eval-gated | ⚪ Todo | — |
473-
474-
</details>
475-
476478
<details>
477479
<summary>⚪ Auto routing mode: budget-fitted tool surface per session (spec 104) — Todo · P1</summary>
478480

@@ -1006,6 +1008,7 @@ graph LR
10061008
| Web UI + macOS app UX audit | In progress | P0 | — | | |
10071009
| Release qualification gate (auto-QA matrix blocks the tag) | In progress | P0 | 4/4 (100%) | [081-release-qa-gate](./specs/081-release-qa-gate/) | |
10081010
| Action log / transparency — info at a glance | In progress | P1 | — | | |
1011+
| tpa-db: versioned TPA signature database for the offline scanner | In progress | P1 | 30/30 (100%) | [101-tpa-db](./specs/101-tpa-db/) | |
10091012
| Token-efficiency benchmark: measured savings, published results | In progress | P1 | 62/64 (97%) | [103-token-bench](./specs/103-token-bench/) | |
10101013
| Telemetry identity & data quality (machine_id + CI-filter hardening) | In progress | P1 | — | | |
10111014
| Telemetry v7: honest funnel + churn instrumentation | In progress | P1 | — | [080-telemetry-v7-churn](./specs/080-telemetry-v7-churn/) | |
@@ -1015,7 +1018,6 @@ graph LR
10151018
| Planning/docs truth automation | In progress | P2 | — | | |
10161019
| Discovery-quality eval harness (Spec 065 second half) | In progress | P3 | — | [065-evaluation-foundation](./specs/065-evaluation-foundation/) | |
10171020
| Spec 114 typed pending-call outcome + call correlation for long-running stdio tool calls | In review | P2 | — | | #1536 |
1018-
| tpa-db: versioned TPA signature database for the offline scanner | Todo | P1 | — | [101-tpa-db](./specs/101-tpa-db/) | |
10191021
| Auto routing mode: budget-fitted tool surface per session (spec 104) | Todo | P1 | — | [104-auto-routing-mode](./specs/104-auto-routing-mode/) | |
10201022
| Windows native tray app `MCP-43` | Todo | P2 | — | | |
10211023
| Remote access tunnel (feature-flagged MVP, spec 089) | Todo | P2 | — | [089-remote-access-tunnel](./specs/089-remote-access-tunnel/) | |
@@ -1156,7 +1158,7 @@ Legend: `shipped` ≥95% checked · `in-flight` 1–94% · `drafted` 0% · `—`
11561158
| [098-tools-preflight](./specs/098-tools-preflight/) | `in-flight` | 28/33 (85%) |
11571159
| [099-describe-check-mode](./specs/099-describe-check-mode/) | `in-flight` | 9/10 (90%) |
11581160
| [100-prompt-rugpull-baseline](./specs/100-prompt-rugpull-baseline/) | — | — |
1159-
| [101-tpa-db](./specs/101-tpa-db/) | — | — |
1161+
| [101-tpa-db](./specs/101-tpa-db/) | `shipped` | 30/30 (100%) |
11601162
| [102-schema-deferred](./specs/102-schema-deferred/) | `shipped` | 89/89 (100%) |
11611163
| [103-token-bench](./specs/103-token-bench/) | `shipped` | 62/64 (97%) |
11621164
| [104-auto-routing-mode](./specs/104-auto-routing-mode/) | — | — |
@@ -1172,3 +1174,5 @@ Legend: `shipped` ≥95% checked · `in-flight` 1–94% · `drafted` 0% · `—`
11721174
| [115-mcp-credential-lifecycle](./specs/115-mcp-credential-lifecycle/) | `shipped` | 86/87 (99%) |
11731175
| [116-config-commit-consistency](./specs/116-config-commit-consistency/) | `in-flight` | 81/124 (65%) |
11741176
| [117-review-bound-approvals](./specs/117-review-bound-approvals/) | `in-flight` | 46/50 (92%) |
1177+
| [118-auto-rescan-on-change](./specs/118-auto-rescan-on-change/) | — | — |
1178+
| [118-pin-visibility-boundary](./specs/118-pin-visibility-boundary/) | — | — |

0 commit comments

Comments
 (0)