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:

  1. Producer DID resolutionWebResolver fetching did.json.
  2. Cross-registry resolutionCrossRegistryResolver fetching foreign contexts and capabilities.
  3. Data-ref fetchingHttpsDataRefFetcher fetching 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:

DefenseWhat it doesSpec
HTTPS-onlyhttp:// URLs are rejected.RFC-ACDP-0008
IP-literal rejectionhttps://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 blockingResolved 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 pinIPs 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 caps1 MB for context retrievals; 64 KB for capabilities and DID documents.RFC-ACDP-0006 §7
Redirect capMax 3 redirects, same-authority only.RFC-ACDP-0006 §7
Timeouts5 s connect, 30 s total.RFC-ACDP-0006 §7.4
Algorithm-downgrade rejectionThe signature algorithm must match the algorithm of the resolved DID verification method.RFC-ACDP-0001 §5.10
Ed25519 mandatoryEd25519 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 in src/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.

FieldDefaultMeaning
reject_ip_literalstruerefuse URLs with a literal IP host
allow_httpfalseHTTPS-only
allow_loopback_resolvedfalserefuse 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 with allow_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 on 127.0.0.1 is 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 the fed-* and did-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 server feature exposes a RateLimiter trait; 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.