Repository navigation
Gate did:gitlawb resolution: nine verification sites are safe only because to_verifying_key never does a lookup #328
Description
Activity
- addedcrate:coregitlawb-core — identity, certs, encrypt, DID/UCANgitlawb-core — identity, certs, encrypt, DID/UCANcrate:git-remotegit-remote-gitlawb — the git remote helpergit-remote-gitlawb — the git remote helpercrate:glgl — the contributor CLIgl — the contributor CLIcrate:nodegitlawb-node — the serving node and REST APIgitlawb-node — the serving node and REST APIkind:securityVulnerability fix or hardeningVulnerability fix or hardeningsev:highMajor break or real security/trust risk, no easy workaroundMajor break or real security/trust risk, no easy workaroundsubsystem:attestationCertificates, anchoring, per-ref attestationCertificates, anchoring, per-ref attestationsubsystem:identityDID/UCAN, http-sig auth, push authorizationDID/UCAN, http-sig auth, push authorization
on Aug 12, 2026 Filed the other half of this as #352: the DHT record that already exists and already has no authenticity binding.
This issue scopes itself away from that on purpose ("Not a request to design DHT anchoring, only to make its arrival visible where it lands"), which is right, but the two meet at exactly the point this one anticipates. At
origin/main50d3cbb the record is self-asserted end to end:p2p/mod.rs:401-412publishes withpublisher: None,expires: None, and a key that is a pure function of the DID string, so the key for any DID is computable by anyone who knows the DID.DidRecord(p2p/mod.rs:62-69) isdid,http_url,peer_id,p2p_port,timestamp. No signature field.grep -rc set_record_filtering crates/gitlawb-node/src/returns nothing, so libp2p's default unfiltered insert applies and the memory store overwrites an occupied entry with no publisher comparison.api/resolve.rs:38-47returnsrecord.http_urlstraight to the caller, skipping theis_public_http_urlcheck thatupsert_peerapplies atdb/mod.rs:2236.
On who can write one: the swarm listens on
/ip4/0.0.0.0/udp/{port}/quic-v1(p2p/mod.rs:258-263) with the port defaulting to 7546 (config.rs:104), Kademlia runs inMode::Server(p2p/mod.rs:208), and there is no peer allowlist in that file. So any host that can reach the p2p port can put a record, with no identity gate in between.It is low severity today only because of the consumer set:
get_didhas one caller, which consults the local peers table first and falls through to the DHT only for an unknown DID, and the two CLI consumers print and stop. Nothing authorizes on it and no node-side code fetches the URL.Which is the connection worth recording here. Gating resolution without signing the record leaves a resolver reading something anyone can overwrite; signing the record without gating resolution leaves the path ungated. The severity of #352 is a function of this issue landing, so whoever picks up either should look at both.
Not reproduced: the overwrite needs a two-node swarm. The mechanism rests on the libp2p-kad source (unfiltered insert, unconditional overwrite) rather than on inference.
What holds today
Did::to_verifying_key(crates/gitlawb-core/src/did.rs) does not resolve anything. It multibase-decodes the method-specific id, checks the ed25519 multicodec, and builds the key from those bytes, so fordid:keythe verifying key is a pure function of the DID string. Every other method is refused ahead of the decode, and two tests pin that as intent rather than omission:to_verifying_key_fails_for_did_web(did.rs:270) andto_verifying_key_fails_for_did_gitlawb(did.rs:280). The peer path states the same rule in its own vocabulary throughPeerWriteDenied::UnsupportedDidMethod(crates/gitlawb-node/src/db/mod.rs:2288), and README:312 describes what it means for announces.None of that is a problem, and I am not asking to change any of it.
Why it is load-bearing well past the certificate code
Because the key is a pure function of the identifier, "verify with the key named by the artifact" and "verify against an independently anchored key" are currently the same predicate. You cannot claim someone else's DID while signing with a different key, since naming the DID is naming the key. That equivalence is what lets several verification sites read as self-anchored while being safe.
Nine production call sites rest on it:
crates/gitlawb-node/src/auth/mod.rs:141(HTTP signature verification)crates/gitlawb-core/src/http_sig.rs:297crates/gitlawb-core/src/ucan.rs:174crates/gitlawb-core/src/cert.rs:121crates/gitlawb-node/src/encrypted_pin.rs:38crates/gitlawb-node/src/db/mod.rs:2294crates/git-remote-gitlawb/src/main.rs:1171crates/gl/src/cert.rs:274crates/gl/src/mcp.rs:778What changes the day resolution lands
did.rs:5-9documents that the canonical DID migrates todid:gitlawb, anchored to the libp2p DHT, once anchoring is live. On that day the key stops being a pure function of the identifier and becomes a lookup whose answer a third party can influence, and the equivalence above stops holding at all nine sites at once. Each one then becomes the shape AGENTS.md warns about under "Verifying signatures and certificates": a key read from the object being verified proves self-consistency, not authenticity, and identities here are permissionless. Nothing in the code records that dependency, so the change would be written as a capability addition inside one function and would land as a trust-boundary change in nine places.That reading is not local to us. DID Resolution v1.0 section 8.1 ranks resolution requiring no interaction with a remote network as the strongest form and names did:key as its example, and the did:key spec describes itself as purely generative, requiring no lookups in a registry. DID Core then spends section 8.3 on what a verifier has to do about key rotation and document versioning once a document can change at all, and section 8.7 on takeover under a stable identifier. None of that applies to us today. All of it would.
The ask
Make enabling resolution for another DID method a gated change rather than an additive one. Concretely, though I am open to a better shape:
to_verifying_keydid:key-only and permanently offline. Add resolution as a separate, explicitly named API that a caller opts into, so a new method cannot silently upgrade the nine existing sites.What this is not
Not a live vulnerability, and not a claim that any current code is wrong: non-did:key methods fail closed today at every one of those sites. Not a request to revisit did:key-only, which is settled and right for now. Not a request to design DHT anchoring, only to make its arrival visible where it lands.
Surfaced while reviewing #326, which is unaffected and fine as it stands. #314 touches the same function and does not interact with this.