Security Model
This page documents the security defenses the crate applies automatically and how to (carefully) relax them for tests. The threat model and the normative requirements are specified in RFC-ACDP-0008 (Security & Threat Model) and the cross-registry SSRF rules in RFC-ACDP-0006 §7. This crate does not invent policy — it enforces what those RFCs require, by default, on every public client API.
The trust boundary
ACDP is a zero-trust substrate. A registry is not a trusted party. The only
thing you trust is the producer's signature, verified against a key resolved
from the producer's own did:web document. Everything the
verification pipeline
does follows from that: recompute the hash yourself, resolve the key yourself,
verify the signature yourself.
The second trust concern is outbound requests. The library makes outbound HTTPS calls in three places, and each is SSRF-guarded identically:
- Producer DID resolution —
WebResolverfetchingdid.json. - Cross-registry resolution —
CrossRegistryResolverfetching foreign contexts and capabilities. - Data-ref fetching —
HttpsDataRefFetcherfetching referenced data.
Defenses applied by default
Every public client API (RegistryClient, WebResolver,
CrossRegistryResolver, HttpsDataRefFetcher) applies these out of the box —
you do not opt in:
| Defense | What it does | Spec |
|---|---|---|
| HTTPS-only | http:// URLs are rejected. | RFC-ACDP-0008 |
| IP-literal rejection | https://1.2.3.4/… is rejected — forces a DNS lookup so the resolved IP can be filtered. | RFC-ACDP-0006 §7 |
| Private/loopback/link-local/multicast/IMDS blocking | Resolved IPs in RFC 1918, loopback, link-local, CGNAT, multicast, ULA (fc00::/7), fe80::/10, and the metadata endpoint (169.254.169.254) are refused — IPv4 and IPv6, including IPv4-mapped. | RFC-ACDP-0008 §4.8/§4.9 |
| DNS-rebinding pin | IPs are filtered at DNS-resolution time, before any TCP connect — a hostname whose answers fall in a forbidden range is refused. See below. | RFC-ACDP-0008 §7.6 |
| Body-size caps | 1 MB for context retrievals; 64 KB for capabilities and DID documents. | RFC-ACDP-0006 §7 |
| Redirect cap | Max 3 redirects, same-authority only. | RFC-ACDP-0006 §7 |
| Timeouts | 5 s connect, 30 s total. | RFC-ACDP-0006 §7.4 |
| Algorithm-downgrade rejection | The signature algorithm must match the algorithm of the resolved DID verification method. | RFC-ACDP-0001 §5.10 |
| Ed25519 mandatory | Ed25519 is always supported; downgrade attacks are rejected. | RFC-ACDP-0001 §5.10 |
The size, redirect, and timeout constants are exposed as
acdp::registry::{MAX_CONTEXT_BYTES, MAX_METADATA_BYTES, MAX_REDIRECTS}and insrc/limits.rs.
DNS-rebinding protection is active
DNS-rebinding (RFC-ACDP-0008 §7.6) is on. crate::safe_http::SafeDnsResolver
is wired into reqwest's dns_resolver hook by every HTTP client the crate
builds (WebResolver, RegistryClient, HttpsDataRefFetcher,
CrossRegistryResolver). Each resolved IP is filtered through the active
SsrfPolicy at DNS time, before the socket is opened, so a host that
DNS-rebinds to a forbidden range can never be connected to.
A host whose answers fall in a forbidden range is refused with
AcdpError::KeyResolution (permanent — HTTP 400, is_transient == false), not
a connect error.
SsrfPolicy
SsrfPolicy is the knob behind all of this. Its default is the secure posture;
you rarely construct it directly.
| Field | Default | Meaning |
|---|---|---|
reject_ip_literals | true | refuse URLs with a literal IP host |
allow_http | false | HTTPS-only |
allow_loopback_resolved | false | refuse hosts that resolve to loopback |
policy.check_url(url) validates a URL up front; classify_url returns a
structured SsrfRejection { reason, detail } for diagnostics.
Testing against localhost
Because loopback is blocked by default, tests that POST to a local listener need to opt in explicitly. The intended seams:
SsrfPolicy::allow_test_loopback()— the default policy withallow_loopback_resolved = true. Pass it to a fetcher/resolver that accepts a custom policy (e.g.HttpsDataRefFetcher::with_ssrf_policy(...),CrossRegistryResolver::with_ssrf_policy(...)).RegistryClient::with_test_transport(base_url)— a client that permits HTTP and loopback for in-process test servers.RegistryClient::with_test_endpoint(...)/new_pinned(...)— pin a DNS answer / custom CA so a TLS test server on127.0.0.1is reachable while the production path stays locked down.
These are test-only. Never construct a loopback-permitting policy in production code — it reopens the SSRF surface the defaults close. The TLS conformance suite (
tests/tls_conformance.rs) uses exactly these seams to drive thefed-*anddid-ssrf-*fixtures against an in-process server.
What the crate does not do
Per RFC-ACDP-0008, some responsibilities sit with the registry or the operator, not this client library:
- Rate limiting (RFC-ACDP-0008 §4.3) — a registry concern. The
serverfeature exposes aRateLimitertrait; see Implementing a registry. - Authentication / authorization of cross-registry calls — out of scope for v0.1.0 (RFC-ACDP-0006). Cross-registry resolution is unauthenticated.
- Visibility enforcement at rest — the registry enforces visibility on retrieval; the consumer applies visibility rules client-side but cannot see what a registry refuses to serve.
For the full threat enumeration (replay, Sybil/spam, existence-leak, supersession races), read RFC-ACDP-0008 directly — these docs do not duplicate it.