@craigruks/x402-server-guard 0.1.1 → 0.1.2

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
@@ -1,8 +1,9 @@
1
1
  # x402-server-guard
2
2
 
3
- **Server-side hardening middleware for [x402](https://github.com/coinbase/x402)
3
+ **Server-side hardening middleware for [x402](https://github.com/x402-foundation/x402)
4
4
  payment endpoints.**
5
5
 
6
+ [![npm](https://img.shields.io/npm/v/@craigruks/x402-server-guard)](https://www.npmjs.com/package/@craigruks/x402-server-guard)
6
7
  [![CI](https://github.com/craigruks/x402-server-guard/actions/workflows/ci.yml/badge.svg)](https://github.com/craigruks/x402-server-guard/actions/workflows/ci.yml)
7
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
8
9
 
@@ -162,13 +163,17 @@ and honesty (is this a strawman, does the reference actually have these gaps, wh
162
163
  this does not do) are answered in
163
164
  [`docs/objection-handling.md`](./docs/objection-handling.md).
164
165
 
165
- | Attack class | Status |
166
- | --- | --- |
167
- | Duplicate-settlement race | done |
168
- | Payment replay | done (same nonce reservation) |
169
- | Cross-resource substitution | done (same nonce reservation, distinct reason) |
170
- | Grant-before-finality (k-confirmations) | done |
171
- | Cache leakage of paid content | done |
166
+ | Attack class | Status | Upstream `@x402/core` 2.29.0 |
167
+ | --- | --- | --- |
168
+ | Duplicate-settlement race | done | partly: closed by the opt-in `upfront` flow ([#3053](https://github.com/x402-foundation/x402/pull/3053)), open in the default flow |
169
+ | Payment replay | done (same nonce reservation) | partly, as for the race |
170
+ | Cross-resource substitution | done (same nonce reservation, distinct reason) | partly: `upfront` denies the reuse, nothing binds or flags the resource |
171
+ | Grant-before-finality (k-confirmations) | done | no: success is one inclusion; a hook can now fail it closed ([#3994](https://github.com/x402-foundation/x402/pull/3994)) |
172
+ | Cache leakage of paid content | done | partly: `private` on the paid 200 since 2.21.0 ([#2990](https://github.com/x402-foundation/x402/pull/2990)), no `no-store`, no `Vary` |
173
+
174
+ The upstream column is a survey, not a dependency: the guard does not call
175
+ `@x402/core`. Evidence for each entry is in
176
+ [`docs/hardening.md`](./docs/hardening.md#what-upstream-now-covers).
172
177
 
173
178
  ## Development
174
179
 
package/SECURITY.md CHANGED
@@ -47,7 +47,9 @@ published under a separate tag.
47
47
 
48
48
  Please report suspected vulnerabilities **privately** via GitHub Security
49
49
  Advisories ("Report a vulnerability" on the repository's Security tab), not
50
- through public issues.
50
+ through public issues. This covers any case where the guard fails to stop an
51
+ attack it claims to mitigate. A public issue that describes a working attack
52
+ discloses it before a fix exists.
51
53
 
52
54
  Include a description, affected version, and a reproduction if possible. Expect an
53
55
  acknowledgement within 72 hours. Please allow a reasonable window for a fix before
package/dist/cache.d.ts CHANGED
@@ -3,7 +3,9 @@
3
3
  *
4
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
- * or not. The reference x402 adapters set no cache directive, leaving that door open.
6
+ * or not. The reference x402 adapters send `private` on the paid 200 as of
7
+ * `@x402/core` 2.21.0 (unchanged through 2.29.0), and set no directive at all before
8
+ * it; neither sets `Vary`.
7
9
  *
8
10
  * The mitigation is a response directive, not a decision about a nonce, so it lives
9
11
  * here as a small pure helper the server or adapter applies to every paid response.
@@ -20,7 +22,9 @@ export interface CacheDirectives {
20
22
  export interface CacheDirectivesOptions {
21
23
  /**
22
24
  * The request header the payment travels in, added to `Vary` so a cache that
23
- * stores despite `no-store` still keys by payer. Defaults to `X-PAYMENT`.
25
+ * stores despite `no-store` still keys by payer. Defaults to `X-PAYMENT`, the x402
26
+ * v1 header; an x402 v2 client sends `PAYMENT-SIGNATURE`, so pass that behind a v2
27
+ * server.
24
28
  */
25
29
  readonly paymentHeader?: string;
26
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,+DAA+D;AAC/D,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,sBAA2B,GAAG,eAAe,CAGjG;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMjF"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,+DAA+D;AAC/D,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,sBAA2B,GAAG,eAAe,CAGjG;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMjF"}
package/dist/cache.js CHANGED
@@ -3,7 +3,9 @@
3
3
  *
4
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
- * or not. The reference x402 adapters set no cache directive, leaving that door open.
6
+ * or not. The reference x402 adapters send `private` on the paid 200 as of
7
+ * `@x402/core` 2.21.0 (unchanged through 2.29.0), and set no directive at all before
8
+ * it; neither sets `Vary`.
7
9
  *
8
10
  * The mitigation is a response directive, not a decision about a nonce, so it lives
9
11
  * here as a small pure helper the server or adapter applies to every paid response.
package/dist/cache.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkBH,+DAA+D;AAC/D,MAAM,UAAU,2BAA2B,CAAC,OAAO,GAA2B,EAAE;IAC9E,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,WAAW,CAAC;IAC3D,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,YAAgC;IACtE,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,EAAE,EAAE,CAAC;QACtD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,UAAU,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;IAC9C,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC7E,CAAC"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAoBH,+DAA+D;AAC/D,MAAM,UAAU,2BAA2B,CAAC,OAAO,GAA2B,EAAE;IAC9E,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,WAAW,CAAC;IAC3D,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,YAAgC;IACtE,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,EAAE,EAAE,CAAC;QACtD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,UAAU,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;IAC9C,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC7E,CAAC"}
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.1";
15
+ export const VERSION = "0.1.2";
16
16
  export { isStorableBySharedCache, paidResponseCacheDirectives, } from "./cache.js";
17
17
  export { canonicalNonce, canonicalResource } from "./canonical.js";
18
18
  export { guardError } from "./error.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craigruks/x402-server-guard",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",
@@ -70,21 +70,21 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "@arethetypeswrong/cli": "0.18.5",
73
- "@biomejs/biome": "2.5.4",
74
- "@cloudflare/workers-types": "5.20260718.1",
75
- "@types/node": "26.1.1",
76
- "@vitest/coverage-v8": "4.1.10",
77
- "@x402/core": "2.19.0",
78
- "esbuild": "0.28.1",
79
- "hono": "4.12.30",
80
- "miniflare": "4.20260722.0",
81
- "publint": "0.3.21",
82
- "tsx": "4.23.1",
73
+ "@biomejs/biome": "2.6.4",
74
+ "@cloudflare/workers-types": "5.20261104.1",
75
+ "@types/node": "26.6.6",
76
+ "@vitest/coverage-v8": "5.1.0",
77
+ "@x402/core": "2.29.0",
78
+ "esbuild": "0.28.2",
79
+ "hono": "4.13.16",
80
+ "miniflare": "4.20260730.0",
81
+ "publint": "0.3.25",
82
+ "tsx": "4.23.15",
83
83
  "typescript": "7.0.2",
84
- "vitest": "4.1.10"
84
+ "vitest": "5.1.0"
85
85
  },
86
86
  "allowScripts": {
87
- "esbuild@0.28.1": true,
88
- "workerd@1.20260708.1": true
87
+ "esbuild@0.28.2": true,
88
+ "workerd@1.20260730.1": true
89
89
  }
90
90
  }
package/src/cache.ts CHANGED
@@ -3,7 +3,9 @@
3
3
  *
4
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
- * or not. The reference x402 adapters set no cache directive, leaving that door open.
6
+ * or not. The reference x402 adapters send `private` on the paid 200 as of
7
+ * `@x402/core` 2.21.0 (unchanged through 2.29.0), and set no directive at all before
8
+ * it; neither sets `Vary`.
7
9
  *
8
10
  * The mitigation is a response directive, not a decision about a nonce, so it lives
9
11
  * here as a small pure helper the server or adapter applies to every paid response.
@@ -22,7 +24,9 @@ export interface CacheDirectives {
22
24
  export interface CacheDirectivesOptions {
23
25
  /**
24
26
  * The request header the payment travels in, added to `Vary` so a cache that
25
- * stores despite `no-store` still keys by payer. Defaults to `X-PAYMENT`.
27
+ * stores despite `no-store` still keys by payer. Defaults to `X-PAYMENT`, the x402
28
+ * v1 header; an x402 v2 client sends `PAYMENT-SIGNATURE`, so pass that behind a v2
29
+ * server.
26
30
  */
27
31
  readonly paymentHeader?: string;
28
32
  }
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.1";
16
+ export const VERSION: string = "0.1.2";
17
17
 
18
18
  export {
19
19
  type CacheDirectives,