Skip to content

SEP-3193: Cross-Server Data-Origin Labels and Flow Policy - #3193

Open
omkarparth wants to merge 2 commits into
modelcontextprotocol:mainfrom
omkarparth:sep/Cross-Server-Data-Origin-Labels-and-Flow-Policy
Open

SEP-3193: Cross-Server Data-Origin Labels and Flow Policy#3193
omkarparth wants to merge 2 commits into
modelcontextprotocol:mainfrom
omkarparth:sep/Cross-Server-Data-Origin-Labels-and-Flow-Policy

Conversation

@omkarparth

@omkarparth omkarparth commented Aug 3, 2026

Copy link
Copy Markdown

Adds a Standards Track SEP proposing two additive primitives — a host-attached
flowOrigin label naming the principals whose content contributed to a request,
and a server-declared flowPolicy stating which origins that server accepts —
together with a runnable, dependency-free reference prototype.

Files added:

  • seps/3193-cross-server-data-origin-labels-and-flow-policy.md
  • sep-flow-policy-poc/ — reference host, two reference servers, attack demo, 34 conformance tests

Motivation and Context

MCP hosts are multi-server by design, and every connected server's output lands in
the same context window that selects the next tool call. Nothing in the protocol
marks which server produced which bytes, so a server receiving a request cannot
tell whether the arguments derive from the user, from its own earlier results, or
from an untrusted third-party server that injected them.

That gap admits a two-server chain in which neither server is compromised and
neither misbehaves by its own contract: a low-assurance server returns content
that steers the model into a higher-assurance server's privileged tool, and the
result is handed straight back out through the low-assurance server. Every call in
the sequence is individually well-formed and authorized.

The host cannot close this alone. Only the receiving server knows its own risk
appetite — a ticketing server may accept arguments derived from scraped web pages
where a payments server must not — and that is a policy statement the host cannot
infer. Per-host heuristics also do not compose across independently authored
servers. This is the same split the web made for Origin and CORS: the browser
computes the origin, the server declares what it accepts, and neither half is
useful alone.

The labels are host-asserted and unsigned, which is made safe by one normative
invariant: origin labels may only restrict, never authorize. A recipient may
refuse on their strength but must never grant anything, so forging a label can
only cause the forger's own request to be denied. That property is what lets this
ship with no cryptographic dependency, and it is what keeps it on the correct side
of the boundary the SEP-2817 discussion settled on, where client-asserted _meta
is explicitly not authorization evidence.

Prior art was checked before filing. This addresses the Security IG discussion-agenda
item "Tool identity across servers", open without a champion since the group's
charter. It is complementary to and disjoint from SEP-2817 (audit-only), SEP-1913
(labels tool risk, not data origin), and SEP-3140 (supplies the verification that
produces the verified assurance state).

How Has This Been Tested?

A dependency-free prototype implements the whole mechanism end to end and runs on
a stock Node install:

node sep-flow-policy-poc/demo.mjs
node --test sep-flow-policy-poc/test/conformance.test.mjs

34 conformance tests, all passing. Coverage includes:

  • The attack itself, run twice. With flow policy disabled the full chain
    succeeds and the customer table leaves the trust boundary, demonstrating the gap
    in the protocol as it stands. With it enabled, the privileged call is never
    dispatched and the exfiltration leg is independently blocked.
  • Legitimate use is unaffected — the same privileged call succeeds when no
    low-assurance server has contributed.
  • Fail-closed parsing — absent labels, malformed labels, empty contributor
    lists, a complete value that is not literally true, unknown assurance states,
    and unrecognized acceptFrom tokens all resolve to the most restrictive reading.
  • Both enforcement points — the host pre-checks and refuses to dispatch; a test
    disables the host check to drive the server's independent re-evaluation.
  • resources/read gating — a read of notes://export?data=<secret> is a
    complete exfiltration channel, so reads are gated on the same terms as calls.
  • Refusal redaction — a test asserts that no principal URI and no acceptFrom
    entry survives into what the model may see.
  • Vendor-token inertnesscom.example.tier:gold fails to match on a host
    that does not understand it, so extension makes an older evaluator more
    restrictive rather than less.
  • The restriction-only invariant — a property test asserts that no label, of
    any shape, can convert a denial into an approval.

The model is simulated: the demo hard-codes the tool sequence an injected page
would induce, because the point under test is the flow control, not the model's
susceptibility.

Breaking Changes

None. The proposal is fully additive and capability-negotiated.

  • A client that sends no flowOrigin behaves exactly as today.
  • A server that declares no flowPolicy behaves exactly as today, and hosts
    perform no pre-dispatch check for it.
  • flowPolicy on a tool declaration and flowOrigin in _meta are optional
    fields that older peers ignore.
  • No existing method signature changes.

One adoption note is called out in the SEP: because an absent label evaluates as
unverified and complete: false, a server adopting a strict policy before its
clients adopt labelling will reject calls. Servers should therefore keep a
permissive default until labelling is widespread.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Design decisions worth reviewing:

  • Why unsigned labels are safe. The security of the mechanism does not rest on
    the integrity of the label at all. Where a recipient wants to rely on origin
    rather than merely restrict on it, it needs a signed assertion, and that belongs
    with the attestation family (SEP-2787 / SEP-2828 / SEP-3004) rather than here.
  • Contributors are deliberately over-approximated. Precise information-flow
    tracking through model reasoning is not solvable in general, and any attempt at
    precision will under-report — the one contributor dropped is exactly the injected
    one. Over-approximation is sound but imprecise; the cost is false rejections,
    which is an availability trade rather than a security one.
  • Context partitions, with a stated trap. Session-wide scoping would make every
    contributor set maximal within a few turns and operators would switch the feature
    off. Hosts may narrow to a partition under a structural, checkable condition. The
    SEP states plainly that summarization does not clear a contributor — treating
    a summary as clean builds a laundering step, and injected instructions survive
    summarization comfortably.
  • No minted error code. Parallel proposals each allocating codes from the same
    reserved range is how collisions arise, and the number carries nothing the payload
    does not. Recipients discriminate on the namespaced data key; the numeric code
    should follow whatever emerges from SEP-2145.
  • Refusals are an oracle. A refusal that names the offending contributor and
    lists what would have been accepted lets an injected instruction iterate until it
    finds an ungated route — the same failure as a login form distinguishing "no such
    user" from "wrong password". Hence the normative rule that the violation set is
    host-facing only.
  • Closed vocabulary, open identifier space. Assurance states are closed so a
    third overlapping trust vocabulary does not emerge alongside SEP-1913 and
    SEP-3140. Identifiers are open via a reverse-DNS vendor prefix, which is safe
    because unknown tokens never match.
  • acceptFrom is an allowlist, not a denylist. A denylist silently admits
    anything the author did not think of.

Rejected alternatives are documented in the SEP: host-only enforcement,
rejectFrom denylists, per-argument labels, transport headers, a dedicated error
code, and overloading SEP-2817's aiInvocation block.

Scope of the prototype. Transport is omitted deliberately — this SEP changes
what accompanies a request, not how bytes move. Context partitions are modelled but
not policed. This is the runnable prototype, not the SEP-2484 conformance artifact,
which additionally requires a scenario in the conformance repository with a
traceability file mapping each MUST and SHOULD to a check ID. I'm happy to add that
if the proposal gains a sponsor.

Three open questions remain in the SEP, on vocabulary ownership if the Security IG
consolidates the three axes, whether vendor-prefixed tokens need registration, and
whether flow policy should be declarable per resource or URI template.

Seeking a sponsor.

@omkarparth omkarparth changed the title Adding SEP for Cross-Server Data-Origin Labels and Flow Policy along … SEP-3193: Cross-Server Data-Origin Labels and Flow Policy Aug 3, 2026
@omkarparth

omkarparth commented Aug 3, 2026

Copy link
Copy Markdown
Author

Hi @pcarleton and @localden , requesting to review the SEP and sponsor the changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant