Add optional security-scan receipt _meta extension (v1) — resolves #1273 - #1404
Add optional security-scan receipt _meta extension (v1) — resolves #1273#1404eeee2345 wants to merge 4 commits into
Conversation
Adds an optional io.modelcontextprotocol.registry/security-scan extension under the existing _meta reverse-DNS namespace, holding an array of evidence-scoped, scanner-neutral security scan receipts. Resolves the converged v1 proposal in modelcontextprotocol#1273. Each receipt binds a verdict (clean | warnings | findings | inconclusive) to a specific scanner, rule_set_ref, policy_profile, and scanned_artifact_digest, with an explicit machine-readable scan_scope of what was actually evaluated. The schema enforces the client invariant named in-thread: scanned_artifact_digest is required so a clean verdict binds to exact bytes, scan_scope is a required non-empty array, and inconclusive verdicts require a machine-readable inconclusive_reason (artifact_digest_mismatch, unsupported_package_type, scope_excludes_handler_validation, evidence_unavailable, stale_scan) so a dependency or package scan that leaves handler-side validation unassessed stays representable rather than collapsing into clean. attestation (publisher-asserted | registry-attested | third-party-attested) keeps self-asserted receipts distinct from attested ones. Signatures and capability posture are intentionally out of scope for this version. scanner and rule_set_ref stay open strings so any community scanner can populate them; the registry does not endorse any particular one. Schema is defined in openapi.yaml and regenerated via make generate-schema. Documented in generic-server-json.md and official-registry-requirements.md, with a CHANGELOG entry. Additive and optional; existing server.json documents remain valid. Signed-off-by: Adam Lin <adam@agentthreatrule.org>
|
Thanks for turning the discussion into a concrete PR. This looks like the right v1 boundary to me: optional, scanner-neutral, Keeping From my side, I would support this as a small additive schema/docs change. Happy to review wording or field-shape adjustments if maintainers prefer a narrower first cut. |
|
This is the right PR to converge on from #1273. I opened #1405 before noticing this one, but closed mine to avoid splitting review. The important invariant is preserved here: One downstream client test I would add when this lands: a receipt with |
|
@HarperZ9 thanks — and for closing #1405 to keep review in one place. Agreed on the invariant: the digest binding plus a non-empty Good call on the downstream client test — a receipt with |
…iant Adds a downstream-client test for the io.modelcontextprotocol.registry/security-scan extension (modelcontextprotocol#1404) and a named render-invariant note in the format spec. The test compiles the canonical draft server.schema.json (the same file tools/validate-examples compiles) and validates _meta security-scan receipts the way a client would read them: - clean receipt with a well-formed scanned_artifact_digest and a non-empty scan_scope is accepted - mismatched/malformed scanned_artifact_digest (not algorithm:hex) is rejected, and a missing digest is rejected - empty scan_scope is rejected (minItems 1) - verdict inconclusive without inconclusive_reason is rejected, and is accepted once the machine-readable reason is present generic-server-json.md gains a Render invariant subsection making the client rule explicit: do not surface clean unless the receipt binds to the current artifact digest and the displayed claim names the covered scan_scope; an unbound digest or empty scope must be treated as inconclusive rather than clean. Signed-off-by: Adam Lin <adam@agentthreatrule.org>
golangci-lint flagged cloneReceipt as unused (the table cases build maps inline, no clone needed). Removing it; the schema test is unchanged. Signed-off-by: Adam Lin <adam@agentthreatrule.org>
|
@HarperZ9 following up when you have a moment — the security-scan receipt _meta extension is ready and I believe we're aligned on the digest-binding + non-empty scan_scope invariant. Happy to adjust anything before review. |
|
@eeee2345 looks good from my end. :) |
|
Hi maintainers — I’m building an independent downstream consumer for the experimental security-scan receipt shape in this PR: mcp-evidence-gate. I pinned a compatibility profile to the current PR head SHA (
The Action is currently referenced at immutable release SHA Could you confirm whether these are still the intended consumer-facing invariants for v1?
I’m happy to adjust the consumer or add focused fixtures based on maintainer feedback. |
|
Downstream follow-up with implemented edge cases: I added consumer-side checks in mcp-evidence-gate#1 and executable cases in dogfood#1:
Should these remain downstream consumer-policy concerns, or should any become documented receipt invariants? The verifier also treats |
|
One observation from an angle this thread has not covered, offered as data rather than as a design objection — the digest binding looks right to me and I am not asking for it to change. The digest binds bytes. Some risk moves without the bytes moving.
A receipt bound to digest How often, measured rather than asserted. We run a daily external diff of the whole registry population (25,030 entries, series from 2026-07-30). Across the events of that class so far: So roughly 45% of ownership and source changes arrive with no artifact change on the same day — a receipt with Two things this is not. It is not an argument for widening v1 — the scope discipline in this PR is the reason it is reviewable. And it is not a case for a What I think it does argue. If Happy to contribute the ownership-change series as public evidence if it is useful — it is free, needs no key, and the format is whatever is convenient. Not asking for anything in return, and not proposing our scanner as a value for |
Content/digest-scoped receipts assert nothing about who controls the name the artifact is published under; a clean receipt bound to a digest stays joinable through ownership transfer, source-repo removal/relocation, unpublish, or name republication. Custody is a distinct axis, not scanner-assessable, so it is a documented scope boundary (clients MUST NOT infer custody from a content scan) rather than a new scan_scope value or inconclusive_reason. Motivated by the registry-population ownership-change discussion on the PR; no schema change. Signed-off-by: Adam Lin <adam@agentthreatrule.org>
|
@Nikolife2016 — this is the kind of contribution the thread needed, and framing it as data rather than a design objection is what makes it land. The measurement carries it: across your daily registry diff, ~45% of the ownership/source events you tracked arrived with no artifact change the same day, so a I want to agree explicitly with the two things you ruled out, because they're the same discipline that keeps this PR reviewable: not widening v1, and no Where this lands is Yes to the offer — please share the ownership-change series; I'd like to cite it as the measured motivation for that boundary, since measured beats asserted. On whether this is a reserved scope value in v1 or prose I'll defer to whoever owns the boundary here; my lean is the docs note now, with an optional scope vocabulary reserved for a later cut so the v1 schema doesn't grow. (And +1 to @yandexuanxuan's consumer-side invariants upthread.) |
Resolves the converged v1 proposal in #1273. Thanks to @JinNing6 and @HarperZ9 for the design discussion in that thread; the shape here follows what the three of us converged on, scoped to the small v1 cut @HarperZ9 outlined.
What this adds
An optional io.modelcontextprotocol.registry/security-scan extension under the existing _meta reverse-DNS namespace, holding an array of evidence-scoped, scanner-neutral security scan receipts. It sits next to io.modelcontextprotocol.registry/publisher-provided and follows the same convention, rather than adding a bare top-level field.
Each receipt binds a verdict to the exact evidence that produced it: scanner, scanner_version, rule_set_ref, policy_profile, scanned_artifact_ref, scanned_artifact_digest, scan_scope, verdict, scanned_at, freshness_expires_at, evidence_ref, evidence_digest, and attestation. The point is that clean only ever means clean under this scanner version, rule set, policy profile, and artifact digest, for the listed scope, not a server-level safety property.
The client invariant @HarperZ9 named is enforced in the schema, not just prose:
attestation (publisher-asserted, registry-attested, third-party-attested) keeps a self-asserted receipt distinct from an attested one. Signatures and capability posture are intentionally out of scope for this version; the docs note capability posture as a possible future sibling under the same namespace, and attestation plus evidence_digest carry verifiability for now without pulling in key distribution.
scanner and rule_set_ref are open strings so any community scanner can populate them; the registry does not endorse any particular one.
Scope and conventions
Files
Validation
make generate-schema, make check-schema, and make validate (schema validity, sync, and all doc examples against both the JSON schema and the Go validator) pass locally. go build ./..., go vet, and go test ./internal/validators/... pass. The change touches only schema and docs, no Go code, API handlers, or database, so existing server behavior is unaffected.
Happy to adjust naming or field shapes to match registry conventions, and to split anything out if a smaller first cut is preferred. Reviews welcome async whenever it suits.