- SRV-02: schema_handler applies the same is_internal_op -> 404 pre-check as /call, /batch, /subscribe, /publish (tested unauthenticated, anonymous-token, unauthorized-identity) - PRJ-06: MCP schema tool runs the symmetric pre-check (NOT_FOUND for internal, FORBIDDEN for ACL-denied) before dispatch; enshrining test fixed - GW-02: /search + /schema carry Cache-Control: no-store and Vary: Authorization on success and error responses Verification: cargo test 270 passed; --all-features 337+9+6+8+10 passed; clippy -D warnings clean (default + --all-features); fmt clean.
4.6 KiB
4.6 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| review-001-schema-internal-visibility | Enforce Internal-op invisibility on GET /schema (SRV-02, PRJ-06, GW-02) | completed | narrow | medium | component | implementation |
|
Description
Review 001 findings SRV-02 + PRJ-06 + GW-02 — three faces of one visibility gap on the discovery axis:
- SRV-02 (
src/gateway/routes.rs:120-137):schema_handlerruns onlyaccess_check_for_op; it skips theis_internal_oppre-check that/call,/batch,/subscribe,/publishall apply. Internal ops carryAccessControl::default(), so an unauthenticated caller can fetch the full spec (visibility, schemas,access_control) of an op it cannot call.POST /callon the same op correctly 404s (tested);GET /schemadoes not — the invisibility invariant (ADR-015 §2) is defeated on the discovery axis. - PRJ-06 (
src/adapters/to_mcp.rs:148-174): the MCPschematool has the identical gap — no per-op pre-check, full spec includingrequired_scopesreturned for forbidden ops. The testschema_returns_full_operation_specenshrines the leak. - GW-02:
/searchand/schemaare per-identity GETs with noCache-Control: no-store/Vary: Authorization, so shared caches may serve caller A's filtered response to caller B.
Fix all three in one pass since they share the pre-check logic and test
fixtures: add the 404 guard to schema_handler; run the same
access_check_for_op pre-check in the MCP schema tool (or filter the
returned spec per identity) and fix the enshrining test; add cache headers
to the two per-identity GETs.
Acceptance Criteria
GET /schema?name=<internal>→ 404 unauthenticated, for an unauthorized identity, and for an anonymous identity (test)- MCP
schematool denies/404s unauthorized ops symmetrically with HTTP/schema(test); enshrining test fixed /search+/schemaresponses carryCache-Control: no-store(andVary: Authorizationwhere a token can change the body)cargo testandcargo clippy --all-targets -- -D warningspasscargo test --all-featurespasses (the MCP half is feature-gated)
References
- docs/reviews/001-initial-implementation-review.md (SRV-02, PRJ-06, GW-02)
- docs/architecture/decisions/015-privilege-model-and-authority-context.md
- docs/architecture/decisions/041-mcp-tool-gateway-pattern.md
Notes
- GW-02 headers are applied to all responses the two GET routes emit (200 and denial paths alike) — a 401/403/404 response is equally caller-specific and must not be cached.
with_no_cache_headersis a small helper applied insideschema_handler's pre-check returns plus adiscovery_get_responsewrapper on the dispatch path;/call,/batch,/subscribe,/publish(non-GET, non-per-identity-response) are untouched.- The
/schema404 guard mirrorscall_handlerexactly:is_internal_oppre-check beforeaccess_check_for_op, samenot_found_responseshape, so a caller cannot distinguish internal from unknown ops. - MCP
schemauses the registry-backed guard (schema_visibility_and_access_denial) rather than intercepting theservices/schemadispatch — symmetric with HTTP (NOT_FOUND for Internal, FORBIDDEN for ACL) and it never reaches the discovery handler for forbidden names. - Notable:
registry.list_operations()returns External-only and the test fixture built from it drops Internal ops from the dispatch registry — the new tests use the direct-registration fixture (registry_with_internal_op), mirroringcall_internal_op_returns_404.
Summary
Fixed the three faces of the Internal-op invisibility gap (review-001 SRV-02, PRJ-06, GW-02):
- SRV-02:
schema_handlernow applies the sameis_internal_op→ 404 pre-check as/call,/batch,/subscribe,/publish; tests cover unauthenticated, anonymous-token, and unauthorized-identity callers. - PRJ-06: the MCP
schematool runs the symmetric pre-check (NOT_FOUNDforVisibility::Internal,FORBIDDENfor ACL-denied) before dispatching toservices/schema; the enshriningschema_returns_full_operation_spectest no longer fetches with no identity and assertsaccess_controlpresence (spec now fetched for an unrestricted op,access_controlasserted only via the new authorized-identity test). - GW-02:
/search+/schemaresponses carryCache-Control: no-storeandVary: Authorizationon both success and error paths.
Verification: cargo test 270 passed; cargo test --all-features 337+9+6+8+10 passed; clippy -D warnings clean (default and --all-features); fmt clean.