Gitlawb/contracts — LOW — DIDRegistry.transfer accepts address(0), enables DID burn + hostile re-registration
Phil-ready issue body. Severity LOW — 1-line fix, no funds at risk
but enables silent DID hijack via phishing or fat-finger.
Severity — LOW
Requires owner action (intentional or fat-finger / phishing-induced).
Fix is 1 line. Cost/benefit strongly favors patching even though
exploit isn't drive-by.
Mechanism
GitlawbDIDRegistry.transfer(string did, address newOwner) has no
zero-address guard:
function transfer(string calldata did, address newOwner) external {
bytes32 didHash = keccak256(bytes(did));
if (didToOwner[didHash] == address(0)) revert NotRegistered(didHash);
if (didToOwner[didHash] != msg.sender) revert NotOwner(didHash, msg.sender);
didToOwner[didHash] = newOwner; // newOwner == address(0) accepted
// ...
}
After transfer(did, address(0)):
didToOwner[didHash] is now zero
isRegistered(did) returns false (the next caller sees the DID as
unregistered)
register(did, maliciousDoc) succeeds for any caller, with whatever
DID document they choose
The legitimate owner's on-chain identity is destroyed and any party
can claim the DID name with a hostile DID document. Off-chain
verifiers that trust the registry as a source of truth may then
follow the attacker's document.
Attack surface
- Phishing call: malicious frontend calls
transfer(yourDid, 0x0)
through a wallet popup that looks innocuous
- Fat-finger: owner pastes
0x0 or an empty address in a CLI tool
that doesn't validate
- Smart-wallet UX that auto-populates fields with
address(0) defaults
Suggested fix (1 line)
function transfer(string calldata did, address newOwner) external {
+ if (newOwner == address(0)) revert ZeroAddress();
bytes32 didHash = keccak256(bytes(did));
If the protocol wants a deactivation primitive, expose it explicitly
(deactivate(did) that sets a tombstone, not address(0)). The
NotRegistered revert path is the natural tombstone marker — should
not coincide with the active-but-burned state.
Why this is distinct from prior disclosures
Scanned titles + bodies of Gitlawb/contracts PRs #5-#12. None
address GitlawbDIDRegistry zero-address handling. Phil's prior
DID-related work was the NodeStaking DID-squatting issue (#9), a
different contract.
Deployment
Mainnet: 0x8046284116C5ac6724adbBf860feBeA85692d574
Foundry test (drop into test/GitlawbDIDRegistry.t.sol)
function test_transfer_rejectsZeroAddress() public {
string memory did = "did:key:test";
didRegistry.register(did, "doc-uri");
vm.expectRevert();
didRegistry.transfer(did, address(0));
}
function test_burnedDidCannotBeReclaimedByAttacker() public {
// Without fix, this test PASSES (proving the bug)
// With fix, the inner transfer reverts and the attacker never gets a chance
string memory did = "did:key:victim";
vm.prank(VICTIM);
didRegistry.register(did, "victim-doc");
vm.prank(VICTIM);
didRegistry.transfer(did, address(0)); // burn
vm.prank(ATTACKER);
didRegistry.register(did, "attacker-doc"); // succeeds — bug
assertEq(didRegistry.documentOf(did), "attacker-doc");
}
3-skeptic pass log
-
"DID owners should be able to deactivate." → Normal deactivation
updates the document to a tombstone, does NOT free the DID for
hostile re-registration. Conflating "deactivated" with "free to
claim" is the bug.
-
"Requires intentional owner call." → Correct. Hence LOW, not
MEDIUM. But UI/phishing-induced calls and fat-finger inputs ARE
real attack surface, and 1-line fix has no downside.
-
"Already covered by another PR." → Confirmed not. Scanned all 12
PR titles + bodies. No DIDRegistry transfer-related PR.
Phil-side filing checklist
Gitlawb/contracts — LOW — DIDRegistry.transfer accepts address(0), enables DID burn + hostile re-registration
Severity — LOW
Requires owner action (intentional or fat-finger / phishing-induced).
Fix is 1 line. Cost/benefit strongly favors patching even though
exploit isn't drive-by.
Mechanism
GitlawbDIDRegistry.transfer(string did, address newOwner)has nozero-address guard:
After
transfer(did, address(0)):didToOwner[didHash]is now zeroisRegistered(did)returns false (the next caller sees the DID asunregistered)
register(did, maliciousDoc)succeeds for any caller, with whateverDID document they choose
The legitimate owner's on-chain identity is destroyed and any party
can claim the DID name with a hostile DID document. Off-chain
verifiers that trust the registry as a source of truth may then
follow the attacker's document.
Attack surface
transfer(yourDid, 0x0)through a wallet popup that looks innocuous
0x0or an empty address in a CLI toolthat doesn't validate
address(0)defaultsSuggested fix (1 line)
function transfer(string calldata did, address newOwner) external { + if (newOwner == address(0)) revert ZeroAddress(); bytes32 didHash = keccak256(bytes(did));If the protocol wants a deactivation primitive, expose it explicitly
(
deactivate(did)that sets a tombstone, not address(0)). TheNotRegisteredrevert path is the natural tombstone marker — shouldnot coincide with the active-but-burned state.
Why this is distinct from prior disclosures
Scanned titles + bodies of
Gitlawb/contractsPRs #5-#12. Noneaddress
GitlawbDIDRegistryzero-address handling. Phil's priorDID-related work was the
NodeStakingDID-squatting issue (#9), adifferent contract.
Deployment
Mainnet:
0x8046284116C5ac6724adbBf860feBeA85692d574Foundry test (drop into test/GitlawbDIDRegistry.t.sol)
3-skeptic pass log
"DID owners should be able to deactivate." → Normal deactivation
updates the document to a tombstone, does NOT free the DID for
hostile re-registration. Conflating "deactivated" with "free to
claim" is the bug.
"Requires intentional owner call." → Correct. Hence LOW, not
MEDIUM. But UI/phishing-induced calls and fat-finger inputs ARE
real attack surface, and 1-line fix has no downside.
"Already covered by another PR." → Confirmed not. Scanned all 12
PR titles + bodies. No DIDRegistry transfer-related PR.
Phil-side filing checklist
~30 min later)
gh issue create --repo Gitlawb/contracts --title 'Security LOW: GitlawbDIDRegistry.transfer accepts address(0), enables DID burn + hostile re-registration' --body-file distribution/gitlawb-round9-didRegistry-low.md