Skip to content

Agents with rotated/compromised keys can't be deregistered — orphan DIDs persist under shared capabilities #29

@philpof102-svg

Description

@philpof102-svg

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)

  1. Signature-gated self-deregisterDELETE /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.

  2. 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.

  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions