From 3809805232692eb037749e98be3c247bd48931b1 Mon Sep 17 00:00:00 2001 From: EdgecaseSystems Date: Sun, 13 Sep 2026 16:49:41 -0500 Subject: [PATCH 1/4] chore: temporarily canonicalize public CDP core --- .../workflows/tmp-canonicalize-cdp-core.yml | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/tmp-canonicalize-cdp-core.yml diff --git a/.github/workflows/tmp-canonicalize-cdp-core.yml b/.github/workflows/tmp-canonicalize-cdp-core.yml new file mode 100644 index 0000000..6371105 --- /dev/null +++ b/.github/workflows/tmp-canonicalize-cdp-core.yml @@ -0,0 +1,48 @@ +name: Temporary canonicalize public CDP core + +on: + push: + branches: + - fix/canonicalize-cdp-core-2026-09-13 + +permissions: + contents: write + +jobs: + patch: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Apply exact core compatibility corrections + shell: bash + run: | + node --input-type=module <<'NODE' + import fs from 'node:fs'; + const path = 'src/x402-core.ts'; + let source = fs.readFileSync(path, 'utf8'); + const oldDescription = 'description: `${PUBLIC_AGENT_DESCRIPTION} See the public root and /openapi.json for buyer-fit guidance, labeled illustrative examples, and /privacy before submitting business context. U.S.-only public service in the 50 states and District of Columbia; U.S. territories unavailable.`,'; + const newDescription = 'description: "SecondLook provides an independent pre-action review of a proposed AI or agent action. It checks supplied facts, authority, constraints, reasoning, risks, and missing information before execution. Use it when an action is costly, external, irreversible, or constraint-sensitive. SecondLook does not authorize, execute, or supply missing authority, and results are not guarantees of correctness.",'; + if (!source.includes(oldDescription)) throw new Error('Expected legacy payment-resource description was not found.'); + source = source.replace(oldDescription, newDescription); + const oldClaims = [' sub: credentials.apiKeyId,',' iss: "cdp",',' iat: now,',' nbf: now,',' exp: now + 120,',' uris: [`POST ${target.host}${target.pathname}`],'].join('\n'); + const newClaims = [' sub: credentials.apiKeyId,',' iss: "cdp",',' aud: ["cdp_service"],',' nbf: now,',' exp: now + 120,',' uri: `POST ${target.host}${target.pathname}`,'].join('\n'); + if (!source.includes(oldClaims)) throw new Error('Expected legacy CDP JWT claims were not found.'); + source = source.replace(oldClaims, newClaims); + fs.writeFileSync(path, source); + NODE + - name: Commit correction + shell: bash + run: | + git diff --check + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add src/x402-core.ts + git commit -m "Canonicalize public CDP x402 core behavior" + git push origin HEAD:${{ github.ref_name }} From d3c408bd0319800c80fa52cc38014025ba8e4dbf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 13 Sep 2026 21:49:50 +0000 Subject: [PATCH 2/4] Canonicalize public CDP x402 core behavior --- src/x402-core.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/x402-core.ts b/src/x402-core.ts index f563831..9d1d8d0 100644 --- a/src/x402-core.ts +++ b/src/x402-core.ts @@ -644,7 +644,7 @@ export function makeX402PaymentRequired(resourceUrl: string, accepted: X402Payme error, resource: { url: resourceUrl, - description: `${PUBLIC_AGENT_DESCRIPTION} See the public root and /openapi.json for buyer-fit guidance, labeled illustrative examples, and /privacy before submitting business context. U.S.-only public service in the 50 states and District of Columbia; U.S. territories unavailable.`, + description: "SecondLook provides an independent pre-action review of a proposed AI or agent action. It checks supplied facts, authority, constraints, reasoning, risks, and missing information before execution. Use it when an action is costly, external, irreversible, or constraint-sensitive. SecondLook does not authorize, execute, or supply missing authority, and results are not guarantees of correctness.", mimeType: "application/json", serviceName: "SecondLook", tags: ["ai", "agent", "review", "pre-action", "decision-support"], @@ -707,10 +707,10 @@ async function cdpAuthorizationHeader(credentials: CdpFacilitatorCredentials, en const claims = base64Url(new TextEncoder().encode(JSON.stringify({ sub: credentials.apiKeyId, iss: "cdp", - iat: now, + aud: ["cdp_service"], nbf: now, exp: now + 120, - uris: [`POST ${target.host}${target.pathname}`], + uri: `POST ${target.host}${target.pathname}`, }))); const privateKey = await crypto.subtle.importKey("jwk", { kty: "OKP", crv: "Ed25519", d: base64Url(keyBytes.slice(0, 32)), x: base64Url(keyBytes.slice(32)), From 77c2b83eb703da8cf50883eb1ecd17d4fd8fc502 Mon Sep 17 00:00:00 2001 From: EdgecaseSystems Date: Sun, 13 Sep 2026 16:50:26 -0500 Subject: [PATCH 3/4] test: cover direct public CDP x402 core contract --- test/x402-core-cdp-regression.test.ts | 101 ++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 test/x402-core-cdp-regression.test.ts diff --git a/test/x402-core-cdp-regression.test.ts b/test/x402-core-cdp-regression.test.ts new file mode 100644 index 0000000..f9c5252 --- /dev/null +++ b/test/x402-core-cdp-regression.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from "vitest"; +import type { PaymentRequirement } from "../src/payments"; +import { + makeX402PaymentRequired, + X402FacilitatorAdapter, + X402_CDP_FACILITATOR, + type X402PaymentPayload, + type X402PaymentRequirements, +} from "../src/x402-core"; + +function base64UrlDecode(value: string): Uint8Array { + const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "="); + return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0)); +} + +function decodeJwtPart(value: string): Record { + return JSON.parse(new TextDecoder().decode(base64UrlDecode(value))) as Record; +} + +describe("direct x402 core CDP compatibility", () => { + it("uses the bounded payment description and documented CDP JWT claims without wrapper repair", async () => { + const accepted: X402PaymentRequirements = { + scheme: "exact", + network: "eip155:8453", + amount: "50000", + asset: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", + payTo: "0x2222222222222222222222222222222222222222", + maxTimeoutSeconds: 60, + extra: { assetTransferMethod: "eip3009", paymentFlow: "upfront", name: "USD Coin", version: "2" }, + }; + const required = makeX402PaymentRequired("https://secondlook.example/v1/paid/second-look", accepted); + expect(required.resource.description.length).toBeLessThanOrEqual(500); + expect(required.resource.description).toContain("does not authorize, execute, or supply missing authority"); + + const pair = await crypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]) as CryptoKeyPair; + const privateJwk = await crypto.subtle.exportKey("jwk", pair.privateKey); + const secret = btoa(String.fromCharCode(...base64UrlDecode(privateJwk.d ?? ""), ...base64UrlDecode(privateJwk.x ?? ""))); + + let authorizationHeader = ""; + const fetcher = (async (_input: RequestInfo | URL, init?: RequestInit): Promise => { + const headers = new Headers(init?.headers); + authorizationHeader = headers.get("authorization") ?? ""; + return new Response(JSON.stringify({ + success: true, + payer: "0x3333333333333333333333333333333333333333", + transaction: `0x${"ef".repeat(32)}`, + network: accepted.network, + amount: accepted.amount, + }), { status: 200 }); + }) as typeof fetch; + + const requirement: PaymentRequirement = { + provider: "x402-facilitator", + protocol: "x402-v2-exact-eip3009-upfront", + amountAtomic: accepted.amount, + asset: accepted.asset, + network: accepted.network, + payTo: accepted.payTo, + requirementsJson: JSON.stringify(accepted), + }; + const payload: X402PaymentPayload = { + x402Version: 2, + resource: required.resource, + accepted, + extensions: required.extensions, + payload: { + signature: `0x${"ab".repeat(65)}`, + authorization: { + from: "0x3333333333333333333333333333333333333333", + to: accepted.payTo, + value: accepted.amount, + validAfter: "1", + validBefore: "9999999999", + nonce: `0x${"cd".repeat(32)}`, + }, + }, + }; + + const adapter = new X402FacilitatorAdapter({ + kind: "cdp", + url: X402_CDP_FACILITATOR, + apiKeyId: "test-cdp-key", + apiKeySecret: secret, + }, fetcher); + await expect(adapter.attemptAcceptance({ requestId: "core-regression", requirement, authorization: payload })) + .resolves.toMatchObject({ outcome: "accepted" }); + + const [, claimsPart] = authorizationHeader.replace(/^Bearer /, "").split("."); + const claims = decodeJwtPart(claimsPart ?? ""); + expect(claims).toMatchObject({ + sub: "test-cdp-key", + iss: "cdp", + aud: ["cdp_service"], + nbf: expect.any(Number), + exp: expect.any(Number), + uri: "POST api.cdp.coinbase.com/platform/v2/x402/settle", + }); + expect(claims).not.toHaveProperty("iat"); + expect(claims).not.toHaveProperty("uris"); + }); +}); From 4fb08e45cb3f8555878807c382729e923c00d6f5 Mon Sep 17 00:00:00 2001 From: EdgecaseSystems Date: Sun, 13 Sep 2026 16:50:35 -0500 Subject: [PATCH 4/4] chore: remove temporary public CDP core workflow --- .../workflows/tmp-canonicalize-cdp-core.yml | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 .github/workflows/tmp-canonicalize-cdp-core.yml diff --git a/.github/workflows/tmp-canonicalize-cdp-core.yml b/.github/workflows/tmp-canonicalize-cdp-core.yml deleted file mode 100644 index 6371105..0000000 --- a/.github/workflows/tmp-canonicalize-cdp-core.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Temporary canonicalize public CDP core - -on: - push: - branches: - - fix/canonicalize-cdp-core-2026-09-13 - -permissions: - contents: write - -jobs: - patch: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.ref_name }} - fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: 22 - - name: Apply exact core compatibility corrections - shell: bash - run: | - node --input-type=module <<'NODE' - import fs from 'node:fs'; - const path = 'src/x402-core.ts'; - let source = fs.readFileSync(path, 'utf8'); - const oldDescription = 'description: `${PUBLIC_AGENT_DESCRIPTION} See the public root and /openapi.json for buyer-fit guidance, labeled illustrative examples, and /privacy before submitting business context. U.S.-only public service in the 50 states and District of Columbia; U.S. territories unavailable.`,'; - const newDescription = 'description: "SecondLook provides an independent pre-action review of a proposed AI or agent action. It checks supplied facts, authority, constraints, reasoning, risks, and missing information before execution. Use it when an action is costly, external, irreversible, or constraint-sensitive. SecondLook does not authorize, execute, or supply missing authority, and results are not guarantees of correctness.",'; - if (!source.includes(oldDescription)) throw new Error('Expected legacy payment-resource description was not found.'); - source = source.replace(oldDescription, newDescription); - const oldClaims = [' sub: credentials.apiKeyId,',' iss: "cdp",',' iat: now,',' nbf: now,',' exp: now + 120,',' uris: [`POST ${target.host}${target.pathname}`],'].join('\n'); - const newClaims = [' sub: credentials.apiKeyId,',' iss: "cdp",',' aud: ["cdp_service"],',' nbf: now,',' exp: now + 120,',' uri: `POST ${target.host}${target.pathname}`,'].join('\n'); - if (!source.includes(oldClaims)) throw new Error('Expected legacy CDP JWT claims were not found.'); - source = source.replace(oldClaims, newClaims); - fs.writeFileSync(path, source); - NODE - - name: Commit correction - shell: bash - run: | - git diff --check - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add src/x402-core.ts - git commit -m "Canonicalize public CDP x402 core behavior" - git push origin HEAD:${{ github.ref_name }}