diff --git a/ROADMAP.md b/ROADMAP.md index e62c9e9f7..979964fa1 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -70,8 +70,8 @@ graph LR classDef done fill:#1f7a1f,stroke:#0d3d0d,color:#ffffff; classDef in_progress fill:#1f6feb,stroke:#0b3d91,color:#ffffff; classDef todo fill:#6e7781,stroke:#3d4248,color:#ffffff; - class sandbox_isolation,scanner_v2,analytics_dashboard,scanner_simplification,schema_deferred done; - class ux_audit,action_log_transparency,agent_scope_hardening,token_bench,telemetry_identity,telemetry_v7_churn in_progress; + class sandbox_isolation,scanner_v2,analytics_dashboard,scanner_simplification,schema_deferred,agent_scope_hardening done; + class ux_audit,action_log_transparency,token_bench,telemetry_identity,telemetry_v7_churn in_progress; class tpa_db,remote_access_tunnel,auto_routing_mode,tool_graph todo; ``` @@ -206,65 +206,6 @@ graph LR -
-🔵 Agent-token scope hardening: every MCP request authorized by its own scope (spec 105) — In progress · P1 - -> The Spec 104 cross-model review verified 'an agent token sees and uses only its granted servers, profile and tiers' against the code one surface at a time and found eight places where a legitimately narrow token could learn about or act on servers outside its grant: cached responses, set_profile and profile-URL responses, retrieve_tools metadata, direct-publication filtering, target-tier execution, aggregated prompts, per-server management ops, and refusal shapes. Spec 105 is the acceptance contract (19 astra rounds, ready-for-plan 2026-09-07). Five fix sessions ran in parallel from the review and MERGED 2026-09-08 (#1223 target tier, #1224 tail_log, #1225 set_profile, #1226 read_cache provenance, #1227 prompt owner + deleted-pin enumeration), each live-verified against a baseline binary and astra-reviewed to CLEAN. Each of those PR bodies carries a 'Follow-ups / Spec 105 gaps' checklist — the todo tasks below are those lists grouped by FR. Prerequisite for auto-routing-mode: Spec 104 FR-016 states the invariant these corrections make true. - -Spec: [105-agent-scope-hardening](./specs/105-agent-scope-hardening/) - -```mermaid -graph LR - scope_fix_target_tier["FR-009 (dispatch half): call_tool_* requires…"] - scope_fix_tail_log["FR-007 (name half): upstream_servers tail_log…"] - scope_fix_set_profile["FR-003: set_profile reports token ∩ profile,…"] - scope_fix_read_cache["FR-001: cached responses carry the producer's…"] - scope_fix_prompts_profile_url["FR-006 + FR-004 (deleted pin): aggregated pro…"] - scope_retrieve_tools["FR-005: retrieve_tools filters by scope BEFOR…"] - scope_direct_publication["FR-008: direct-surface definitions take owner…"] - scope_refusal_shapes["FR-010: scope-first refusal precedence; dispa…"] - scope_selectable_profile_predicate["FR-003/FR-004 remainder: selectable-profile p…"] - scope_cache_legacy_invalidation["FR-002 + FR-001 remainder: legacy/unstamped a…"] - scope_log_attribution["FR-007 remainder: per-record canonical log ow…"] - scope_target_identity_producers["FR-009 remainder: producer-side exact-name id…"] - scope_fix_stored_script_admin["FR-012: stored-script enumeration is administ…"] - scope_regression_suite["FR-011/FR-013/FR-014: two-fixture differentia…"] - - scope_retrieve_tools --> scope_refusal_shapes - scope_fix_set_profile --> scope_selectable_profile_predicate - scope_fix_prompts_profile_url --> scope_selectable_profile_predicate - scope_fix_read_cache --> scope_cache_legacy_invalidation - scope_fix_tail_log --> scope_log_attribution - scope_fix_target_tier --> scope_target_identity_producers - scope_retrieve_tools --> scope_regression_suite - scope_direct_publication --> scope_regression_suite - scope_refusal_shapes --> scope_regression_suite - - classDef done fill:#1f7a1f,stroke:#0d3d0d,color:#ffffff; - classDef todo fill:#6e7781,stroke:#3d4248,color:#ffffff; - class scope_fix_target_tier,scope_fix_tail_log,scope_fix_set_profile,scope_fix_read_cache,scope_fix_prompts_profile_url,scope_retrieve_tools,scope_direct_publication,scope_refusal_shapes,scope_selectable_profile_predicate,scope_cache_legacy_invalidation,scope_log_attribution,scope_target_identity_producers,scope_fix_stored_script_admin done; - class scope_regression_suite todo; -``` - -| Task | Status | Refs | -| --- | --- | --- | -| FR-009 (dispatch half): call_tool_* requires the TARGET tool's tier, fail closed on unresolved tiers; approval records keep exact ns:name identity | 🟢 Done | #1223 | -| FR-007 (name half): upstream_servers tail_log authorizes the server against effective scope before lookup, non-disclosing | 🟢 Done | #1224 | -| FR-003: set_profile reports token ∩ profile, selectable-profile predicate, non-selectable == nonexistent | 🟢 Done | #1225 | -| FR-001: cached responses carry the producer's authorization snapshot; read_cache and the REST cache branch refuse narrower readers | 🟢 Done | #1226 | -| FR-006 + FR-004 (deleted pin): aggregated prompts authorized by canonical registration owner; profile URL / set_profile stop enumerating on a deleted pin | 🟢 Done | #1227 | -| FR-005: retrieve_tools filters by scope BEFORE limiting; indexed counts, usage ranking, debug output and session risk computed over the authorized population only | 🟢 Done | #1325 | -| FR-008: direct-surface definitions take owner and tier from their own registration identity at every publication seam, both skew directions, full and deferred | 🟢 Done | #1326 | -| FR-010: scope-first refusal precedence; dispatch denials and 'available servers' never name hidden servers; describe_tool not-found and alias resolution computed over the authorized corpus | 🟢 Done | #1328 | -| FR-003/FR-004 remainder: selectable-profile predicate for UNPINNED tokens on /mcp/p/, /mcp/p, /mcp/p/ and set_profile; identical status+body across missing / deleted / not-selectable / pin-mismatch / no-profiles (#1225 + #1227 follow-up lists) | 🟢 Done | #1283 | -| FR-002 + FR-001 remainder: legacy/unstamped and internal (registry, guesser) cache entries refused for every caller and durably invalidated; monotone recursive provenance; existence-non-disclosing refusal on MCP and REST (#1226 follow-up list) | 🟢 Done | #1282 | -| FR-007 remainder: per-record canonical log ownership (a/b vs a_b share one file), filter-before-limit + authorized lines_returned, subject-bound OAuth-callback logging, canonical container ownership in Docker cleanup (#1224 follow-up list) | 🟢 Done | #1284 | -| FR-009 remainder: producer-side exact-name identity (checkToolApprovals / differential index collapse ns:erase to erase), direct-name dispatch + preflight share lookupToolApproval, unresolved/stale identity refuses scoped callers, full 54-cell acceptance tables (#1223 follow-up list) | 🟢 Done | #1279 | -| FR-012: stored-script enumeration is administrator-only (PR H0) | 🟢 Done | #1285 | -| FR-011/FR-013/FR-014: two-fixture differential oracle with sentinels across the applicability matrix, credential-authenticated HTTP matrix over every /mcp surface, admin p95 perf gate on the frozen 527-tool snapshot | ⚪ Todo | — | - -
-
🔵 Token-efficiency benchmark: measured savings, published results — In progress · P1 @@ -827,6 +768,63 @@ Spec: [102-schema-deferred](./specs/102-schema-deferred/) · PR: #1063
+
+🟢 Agent-token scope hardening: every MCP request authorized by its own scope (spec 105) — Done · P1 + +> The Spec 104 cross-model review verified 'an agent token sees and uses only its granted servers, profile and tiers' against the code one surface at a time and found eight places where a legitimately narrow token could learn about or act on servers outside its grant: cached responses, set_profile and profile-URL responses, retrieve_tools metadata, direct-publication filtering, target-tier execution, aggregated prompts, per-server management ops, and refusal shapes. Spec 105 is the acceptance contract (19 astra rounds, ready-for-plan 2026-09-07). Five fix sessions ran in parallel from the review and MERGED 2026-09-08 (#1223 target tier, #1224 tail_log, #1225 set_profile, #1226 read_cache provenance, #1227 prompt owner + deleted-pin enumeration), each live-verified against a baseline binary and astra-reviewed to CLEAN. Each of those PR bodies carries a 'Follow-ups / Spec 105 gaps' checklist — the todo tasks below are those lists grouped by FR. Prerequisite for auto-routing-mode: Spec 104 FR-016 states the invariant these corrections make true. + +Spec: [105-agent-scope-hardening](./specs/105-agent-scope-hardening/) + +```mermaid +graph LR + scope_fix_target_tier["FR-009 (dispatch half): call_tool_* requires…"] + scope_fix_tail_log["FR-007 (name half): upstream_servers tail_log…"] + scope_fix_set_profile["FR-003: set_profile reports token ∩ profile,…"] + scope_fix_read_cache["FR-001: cached responses carry the producer's…"] + scope_fix_prompts_profile_url["FR-006 + FR-004 (deleted pin): aggregated pro…"] + scope_retrieve_tools["FR-005: retrieve_tools filters by scope BEFOR…"] + scope_direct_publication["FR-008: direct-surface definitions take owner…"] + scope_refusal_shapes["FR-010: scope-first refusal precedence; dispa…"] + scope_selectable_profile_predicate["FR-003/FR-004 remainder: selectable-profile p…"] + scope_cache_legacy_invalidation["FR-002 + FR-001 remainder: legacy/unstamped a…"] + scope_log_attribution["FR-007 remainder: per-record canonical log ow…"] + scope_target_identity_producers["FR-009 remainder: producer-side exact-name id…"] + scope_fix_stored_script_admin["FR-012: stored-script enumeration is administ…"] + scope_regression_suite["FR-011/FR-013/FR-014: two-fixture differentia…"] + + scope_retrieve_tools --> scope_refusal_shapes + scope_fix_set_profile --> scope_selectable_profile_predicate + scope_fix_prompts_profile_url --> scope_selectable_profile_predicate + scope_fix_read_cache --> scope_cache_legacy_invalidation + scope_fix_tail_log --> scope_log_attribution + scope_fix_target_tier --> scope_target_identity_producers + scope_retrieve_tools --> scope_regression_suite + scope_direct_publication --> scope_regression_suite + scope_refusal_shapes --> scope_regression_suite + + classDef done fill:#1f7a1f,stroke:#0d3d0d,color:#ffffff; + class scope_fix_target_tier,scope_fix_tail_log,scope_fix_set_profile,scope_fix_read_cache,scope_fix_prompts_profile_url,scope_retrieve_tools,scope_direct_publication,scope_refusal_shapes,scope_selectable_profile_predicate,scope_cache_legacy_invalidation,scope_log_attribution,scope_target_identity_producers,scope_fix_stored_script_admin,scope_regression_suite done; +``` + +| Task | Status | Refs | +| --- | --- | --- | +| FR-009 (dispatch half): call_tool_* requires the TARGET tool's tier, fail closed on unresolved tiers; approval records keep exact ns:name identity | 🟢 Done | #1223 | +| FR-007 (name half): upstream_servers tail_log authorizes the server against effective scope before lookup, non-disclosing | 🟢 Done | #1224 | +| FR-003: set_profile reports token ∩ profile, selectable-profile predicate, non-selectable == nonexistent | 🟢 Done | #1225 | +| FR-001: cached responses carry the producer's authorization snapshot; read_cache and the REST cache branch refuse narrower readers | 🟢 Done | #1226 | +| FR-006 + FR-004 (deleted pin): aggregated prompts authorized by canonical registration owner; profile URL / set_profile stop enumerating on a deleted pin | 🟢 Done | #1227 | +| FR-005: retrieve_tools filters by scope BEFORE limiting; indexed counts, usage ranking, debug output and session risk computed over the authorized population only | 🟢 Done | #1325 | +| FR-008: direct-surface definitions take owner and tier from their own registration identity at every publication seam, both skew directions, full and deferred | 🟢 Done | #1326 | +| FR-010: scope-first refusal precedence; dispatch denials and 'available servers' never name hidden servers; describe_tool not-found and alias resolution computed over the authorized corpus | 🟢 Done | #1328 | +| FR-003/FR-004 remainder: selectable-profile predicate for UNPINNED tokens on /mcp/p/, /mcp/p, /mcp/p/ and set_profile; identical status+body across missing / deleted / not-selectable / pin-mismatch / no-profiles (#1225 + #1227 follow-up lists) | 🟢 Done | #1283 | +| FR-002 + FR-001 remainder: legacy/unstamped and internal (registry, guesser) cache entries refused for every caller and durably invalidated; monotone recursive provenance; existence-non-disclosing refusal on MCP and REST (#1226 follow-up list) | 🟢 Done | #1282 | +| FR-007 remainder: per-record canonical log ownership (a/b vs a_b share one file), filter-before-limit + authorized lines_returned, subject-bound OAuth-callback logging, canonical container ownership in Docker cleanup (#1224 follow-up list) | 🟢 Done | #1284 | +| FR-009 remainder: producer-side exact-name identity (checkToolApprovals / differential index collapse ns:erase to erase), direct-name dispatch + preflight share lookupToolApproval, unresolved/stale identity refuses scoped callers, full 54-cell acceptance tables (#1223 follow-up list) | 🟢 Done | #1279 | +| FR-012: stored-script enumeration is administrator-only (PR H0) | 🟢 Done | #1285 | +| FR-011/FR-013/FR-014: two-fixture differential oracle with sentinels across the applicability matrix, credential-authenticated HTTP matrix over every /mcp surface, admin p95 perf gate on the frozen 527-tool snapshot | 🟢 Done | #1332 | + +
+
🟢 Tray↔core decoupling: socket/REST API only, no config-file reads — Done · P2 @@ -888,7 +886,6 @@ graph LR | Web UI + macOS app UX audit | In progress | P0 | — | | | | Release qualification gate (auto-QA matrix blocks the tag) | In progress | P0 | — | [081-release-qa-gate](./specs/081-release-qa-gate/) | | | Action log / transparency — info at a glance | In progress | P1 | — | | | -| Agent-token scope hardening: every MCP request authorized by its own scope (spec 105) | In progress | P1 | 94/113 (83%) | [105-agent-scope-hardening](./specs/105-agent-scope-hardening/) | | | Token-efficiency benchmark: measured savings, published results | In progress | P1 | 62/64 (97%) | [103-token-bench](./specs/103-token-bench/) | | | Telemetry identity & data quality (machine_id + CI-filter hardening) | In progress | P1 | — | | | | Telemetry v7: honest funnel + churn instrumentation | In progress | P1 | — | [080-telemetry-v7-churn](./specs/080-telemetry-v7-churn/) | | @@ -913,6 +910,7 @@ graph LR | Registries — easier search + add-server | Done | P1 | 21/24 (88%) | [070-registry-easy-upstream-add](./specs/070-registry-easy-upstream-add/) | | | Scanner simplification (deterministic default, opt-in deep scan) | Done | P1 | 38/42 (90%) | [077-scanner-simplification](./specs/077-scanner-simplification/) | | | Deferred-schema serialization for the direct tools/list surface (spec 102) | Done | P1 | 89/89 (100%) | [102-schema-deferred](./specs/102-schema-deferred/) | #1063 | +| Agent-token scope hardening: every MCP request authorized by its own scope (spec 105) | Done | P1 | 94/113 (83%) | [105-agent-scope-hardening](./specs/105-agent-scope-hardening/) | | | Tray↔core decoupling: socket/REST API only, no config-file reads | Done | P2 | — | | | | Spec 107 server edition SSO front door hardened for real IdPs | Done | P2 | 126/126 (100%) | [107-server-edition-sso-hardening](./specs/107-server-edition-sso-hardening/) | | diff --git a/roadmap.yaml b/roadmap.yaml index fa7d68267..7565aec90 100644 --- a/roadmap.yaml +++ b/roadmap.yaml @@ -396,7 +396,7 @@ epics: - id: agent-scope-hardening title: "Agent-token scope hardening: every MCP request authorized by its own scope (spec 105)" - status: in_progress + status: done priority: P1 spec: specs/105-agent-scope-hardening depends_on: [schema-deferred] @@ -477,8 +477,9 @@ epics: depends_on: [] - id: scope-regression-suite title: "FR-011/FR-013/FR-014: two-fixture differential oracle with sentinels across the applicability matrix, credential-authenticated HTTP matrix over every /mcp surface, admin p95 perf gate on the frozen 527-tool snapshot" - status: todo + status: done priority: P2 + pr: "#1332" depends_on: [scope-retrieve-tools, scope-direct-publication, scope-refusal-shapes] - id: auto-routing-mode