You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+20Lines changed: 20 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,25 @@
1
1
# @opensea/tool-sdk
2
2
3
+
## 0.28.2
4
+
5
+
### Patch Changes
6
+
7
+
- 1c2df8f: Fix four lower-severity tool-sdk audit findings:
8
+
9
+
- Pin EIP-712 domain name/version to canonical USDC for canonical assets.
10
+
- Document and surface the default 10,000-block registry lookup window.
11
+
- Cache resolved manifests in `createToolHandler` after the first success.
12
+
- Decouple shared caller API-key provisioning from each request's abort signal.
13
+
14
+
- 8e1b0dc: Validate `metadataURI` before fetching in `inspect` (SSRF): require http(s) and reject private/internal hosts. Harden the shared `isPrivateHostname` guard to numerically parse IP literals, closing alternate-encoding bypasses (decimal/octal/hex/partial dotted IPv4 and IPv4-mapped/compatible IPv6), and broaden the rejected ranges (0.0.0.0/8, 169.254.0.0/16 link-local, CGNAT 100.64.0.0/10, IPv6 unique-local fc00::/7). Thanks to @Nexory for reporting this SSRF (tool-sdk#12).
15
+
- 83b406a: Security fix: `paidAuthenticatedFetch` now enforces `maxAmount` as a per-invocation spending cap. At most one nonzero payment authorization may be signed per call — a second 402 after a paid authorization is rejected instead of signed — and cumulative authorized value may never exceed `maxAmount`. Previously a malicious tool could return two paid x402 challenges, each individually under `maxAmount`, and obtain two independently-settleable EIP-3009 authorizations (up to 2x the configured cap). The legacy zero-value predicate-gate → paid challenge flow keeps working.
16
+
- c7907d4: Security fix: harden `verifyXPaymentAuth` (used by `predicateGate` and `paidPredicateGate`) against replay/misuse of the EIP-3009 X-Payment identity proof:
17
+
18
+
- The free identity-only `predicateGate` now rejects (401) any X-Payment authorization with a non-zero `value` — a non-zero `TransferWithAuthorization` is a live, executable USDC pull the operator should never receive on a free gate. `paidPredicateGate` still accepts non-zero values (the facilitator settles them).
19
+
-`validBefore` is now capped to at most 1 hour (3600s) in the future (401 otherwise), so a captured X-Payment header can't act as a long-lived bearer token. Well-behaved clients sign now+300s/now+600s, so this is non-breaking.
20
+
- The authorization's resolved chainId is now pinned to the gate's configured chain (401 on mismatch), so e.g. a base-sepolia-signed authorization can't authenticate to a mainnet gate.
21
+
- The `to` (operator recipient) check is now unconditional and fails closed (500) when no operator address is configured, closing an unauthenticated-recipient hole.
Copy file name to clipboardExpand all lines: README.md
+13-4Lines changed: 13 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -601,9 +601,12 @@ they need to satisfy.
601
601
602
602
Preferred auth mechanism: `X-Payment` header (zero-value EIP-3009 `TransferWithAuthorization` via the 402 challenge flow). Also accepts `Authorization: EIP-3009 <base64url(json)>` and deprecated `SIWE <base64url(message)>.<signature>` for backward compatibility.
603
603
604
-
> **Note:** The gate enforces a short-lived `validBefore` window (the SDK
605
-
> defaults to 5 minutes). Each authorization includes a random nonce bound
606
-
> into the signature. The gate does not track nonces server-side.
604
+
> **Note:** The gate bounds the `validBefore` window server-side: the
605
+
> authorization must not be expired and must be at most 1 hour in the future
606
+
> (SDK clients sign a much shorter window — 5 min zero-value / 10 min paid).
607
+
> Each authorization includes a random nonce bound into the signature, but the
608
+
> gate does **not** deduplicate nonces server-side, so an `X-Payment` header is
609
+
> replayable within its validity window. See [Replay protection](#replay-protection) if you need single-use semantics.
607
610
608
611
#### Delegated agent access (delegate.xyz)
609
612
@@ -1027,11 +1030,17 @@ Recommended approaches by platform:
1027
1030
1028
1031
You can also implement a custom `GateMiddleware` that performs rate-limit checks in the `check()` hook and returns a 429 response when limits are exceeded.
1029
1032
1033
+
### Replay protection
1034
+
1035
+
The `X-Payment` identity proof is a stateless bearer token: `predicateGate` verifies the EIP-3009 signature and fields but does **not** record which authorizations it has already accepted. The SDK bounds the replay window server-side — an authorization must not be expired and must be at most 1 hour in the future — but a captured header remains replayable until its `validBefore`. On the paid path this is limited further because the facilitator settling the onchain `TransferWithAuthorization` consumes the nonce, so a real payment can't be double-settled; the residual surface is identity/access on the free gate (the onchain predicate still gates who is allowed).
1036
+
1037
+
The SDK deliberately does not ship server-side nonce tracking — single-use semantics require a shared, TTL'd store, and mandating one (e.g. Redis) would tie tool creators to a specific data store and break serverless/multi-instance deployments. If your tool needs stronger-than-window replay protection, implement it yourself in a custom `GateMiddleware`: on each request, read the authorization's `nonce` (bytes32), reject (401) if it is already present in your store, otherwise record it with a TTL equal to your `validBefore` window. Use whatever store fits your deployment (Cloudflare Durable Objects / KV, Upstash Redis on Vercel, DynamoDB, etc.).
1038
+
1030
1039
### Sensitive Data Handling
1031
1040
1032
1041
-**Private keys** are never handled directly by the SDK runtime. They flow through `@opensea/wallet-adapters`, which supports Privy, Turnkey, Fireblocks, and local key signing. The SDK accepts a `WalletAdapter` interface — it never reads or stores raw key material.
1033
1042
-**Environment variables** — the `deploy` command detects sensitive env vars (names ending in `_KEY`, `_SECRET`, `_TOKEN`, `_PASSWORD`, `_PRIVATE`) and masks their values during interactive prompts.
1034
-
-**EIP-3009 auth** — the `predicateGate` middleware verifies EIP-3009 zero-value authorizations via `ecrecover` (no RPC call needed). The SDK defaults to a 5-minute `validBefore`window to limit replay.
1043
+
-**EIP-3009 auth** — the `predicateGate` middleware verifies EIP-3009 zero-value authorizations via `ecrecover` (no RPC call needed). It rejects a non-zero `value` on the free identity gate, pins the authorization to the gate's configured chain, and caps `validBefore`to at most 1 hour ahead to limit replay. See [Replay protection](#replay-protection) for nonce-dedupe guidance.
1035
1044
-**x402 payments** — the `paidFetch` client validates payment parameters before signing: `maxAmount` caps the spend, `allowedRecipients` restricts payees, and `allowedAssets` defaults to the canonical USDC contract for the network. These guard against malicious 402 responses.
Copy file name to clipboardExpand all lines: docs/predicate-gating-guide.md
+5-4Lines changed: 5 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -86,7 +86,7 @@ The middleware (`src/lib/middleware/predicate-gate.ts`) does the following on ea
86
86
1. Checks for an `X-Payment` header (preferred) or `Authorization: EIP-3009 <token>` header (also accepts deprecated `Authorization: SIWE <token>` for backward compatibility)
87
87
2. If no auth is present and `operatorAddress` is configured, returns 402 with `PaymentRequirements` (`payTo`=operator, `maxAmountRequired`=`"0"`, `scheme`=`"exact"`)
88
88
3. Decodes and validates the authorization (from `X-Payment` or `Authorization` header)
89
-
4. Checks `validBefore` (must be in the future) and `validAfter` (must be in the past)
89
+
4. Checks `validBefore` (must be in the future, and at most 1 hour ahead) and `validAfter` (must be in the past)
90
90
5. Recovers the signer via `ecrecover` on the EIP-712 typed data — no RPC call needed
91
91
6. Calls `registry.tryHasAccess(toolId, recoveredAddress, data)` — a staticcall to the onchain `ToolRegistry`
92
92
7. If `(ok=true, granted=true)`, sets `ctx.callerAddress` and `ctx.gates.predicate.granted = true`
@@ -104,7 +104,7 @@ Status code mapping:
104
104
105
105
The `predicate` field in the 403 body is the registered access predicate's address, so callers can self-diagnose what they need to satisfy.
106
106
107
-
The gate enforces a short-lived `validBefore` window (the SDK defaults to 5 minutes). Each EIP-3009 authorization includes a random `nonce` bound into the signature — the gate does not track nonces server-side, so callers should keep `validBefore` short to limit the replay window.
107
+
The gate bounds the `validBefore` window server-side: the authorization must not be expired and must be at most 1 hour in the future (SDK clients sign a much shorter window — 5 min zero-value / 10 min paid). Each EIP-3009 authorization includes a random `nonce` bound into the signature, but the gate does **not** deduplicate nonces server-side, so the `X-Payment` header stays replayable within its validity window. The SDK does not mandate a nonce store (that would tie creators to a specific data store and break serverless deployments); if you need single-use semantics, dedupe the `nonce` in a custom `GateMiddleware` backed by whatever store fits your deployment. See the "Replay protection" section of the [tool-sdk README](../README.md#replay-protection).
108
108
109
109
## Step 2: Register with `--access-predicate`
110
110
@@ -176,11 +176,12 @@ The `X-Payment` header carries a base64-encoded JSON payload containing an EIP-3
176
176
177
177
Key constraints enforced by the middleware:
178
178
179
-
-**`validBefore`** must be in the future (the SDK defaults to `now + 5 minutes`)
179
+
-**`validBefore`** must be in the future and at most 1 hour ahead (SDK clients default to `now + 5 minutes`)
180
180
-**`validAfter`** must be in the past (typically `0`)
181
-
-**`value`** must be `"0"` (zero-value transfer — used for identity proof, not payment)
181
+
-**`value`** must be exactly the string `"0"` (zero-value transfer — used for identity proof, not payment; the free `predicateGate` rejects any non-`"0"` value, so third-party clients must emit the canonical `"0"`, not e.g. `"0x0"` or `"00"`)
182
182
-**`from`** is recovered via `ecrecover` and used as the caller address
183
183
-**`to`** must match the gate's `operatorAddress` (the 402 challenge advertises this as `payTo`)
184
+
- the authorization's resolved **chainId** (from its declared `network`) must match the gate's configured chain
0 commit comments