Skip to content

feat(did): refuse redirects when resolving did:web by default - #133

Open
EfeDurmaz16 wants to merge 1 commit into
agentcommercekit:mainfrom
EfeDurmaz16:did-resolver-no-redirect
Open

feat(did): refuse redirects when resolving did:web by default#133
EfeDurmaz16 wants to merge 1 commit into
agentcommercekit:mainfrom
EfeDurmaz16:did-resolver-no-redirect

Conversation

@EfeDurmaz16

@EfeDurmaz16 EfeDurmaz16 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What

getResolver for did:web now sends redirect: "error" when fetching the DID document. A new followRedirects?: boolean option (default false) restores the previous behavior.

Why

allowedHttpHosts is applied to the URL built from the DID:

const url = isHttpAllowed(path, allowedHttpHosts)
  ? `http://${path}`
  : `https://${path}`

The fetch that follows it used the platform default, which follows redirects. So the allowlist only governs the first hop: a did:web resolved over https can answer 302 Location: http://…, and the resolver follows it to a scheme and host the check was there to reject.

flowchart LR
  D["did:web:issuer.example"] --> C{"allowedHttpHosts<br/>allows http for this host?"}
  C -- no --> U["fetch https://issuer.example/.well-known/did.json"]
  U --> H{"host answers 302<br/>Location: http://internal/…"}
  H -->|before: redirect followed| BAD["request lands on the scheme<br/>and host the check rejected"]
  H -->|after: redirect error| OK["resolution fails as notFound"]

  classDef bad stroke-dasharray: 4 3
  class BAD bad
Loading

Because the redirect target is chosen by the DID's own host, this also makes resolution an outbound request an untrusted party controls. That matters for anything that resolves a DID before verifying a signature, which is the normal order: you need the document to check the signature. A server verifying an ACK-ID proof from an arbitrary issuer will fetch whatever that issuer's host points it at.

did:web documents are served directly at /.well-known/did.json (or the configured docPath), so refusing redirects costs legitimate resolution nothing. Deployments that genuinely sit behind a redirect can opt in.

How

  • DidWebResolverOptions gains followRedirects?: boolean, documented with the reason, defaulting to false.
  • fetchDidDocumentAtUrl passes redirect: followRedirects ? "follow" : "error" alongside the existing mode: "cors".
  • Existing tests that assert the fetch options were updated to include the new field; two tests cover the default (refuses, resolves to notFound) and the opt-in (redirect: "follow").

Test plan

pnpm build
pnpm --filter @agentcommercekit/did test   # 72 passed

Found while building an ACK-ID identity-gated x402 demo, where a seller resolves buyer DIDs it has never seen before.

Implemented and tested by gpt 5.6-sol and fable 5, reviewed by me.

Summary by CodeRabbit

  • New Features

    • Added an optional setting to allow HTTP redirects when resolving did:web documents.
    • Redirects are now blocked by default to improve resolution safety.
  • Documentation

    • Documented how to restore redirect-following behavior with followRedirects: true.

allowedHttpHosts is applied to the URL built from the DID, but the document
fetch followed redirects, so the check only governed the first hop: a did:web
served over https could redirect the resolver to plain http, or to a host the
allowlist would have rejected. Because the redirect target is chosen by the
DID's own host, this also turns resolution into an outbound request the
issuer controls.

did:web documents are served directly at a well-known path, so redirects are
refused by default via redirect: "error". followRedirects: true restores the
previous behavior for deployments that need it.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The did:web resolver now rejects HTTP redirects by default through redirect: "error". A followRedirects option restores redirect following when enabled, with updated tests and a minor-release changeset.

Changes

did:web redirect handling

Layer / File(s) Summary
Resolver redirect policy
packages/did/src/did-resolvers/web-did-resolver.ts
Adds the optional followRedirects setting, defaults it to false, and passes the corresponding redirect mode to document fetching.
Redirect validation and release metadata
packages/did/src/did-resolvers/web-did-resolver.test.ts, .changeset/wild-hounds-shave.md
Updates fetch expectations, tests default redirect rejection and opt-in following, and documents the minor release behavior.

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

Possibly related PRs

  • agentcommercekit/ack#129: Modifies the same did:web document-fetching path and its tests with timeout and abort-signal handling.

Suggested reviewers: venables

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: did:web resolution now refuses redirects by default.
✨ 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