Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ updates:
minor-patch:
update-types: ["minor", "patch"]
ignore:
# eslint 10 defaults churn lint org-wide; upgrade is a deliberate cross-repo pass (2026-07-08)
- dependency-name: "eslint"
update-types: ["version-update:semver-major"]
- dependency-name: "@eslint/js"
update-types: ["version-update:semver-major"]
# TS majors are a deliberate org-wide pass, not a routine bump
- dependency-name: "typescript"
update-types: ["version-update:semver-major"]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

- uses: actions/setup-node@v7
with:
node-version: "24"
node-version: "26"
registry-url: "https://registry.npmjs.org"

- name: Set version from tag
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Anonymous POST flows through to the handler unauthenticated and gets a 402 with
## Tooling

- **Bun**: package manager.
- **ESLint 9**: linting.
- **ESLint 10**: linting.
- **tsup**: CJS + ESM build with subpath exports.
- **Vitest**: tests.
- **Lefthook**: pre-commit lint, pre-push typecheck.
Expand Down
685 changes: 289 additions & 396 deletions bun.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import js from '@eslint/js';
import { defineConfig, globalIgnores } from 'eslint/config';
import importPlugin from 'eslint-plugin-import';
import importPlugin from 'eslint-plugin-import-x';
import unusedImports from 'eslint-plugin-unused-imports';
import tseslint from 'typescript-eslint';

Expand All @@ -23,6 +23,9 @@ export default defineConfig([
alphabetize: { caseInsensitive: true, order: 'asc' },
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'type'],
'newlines-between': 'never',
// import-x calls the package's own name internal when it resolves to a
// built dist/ and external when it does not, so pin it to external.
pathGroups: [{ group: 'external', pattern: '@agent-score/**' }],
}],
'no-console': ['warn', { allow: ['warn', 'error'] }],
'prefer-const': 'error',
Expand Down
6 changes: 3 additions & 3 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# `bunx <tool>` resolves the tool independently of this project, so it can fetch
# a different major and then fail, or pass, for reasons that have nothing to do
# with the code being checked. That is not hypothetical here: this repo
# deliberately holds eslint at 9 and TypeScript at 6 while 10 and 7 are
# published, so `bunx eslint` would pull exactly the major the manifest pins
# away from. `bunx` also writes to the lockfile as a side effect.
# deliberately holds TypeScript at 6 while 7 is published, so `bunx tsc`
# would pull exactly the major the manifest pins away from. `bunx` also writes
# to the lockfile as a side effect.
#
# The pre-push hook already goes through package scripts; the lint hook was the
# one left on bunx.
Expand Down
31 changes: 15 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agent-score/commerce",
"version": "3.1.1",
"version": "3.1.2",
"description": "Agentic commerce SDK: identity middleware (Hono, Express, Fastify, Next.js, Web Fetch) + payment helpers + 402 builders + discovery + Stripe multichain. The full merchant-side toolkit for AgentScore-powered agentic commerce.",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down Expand Up @@ -151,8 +151,7 @@
},
"overrides": {
"axios": "^1.20.0",
"esbuild": "^0.28.1",
"viem": "2.57.4"
"viem": "2.57.7"
},
"peerDependencies": {
"@solana/kit": ">=8.0.0 <9.0.0",
Expand Down Expand Up @@ -193,32 +192,32 @@
"devDependencies": {
"@a2a-js/sdk": "^1.3.0",
"@coinbase/x402": "^2.1.0",
"@eslint/js": "^9.39.5",
"@solana/kit": "^8.4.0",
"@eslint/js": "^10.0.1",
"@solana/kit": "^8.5.0",
"@solana/mpp": "^0.7.0",
"@types/express": "^5.0.6",
"@types/node": "^26.6.5",
"@vitest/coverage-v8": "^5.0.3",
"@vitest/coverage-v8": "^5.0.5",
"@x402/core": "2.28.0",
"@x402/evm": "2.28.0",
"@x402/extensions": "2.28.0",
"dotenv": "^18.0.7",
"eslint": "^9.39.5",
"eslint-plugin-import": "^2.32.0",
"dotenv": "^18.0.8",
"eslint": "^10.13.0",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-unused-imports": "^4.4.1",
"express": "^5.3.0",
"fastify": "^5.12.5",
"fastify": "^5.12.7",
"hono": "^4.13.13",
"ioredis": "^6.0.0",
"jose": "^6.2.12",
"knip": "^6.41.0",
"lefthook": "^2.2.1",
"mppx": "0.13.4",
"tsup": "^8.5.1",
"lefthook": "^2.2.2",
"mppx": "0.13.5",
"tsup": "^9.0.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.71.1",
"viem": "2.57.4",
"vitest": "^5.0.3"
"typescript-eslint": "^8.71.2",
"viem": "2.57.7",
"vitest": "^5.0.5"
},
"packageManager": "bun@1.4.3"
}
11 changes: 7 additions & 4 deletions src/identity/ucp-jwks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ async function loadJose(): Promise<typeof import('jose')> {
} catch (err) {
throw new Error(
`UCP signing requires the \`jose\` library, which is an optional peer dependency. ${JOSE_INSTALL_HINT}\nOriginal error: ${err instanceof Error ? err.message : String(err)}`,
{ cause: err },
);
}
}
Expand Down Expand Up @@ -578,10 +579,10 @@ async function buildEnvSigningKey(
let jwkDict: Record<string, unknown>;
try {
jwkDict = JSON.parse(envJwk) as Record<string, unknown>;
} catch (err) {
throw new Error(
`${opts.envJwkVar} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
);
} catch {
// JSON.parse's message quotes an excerpt of its input, which here is private
// key material, so neither the message nor the error itself is carried.
throw new Error(`${opts.envJwkVar} is not valid JSON.`);
}
if (!jwkDict || typeof jwkDict !== 'object' || Array.isArray(jwkDict) || Object.keys(jwkDict).length === 0) {
throw new Error(`${opts.envJwkVar} must be a non-empty JWK object.`);
Expand Down Expand Up @@ -622,6 +623,8 @@ async function buildEnvSigningKey(
? String((err as { code?: unknown }).code)
: null;
const codeSuffix = code ? ` [${code}]` : '';
// The cause is withheld on purpose: it can carry key bytes.
// eslint-disable-next-line preserve-caught-error
throw new Error(
`${opts.envJwkVar} has malformed key material (${className}${codeSuffix}). ` +
'Verify the JWK is well-formed and matches the declared kty/crv. ' +
Expand Down
8 changes: 8 additions & 0 deletions tests/identity/load-ucp-signing-key-from-env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ describe('loadUCPSigningKeyFromEnv: error paths', () => {
);
});

it('keeps the env value out of the malformed-JSON error', async () => {
process.env.UCP_SIGNING_KEY_JWK_PRIVATE = '{"secret":SENSITIVEBYTES}';
const err = await loadUCPSigningKeyFromEnv().catch((e: unknown) => e);
expect(err).toBeInstanceOf(Error);
expect((err as Error).message).not.toMatch(/SENSITIVE|secret/);
expect((err as Error).cause).toBeUndefined();
});

it('throws for unsupported kty/crv, naming the actual values', async () => {
process.env.UCP_SIGNING_KEY_JWK_PRIVATE = JSON.stringify({ kty: 'RSA', n: 'abc', e: 'AQAB' });
await expect(loadUCPSigningKeyFromEnv()).rejects.toThrow(/unsupported kty\/crv.*RSA/);
Expand Down
Loading