From 80c80a7d00257a63e6d4729cfb89271ef4cd0d21 Mon Sep 17 00:00:00 2001 From: Arc Studio Agent Date: Wed, 7 Oct 2026 19:05:58 +0000 Subject: [PATCH] fix(did): default allowedHttpHosts to [] in getDidResolver The previous default allowed localhost, 127.0.0.1 and 0.0.0.0 to serve DID documents over plain HTTP. Because getDidResolver() is a library entrypoint, that default silently applied to any production service that called it without explicit webOptions, opening DID documents to MITM substitution on non-TLS networks. Change the default to an empty list (HTTPS-only) and update all demos and examples to pass their own allowedHttpHosts explicitly, so local development continues to work without polluting the library default. --- demos/e2e/src/index.ts | 2 +- demos/identity-a2a/src/bank-client-agent.ts | 2 +- demos/identity-a2a/src/issuer.ts | 2 +- demos/identity/src/index.ts | 2 +- demos/payments/src/index.ts | 2 +- demos/payments/src/payment-service.ts | 2 +- demos/payments/src/receipt-service.ts | 2 +- demos/payments/src/server.ts | 2 +- examples/issuer/src/middleware/did-resolver.ts | 2 +- examples/verifier/src/routes/verify.ts | 2 +- packages/did/src/did-resolvers/get-did-resolver.ts | 9 ++++++++- 11 files changed, 18 insertions(+), 11 deletions(-) diff --git a/demos/e2e/src/index.ts b/demos/e2e/src/index.ts index 7f2c4f30..b8b5fe58 100644 --- a/demos/e2e/src/index.ts +++ b/demos/e2e/src/index.ts @@ -41,7 +41,7 @@ const CHAIN_ID = caip2ChainIds.baseSepolia * Their constructor adds their own DIDs to the resolver cache to * ensure they are always resolvable. */ -const resolver = getDidResolver() +const resolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) const credentialIssuer = await CredentialIssuer.create({ baseUrl: "https://issuer.example.com", diff --git a/demos/identity-a2a/src/bank-client-agent.ts b/demos/identity-a2a/src/bank-client-agent.ts index 54bb45f1..32dce526 100644 --- a/demos/identity-a2a/src/bank-client-agent.ts +++ b/demos/identity-a2a/src/bank-client-agent.ts @@ -382,7 +382,7 @@ class BankClientAgent extends Agent { try { logger.log("šŸ” Resolving bank teller DID document...") - const resolver = getDidResolver() + const resolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) const didResult = await resolveDid(serverDid, resolver) const didDocument = didResult.didDocument diff --git a/demos/identity-a2a/src/issuer.ts b/demos/identity-a2a/src/issuer.ts index b8f1e93e..9711678b 100644 --- a/demos/identity-a2a/src/issuer.ts +++ b/demos/identity-a2a/src/issuer.ts @@ -20,7 +20,7 @@ const issuerDidDocument = createDidDocumentFromKeypair({ const signer = createJwtSigner(issuerKeypair) -const resolver = getDidResolver() +const resolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) resolver.addToCache(issuerDid, issuerDidDocument) export const didResolverWithIssuer = resolver diff --git a/demos/identity/src/index.ts b/demos/identity/src/index.ts index 1eef85f8..5e4e61ab 100644 --- a/demos/identity/src/index.ts +++ b/demos/identity/src/index.ts @@ -32,7 +32,7 @@ import { serveAgent } from "./serve-agent" const currentDir = dirname(fileURLToPath(import.meta.url)) -const resolver = getDidResolver() +const resolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) const welcomeMessage = `${colors.bold("\nšŸ” Welcome to the Agent Commerce Kit Identity (ACK-ID) Demo! šŸ”")} This interactive demo will guide you through the fundamental concepts of ACK-ID. We'll demonstrate how to establish verifiable identities for AI agents and their owners, and how agents can securely verify each other's identities before interacting. diff --git a/demos/payments/src/index.ts b/demos/payments/src/index.ts index c1cbbea6..98eb898e 100644 --- a/demos/payments/src/index.ts +++ b/demos/payments/src/index.ts @@ -244,7 +244,7 @@ The Client attempts to access a protected resource on the Server. Since no valid ), colors.bold("šŸ“„ Receipt Details (Verifiable Credential):"), ) - const resolver = getDidResolver() + const resolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) const parsedDetails = typeof details === "string" ? await parseJwtCredential(details, resolver) diff --git a/demos/payments/src/payment-service.ts b/demos/payments/src/payment-service.ts index 713d0319..4218a5fd 100644 --- a/demos/payments/src/payment-service.ts +++ b/demos/payments/src/payment-service.ts @@ -131,7 +131,7 @@ async function validatePaymentOption( paymentOptionId: string, paymentRequestToken: JwtString, ) { - const didResolver = getDidResolver() + const didResolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) log(colors.dim(`${name} Verifying payment request token...`)) const { paymentRequest } = await verifyPaymentRequestToken( diff --git a/demos/payments/src/receipt-service.ts b/demos/payments/src/receipt-service.ts index 8dc74fe9..23d901ef 100644 --- a/demos/payments/src/receipt-service.ts +++ b/demos/payments/src/receipt-service.ts @@ -66,7 +66,7 @@ app.post("/", async (c) => { const serverIdentity = await getKeypairInfo( env(c).RECEIPT_SERVICE_PRIVATE_KEY_HEX, ) - const didResolver = getDidResolver() + const didResolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) const { payload } = v.parse(bodySchema, await c.req.json()) diff --git a/demos/payments/src/server.ts b/demos/payments/src/server.ts index 344d8ce8..ec808376 100644 --- a/demos/payments/src/server.ts +++ b/demos/payments/src/server.ts @@ -41,7 +41,7 @@ app.onError((e, c) => { */ app.get("/", async (c): Promise> => { const serverIdentity = await getKeypairInfo(env(c).SERVER_PRIVATE_KEY_HEX) - const didResolver = getDidResolver() + const didResolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) const { publicKey: solanaServerPublicKey } = await ensureSolanaKeys( "SOLANA_SERVER_PUBLIC_KEY", diff --git a/examples/issuer/src/middleware/did-resolver.ts b/examples/issuer/src/middleware/did-resolver.ts index 414131c0..2df2da4b 100644 --- a/examples/issuer/src/middleware/did-resolver.ts +++ b/examples/issuer/src/middleware/did-resolver.ts @@ -10,7 +10,7 @@ declare module "hono" { export function didResolver(): MiddlewareHandler { return async (c, next) => { const issuer = c.get("issuer") - const resolver = getDidResolver() + const resolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) // There are certain points where we need to resolve our own DID (when we parse a JWT that we signed, for example). // However, CF workers cannot invoke themselves, so an attempt to resolve our own DID will fail. To get around this, // we pre-populate the cache with our own DID document. diff --git a/examples/verifier/src/routes/verify.ts b/examples/verifier/src/routes/verify.ts index 3c628f09..e4807b5b 100644 --- a/examples/verifier/src/routes/verify.ts +++ b/examples/verifier/src/routes/verify.ts @@ -34,7 +34,7 @@ app.post( "/", sValidator("json", bodySchema), async (c): Promise> => { - const resolver = getDidResolver() + const resolver = getDidResolver({ webOptions: { allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"] } }) let { credential } = c.req.valid("json") if (isJwtString(credential)) { diff --git a/packages/did/src/did-resolvers/get-did-resolver.ts b/packages/did/src/did-resolvers/get-did-resolver.ts index f793c8a7..4caaeeea 100644 --- a/packages/did/src/did-resolvers/get-did-resolver.ts +++ b/packages/did/src/did-resolvers/get-did-resolver.ts @@ -21,10 +21,17 @@ interface GetDidResolverOptions extends ResolverOptions { * * @param options - The {@link GetDidResolverOptions} to use for the did resolver * @returns A new {@link DidResolver} instance + * + * @remarks + * By default no hosts are allowed to serve DID documents over plain HTTP — + * all resolution uses HTTPS. Pass `webOptions.allowedHttpHosts` to permit + * specific hosts (e.g. `["localhost", "127.0.0.1"]` during local development). + * Allowing HTTP in production exposes DID documents to man-in-the-middle + * substitution, so the default is intentionally restrictive. */ export function getDidResolver({ webOptions = { - allowedHttpHosts: ["localhost", "127.0.0.1", "0.0.0.0"], + allowedHttpHosts: [], }, ...options }: GetDidResolverOptions = {}): DidResolver {