Skip to content

fix(did): reject did:key:z in isDidKeyUri - #164

Open
Dusk1e wants to merge 1 commit into
agentcommercekit:mainfrom
Dusk1e:fix/did-key-uri-guard
Open

fix(did): reject did:key:z in isDidKeyUri#164
Dusk1e wants to merge 1 commit into
agentcommercekit:mainfrom
Dusk1e:fix/did-key-uri-guard

Conversation

@Dusk1e

@Dusk1e Dusk1e commented Aug 16, 2026

Copy link
Copy Markdown

isDidKeyUri returns true for did:key:z — the multibase prefix with no key material behind it.

import { isDidKeyUri, getDidResolver } from "@agentcommercekit/did"

isDidKeyUri("did:key:z") // true

await getDidResolver().resolve("did:key:z")
// { didDocument: null, didResolutionMetadata: { error: "invalidDid" } }

So the guard vouches for a DID the resolver in this same package rejects. Anything that narrows to DidKeyUri on the strength of it is holding a value with no key bytes in it.

Why

The check slices the multibase value off by index:

if (typeof did !== "string" || !did.startsWith("did:key:z")) {
  return false
}

const mbValue = did.slice(8) // Get everything after "did:key:z"
return /^[a-km-zA-HJ-NP-Z1-9]+$/.test(mbValue)

"did:key:" is 8 characters and "did:key:z" is 9, so slice(8) keeps the z — the comment describes the intent, not what the line does. z is itself a base58btc character, so it satisfies the + on its own and the empty value passes. The grammar quoted directly above the function requires at least one character after the prefix:

did-key-format := did:key:<mb-value>
mb-value       := z[a-km-zA-HJ-NP-Z1-9]+

Change

The guard is now the grammar as one pattern over the whole URI, so the quantifier lands on the base58btc value and there is no index to get wrong:

const didKeyUriRegex = /^did:key:z[a-km-zA-HJ-NP-Z1-9]+$/

This is the same shape createDidKeyUri's existing tests already assert its output against, so the two now agree on what a did:key URI is.

did:key:z is the only string whose result changes. The old slice differed from a correct one only by the leading z, and since z is in the character class, every other input was already classified the same way.

Tests

Added to the existing isDidKeyUri block in packages/did/src/methods/did-key.test.ts:

  • did:key:z and did:key: are rejected
  • characters outside the base58btc alphabet (0, O, I, l) are rejected
  • non-string input is rejected
  • the guard and getDidResolver().resolve() agree on did:key:z

The first and last fail on main. packages/did is green (74 tests), oxlint reports nothing on the changed files, and oxfmt --check passes on them.

One note on my local run: packages/did/src/did-resolvers/pkh-did-resolver.test.ts cannot run on Windows because the did-pkh fixture filenames contain :, which is what #145 describes. That is unrelated to this change and I left it alone.

AI assistance disclosure

Per the repository AI policy: this contribution was AI-assisted using Claude Code (Claude Opus). AI assistance was used to locate the defect, write the fix and the tests, and run verification locally. I reviewed the final diff, can explain the change and why it is confined to a single input, and take responsibility for what is submitted here.

Summary by CodeRabbit

  • Bug Fixes
    • Improved did:key URI validation to require valid key material.
    • Invalid identifiers, including incomplete keys and those with unsupported characters, are now rejected.
    • Non-string inputs and identifiers rejected by the resolver are handled correctly.

The guard checked `startsWith("did:key:z")` and then tested the rest of
the string against the base58btc class, slicing from index 8. That is the
length of `"did:key:"`, not of `"did:key:z"`, so the `z` stayed in the
string being tested and satisfied the `+` in `z[a-km-zA-HJ-NP-Z1-9]+` on
its own. `isDidKeyUri("did:key:z")` returned true for a DID carrying no
key material, which `getDidResolver().resolve()` reports as `invalidDid`.

Replace the index arithmetic with a single pattern spanning the whole URI,
so the quantifier applies to the base58btc value as the documented grammar
intends. `z` is itself a base58btc character, so that was the only string
the old slice let through; no other input changes.
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a8a49651-74f9-49e9-b80c-bc123a8dd3ae

📥 Commits

Reviewing files that changed from the base of the PR and between 0b8fdaa and 50904ae.

📒 Files selected for processing (3)
  • .changeset/did-key-uri-guard.md
  • packages/did/src/methods/did-key.test.ts
  • packages/did/src/methods/did-key.ts

Included review availability: Your plan includes up to 2 reviews per rolling hour; 1 remains after this review.


Walkthrough

The isDidKeyUri implementation now validates the complete URI with a shared regex. The regex requires key material after the z prefix. Tests cover empty keys, invalid characters, non-string inputs, and resolver rejection. A patch changeset documents the correction.

Changes

did:key validation

Layer / File(s) Summary
Full URI validation and regression coverage
packages/did/src/methods/did-key.ts, packages/did/src/methods/did-key.test.ts, .changeset/did-key-uri-guard.md
isDidKeyUri now applies a complete URI regex that requires valid base58btc key material. Tests cover malformed and non-string inputs. The changeset documents the patch.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 50904

The PR tightens did:key URI validation to reject an empty key value and adds focused coverage; no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR fixes did:key validation but does not address #145, which requires Windows-safe did-pkh fixture filenames. Rename or map the colon-containing did-pkh fixture filenames to Windows-safe names while preserving their DID values.
Out of Scope Changes check ⚠️ Warning The regex, tests, and changeset address did:key validation, which is unrelated to the Windows fixture compatibility objective in #145. Link this PR to an issue covering did:key validation, or implement the Windows-safe fixture rename or mapping required by #145.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: rejecting did:key:z in isDidKeyUri.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant