From a95bab26e0a4bdc6b029f99ecb8d05d23a220566 Mon Sep 17 00:00:00 2001 From: KutluhanETH Date: Mon, 7 Sep 2026 22:27:54 +0000 Subject: [PATCH] fix(ack-id): require DID URIs in controllerClaimSchema Validate controller credential subject id and controller as DID URIs in both Valibot and Zod so empty strings and non-DID values fail at the schema boundary. Fixes #207 --- .changeset/ack-id-controller-claim-did-uri.md | 5 +++ packages/ack-id/src/schemas/schemas.test.ts | 44 +++++++++++++++++++ packages/ack-id/src/schemas/valibot.ts | 5 ++- packages/ack-id/src/schemas/zod.ts | 5 ++- 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 .changeset/ack-id-controller-claim-did-uri.md create mode 100644 packages/ack-id/src/schemas/schemas.test.ts diff --git a/.changeset/ack-id-controller-claim-did-uri.md b/.changeset/ack-id-controller-claim-did-uri.md new file mode 100644 index 00000000..af01212e --- /dev/null +++ b/.changeset/ack-id-controller-claim-did-uri.md @@ -0,0 +1,5 @@ +--- +"@agentcommercekit/ack-id": patch +--- + +Require `controllerClaimSchema` `id` and `controller` fields to be DID URIs in both Valibot and Zod, rejecting empty strings and other non-DID values at the schema boundary. diff --git a/packages/ack-id/src/schemas/schemas.test.ts b/packages/ack-id/src/schemas/schemas.test.ts new file mode 100644 index 00000000..ca49bfe3 --- /dev/null +++ b/packages/ack-id/src/schemas/schemas.test.ts @@ -0,0 +1,44 @@ +import * as v from "valibot" +import { describe, expect, it } from "vitest" + +import { controllerClaimSchema as valibotControllerClaimSchema } from "./valibot" +import { controllerClaimSchema as zodControllerClaimSchema } from "./zod" + +const validClaim = { + id: "did:web:agent.example.com", + controller: "did:web:controller.example.com", +} + +describe("controllerClaimSchema", () => { + it("accepts DID URIs for id and controller", () => { + expect(v.safeParse(valibotControllerClaimSchema, validClaim).success).toBe( + true, + ) + expect(zodControllerClaimSchema.safeParse(validClaim).success).toBe(true) + }) + + it("rejects empty id and controller strings", () => { + for (const input of [ + { ...validClaim, id: "" }, + { ...validClaim, controller: "" }, + { id: "", controller: "" }, + ]) { + expect(v.safeParse(valibotControllerClaimSchema, input).success).toBe( + false, + ) + expect(zodControllerClaimSchema.safeParse(input).success).toBe(false) + } + }) + + it("rejects non-DID id and controller values", () => { + for (const input of [ + { ...validClaim, id: "agent.example.com" }, + { ...validClaim, controller: "not-a-did" }, + ]) { + expect(v.safeParse(valibotControllerClaimSchema, input).success).toBe( + false, + ) + expect(zodControllerClaimSchema.safeParse(input).success).toBe(false) + } + }) +}) diff --git a/packages/ack-id/src/schemas/valibot.ts b/packages/ack-id/src/schemas/valibot.ts index 2f579840..11e11531 100644 --- a/packages/ack-id/src/schemas/valibot.ts +++ b/packages/ack-id/src/schemas/valibot.ts @@ -1,6 +1,7 @@ +import { didUriSchema } from "@agentcommercekit/did/schemas/valibot" import * as v from "valibot" export const controllerClaimSchema = v.object({ - id: v.string(), - controller: v.string(), + id: didUriSchema, + controller: didUriSchema, }) diff --git a/packages/ack-id/src/schemas/zod.ts b/packages/ack-id/src/schemas/zod.ts index 30751a5b..20007819 100644 --- a/packages/ack-id/src/schemas/zod.ts +++ b/packages/ack-id/src/schemas/zod.ts @@ -1,6 +1,7 @@ +import { didUriSchema } from "@agentcommercekit/did/schemas/zod" import * as z from "zod" export const controllerClaimSchema = z.object({ - id: z.string(), - controller: z.string(), + id: didUriSchema, + controller: didUriSchema, })