docs(architecture): Layer 7 — STRIDE on the Storage broker north face#219
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
WalkthroughThis PR revises the Layer 6 threat model documentation to add an E5 data‑plane client and expand the DFD to twelve flows (F1–F12), adds six north‑face STRIDE live‑threat entries for the storage broker, and expands residual‑risk thematic tracking for north‑face concerns. ChangesNorth-face data-plane threat model expansion
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
L6 (#215) gave the Storage broker a north face — OCU's authenticated SPA plus the file/artifact API — reached by a new external actor (the data-plane client) over a new boundary flow. The threat model covered the broker's south mount (F7) and backend leg (F10) but not this surface. Add six STRIDE rows on the north face (P4-S3/T3/I3/D3/R2/E3, flow F12, actor E5 data-plane client, A2-class): - S: embed-token replay/forgery + clickjack→session-fixation (SEC-82/83) - T: CSRF on the SameSite=None cookie-bound API + token-in-URL leak (SEC-84) - I: cross-filesystem_id read / preview-render leak / non-downloadable bytes to browser (SEC-49/73/83) - D: inbound byte-path + UI-face flood, archive-bomb on ingest (SEC-78/80) - R: north-face file-activity attribution (SEC-79 fail-closed) - E: intent/downloadable bypass + artifact-id traversal via the HTTP API (SEC-49/73/80) Update §1 (twelve flows, five actors) and §2 (E5 actor, F12 flow, P4 north-face sub-element) to match. §5 residual register: extend the exhaustion / per-action-authz / downloadable themes, add three north-face theme rows (file-activity attribution #181; embeddable-UI auth #217; preview-render parser isolation #218). Two new gaps with no prior issue get tracking issues #217 (embed-token replay-binding) and #218 (preview-render parser isolation). §7 unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5f9fe79 to
08ad82e
Compare
The contracts predated the file-surface stack (#213/#214/#215/#216/#219). Add the Storage broker north face — the data-plane client HTTP API and embeddable SPA — alongside the existing south-face mount and file-op RPC. New schema contracts/storage/file-artifact-api.schema.json: - operation set (upload/listFiles/getManifest/download/downloadArchive/ previewRender/delete) mapped to the PoC route shape; per-operation bodies left tbd, like the south face — no invented wire bodies - three-axis authz reuse (scope/intent/downloadable, NFR-SEC-49/73) - embed-token verify contract (peer mints, north verifies, exp ≤120s, NFR-SEC-82); first-party cookie + CSRF + CSP envelope (NFR-SEC-83/84) - inbound byte ceiling (NFR-SEC-78), archive validation (NFR-SEC-80), content classification (NFR-SEC-81) as x-ocu-default, not frozen - OCSF File System Activity event (class_uid 1001, NFR-SEC-79) - embed-token binding (#217) and preview-render parser isolation (#218) carried as tracked tbd items; opaque object id, list pagination, resumable upload, and share-by-link are not modelled (not sourced) 08-contracts.md: §1 north-face surface row, §2 OpenAPI-3.1 note, §3 seven contract-enforced north-face mitigation rows (SEC-78/79/80/81/82/83/84), §5 the new schema file. Diagram: data-plane client actor + north-face edge. Stays v1alpha — bodies tbd and open issues mean beta would overclaim stability; the storage set bumps together when sourced (NFR-IC-04). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ts (#204) * docs(architecture): Layer 8 — contract-surface overview (08-contracts.md) Maps every boundary that carries a wire contract to its format and to OCU's role (define / conform / relying-party), with the versioning policy and the Layer 7 mitigations each contract must carry. The inventory is the ten internal boundaries from Layer 6 §4 plus the external actors from Layer 4 §4. Five external surfaces are integration contracts OCU conforms to (MCP authz, SAML/OIDC, PKCS#11/KMIP, chained-proxy, ICAP), consistent with the Layer 5 context map; the rest OCU defines. Four formats cover the defined surfaces: MCP JSON-Schema for the agent edge, OpenAPI 3.1 for inbound REST, Protobuf/gRPC for internal RPC, AsyncAPI 3.0 over the OCSF Published Language for audit fan-in. Versioning is additive-only; breaking changes take a major version with a deprecation header (NFR-IC-04) for OCU-defined surfaces, and the MCP edge uses date-revision negotiation as its compatibility signal. Executable schema files, mock servers, and the SkillProvider contract are deferred. diagrams/08-contracts.mmd overlays the format on each container crossing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): correct MCP dialect default + split run-on sentence The MCP §2 bullet wrongly implied the 2025-06-18 revision pins no dialect. The revision defaults embedded tool schemas to JSON Schema 2020-12 and lets `$schema` declare an alternative; correct the bullet to state default + override, and recast the OpenAPI bullet's one-dialect note on that basis. Split the §4 versioning run-on into shorter sentences for readability. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): correct exec/mount formats + transport-fence split The exec/PTY+CDP channel is a single bidirectional WebSocket per session carrying tagged-JSON control frames and raw binary stdio frames, not a unary gRPC call — aligned with NFR-IC-03. The file-operation mount is an HTTP+JSON mount config (filesystem_id, broker-signed lease) over a FUSE/virtio-fs/9p substrate, not gRPC — aligned with NFR-SEC-25. gRPC is scoped to the unary internal RPC legs (session set-up, lease pull); §2 names WebSocket as the fifth format. Add the transport-vs-direction split: the transport substrate (TCP/UDS/vsock; FUSE/virtio-fs/9p) is a deployment-overlay and component-spec choice, not a contract (NFR-SEC-26/25); the contract fixes channel direction — control/exec host-dialled with non-host peers rejected (NFR-SEC-43), outbound guest-out under egress policy (NFR-SEC-27). Fix the audience-authz citation to trust-boundaries §8 (Workload-identity floor), the canonical home of token-class material, not §3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): Layer-8 schema drafts + contracts-lint gate Add the five OCU-defined contract schemas the overview maps to, drafted against the wire sources and bounded by the NFRs: - contracts/mcp/2025-06-18/ocu-constraints.schema.json — MCP conform profile (pins the revision, layers OCU bounds; defines no MCP types). - contracts/exec/exec-channel.schema.json — exec/PTY WebSocket envelope; CreateProcess, the server/client message union, capability negotiation. - contracts/storage/mount-config.schema.json — mount config; a guest config carries no broker lease by construction. - contracts/storage/file-ops.schema.json — file-op names; bodies tbd. - contracts/audit/audit-fanin.asyncapi.yaml — OCSF fan-in over a durable bus; compute-metering and saturation payloads tbd (#150). Unsourced numeric bounds are x-ocu-default annotations the operator retunes within the NFR-SEC-46/51 floor, not frozen contract values. Add .github/workflows/contracts-lint.yml: ajv 2020-12 meta-validation, asyncapi validate, and a provenance guard, triggered on contracts/**. Update 08-contracts.md: §5 lists the drafted schemas by real path and keeps openapi/proto, the transparency-log envelope, and mock servers as not-built; fix the §3 audience citation to trust-boundaries §3, the SOAR webhook anchor to NFR-COMP-27, and scope the gRPC claim to the unary session/lease legs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(contracts): ajv --strict=false so spec-legal vendor keywords pass ajv strict mode rejected the x-ocu-* vendor extensions and the $comment-* convention, which JSON Schema 2020-12 permits as unknown keywords. Validate against the meta-schema without keyword-policing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(contracts): exec frame union, provenance, TBD links — final review pass A multi-lens adversarial review surfaced one correctness defect and four cleanups, all confirmed: - exec-channel: TraceEvent is a valid frame in both directions, so it appears in both the server and client message sets. The top-level oneOf then rejected every TraceEvent frame (matches two branches). Switch to anyOf — frame direction is a transport property, not an envelope distinction, so the envelope asserts no mutual exclusivity the wire does not have. - exec-channel: reword the V2-payload open question from "is observed" to "is the expected payload" — state the fact, not a derivation. - contracts-lint: extend the provenance guard to catch `anthropic` and `observed`. - 08-contracts.md: link every open question and not-built artifact to a tracking issue (#151, #158, #205-#209); no bare placeholders remain. - audit AsyncAPI: drop the restated "TTL <=15 min" from a channel summary; the NFR-SEC-29 anchor already owns the value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(contracts): CodeRabbit review — enforce tools-only, mount scope XOR, u64 maximum, pin CI tools - mcp profile: capabilities now additionalProperties:false — rejects every non-tools capability (sampling, experimental, …), not just a four-key denylist; the documented tools-only surface is now enforced. - mount-config: a mount is scoped by filesystem_id XOR memory_store_id (oneOf), so a memory-backed mount no longer has to invent a filesystem id; the RW `mounts` array pins writes:true to mirror readonly_mounts' writes:false. - exec-channel: memory_limit_bytes drops the 2^64-1 JSON-number maximum (a validator rounds it past the IEEE-754 safe-integer range); the u64 domain is enforced by the wire type, documented in $comment. - contracts-lint: pin ajv-cli@5.0.0 and @asyncapi/cli@6.0.0 for deterministic runs (keep --spec=draft2020 — ajv-cli@5 defaults to draft-07 and errors without it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): Layer 8 — north-face file/artifact contract surface The contracts predated the file-surface stack (#213/#214/#215/#216/#219). Add the Storage broker north face — the data-plane client HTTP API and embeddable SPA — alongside the existing south-face mount and file-op RPC. New schema contracts/storage/file-artifact-api.schema.json: - operation set (upload/listFiles/getManifest/download/downloadArchive/ previewRender/delete) mapped to the PoC route shape; per-operation bodies left tbd, like the south face — no invented wire bodies - three-axis authz reuse (scope/intent/downloadable, NFR-SEC-49/73) - embed-token verify contract (peer mints, north verifies, exp ≤120s, NFR-SEC-82); first-party cookie + CSRF + CSP envelope (NFR-SEC-83/84) - inbound byte ceiling (NFR-SEC-78), archive validation (NFR-SEC-80), content classification (NFR-SEC-81) as x-ocu-default, not frozen - OCSF File System Activity event (class_uid 1001, NFR-SEC-79) - embed-token binding (#217) and preview-render parser isolation (#218) carried as tracked tbd items; opaque object id, list pagination, resumable upload, and share-by-link are not modelled (not sourced) 08-contracts.md: §1 north-face surface row, §2 OpenAPI-3.1 note, §3 seven contract-enforced north-face mitigation rows (SEC-78/79/80/81/82/83/84), §5 the new schema file. Diagram: data-plane client actor + north-face edge. Stays v1alpha — bodies tbd and open issues mean beta would overclaim stability; the storage set bumps together when sourced (NFR-IC-04). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): Layer 8 — fleet-audit fixes across the contract set A per-contract fleet audit (one auditor per file + cross-contract link check, each finding adversarially verified with quoted evidence) caught unsourced concrete values that earlier reviews missed — mostly in the pre-existing schemas, not the new north-face file. BLOCKs (invented values with no source / NFR figure): - file-ops: drop "~4MB / 128M" chunk constants (NFR-SEC-46 states no figure) and the "opaque pagination cursor" hint (no source; the north face already refuses one); soften the chunk-model assertion to what NFR-SEC-46 carries - mount-config: mark the unsourced `source` field x-ocu-tbd (not frozen) - exec-channel: drop the concrete algorithm name — accept_zstd/supports_zstd → accept_compression/supports_compression as negotiation flags, algorithm pinned to a new x-ocu-open-questions entry FLAGs: - mount-config: rename ca_cert → ca_cert_pem to match the sourced wire name (and its `required` entry) - audit-fanin: storageBrokerAudit channel names both broker faces (NFR-SEC-79) - 08-contracts.md: getMetadata → getManifest (the sourced op name) - unify schema $id authority host to schemas.open-computer-use.dev NITs: Signal bound cites NFR-SEC-51 (input validation) not SEC-46; MCP missing-header fallback qualifier made exact; audit envelope maxLength fields annotated x-ocu-design (NFR-SEC-51-derived, not frozen). ajv 2020-12 compile green on all five schemas; AsyncAPI valid; provenance clean. The new file-artifact-api.schema.json passed the audit unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): Layer 8 — restore sourced storage shapes, cover gaps A fleet restore/gap pass (grounded in a sanitized field-name brief, never provenance) recovered storage shapes the earlier audit had wrongly dropped as "invented" — they are real field/message shapes, the audit agents just could not see the source. file-ops (south face, broker RPC): - promote ReadFileRequest_Range (ranged read offset/length), ListDirectoryRecursiveCursor (opaque pagination cursor), FileUploadRequest_Params (chunked chunk/numChunks) from prose hints to real $defs, wired into the request envelope as op-gated carriers - add SizeLimits $def: RPC message ceiling (~4 MiB default), read-chunk default (128 MiB), broker max-file-size (tbd, server policy), per-mount VFS cache ceiling (1 GiB, owner is mount-config) — figures are x-ocu-default, not frozen consts - AuthorizationMetadata noted at request field 4/5 - add getFileMetadata + listFiles as distinct ops; honest tbd hints for fileDownload / importFiles / importZip / migrateFilesystem / removeFilesystem mount-config: - per-mount cache_duration_s with role-directional defaults (uploads 1s, tool_results 3s, transcripts 10s, outputs 3600s) - MountRole enum + role↔RW binding (uploads/tool_results/transcripts RO, outputs RW), host-enforced; crypt modelled off (isolation by scope) ajv 2020-12 green on all three storage schemas; no dangling $refs; south/north names stay distinct; provenance clean (field names only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): Layer 8 — contracts/ navigator README + §5 count fix Add contracts/README.md: per-file surface/format/validator table, how to read a schema, the x-ocu-* annotation conventions (sourced vs design vs default vs tbd), and the change/versioning pointer. Closes the navigation gap — 08-contracts.md is the surface map, this is the reader's guide. Fix §5: six schema files drafted (storage carries three), not five; link the new README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): close inter-layer seams for the file-surface thread An inter-layer cross-review (vertical L2→L8 seams + horizontal cross-doc links, every finding adversarially verified) found the file/artifact surface landed L4→L8 but never reached L3, plus contract/diagram/glossary seams. L3 trust-boundaries (the BLOCK): the Storage broker had only its south face. Add the north data-plane face — §2 zone 3 names two faces, the embed-token → first-party-session crossing, archive-validation + content-classification on ingest (SEC-78/79/80/81/82/83), and the per-tenant broker rule (SEC-76); §3 gains a Data-plane client external-actor row; §7.1 notes the non-guest north path (host-side caller↔broker, NFR-SEC-43 unaffected); the zone-3 secondary-anchor line carries the north-face NFRs. L8 §3: add the Three-axis authz (SEC-49) and Downloadable-at-read (SEC-73) mitigation rows — the controlling NFRs of threat rows P4-I3/P4-E3 were unrepresented. Audit fan-in: the FileSystemActivity message gains the NFR-SEC-79 required-field overlay (filesystem_id/intent/downloadable) so the mandated field set is pinned on both faces, not deferred to the bare OCSF class; the message summary names both faces. L7 diagram: add the data-plane client (north face / F12) and the P4 north-face STRIDE cluster + #217/#218. L7 §1: the data-store, IdP/SOAR, and F-number elements are named STRIDE elements, not nodes on c4-container.mmd — wording corrected to not over-claim the diagram. L6 §3: add NFR-SEC-83 to the broker NFR-anchor column (prose already cited it). glossary: add Data-plane client, South/north face, Downloadable, Embed token; extend Storage broker with the two-face model. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): Layer 8 — reconcile mount-config drift vs the real surface A drift-reconcile pass (research the underfilled provisioning fields, adjudicate the design-added ones, adversarially verified) cleaned the mount-config against the real guest-mount surface. INCLUDE — the one genuinely-missing in-scope field: - backend_cache_ttl: a config-level broker-side cache window, distinct from the per-mount cache_duration_s; added to ProvisionMountConfig only (broker-side, not the guest variant), x-ocu-tbd pending unit/scope confirmation. DROP — design-adds that are derivable-inside or redundant: - source: not on the wire; the broker derives the backend source path from filesystem_id + the mount's directional intent. Least-data (NFR-SEC-25) — a derivable value the guest cannot be trusted to set is not a wire field. Documented in the MountBase $comment. - (no schema change for fuse_mounts / readonly_dev_start_index: the RO/RW split they encode is already carried, more cleanly, by mounts[]/readonly_mounts[] + the writes boolean.) REFRAME — provenance honesty: - role / MountRole: the role NAMES are the directional-window vocabulary, but role-as-a-stored-field is an OCU design construct, not a sourced provisioning field. Comments corrected (were "Sourced role set"). OUT-OF-SCOPE (not added; tracked separately): resolv_conf / etc_hosts belong to the guest network/init surface (Compute plane), not the file-mount contract; mount_model_tools / mount_rclone_tools belong to the SkillProvider surface (v1 non-goal). No §5 not-built line added without a real tracking issue. ajv 2020-12 green; provenance clean; field names only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): Layer 8 — drop the MountRole field (derivable, not sourced) role / MountRole was an OCU design construct, not a provisioning-struct field: the real surface carries RW/RO structurally (the readonly_mounts split) and the freshness window as cache_duration_s itself, with no role field. role only duplicated values already carried by writes + cache_duration_s, bound by two if/then constraints — redundant and unsourced. Drop the role property, its $def, and the role→writes allOf blocks; remove it from required. RW/RO stays in writes + the mounts/readonly_mounts split; the directional window stays in cache_duration_s. The role names survive as the cache-window vocabulary. Same least-data reasoning that dropped `source` (NFR-SEC-25): a value the broker derives is not a wire field. ajv 2020-12 green; provenance clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): Layer 8 — fix RW-enforcement symmetry + role-comment wording Final review (post role/source drop) flagged two consequences of dropping role: - ProvisionMountConfig.mounts items were a bare MountBase $ref with no writes:const:true, so a provisioning RW-array entry could carry writes:false — contradicting the structural-split invariant the prose asserts. Mirror the GuestMountConfig.mounts constraint: writes:const:true on the provisioning RW array too. Now both variants pin writes:true on mounts[] and false on readonly_mounts[]. - $comment-no-role narrated drafting history ("an earlier draft carried ... it was dropped") — a process-narration tell. Reworded to a present-tense design record that keeps the rejection rationale without the history clause. ajv 2020-12 green; provenance clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(architecture): Layer 8 — require x_deny_reason on a deny outcome CodeRabbit: the FileActivityEvent outcome description says "a deny carries the structured deny-reason" but the schema only required disposition_id, so a deny with no reason validated — text and schema disagreed. Add an if/then: when disposition_id is "deny", x_deny_reason is required. allow outcomes unchanged. ajv 2020-12 green; verified deny-without-reason now rejected, deny-with-reason and allow accepted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Extends the Layer 7 threat model (
06-threat-model.md) to cover the Storage broker north face that Layer 6 (#215) added — OCU's authenticated SPA + file/artifact API, reached by a new external actor (the data-plane client) over a new boundary flow. The model already covered the broker's south mount (F7) and backend leg (F10); the north face had no STRIDE coverage.Rows added (§3.2)
Six rows on the north face — actor E5 data-plane client (A2-class), flow F12, all PARTIAL:
filesystem_idread / preview leak / non-downloadable bytes to browserScope / register updates
New tracking issues
Two gaps with no prior issue, surfaced by this pass:
exp ≤120sbut nojti/nonce single-use)Anchors only NFRs already merged via #213; no new NFR invented. Worded to the merged NFR-SEC-82/84 text (the UI verifies / sets the session; the peer backend mints the token).
🤖 Generated with Claude Code
Summary by CodeRabbit