From 4967a9eccc5ac74e30780ad9bb8bd406c6cc5ea7 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Tue, 16 Jun 2026 00:34:46 +0200 Subject: [PATCH] spec: tighten ifc-fides framing and evidenceRef canonicalization (review @Rul1an) ifc-fides: - lead Label semantics with the load-bearing wire/host split (wire markers are advisory hints; reader-set semantics are host-resolved) - state the join asymmetry as principled, not incidental: integrity join is total/wire-computable (small closed lattice); confidentiality join is partial because public is the only wire-computable case (reader set = top), while private join is the intersection the opaque markers don't carry - frame wire opaqueness as a property of the security model, not a spec limitation - add fail-closed handling when resolution is unavailable (ref absent, digest-only, source unreachable): never treat opaque labels as equal or as public; unknown/ mixed provenance classifies private; resolved reader sets are not durable grants trust-annotations: - show jcs/rfc8785 alongside cbor/rfc8949 so canonicalization reads as a per-reference envelope choice rather than a default - note type/digest/canonicalization as the local-re-derivation minimum Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- specification/draft/ifc-fides.mdx | 51 +++++++++++++++++------ specification/draft/trust-annotations.mdx | 9 +++- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/specification/draft/ifc-fides.mdx b/specification/draft/ifc-fides.mdx index 7f9af0a..622c073 100644 --- a/specification/draft/ifc-fides.mdx +++ b/specification/draft/ifc-fides.mdx @@ -86,12 +86,24 @@ be inlined by deployments that accept the wire cost) has the shape: ### Label semantics +The load-bearing distinction is between the wire and the host: **wire markers are +advisory hints; reader-set semantics are host-resolved.** The asymmetry between +the two joins below follows from that one cut. + - **Join on accumulation.** As a session ingests labeled results, the context label is the *join* of what it has seen: integrity degrades toward `untrusted`, confidentiality narrows toward the smallest permitted reader set. - The **integrity join is total and computable from the wire values alone** - (`untrusted` dominates). The **confidentiality join is NOT computable from the - opaque wire markers alone** — see [Reader-set resolution](#reader-set-resolution). + The two joins differ in *where* they can be computed, and the difference is + principled rather than incidental: + - **Integrity join is total and wire-computable.** The integrity lattice is + small and closed (`trusted ⊑ untrusted`), so `untrusted` dominates and the + join needs nothing beyond the wire values. + - **Confidentiality join is partial and host-resolved.** Reader sets are open + and host-knowledge-dependent. `public` is the one wire-computable case, + because its reader set is universal (`⊤`): `public ⊔ anything = public`. + `private ⊔ private`, by contrast, is the *intersection* of two reader sets + that the opaque markers don't carry, so it is **not** computable from the + wire — see [Reader-set resolution](#reader-set-resolution). - **Policy check before egress.** Before a write/egress tool call, the host checks whether the current context label may flow to the call's target. When a label is absent, the host falls back to its default (trusted-action) @@ -103,13 +115,16 @@ be inlined by deployments that accept the wire cost) has the shape: ### Reader-set resolution -`"private"` is intentionally opaque on the wire. Two distinct `"private"` -markers (e.g. file contents from two different private repositories) are **not -equal**, and their confidentiality join is **not** the same `"private"` token: -data derived from both may flow only to principals who can read *both* sources — -the intersection of their reader sets. The opaque marker cannot express this -intersection, so a host that needs to make a precise cross-source flow decision -MUST resolve each `"private"` marker to a concrete reader set before joining. +`"private"` is intentionally opaque on the wire — and that opaqueness is a +property of the security model, not a limitation of the spec. A reader set is not +transmissible without policy context, so the wire shape correctly declines to +carry it. Two distinct `"private"` markers (e.g. file contents from two different +private repositories) are **not equal**, and their confidentiality join is **not** +the same `"private"` token: data derived from both may flow only to principals who +can read *both* sources — the intersection of their reader sets. The opaque marker +cannot express this intersection, so a host that needs to make a precise +cross-source flow decision MUST resolve each `"private"` marker to a concrete +reader set before joining. Resolution is a host-side concern, performed at policy-decision time: @@ -121,8 +136,19 @@ Resolution is a host-side concern, performed at policy-decision time: 3. The host computes the flow decision over the resolved sets (intersection for a join of multiple private sources) and then discards them. -Keeping resolution host-side is what lets the wire stay at `public` / `private` -while still supporting precise multi-source decisions. +**When resolution is unavailable** — the `ref` is absent, the label is +digest-only, or the originating system is unreachable at decision time — the host +MUST NOT treat two opaque labels as equal, and MUST NOT treat `"private"` as +`"public"`. It denies, prompts, or applies its configured fail-closed policy. Two +`"private"` labels are equal only once resolution proves their sources are; until +a source is established, unknown or mixed provenance classifies as `"private"`, +never defaulted to `"public"` from a repository-level shortcut. + +The resolved reader set is a decision-time read performed under the host's own +credentials. It is not a durable grant: a host SHOULD NOT cache it as one or +serialize it back into annotations or evidence unless a deployment explicitly opts +in. This keeps the wire free of user identities while still letting the host make a +precise decision when it holds source provenance plus its own credentials. ### Relationship to `trust-annotations` @@ -164,3 +190,4 @@ the IFC label is the precise, host-checkable evidence behind them. | ---------- | ------------------------------------------------------------ | | 2026-06-10 | Initial draft skeleton. Reframed from a top-level `ifc` extension to a `trust-annotations` `evidenceRef` profile (`ifc.fides.v1`). | | 2026-06-15 | Confidentiality limited to `public` / `private` on the wire (dropped reader-list); added Reader-set resolution section; emitter classifies per resource, not per repository. (Review: @JoannaaKL.) | +| 2026-06-16 | Lead the semantics with the wire-hint / host-resolved split; state the integrity-total vs confidentiality-partial asymmetry as principled (`public` = `⊤` is wire-computable, `private ⊔ private` is not); add fail-closed handling when resolution is unavailable and a no-durable-grant rule for resolved sets. (Review: @Rul1an.) | diff --git a/specification/draft/trust-annotations.mdx b/specification/draft/trust-annotations.mdx index 0fc51d4..395e3c8 100644 --- a/specification/draft/trust-annotations.mdx +++ b/specification/draft/trust-annotations.mdx @@ -101,6 +101,12 @@ the SEP-1913 discussion): This single slot subsumes the previously separate `attestationChainRef` / `policyDecisionRef` ideas — both become `type` values. +`canonicalization` is per-reference precisely so different evidence producers can +be re-derived independently. `cbor/rfc8949` and `jcs/rfc8785` (JSON +Canonicalization Scheme) are both valid envelope choices — neither is the default, +and the `type`/`digest`/`canonicalization` triple is the minimum a client needs +for local re-derivation regardless of which is used. + ### Coarse vs. rich classification (DataClass) SEP-1913 carried a four-level data classification @@ -114,7 +120,7 @@ is recovered as an `evidenceRef` profile: "type": "data-class.v1", // record resolves to e.g. { "class": "highly_confidential", "regulatory": ["hipaa"] } "digest": "sha256:…", - "canonicalization": "cbor/rfc8949" + "canonicalization": "jcs/rfc8785" // a JSON-canonicalized record; CBOR is equally valid } ``` @@ -174,3 +180,4 @@ and `evidenceRef` is how that claim is made checkable. | Date | Change | | ---------- | ------------------------------------------------------------- | | 2026-06-10 | Initial draft skeleton. Narrowed to `sensitive` + `untrusted` + `evidenceRef`; DataClass demoted to a profile; `requires_review` moved to `action-metadata`. | +| 2026-06-16 | Show `jcs/rfc8785` alongside `cbor/rfc8949` so canonicalization reads as a per-reference envelope choice, not a default; note the `type`/`digest`/`canonicalization` triple as the local-re-derivation minimum. (Review: @Rul1an.) |