Egress origin-closure stage: block cross-identity data deterministically (#227) - #267
Conversation
Add a new ordered stage to the pure disclosure core, AFTER the existing disclosure-coverage stage, that blocks an outbound item whose typed-identity ProvenanceOrigin lies outside the bound recipient's identity closure (a counterparty other than the recipient, or owner-internal non-public data to a stranger) unless a grant ProvenanceLabelAllowlist caveat authorizes that origin. Fail-closed on unresolved origin/recipient. Typed-field comparison only; no query text, no LLM judgment. New CrossIdentityBlock decision; DisclosurePolicyKey/DisclosurePolicy shape unchanged. Wired in the single shared enforce_disclosure_egress chokepoint: recipient resolved from the briefcase task_shape, authorized origins pre-resolved via effectively_allows_provenance_label. Both worker-requested and kernel-origin/proactive rated dispatch inherit the identical check (riding the #207 origin-symmetric plumbing) with no second ungated path; the dead EgressClassifier stays deleted and gate() stays pure. The cross-identity block records a reconstructible disclosure.cross_identity_blocked audit row and returns the generic worker denial. Tests: schema-level matrix over origin x recipient x sensitivity x caveat, plus dispatch-integration cases at the chokepoint. Adds threat-claim CLAIM-44 with named tests, closing Bell's "internal data to a stranger". Closes #227
|
Warning Review limit reached
Next review available in: 48 minutes Limit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
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 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e5c1947525
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ProvenanceOrigin::Owner { principal } => { | ||
| matches!(recipient, RecipientIdentity::Owner { principal: to } if principal == to) | ||
| } |
There was a problem hiding this comment.
Exempt owner origins from recipient matching
For every worker sub-grant, whose provenance allowlist is empty, a covered non-public item with an Owner origin and a counterparty recipient reaches CrossIdentityBlock here. This contradicts the settled D-174 rule in .raw/openspine-decision-log.md:4162, which limits this stage to another counterparty's origin and explicitly exempts owner origins; consequently legitimate egress based on owner context is denied even after disclosure coverage was granted. Treat ProvenanceOrigin::Owner as reaching any recipient at this stage.
AGENTS.md reference: AGENTS.md:L60-L65
Useful? React with 👍 / 👎.
What
Adds the v1 enforcement D-174 promises: a new ordered stage in the egress deterministic core, run after the existing disclosure-coverage stage, that blocks an outbound item whose typed-identity
ProvenanceOriginlies outside the bound recipient's identity closure — a counterparty other than the recipient, or owner-internal (non-public) data to a stranger — unless a grantProvenanceLabelAllowlistcaveat authorizes that origin. Fail-closed on unresolved origin. Comparison is typed-field only (no query text, no LLM judgment).disclosure_policy.rs): newRecipientIdentity,check_egressgains anauthorized_originsarg +RecipientIdentityonOutboundQuery, and a newDisclosureGateDecision::CrossIdentityBlock.DisclosurePolicyKey/DisclosurePolicyshape unchanged.enforce_disclosure_egress): recipient resolved from the briefcasetask_shape.counterparty(theis_counterparty_erasedprecedent), authorized origins pre-resolved viaeffectively_allows_provenance_label. Cross-identity blocks cancel reservations, audit a reconstructibledisclosure.cross_identity_blockedrow (origin + sensitivity + recipient + egress class — the Auditor story), route an owner escalation, and return the generic worker denial.Why
Closes the Immune-system gap for Bell: a stranger must never receive another counterparty's — or the owner's internal — data, even at the same sensitivity class. This is the final provenance-track ticket; all prerequisites (#222/#224/#225/#226/#205–#207) are merged.
No second ungated path (rides #207)
Both worker-requested (
mediate_and_dispatch_action) and kernel-origin/proactive (..._kernel_origin) rated dispatch already funnel through the singleenforce_disclosure_egresschokepoint, so the stage is placed there once — no change tonotify_owner_with_digest(owner.notifystays unrated).gate()stays pure; the deadEgressClassifierstays deleted.Tests
./scripts/check.shpasses.Implementation-notes summary
check_egresscore, AFTER disclosure-coverage: blocks an item whose typedProvenanceOriginis outside the bound recipient's identity closure, unless a grant caveat authorizes it; fail-closed on unresolved origin. NewCrossIdentityBlockdecision; policy shapes unchanged.enforce_disclosure_egresschokepoint: recipient from the briefcase, authorized origins viaeffectively_allows_provenance_label; both worker-requested and kernel-origin dispatch inherit it — no second ungated path (rides Extend the disclosure hook to kernel-origin and proactive dispatch paths #207); deadEgressClassifieruntouched.disclosure.cross_identity_blocked(reconstructible from origin + sensitivity + recipient + egress class) and returns the generic worker denial.Closes #227
Summary by cubic
Blocks cross-identity external egress deterministically after disclosure coverage by comparing each item’s typed origin to the bound recipient. Previously, covered egress could send another counterparty’s or owner-internal data to a stranger; now the new origin-closure stage blocks unless a grant
ProvenanceLabelAllowlistauthorizes that origin, and fails closed on unresolved origin/recipient.openspine-schemas:OutboundQuerygainsrecipient;check_egressgainsauthorized_origins; newDisclosureGateDecision::CrossIdentityBlock.DisclosurePolicyKey/DisclosurePolicyunchanged. Callers and pattern matches must handle the new argument and decision.openspine-kernel: implement at the singleenforce_disclosure_egresschokepoint. Resolverecipientfrom the briefcase counterparty; pre-resolveauthorized_originsfrom the grant chain. Both worker-requested and kernel-origin/proactive dispatch share this path; there is no second ungated path.disclosure.cross_identity_blocked(origin + sensitivity + recipient + egress class), route an owner escalation, and return the existing generic worker denial. Tests cover origin × recipient × class × caveat and chokepoint integration. Adds CLAIM-44 in docs.Written for commit e5c1947. Summary will update on new commits.