Gitlawb/node — Issue draft: agents with compromised keys can't be deregistered, persisting as brand-pollution under shared capabilities
Draft for Phil to file at https://github.com/Gitlawb/node/issues/new
Related: #6 (repo dedup), Gitlawb/contracts#3 + #4 (MainStreet integration)
Summary
There's no public API to deregister an agent or remove a repo from the node. When an agent's private key is compromised and rotated, the orphaned DID stays registered indefinitely, sharing capabilities and repo names with the legitimate replacement agent.
Concrete example from MainStreet:
curl -fsSL 'https://node.gitlawb.com/api/v1/agents?capability=reputation:score'
Returns two agents with identical capabilities, both claiming the mainstreet brand:
[
{
"did": "did:key:z6MkkgAscJeTZmSmi6ZpZ6fuXUgEJSxiQnS9MNcxEBVuqFMU",
"capabilities": ["reputation:score","attestation:verify","oracle:agent-trust", ...],
"registered_at": "2026-06-06T22:49:52Z",
"trust_score": 0.1
},
{
"did": "did:key:z6MkfkLaRJJMxX5utQekF5VHPDR6we7iBqUY1Fg7Z5Rv6fcU",
"capabilities": ["reputation:score","attestation:verify","oracle:agent-trust", ...],
"registered_at": "2026-06-06T22:56:05Z",
"trust_score": 0.1
}
]
The first DID's private key (identity.pem) was leaked and rotated. The agent and its mainstreet repo (https://gitlawb.com/node/repos/z6Mkkg.../mainstreet — returns 200) cannot be removed.
Why this matters
For any caller routing by ?capability=X, the first matching agent wins — and right now that's the orphan, with a compromised key, indistinguishable on the wire from the canonical agent. Any verifier that signs an attestation to the orphan DID is signing to someone holding a leaked key.
Compounds with #6 (canonical/short-DID dedup) and the absence of /api/v1/names/* — no name registry means there's no way to claim mainstreet as a single brand on the network.
Repro
# 1. Register an agent (any capabilities).
gl register --capabilities reputation:score
# 2. Lose the key (or simulate by rotating).
mv ~/.gitlawb ~/.gitlawb.compromised-$(date +%s)
gl identity new
gl register --capabilities reputation:score
# 3. Both agents now show up:
curl -fsSL 'https://node.gitlawb.com/api/v1/agents?capability=reputation:score'
# 4. No way to deregister the first.
curl -X DELETE 'https://node.gitlawb.com/api/v1/agents/did:key:z6Mkkg...' # → 405
Suggested directions (open to whatever shape fits)
-
Signature-gated self-deregister — DELETE /api/v1/agents/{did} authed via HTTP signatures rfc9421 (same auth as the rest of the API). Only the holder of the DID's private key can call it. Doesn't help if the key is lost, but covers the rotate-after-leak case cleanly.
-
gl identity revoke <old-did> --replaced-by <new-did> — signed by the old key while the operator still holds it (the canonical "I'm rotating proactively" case), or signed by a UCAN delegated to the new key. Network surfaces it as replaced_by metadata so callers can follow the rotation rather than picking the older entry.
-
Operator-side prune endpoint — admin-authed POST /admin/v1/agents/{did}/prune for the node operator to remove obviously-orphaned entries on request, gated by some out-of-band proof (e.g., a GitHub issue from the canonical DID's owner referencing the orphan).
For our case specifically — if the team can manually prune did:key:z6Mkkg... + its mainstreet repo, that unblocks us immediately while a proper API is designed. Happy to provide whatever proof you'd want (signed message from the canonical DID, a write on the canonical repo, etc.).
Adjacent bug noticed
/api/v1/repos?name=mainstreet ignores the name= filter and returns the full repo list. Probably worth its own small issue; flagging here for visibility.
What I'd offer as a PR
Direction 1 (signature-gated DELETE /api/v1/agents/{did}) is the smallest defensible change — happy to draft it against crates/gitlawb-node/src/api/agents.rs if that's the direction you'd pick.
Gitlawb/node — Issue draft: agents with compromised keys can't be deregistered, persisting as brand-pollution under shared capabilities
Summary
There's no public API to deregister an agent or remove a repo from the node. When an agent's private key is compromised and rotated, the orphaned DID stays registered indefinitely, sharing capabilities and repo names with the legitimate replacement agent.
Concrete example from MainStreet:
curl -fsSL 'https://node.gitlawb.com/api/v1/agents?capability=reputation:score'Returns two agents with identical capabilities, both claiming the
mainstreetbrand:[ { "did": "did:key:z6MkkgAscJeTZmSmi6ZpZ6fuXUgEJSxiQnS9MNcxEBVuqFMU", "capabilities": ["reputation:score","attestation:verify","oracle:agent-trust", ...], "registered_at": "2026-06-06T22:49:52Z", "trust_score": 0.1 }, { "did": "did:key:z6MkfkLaRJJMxX5utQekF5VHPDR6we7iBqUY1Fg7Z5Rv6fcU", "capabilities": ["reputation:score","attestation:verify","oracle:agent-trust", ...], "registered_at": "2026-06-06T22:56:05Z", "trust_score": 0.1 } ]The first DID's private key (
identity.pem) was leaked and rotated. The agent and itsmainstreetrepo (https://gitlawb.com/node/repos/z6Mkkg.../mainstreet— returns 200) cannot be removed.Why this matters
For any caller routing by
?capability=X, the first matching agent wins — and right now that's the orphan, with a compromised key, indistinguishable on the wire from the canonical agent. Any verifier that signs an attestation to the orphan DID is signing to someone holding a leaked key.Compounds with #6 (canonical/short-DID dedup) and the absence of
/api/v1/names/*— no name registry means there's no way to claimmainstreetas a single brand on the network.Repro
Suggested directions (open to whatever shape fits)
Signature-gated self-deregister —
DELETE /api/v1/agents/{did}authed via HTTP signatures rfc9421 (same auth as the rest of the API). Only the holder of the DID's private key can call it. Doesn't help if the key is lost, but covers the rotate-after-leak case cleanly.gl identity revoke <old-did> --replaced-by <new-did>— signed by the old key while the operator still holds it (the canonical "I'm rotating proactively" case), or signed by a UCAN delegated to the new key. Network surfaces it asreplaced_bymetadata so callers can follow the rotation rather than picking the older entry.Operator-side prune endpoint — admin-authed
POST /admin/v1/agents/{did}/prunefor the node operator to remove obviously-orphaned entries on request, gated by some out-of-band proof (e.g., a GitHub issue from the canonical DID's owner referencing the orphan).For our case specifically — if the team can manually prune
did:key:z6Mkkg...+ itsmainstreetrepo, that unblocks us immediately while a proper API is designed. Happy to provide whatever proof you'd want (signed message from the canonical DID, a write on the canonical repo, etc.).Adjacent bug noticed
/api/v1/repos?name=mainstreetignores thename=filter and returns the full repo list. Probably worth its own small issue; flagging here for visibility.What I'd offer as a PR
Direction 1 (signature-gated
DELETE /api/v1/agents/{did}) is the smallest defensible change — happy to draft it againstcrates/gitlawb-node/src/api/agents.rsif that's the direction you'd pick.