Security model
DID.is turns arbitrary identifiers, URLs and credentials from anonymous clients into network requests and cryptographic decisions. This page says how we keep those decisions correct, what protects the service, and what we do not claim.
Principles
Everything fetched is untrusted
DID documents, logs, status lists, agent cards and MCP servers are treated as attacker-controlled, including on well-known hosts.
Unknown is never a pass
If a mechanism is unsupported or a check cannot be completed, the result says INDETERMINATE or UNSUPPORTED. It never falls back to a guess.
Evidence, not scores
Each dimension is evaluated and reported on its own. Nothing is averaged into a number, and a real-world organisation is never inferred from a resolved document.
Verification cannot be bought
No plan, payment or listing changes a verdict or a claim. Directory listings require proof of control and opt-in.
Threats and mitigations
Each mitigation is covered by automated tests in the resolver.
| Threat | Example | Mitigation |
|---|---|---|
| Server-side request forgery | did:web:169.254.169.254, internal hostnames, DNS answers or redirects pointing inward | IP literals and localhost refused by syntax; every DNS answer must be public; the connection is pinned to the validated address; special-purpose IPv4/IPv6 ranges blocked; at most two redirects, each re-validated, never HTTPS → HTTP |
| DNS rebinding | A second lookup returning a private address | Validated addresses are pinned for the connection; there is no second lookup |
| Resource exhaustion | Huge or slow bodies, decompression bombs, unbounded logs | Streaming size caps per document type, decompression cap, 4 s upstream timeout, bounded concurrency and fail-fast admission (503 with Retry-After) |
| Forged evidence | Linkage credentials from another DID, keys outside the document, fake did:webvh logs | Issuer = subject = DID; keys taken only from the DID's own verification relationships; webvh SCID, hash chain and update-key proofs; strict id equality |
| Signature downgrade | alg: none, unknown crit, unsupported suites treated as valid | none, crit and b64: false refused; unsupported suites reported as UNSUPPORTED; Data Integrity verified per cryptosuite |
| Revocation bypass | Unreachable or forged status lists | Fail-closed: the status list credential must verify with the same issuer and matching purpose, and be unexpired |
| Delegation escalation | Widened capabilities, broken links, cycles | Signatures by capabilityDelegation keys, proof-hash linkage, attenuation, time containment, acyclicity, depth ≤ 8, trusted roots |
| Tool poisoning | MCP servers changing tools after review | Per-tool fingerprints (RFC 8785), drift against previous observations; declared and heuristic side-effect classes shown separately |
| Identifier confusion | Double percent-decoding changing a DID between layers | Raw-path forwarding; identifiers are decoded exactly once |
| Misleading presentation | Reading "resolved" as "trusted" | Every dimension states what it proves and what it does not; verdicts list their limits |
Controls
Network egress
- HTTPS only; userinfo in URLs refused
- All DNS answers must be public; connections pinned
- Per-type body caps (1 MiB documents, 4 MiB webvh logs)
- TLS chain, validity and hostname enforced in the handshake
Public API
- Allowlisted routes and methods only; no admin routes
- 512 KiB request bodies
- Per-client token buckets; 429 with Retry-After
- RFC 9457 problems with stable codes; no-store responses
Cryptography
- Ed25519, ECDSA P-256 and secp256k1 with point validation
- RFC 8785 canonicalisation for Data Integrity
- No remote @context retrieval; offline context catalogue
- Private JWK members rejected in did:jwk
Accounts and keys
- API keys stored as hashes and shown once
- Webhooks signed with HMAC-SHA256 over timestamp and raw body
- Private resolutions bypass public history and shared cache
- No IP address or user agent stored with sessions
Web
- X-Frame-Options DENY, nosniff, strict referrer and permissions policies
- Remote data rendered as text, never as HTML
- No service secrets in the browser
- No third-party trackers
Runtime
- Resolver runs without published ports on an internal network
- Non-root, read-only filesystem, all capabilities dropped
- Encrypted off-host backups with a rollback point per release
- Host and off-host health alerts
What remains
- Split view on did:web: an origin can serve different documents to different resolvers. did:web has no defence; did:webvh witnesses mitigate it for witnessed versions.
- Withheld did:webvh entries: a host can omit newer log entries. Witnesses and watchers reduce this risk but do not remove it.
- TLS evidence relies on the public WebPKI root store.
- MCP side-effect classes are heuristics and are labelled as such.
- Webhook signing secrets must be recoverable to sign deliveries; they are stored server-side, not in a hardware key store.
- No external penetration test or formal verification has been performed yet. We will publish one here when it exists.
Report a vulnerability
Email [email protected] with steps to reproduce. We acknowledge within 2 business days and will not pursue good-faith research that respects the acceptable use policy. Please do not test against other people's accounts or degrade the service.