@craigruks/x402-server-guard 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -76,7 +76,7 @@ return grant(resource);
76
76
  > isolate holds its own map, so replay and race protection do not hold across
77
77
  > instances. For those deploys, pass a store backed by an atomic compare-and-set: a
78
78
  > Durable Object, Redis `SET NX`, or a database unique constraint (not a plain
79
- > get-then-put store like Workers KV, which reopens the race). Any backend that
79
+ > get-then-put store like Workers KV, which reopens the race condition). Any backend that
80
80
  > implements the `NonceStore` contract works. One is built start to finish in the box, a
81
81
  > Cloudflare Durable Object adapter:
82
82
  > `import { createDurableObjectNonceStore } from "@craigruks/x402-server-guard/cloudflare"`
@@ -137,6 +137,21 @@ All four enumerated attack classes are covered; see the table below.
137
137
  drops into Hono, Express, Next, or Fastify. A Hono binding is shown in the
138
138
  examples; an `@x402/core`-hook convenience wrapper is a thin layer over `protect`.
139
139
 
140
+ ## Taking the dependency (or not)
141
+
142
+ If you run a payment endpoint, you are right to want as few dependencies as possible.
143
+ This library has none at runtime, and the core is small enough to read in one sitting. If
144
+ you would rather not add a dependency at all, lift the primitives you use straight into
145
+ your own code: the reservation, the finality hold, and the cache directives are each a
146
+ small, self-contained file, and a coding agent can pull over the parts you need in a few
147
+ minutes. That is a supported way to use this.
148
+
149
+ If you do take the dependency, we have tried to make depending on us safe. Releases
150
+ publish through GitHub Actions using npm trusted publishing (OIDC), so no long-lived npm
151
+ token exists for an attacker to steal; tokens are disallowed on the package, publishing
152
+ requires two-factor auth, and CI-published releases carry build provenance that ties the
153
+ package to the exact commit and workflow that built it.
154
+
140
155
  ## Mitigations
141
156
 
142
157
  Each ships with a paired test proving the attack against a vanilla server and
@@ -173,6 +188,14 @@ Dev tooling lives in `package.json` scripts (`npm run …`); the [`justfile`](./
173
188
  is the discoverable index for repo operations that aren't npm: supply-chain
174
189
  checks, CI, release prep. Run `just` to see them all.
175
190
 
191
+ ## Acknowledgments
192
+
193
+ Shengchen Ling, an author of "Free-Riding the Agentic Web: A Systematic Security
194
+ Analysis of x402 Payments" ([arXiv:2605.30998](https://arxiv.org/abs/2605.30998)),
195
+ reviewed how this library maps to the paper's flaw classes and invariants and
196
+ confirmed it aligns with the paper's intended interpretation. This is not an
197
+ audit or endorsement of the implementation.
198
+
176
199
  ## License
177
200
 
178
201
  [MIT](./LICENSE) © Craig Ruks
package/dist/cache.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Cache directives for paid responses.
3
3
  *
4
- * A shared cache (CDN or reverse proxy) keys on the URL and knows nothing about
4
+ * A shared cache (CDN or reverse proxy) is keyed on the URL and knows nothing about
5
5
  * payment, so a cacheable paid 200 can be stored and served to the next caller, paid
6
6
  * or not. The reference x402 adapters set no cache directive, leaving that door open.
7
7
  *
package/dist/cache.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Cache directives for paid responses.
3
3
  *
4
- * A shared cache (CDN or reverse proxy) keys on the URL and knows nothing about
4
+ * A shared cache (CDN or reverse proxy) is keyed on the URL and knows nothing about
5
5
  * payment, so a cacheable paid 200 can be stored and served to the next caller, paid
6
6
  * or not. The reference x402 adapters set no cache directive, leaving that door open.
7
7
  *
package/dist/guard.js CHANGED
@@ -18,7 +18,7 @@ export function createGuard(options = {}) {
18
18
  const canonicalizeResource = options.canonicalizeResource ?? canonicalResource;
19
19
  return {
20
20
  async reserve(params) {
21
- // Fold both keys to canonical form up front; everything below keys on the
21
+ // Fold both keys to canonical form up front; everything below is keyed on the
22
22
  // canonical values (see canonical.ts for why two encodings must not split).
23
23
  const nonce = canonicalizeNonce(params.nonce);
24
24
  const resource = canonicalizeResource(params.resource);
package/dist/guard.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * race and payment replay is the first mitigation to land.
13
13
  */
14
14
  /** Current package version (semver). */
15
- export const VERSION = "0.1.0";
15
+ export const VERSION = "0.1.1";
16
16
  export { isStorableBySharedCache, paidResponseCacheDirectives, } from "./cache.js";
17
17
  export { canonicalNonce, canonicalResource } from "./canonical.js";
18
18
  export { guardError } from "./error.js";
@@ -5,10 +5,10 @@
5
5
  * are never split by an `await`. The in-memory store below is atomic by construction
6
6
  * (a synchronous body runs to completion); a distributed store must use a genuine
7
7
  * atomic compare-and-set (a Durable Object, Redis `SET .. NX`, a unique constraint).
8
- * Get-then-put stores (Workers KV, S3) reopen the race and are not safe here.
8
+ * Get-then-put stores (Workers KV, S3) reopen the race condition and are not safe here.
9
9
  *
10
10
  * `reserve` and `release` return a `Result`, so a store failure is a value the guard
11
- * turns into a fail-closed deny. Replay keys on the nonce, never the signature, which
11
+ * turns into a fail-closed deny. Replay is keyed on the nonce, never the signature, which
12
12
  * is what makes it immune to signature malleability. Full rationale: docs/hardening.md.
13
13
  * The contract itself lives in `store-types.ts` (Node-free, so non-Node adapters can
14
14
  * import it); this module owns the Node in-memory implementation.
@@ -62,7 +62,7 @@ export class MemoryNonceStore {
62
62
  return Promise.resolve(ok({ status: "already-reserved", boundResource: existing.resource }));
63
63
  }
64
64
  // Reject a fresh reservation once full rather than grow unbounded or evict a live
65
- // entry (which reopens the race). Overwriting a dead entry is exempt (no growth).
65
+ // entry (which reopens the race condition). Overwriting a dead entry is exempt (no growth).
66
66
  if (existing === undefined && this.reserved.size >= this.maxEntries) {
67
67
  return Promise.resolve(err(guardError("store-at-capacity", "nonce store at capacity")));
68
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nonce-store.js","sourceRoot":"","sources":["../src/nonce-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,GAAG,EAAE,EAAE,EAAe,MAAM,aAAa,CAAC;AAiCnD,oFAAoF;AACpF,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAElC;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,SAAS,CAAC;AAEtC,MAAM,UAAU,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,gBAAgB;IACV,QAAQ,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC7C,SAAS,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAC5B,GAAG,CAAe;IAClB,UAAU,CAAS;IAEpC,YAAY,OAAO,GAA4B,EAAE;QAC/C,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAC9D,CAAC;IAED,iDAAiD;IACjD,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,OAAO,CAAC,EACN,KAAK,EACL,QAAQ,EACR,SAAS,GACK;QACd,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAErB,6EAA6E;QAC7E,oDAAoD;QACpD,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;YACrB,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;YACvD,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,aAAa,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC/F,CAAC;QACD,kFAAkF;QAClF,kFAAkF;QAClF,IAAI,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpE,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,mBAAmB,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC;QAC1F,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,CAAC,KAAa,EAAE,KAAa;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,+EAA+E;QAC/E,2EAA2E;QAC3E,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YACjD,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,kFAAkF;IAC1E,UAAU,CAAC,GAAW;QAC5B,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,GAAG,sBAAsB,EAAE,CAAC;YAClD,OAAO;QACT,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;QACrB,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3C,IAAI,KAAK,CAAC,SAAS,IAAI,GAAG,EAAE,CAAC;gBAC3B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAO,GAA4B,EAAE;IAC1E,OAAO,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"nonce-store.js","sourceRoot":"","sources":["../src/nonce-store.ts"],"names":[],"mappings":"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"}
@@ -34,7 +34,7 @@ export type ReserveError = StoreError | GuardError<"store-at-capacity">;
34
34
  export interface ReserveParams {
35
35
  /**
36
36
  * The payment's nonce; the x402 exact scheme uses a random 32-byte value. The guard
37
- * keys on this nonce alone (folded to a canonical form by default, see canonical.ts).
37
+ * is keyed on this nonce alone (folded to a canonical form by default, see canonical.ts).
38
38
  * It does not scope by chain or asset, so a store shared across several chains or
39
39
  * assets should namespace the nonce per (chain, asset) itself to keep two distinct
40
40
  * payments from colliding; a direct store caller must also pre-fold it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craigruks/x402-server-guard",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Server-side hardening middleware for x402 payment endpoints. Mitigations for settlement races, replay, cross-resource substitution, and cache leakage. Not a security guarantee.",
5
5
  "keywords": [
6
6
  "x402",
@@ -19,7 +19,7 @@
19
19
  "type": "git",
20
20
  "url": "git+https://github.com/craigruks/x402-server-guard.git"
21
21
  },
22
- "homepage": "https://github.com/craigruks/x402-server-guard#readme",
22
+ "homepage": "https://craigruks.github.io/x402-server-guard/",
23
23
  "bugs": {
24
24
  "url": "https://github.com/craigruks/x402-server-guard/issues"
25
25
  },
@@ -70,16 +70,16 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "@arethetypeswrong/cli": "0.18.5",
73
- "@biomejs/biome": "2.5.3",
74
- "@cloudflare/workers-types": "5.20260714.1",
73
+ "@biomejs/biome": "2.5.4",
74
+ "@cloudflare/workers-types": "5.20260718.1",
75
75
  "@types/node": "26.1.1",
76
76
  "@vitest/coverage-v8": "4.1.10",
77
- "@x402/core": "2.18.0",
77
+ "@x402/core": "2.19.0",
78
78
  "esbuild": "0.28.1",
79
- "hono": "4.12.29",
80
- "miniflare": "4.20260708.1",
79
+ "hono": "4.12.30",
80
+ "miniflare": "4.20260722.0",
81
81
  "publint": "0.3.21",
82
- "tsx": "4.23.0",
82
+ "tsx": "4.23.1",
83
83
  "typescript": "7.0.2",
84
84
  "vitest": "4.1.10"
85
85
  },
package/src/cache.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Cache directives for paid responses.
3
3
  *
4
- * A shared cache (CDN or reverse proxy) keys on the URL and knows nothing about
4
+ * A shared cache (CDN or reverse proxy) is keyed on the URL and knows nothing about
5
5
  * payment, so a cacheable paid 200 can be stored and served to the next caller, paid
6
6
  * or not. The reference x402 adapters set no cache directive, leaving that door open.
7
7
  *
package/src/guard.ts CHANGED
@@ -70,7 +70,7 @@ export function createGuard(options: GuardOptions = {}): Guard {
70
70
  const canonicalizeResource = options.canonicalizeResource ?? canonicalResource;
71
71
  return {
72
72
  async reserve(params: ReserveParams): Promise<Reservation> {
73
- // Fold both keys to canonical form up front; everything below keys on the
73
+ // Fold both keys to canonical form up front; everything below is keyed on the
74
74
  // canonical values (see canonical.ts for why two encodings must not split).
75
75
  const nonce = canonicalizeNonce(params.nonce);
76
76
  const resource = canonicalizeResource(params.resource);
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  /** Current package version (semver). */
16
- export const VERSION: string = "0.1.0";
16
+ export const VERSION: string = "0.1.1";
17
17
 
18
18
  export {
19
19
  type CacheDirectives,
@@ -5,10 +5,10 @@
5
5
  * are never split by an `await`. The in-memory store below is atomic by construction
6
6
  * (a synchronous body runs to completion); a distributed store must use a genuine
7
7
  * atomic compare-and-set (a Durable Object, Redis `SET .. NX`, a unique constraint).
8
- * Get-then-put stores (Workers KV, S3) reopen the race and are not safe here.
8
+ * Get-then-put stores (Workers KV, S3) reopen the race condition and are not safe here.
9
9
  *
10
10
  * `reserve` and `release` return a `Result`, so a store failure is a value the guard
11
- * turns into a fail-closed deny. Replay keys on the nonce, never the signature, which
11
+ * turns into a fail-closed deny. Replay is keyed on the nonce, never the signature, which
12
12
  * is what makes it immune to signature malleability. Full rationale: docs/hardening.md.
13
13
  * The contract itself lives in `store-types.ts` (Node-free, so non-Node adapters can
14
14
  * import it); this module owns the Node in-memory implementation.
@@ -106,7 +106,7 @@ export class MemoryNonceStore implements NonceStore {
106
106
  return Promise.resolve(ok({ status: "already-reserved", boundResource: existing.resource }));
107
107
  }
108
108
  // Reject a fresh reservation once full rather than grow unbounded or evict a live
109
- // entry (which reopens the race). Overwriting a dead entry is exempt (no growth).
109
+ // entry (which reopens the race condition). Overwriting a dead entry is exempt (no growth).
110
110
  if (existing === undefined && this.reserved.size >= this.maxEntries) {
111
111
  return Promise.resolve(err(guardError("store-at-capacity", "nonce store at capacity")));
112
112
  }
@@ -30,7 +30,7 @@ export type ReserveError = StoreError | GuardError<"store-at-capacity">;
30
30
  export interface ReserveParams {
31
31
  /**
32
32
  * The payment's nonce; the x402 exact scheme uses a random 32-byte value. The guard
33
- * keys on this nonce alone (folded to a canonical form by default, see canonical.ts).
33
+ * is keyed on this nonce alone (folded to a canonical form by default, see canonical.ts).
34
34
  * It does not scope by chain or asset, so a store shared across several chains or
35
35
  * assets should namespace the nonce per (chain, asset) itself to keep two distinct
36
36
  * payments from colliding; a direct store caller must also pre-fold it.