Skip to content

Commit 5b0cfbe

Browse files
authored
docs(security): Spec 077 — scanner simplification (spec/plan/tasks) (#784)
* docs(security): Spec 077 scanner-simplification (spec/plan/tasks) Planning package for making the deterministic offline detect engine the always-on default scanner and demoting the Docker scanners + source extraction to an opt-in deep scan that never blocks or degrades the baseline verdict, with a single unified report. - specs/077-scanner-simplification/spec.md: 4 user stories, 21 FRs, 8 SCs - plan.md: constitution check (all 6 principles), structure, complexity tracking - research.md: 8 grounded decisions (D1-D8) + alternatives - data-model.md + contracts/: unified report + security config schema - quickstart.md: 7 verification scenarios - CLAUDE.md: Recent Changes entry (speckit agent-context) Docs/spec-only; no code touched. Implementation follows via per-story PRs. Related: Spec 077 (specs/077-scanner-simplification) * chore(roadmap): regenerate ROADMAP.md to include Spec 077 roadmap.yaml (from main via #785) references specs/077; regenerate the rendered view so the per-spec badge (0/42, drafted) and the epic status row reflect the spec landing in this PR. Keeps the roadmap-up-to-date CI check green. Related: Spec 077 (specs/077-scanner-simplification)
1 parent 376d958 commit 5b0cfbe

11 files changed

Lines changed: 947 additions & 1 deletion

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,5 @@ tail -f ~/Library/Logs/mcpproxy/main.log # main log (macOS; Linux: ~/.mcpproxy/
157157
- **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).
158158

159159
## Recent Changes
160+
- 077-scanner-simplification: Added Go 1.24 (backend/core), TypeScript 5.9 / Vue 3.5 (frontend Web UI) + Existing only — `internal/security/detect` (stdlib + `golang.org/x/text/unicode/norm`, already an indirect dep), `internal/security/scanner`, BBolt (scanner records + tool approvals), Bleve (index, untouched), zap (logging). **No new third-party dependency.**
160161
- 076-deterministic-tool-scanner: Added Go 1.24 + stdlib only for detection (`unicode`, `unicode/utf8`, `encoding/base64`, `encoding/hex`, `regexp`); `golang.org/x/text/unicode/norm` (already an indirect dep via x/text) for NFKC; existing `internal/security/patterns/`, `internal/security/scanner/`, `internal/runtime/tool_quarantine.go`. No new third-party dependency.

ROADMAP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ graph TD
135135

136136
| Epic | Status | Assignee | Priority | Progress | Spec | PR |
137137
| --- | --- | --- | --- | --- | --- | --- |
138-
| Scanner simplification (deterministic default, opt-in deep scan) | In progress | unassigned | P1 | | [077-scanner-simplification](./specs/077-scanner-simplification/) | |
138+
| Scanner simplification (deterministic default, opt-in deep scan) | In progress | unassigned | P1 | 0/42 (0%) | [077-scanner-simplification](./specs/077-scanner-simplification/) | |
139139
| Windows native tray app `MCP-43` | In review | BackendEngineer | P2 | 25/60 (42%) | [002-windows-installer](./specs/002-windows-installer/) | |
140140
| Web UI + macOS app UX audit | Todo | unassigned | P0 || [064-glass-cockpit](./specs/064-glass-cockpit/) | |
141141
| Action log / transparency — info at a glance | Todo | unassigned | P0 | 63/66 (95%) | [024-expand-activity-log](./specs/024-expand-activity-log/) | |
@@ -232,3 +232,4 @@ Legend: `shipped` ≥95% checked · `in-flight` 1–94% · `drafted` 0% · `—`
232232
| [074-discovery-intervals](./specs/074-discovery-intervals/) | `drafted` | 0/19 (0%) |
233233
| [075-macos-tcc-connect](./specs/075-macos-tcc-connect/) | `in-flight` | 11/30 (37%) |
234234
| [076-deterministic-tool-scanner](./specs/076-deterministic-tool-scanner/) | `in-flight` | 22/24 (92%) |
235+
| [077-scanner-simplification](./specs/077-scanner-simplification/) | `drafted` | 0/42 (0%) |
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Specification Quality Checklist: Scanner Simplification — Deterministic Default, Opt-In Deep Scan
2+
3+
**Purpose**: Validate specification completeness and quality before proceeding to planning
4+
**Created**: 2026-06-30
5+
**Feature**: [spec.md](../spec.md)
6+
7+
## Content Quality
8+
9+
- [x] No implementation details (languages, frameworks, APIs)
10+
- [x] Focused on user value and business needs
11+
- [x] Written for non-technical stakeholders
12+
- [x] All mandatory sections completed
13+
14+
## Requirement Completeness
15+
16+
- [x] No [NEEDS CLARIFICATION] markers remain
17+
- [x] Requirements are testable and unambiguous
18+
- [x] Success criteria are measurable
19+
- [x] Success criteria are technology-agnostic (no implementation details)
20+
- [x] All acceptance scenarios are defined
21+
- [x] Edge cases are identified
22+
- [x] Scope is clearly bounded
23+
- [x] Dependencies and assumptions identified
24+
25+
## Feature Readiness
26+
27+
- [x] All functional requirements have clear acceptance criteria
28+
- [x] User scenarios cover primary flows
29+
- [x] Feature meets measurable outcomes defined in Success Criteria
30+
- [x] No implementation details leak into specification
31+
32+
## Notes
33+
34+
- The four design decisions (default-deterministic approach, curated hard-tier phrase posture, opt-in deep scan, single unified report) were resolved during brainstorming and carried into the spec, so no [NEEDS CLARIFICATION] markers were required.
35+
- Success criteria SC-003/SC-004 reference an "evaluation corpus" as a measurement instrument (a measurable outcome), not an implementation detail.
36+
- One deliberate, documented posture change exists (FR-004 / Edge Cases): some phrases that legacy rules hard-blocked may become review-only unless included in the curated hard-tier set. This is a security-posture decision, not an ambiguity.
37+
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`. All items pass.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://mcpproxy.app/schemas/077/scan-report.json",
4+
"title": "ScanReport",
5+
"description": "Unified per-server security scan report. Baseline verdict is independent of deep-scan availability (Spec 077).",
6+
"type": "object",
7+
"required": ["server", "status", "findings", "deep_scan"],
8+
"additionalProperties": true,
9+
"properties": {
10+
"server": { "type": "string", "description": "Server name." },
11+
"status": {
12+
"type": "string",
13+
"enum": ["clean", "warning", "dangerous"],
14+
"description": "Verdict derived from BASELINE findings only. No 'degraded'/'failed' from deep-scan gaps (FR-014)."
15+
},
16+
"risk_score": { "type": "number", "minimum": 0 },
17+
"scanned_at": { "type": "string", "format": "date-time" },
18+
"findings": {
19+
"type": "array",
20+
"description": "Merged + deduplicated across all scanners that ran. No (rule_id, location) duplicates (FR-010/FR-011).",
21+
"items": { "$ref": "#/$defs/finding" }
22+
},
23+
"deep_scan": { "$ref": "#/$defs/deepScanDescriptor" }
24+
},
25+
"$defs": {
26+
"finding": {
27+
"type": "object",
28+
"required": ["rule_id", "location", "severity", "tier", "sources"],
29+
"additionalProperties": true,
30+
"properties": {
31+
"rule_id": { "type": "string" },
32+
"location": { "type": "string", "description": "server:tool; dedup key with rule_id." },
33+
"severity": { "type": "string", "enum": ["info", "low", "medium", "high", "critical"] },
34+
"tier": { "type": "string", "enum": ["hard", "soft"], "description": "Only hard baseline findings gate approval (FR-021)." },
35+
"threat_type": { "type": "string" },
36+
"confidence": { "type": "number", "minimum": 0, "description": "Higher when independent sources agree (FR-012)." },
37+
"sources": {
38+
"type": "array",
39+
"minItems": 1,
40+
"items": { "type": "string" },
41+
"description": "Contributing scanner ids; all sources of a merged finding."
42+
},
43+
"message": { "type": "string" }
44+
}
45+
},
46+
"deepScanDescriptor": {
47+
"type": "object",
48+
"required": ["enabled", "ran", "available"],
49+
"additionalProperties": false,
50+
"description": "Informational only; MUST NOT influence status (FR-008).",
51+
"properties": {
52+
"enabled": { "type": "boolean" },
53+
"ran": { "type": "boolean" },
54+
"available": { "type": "boolean" },
55+
"scanners_failed": {
56+
"type": "array",
57+
"items": {
58+
"type": "object",
59+
"required": ["id", "reason"],
60+
"properties": {
61+
"id": { "type": "string" },
62+
"reason": { "type": "string" }
63+
}
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://mcpproxy.app/schemas/077/security-config.json",
4+
"title": "SecurityConfig (Spec 077 delta)",
5+
"description": "The `security` config block after Spec 077. Shows the new deep_scan group, the removed key, and back-compat aliases that migrate on load.",
6+
"type": "object",
7+
"additionalProperties": true,
8+
"properties": {
9+
"deep_scan": {
10+
"type": "object",
11+
"description": "Opt-in heavy-scan layer. Default OFF. When disabled, only the in-process deterministic baseline runs (FR-006).",
12+
"additionalProperties": false,
13+
"properties": {
14+
"enabled": { "type": "boolean", "default": false, "description": "Master opt-in for Docker scanners + source extraction." },
15+
"fetch_package_source": { "type": ["boolean", "null"], "default": true, "description": "Absorbs the deprecated top-level scanner_fetch_package_source." },
16+
"disable_no_new_privileges": { "type": "boolean", "default": false, "description": "Absorbs deprecated scanner_disable_no_new_privileges (snap/AppArmor escape hatch)." },
17+
"scanners": { "type": "array", "items": { "type": "string" }, "default": [], "description": "Optional per-scanner enable list under the umbrella." }
18+
}
19+
},
20+
"scan_timeout_default": { "type": "string", "description": "Unchanged." },
21+
"integrity_check_interval": { "type": "string", "description": "Unchanged." }
22+
},
23+
"x-migration": {
24+
"removed": ["auto_scan_quarantined"],
25+
"aliases": {
26+
"scanner_fetch_package_source": "deep_scan.fetch_package_source",
27+
"scanner_disable_no_new_privileges": "deep_scan.disable_no_new_privileges"
28+
},
29+
"note": "Old configs load unchanged: aliases map on load with identical effect; auto_scan_quarantined is ignored if present (FR-016/FR-017/SC-007)."
30+
},
31+
"x-unchanged": {
32+
"quarantine_enabled": "global tri-state, default true — out of scope",
33+
"auto_approve_tool_changes": "per-server, out of scope"
34+
}
35+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Phase 1 Data Model: Scanner Simplification
2+
3+
Entities are described at the domain level and mapped to the existing Go types they
4+
extend (this is a refactor, not a greenfield schema). Field names in code may differ
5+
slightly; the contract JSON in `./contracts/` is authoritative for wire shapes.
6+
7+
---
8+
9+
## ScanReport (per server)
10+
11+
The single consolidated result for one server's tool set. Extends the existing
12+
`ScanSummary` / aggregated report.
13+
14+
| Field | Type | Notes |
15+
|-------|------|-------|
16+
| `server` | string | Server name. |
17+
| `status` | enum `clean` \| `warning` \| `dangerous` | **Derived from baseline findings ONLY** (FR-014). No `degraded`/`failed` from deep-scan gaps. |
18+
| `risk_score` | number | From `CalculateRiskScore` over the merged, deduped findings (consensus-weighted). |
19+
| `findings` | Finding[] | Merged + deduplicated across all scanners that ran (FR-010/FR-011). |
20+
| `deep_scan` | DeepScanDescriptor | Separate availability dimension (FR-008). |
21+
| `scanned_at` | timestamp | When the scan settled. |
22+
23+
**Validation / rules**:
24+
- `status` MUST be a function of baseline findings only; deep findings never change it.
25+
- `findings` MUST contain no `(rule_id, location)` duplicates.
26+
- A `dangerous` status MUST correspond to at least one hard-tier baseline finding.
27+
28+
**State**: A report is produced per scan and replaces the prior report for that
29+
server. It is emitted to clients via a single settled event (see below).
30+
31+
---
32+
33+
## Finding
34+
35+
One detected issue. Extends the existing `ScanFinding`.
36+
37+
| Field | Type | Notes |
38+
|-------|------|-------|
39+
| `rule_id` | string | Detection rule / check id (e.g. `phrase_injection`, `unicode.hidden`). |
40+
| `location` | string | `server:tool` (detect vocabulary), the dedup key with `rule_id`. |
41+
| `severity` | enum `info` \| `low` \| `medium` \| `high` \| `critical` | User-readable severity (FR-013). |
42+
| `tier` | enum `hard` \| `soft` | Hard → contributes to blocking verdict; soft → review-only. |
43+
| `threat_type` | string | Classified category; consensus match key with `location`. |
44+
| `confidence` | number | Raised when multiple independent sources agree (FR-012). |
45+
| `sources` | string[] | Contributing scanner ids (e.g. `tpa-descriptions`, `cisco-mcp-scanner`). ≥1. |
46+
| `signals` | Signal[] | Detect-engine signals (present for baseline findings; empty for external). |
47+
| `message` | string | Human-readable description. |
48+
49+
**Validation / rules**:
50+
- `sources` MUST be non-empty; a merged finding lists all contributing sources.
51+
- `confidence` for a finding agreed by N distinct sources MUST exceed the
52+
single-source confidence (monotonic in consensus).
53+
- Only `tier == hard` baseline findings gate approval (FR-021).
54+
55+
---
56+
57+
## DeepScanDescriptor
58+
59+
Informational status of the opt-in layer. New object on the report.
60+
61+
| Field | Type | Notes |
62+
|-------|------|-------|
63+
| `enabled` | bool | From `security.deep_scan.enabled`. |
64+
| `ran` | bool | Whether any deep scanner executed this scan. |
65+
| `available` | bool | False when Docker/source-extraction/prereqs are unavailable. |
66+
| `scanners_failed` | { id, reason }[] | Per-scanner best-effort failures (e.g. AppArmor/snap, extraction failure). Informational only. |
67+
68+
**Rules**: This object MUST NOT influence `ScanReport.status`. When `enabled=false`,
69+
`ran=false`, `available=false`, `scanners_failed=[]`.
70+
71+
---
72+
73+
## SecurityConfig (config surface changes)
74+
75+
Under `security` in `mcp_config.json`. Extends the existing `SecurityConfig`.
76+
77+
| Field | Type | Default | Notes |
78+
|-------|------|---------|-------|
79+
| `deep_scan.enabled` | bool | `false` | Master opt-in for the heavy layer (FR-006). |
80+
| `deep_scan.fetch_package_source` | *bool | `true` (within deep scan) | Absorbs `scanner_fetch_package_source`. |
81+
| `deep_scan.disable_no_new_privileges` | bool | `false` | Absorbs `scanner_disable_no_new_privileges` (snap/AppArmor escape hatch). |
82+
| `deep_scan.scanners` | string[] | `[]` | Optional per-scanner enable list under the umbrella. |
83+
| ~~`auto_scan_quarantined`~~ || removed | Orphaned/never-consumed (FR-016). |
84+
85+
**Migration (FR-017 / SC-007)**: On load, top-level `scanner_fetch_package_source`
86+
and `scanner_disable_no_new_privileges` map into `deep_scan.*` with identical effect;
87+
`auto_scan_quarantined` is ignored if present. Old configs load without edits.
88+
89+
**Unchanged**: `quarantine_enabled` (global), `auto_approve_tool_changes` (per-server),
90+
and all tool-approval hashing/state (Spec 032) — out of scope (FR-019).
91+
92+
---
93+
94+
## BundledScanner registry defaults (FR-018)
95+
96+
| Scanner | Default `enabled` |
97+
|---------|-------------------|
98+
| `tpa-descriptions` (in-process detect engine) | **true** |
99+
| `cisco-mcp-scanner`, `mcp-ai-scanner`, `mcp-scan`, `nova-proximity`, `ramparts`, `semgrep-mcp`, `trivy-mcp` | **false** |
100+
101+
Docker scanners only run when `deep_scan.enabled=true` AND individually enabled.

0 commit comments

Comments
 (0)