spec(ifc-fides): address review; reframe action-metadata for SEP-2061 closure

ifc-fides (review by @JoannaaKL):
- confidentiality is public/private only on the wire; drop the reader-list
  (logins aren't uniform across servers, are themselves access-restricted,
  and reader sets can be huge)
- add Reader-set resolution section: two private markers aren't equal, the
  confidentiality join is the intersection and isn't computable from opaque
  wire markers, so hosts resolve to concrete reader sets at decision time
- integrity join is total/wire-computable; confidentiality join is not
- emitter MUST classify per resource, not per repository (public repos serve
  non-world-readable sub-resources), making github-mcp-server a real proof point

action-metadata / docs:
- SEP-2061 closed 2026-06-13 in favour of this extension; this draft is now the
  canonical home. Updated action-metadata.mdx, README, sep-disposition,
  related-work, and intent-comment status accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Sam Morrow 2026-06-15 23:29:44 +02:00
commit b799af5bea
No known key found for this signature in database
2 changed files with 87 additions and 40 deletions

View file

@ -10,8 +10,9 @@ title: Action Metadata
> [SEP-2061: Action Security Metadata](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061)
> by [@rreichel3](https://github.com/rreichel3) into the IG's experimental repo,
> per the May 28 2026 decision to pursue trust/privacy work as an extension
> first. SEP-2061 remains the canonical discussion thread for the field
> semantics.
> first. SEP-2061 was [closed](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2061#issuecomment-4675049171)
> on 2026-06-13 in favour of this extension as the home for the work; this draft
> is now the canonical place to discuss the field semantics.
## Abstract
@ -81,9 +82,9 @@ Carried under the extension-namespaced key on `ToolAnnotations`:
| `outcome` | Real-world effect class: `benign` / `consequential` / `irreversible`. |
| `requiresReview` | The tool author signals that a host SHOULD obtain explicit human confirmation before invocation. |
> Exact enum value sets are inherited from SEP-2061 and are **not** re-litigated
> here; this draft tracks that proposal. Where SEP-2061 evolves, this file
> follows.
> Exact enum value sets are inherited from SEP-2061 as the starting point and
> are **not** re-litigated here. With SEP-2061 now closed, this draft is where
> they evolve.
### `requiresReview` lives here, deliberately
@ -130,3 +131,4 @@ would anchor the draft in a real ecosystem.
| Date | Change |
| ---------- | ------------------------------------------------------------------- |
| 2026-06-10 | Initial draft skeleton, carrying SEP-2061 into the experimental repo; absorbed `requiresReview` from the trust taxonomy. |
| 2026-06-15 | SEP-2061 closed in favour of this extension; this draft is now the canonical home for the field semantics. |

View file

@ -44,11 +44,12 @@ integrity (is this data trusted) as context accumulates across tool calls, and
deny or prompt before a flow violates policy.
A public MCP server is the natural emitter. [`github-mcp-server`](https://github.com/github/github-mcp-server)
returns repository data whose confidentiality is determined by repo visibility
and collaborator sets — exactly the public/private signal above — but does
**not** emit IFC labels today. Closing that emitter gap is the concrete proof
point for this profile: a host-side consumer of the label shape already exists,
so the missing half is a server willing to emit it.
returns repository data whose confidentiality follows from repository visibility
and collaborator sets — the same public/private signal — but does **not** emit
IFC labels today. Closing that emitter gap is the concrete proof point for this
profile: a host-side consumer of the label shape already exists, so the missing
half is a server willing to emit it, classifying each resource it returns (see
[per-resource classification](#reference-implementation)).
## Specification
@ -67,32 +68,62 @@ be inlined by deployments that accept the wire cost) has the shape:
```jsonc
{
"integrity": "trusted", // "trusted" | "untrusted" (FIDES §4.1 two-level lattice)
"confidentiality": "public" // "public" | "private" | ["login1","login2", …]
"confidentiality": "public" // "public" | "private"
}
```
| Field | Meaning |
| :--- | :--- |
| `integrity` | Two-level integrity lattice (`trusted` ⊑ `untrusted`): trusted data may flow to untrusted sinks, not vice versa. |
| `confidentiality` | `"public"` = world-readable; `"private"` = an opaque marker the host resolves to a reader set (e.g. repo collaborators); an explicit `string[]` = pre-resolved reader logins. Fewer readers = more confidential = higher in the lattice. |
| `confidentiality` | `"public"` = world-readable; `"private"` = an opaque marker meaning "restricted to some reader set". The concrete reader set is resolved host-side at policy-decision time (see [Reader-set resolution](#reader-set-resolution)). |
> **Confidentiality is `public` / `private` only — never a reader list on the
> wire.** Emitting concrete reader identities (e.g. logins) is out of scope: user
> identity is not uniform across servers using different auth methods, the
> identities are themselves access-restricted data, and a single resource can
> have hundreds of readers. The opaque marker keeps the wire shape stable and the
> sensitive resolution host-side.
### Label semantics
- **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 reader set.
`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).
- **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)
policy rather than assuming the worst — labels are an *additive* signal.
- **Confidentiality resolution.** `"private"` is intentionally opaque on the
wire; resolving it to a concrete reader set (e.g. via a collaborators lookup)
is a host concern, so servers need not enumerate audiences inline.
> The normative integrity/confidentiality lattice definitions follow the FIDES
> paper, §4.1 and §4.3. This draft references the model rather than restating
> the proofs.
### 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.
Resolution is a host-side concern, performed at policy-decision time:
1. The host maps each contributing `"private"` label back to its source (e.g.
via the `evidenceRef.ref` locator, or its own record of which tool result
carried the label).
2. The host queries the originating system for the current reader set (e.g. a
repository collaborators lookup) using its own credentials.
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.
### Relationship to `trust-annotations`
`ifc-fides` never appears without a host `trust-annotations` annotation
@ -106,17 +137,24 @@ the IFC label is the precise, host-checkable evidence behind them.
applies a flow policy before egress operations already exists in practice.
(Linked once a public reference is available.)
- **Emitter (gap / proof point):** [`github-mcp-server`](https://github.com/github/github-mcp-server)
is the candidate — it already knows repo visibility and collaborator sets,
which are exactly the confidentiality inputs.
is the candidate — it already knows repository visibility and collaborator
sets, which are the confidentiality inputs. Repository visibility is only a
*default* hint, not the whole story: a public repository can serve
sub-resources that are **not** world-readable (draft security advisories,
draft releases, the collaborator roster itself, authenticated-user fields), so
a correct emitter MUST classify **per resource returned**, not per repository.
That makes the emitter a non-trivial proof point rather than a one-line
`repo.private` read.
## Open questions
- Should the label be inlinable on `_meta.ifc` directly for low-friction
adoption, or always behind `evidenceRef` for schema minimalism? (Lean:
permit both; `evidenceRef` is canonical, inline is a convenience.)
- How does GitHub Enterprise `internal` repo visibility map onto the
public/private/reader-set confidentiality model? (Audience is the whole org,
strictly broader than collaborators — likely falls back to default policy.)
- How does GitHub Enterprise `internal` repository visibility map onto the
`public` / `private` confidentiality model? (Audience is the whole org,
strictly broader than collaborators — likely classified `private` and resolved
host-side, or falls back to default policy.)
- Registry coordination with other attestation/evidence profiles
(e.g. SEP-2787) so `evidenceRef.type` values don't collide.
@ -125,3 +163,4 @@ the IFC label is the precise, host-checkable evidence behind them.
| Date | Change |
| ---------- | ------------------------------------------------------------ |
| 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.) |