Skip to content

How to Consume DCP Safely

A DCP message is untrusted data: every field is producer-asserted, and no field carries a verified identity, permission, or guarantee. Safe consumption means never treating a DCP field as authority — the transport authenticates and authorizes; DCP only describes. This guide lists the concrete rules a consumer implementation must enforce, drawn from SECURITY.md.

This is the practical companion to DCP carries no trust — that page explains the principle; this page tells you what to actually check in code.

There is no DCP field that becomes trustworthy just because the JSON parses and the schema validates. attributed_to (on an Event), responsible_party (on a Task), and requested_reviewers (on a ReviewRequest) are descriptive labels, not authenticated principals (SPEC §8). Do not use them for access control, audit attribution, or identity decisions. If you need to know who actually sent a message, that binding comes from the transport — see provenance versus identity for the full field-by-field treatment.

2. message_type is not authority — always trust the body

Section titled “2. message_type is not authority — always trust the body”

message_type is a denormalized convenience copy of <entity_type>.<verb>, provided for human and operator readability. It is not the normative source of a message’s type — the body is. A conformant validator MUST check that message_type === entity_type + "." + verb; on conflict, the body wins and the message MUST be treated as malformed (SPEC §4.1). Never route, authorize, or trust on message_type alone. See the validation error reference for how this rule shows up in the reject corpus.

3. Never resolve a ref as a network address

Section titled “3. Never resolve a ref as a network address”

refs[].ref is an opaque descriptive pointer, drawn from the closed rel vocabulary. It is not a URL to fetch. Auto-dereferencing an untrusted ref invites SSRF, credential leakage via redirects, and DNS rebinding (SPEC §5.3). If your system needs to follow a reference, resolve it through your own trusted lookup — never by treating the string as an address and connecting to it.

4. Sanitize Finding.location.path independently

Section titled “4. Sanitize Finding.location.path independently”

path is a descriptive locator only. It provides no guarantee of referring to an actual file, and it MUST NOT be passed to filesystem APIs, shell commands, or URL constructors without independent sanitization. Treat it as a hostile string: assume path traversal sequences, injection payloads, or control characters until proven otherwise.

5. Treat identifiers as opaque — derive nothing beyond the prefix

Section titled “5. Treat identifiers as opaque — derive nothing beyond the prefix”

An identifier like task_42 or finding_7f3 matches ^[a-z]+_[A-Za-z0-9._-]+$. The prefix denotes category only — it is not an authority, a namespace grant, or a routing token. Identifiers are producer-asserted: DCP gives no referential-integrity guarantee and does not guarantee global uniqueness across independent producers. Scope identifiers using your own transport context (tenant, project, mesh session), never DCP fields alone.

6. Enforce freshness with transport timestamps, not DCP ones

Section titled “6. Enforce freshness with transport timestamps, not DCP ones”

issued_at and occurred_at are self-reported and may be arbitrarily skewed — a producer can claim any value. DCP defines no freshness guarantee. If your system has a freshness requirement (reject stale events, detect replay-like patterns), enforce the time window using your transport’s delivery timestamp, not the DCP envelope.

7. Respect the schema’s DoS bounds — and add your own

Section titled “7. Respect the schema’s DoS bounds — and add your own”

The schemas bound field lengths, array sizes, and object property counts as a baseline: delta has at most 64 entries, refs[].ref is capped at 512 characters, and each extension value is an object with at most 20 properties. These bounds prevent obvious amplification, but they are not a substitute for your own ingestion limits. Enforce a total message-size cap at the transport boundary independently of schema validation.

8. A status like “approved” is a record, not an authorization

Section titled “8. A status like “approved” is a record, not an authorization”

A ReviewRequest with status approved, or a Decision with status accepted, records that an outcome was reported — it is not a credential and does not authorize anything downstream. DCP gates nothing; it has no approval engine and no workflow engine. If your system needs to act on an approval, that action requires its own independent authorization check, informed by — but never satisfied by — the DCP record.

In every case, the shape is the same: DCP describes a claim, and your system decides whether to act on it. The transport is where authentication and authorization live; DCP is the semantic layer sitting on top of it. Before wiring any consumer into production, run messages through schema and cross-field validation first — a message that doesn’t even validate structurally is not worth reasoning about for trust purposes at all.

By InterIP Networks · Last updated 2026-07-01.