botanary-mcp 0.7.0 → 0.9.0

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.
Files changed (41) hide show
  1. package/README.md +148 -12
  2. package/dist/src/api-client.d.ts +9 -1
  3. package/dist/src/api-client.d.ts.map +1 -1
  4. package/dist/src/api-client.js +27 -2
  5. package/dist/src/api-client.js.map +1 -1
  6. package/dist/src/hitl/resume.d.ts +20 -5
  7. package/dist/src/hitl/resume.d.ts.map +1 -1
  8. package/dist/src/hitl/resume.js +32 -37
  9. package/dist/src/hitl/resume.js.map +1 -1
  10. package/dist/src/http-path.d.ts +92 -0
  11. package/dist/src/http-path.d.ts.map +1 -0
  12. package/dist/src/http-path.js +201 -0
  13. package/dist/src/http-path.js.map +1 -0
  14. package/dist/src/runtime.d.ts +40 -0
  15. package/dist/src/runtime.d.ts.map +1 -1
  16. package/dist/src/runtime.js +98 -2
  17. package/dist/src/runtime.js.map +1 -1
  18. package/dist/src/tools.d.ts.map +1 -1
  19. package/dist/src/tools.js +252 -13
  20. package/dist/src/tools.js.map +1 -1
  21. package/dist/src/wallet/api-get.d.ts +4 -2
  22. package/dist/src/wallet/api-get.d.ts.map +1 -1
  23. package/dist/src/wallet/api-get.js +50 -26
  24. package/dist/src/wallet/api-get.js.map +1 -1
  25. package/dist/src/wallet/api-write.d.ts +50 -0
  26. package/dist/src/wallet/api-write.d.ts.map +1 -0
  27. package/dist/src/wallet/api-write.js +122 -0
  28. package/dist/src/wallet/api-write.js.map +1 -0
  29. package/dist/src/wallet/routes.manifest.d.ts +8 -0
  30. package/dist/src/wallet/routes.manifest.d.ts.map +1 -1
  31. package/dist/src/wallet/routes.manifest.js +26 -0
  32. package/dist/src/wallet/routes.manifest.js.map +1 -1
  33. package/dist/src/wallet/sign-request.d.ts +36 -0
  34. package/dist/src/wallet/sign-request.d.ts.map +1 -0
  35. package/dist/src/wallet/sign-request.js +10 -0
  36. package/dist/src/wallet/sign-request.js.map +1 -0
  37. package/dist/src/wallet/write-routes.manifest.d.ts +57 -0
  38. package/dist/src/wallet/write-routes.manifest.d.ts.map +1 -0
  39. package/dist/src/wallet/write-routes.manifest.js +85 -0
  40. package/dist/src/wallet/write-routes.manifest.js.map +1 -0
  41. package/package.json +3 -2
package/README.md CHANGED
@@ -70,6 +70,9 @@ advertised tool list changes" further down.
70
70
  | `wallet_mandates` | Reads `GET /delegations` + `GET /mandates`: what the agent lane may spend unattended - active delegations with budget info, and any outstanding mandates. |
71
71
  | `wallet_agents` | Reads `GET /agents` + `GET /agents/requests`: connected agents and any pending approval requests they've filed. |
72
72
  | `wallet_api_get` | Read ANY `GET` endpoint in Botanary's frozen OpenAPI contract, authenticated as the owner. **GET-only, deliberately** - see below. |
73
+ | `wallet_api_write` | Call a genuinely administrative write endpoint - claim an agent, mark a notification read, rename an account, edit a pay-sh allowlist, manage conversations - authenticated as the owner. Every route whose response is an unsigned build, or that the contract marks `x-client-signed`, is excluded by construction; a small explicit denylist covers what that mechanical rule cannot see. **Never a fund-moving or authority-granting write** - see below. |
74
+ | `list_sign_kinds` | Reads `GET /sign-kinds`: every sign-request kind this backend currently registers, each with a summary, the routes it builds and relays through, and whether it is available right now. Call this before `wallet_sign_request` to learn what a kind takes. |
75
+ | `wallet_sign_request` | Park a typed, kind-discriminated intent (`POST /sign-requests`) and hand it to the owner to review and sign in their own browser. The one write primitive covering every owner-authorized action that is not `wallet_send`. **Cannot sign, cannot relay** - see below. |
73
76
  | `resolve_token` | Resolve a token address or CAIP-19 asset ref to its full identity - symbol, name, decimals, and which source vouches for it. Refuses bare symbols: a symbol is a label a contract picked for itself, and two contracts on one chain can share one, so the product will not guess. Call `list_tokens` when you only have a symbol. |
74
77
  | `list_tokens` | Every token this account holds on a chain, with the contract address and CAIP-19 asset ref you need to spend it. This is the discovery command for `wallet_send`, which takes an address and refuses a symbol. When two rows share a symbol, show both and let the user choose; never guess. |
75
78
 
@@ -131,8 +134,14 @@ interrupted. The handle file itself carries **no secret**. Any credential the op
131
134
  token, a signer's key, an approval credential) lives in the same keychain chain `wallet_login` uses, and is
132
135
  deleted the moment the operation terminates - the handle cannot be replayed from an old transcript.
133
136
 
134
- To rejoin a wait: **call `botanary_wait` with the handle** - never re-call the original tool (`wallet_login`,
135
- `wallet_send`, etc.). Re-calling starts a brand-new operation; the wait will not pick up where it left off.
137
+ To rejoin a wait: **call `botanary_wait` with the handle** - never re-call the original tool (`wallet_send`,
138
+ `get_pairing_code`, `request_approval`, `propose_payment`, `wallet_sign_request`). Re-calling starts a
139
+ brand-new operation; the wait will not pick up where it left off.
140
+
141
+ `wallet_login` is the one flow that opens **no** handle, so there is nothing to rejoin: a login that drops
142
+ is a login you run again. That is deliberate - resuming one would mean re-reading the freshly minted
143
+ session token, and a session token belongs in the keychain and the `Authorization` header, never in a tool
144
+ result.
136
145
 
137
146
  ## Why elicitation is never an approval
138
147
 
@@ -148,17 +157,133 @@ your browser, here is a link or a code to review it."
148
157
 
149
158
  The only boundary that counts is the one on-chain.
150
159
 
151
- ## `wallet_api_get` is GET-only, deliberately - and there is no `api_post`
160
+ ## The write boundary: three primitives, never a generic `api_post`
161
+
162
+ **There is no GENERIC write counterpart, and there must not be one.** A tool that took an arbitrary path
163
+ and body and forwarded it to any mutating endpoint would let a model reach *any* mutation - freeze the
164
+ account, remove a signer, drain a grant - with no typed intent, no review panel, and no lane arbitration.
165
+ That argument hasn't changed. What has changed is that it is no longer served by a single refusal -
166
+ `wallet_api_get` being GET-only and nothing else. It is now served by THREE primitives, each admitting a
167
+ different, narrow slice of what a caller can reach, with the dangerous middle - anything that could move
168
+ funds or grant authority on its own - structurally unreachable by all three:
169
+
170
+ | Primitive | What it reaches | How the boundary holds |
171
+ |---|---|---|
172
+ | `wallet_api_get` | Any documented `GET` | Read-only. There is no body to send, no method but `GET`. |
173
+ | `wallet_sign_request` | Any typed sign-kind | Never signs, never relays - returns a URL the OWNER opens and signs in their own browser. |
174
+ | `wallet_api_write` | A small set of administrative `POST`/`PUT`/`PATCH`/`DELETE` routes | Every route that returns an unsigned build for the owner to sign is excluded from its manifest **by construction** - see below. |
175
+
176
+ ### `wallet_api_get` is GET-only, deliberately
152
177
 
153
178
  Every path is validated against a manifest generated from the frozen OpenAPI contract
154
179
  (`openapi/botanary-v1.yaml`, via `pnpm generate:routes` - see "Development" below) before anything
155
- reaches the network. An undocumented path, or any method other than `GET`, is refused.
156
-
157
- **There is no write counterpart, and there must not be one.** A generic `api_post` would let a model
158
- reach *any* mutation - freeze the account, remove a signer, drain a grant - with no typed intent, no
159
- review panel, and no lane arbitration. Every write instead goes through `wallet_send` (or a future
160
- `wallet_swap`): a typed intent the owner reviews and signs in their own browser, or the agent lane's own
161
- on-chain-bound mandate. `wallet_api_get` is deliberately incapable of being that shortcut.
180
+ reaches the network. An undocumented path, or any method other than `GET`, is refused. `wallet_api_get`
181
+ is deliberately incapable of writing anything, and always will be.
182
+
183
+ ### One parse: the string that is validated is the string that is requested
184
+
185
+ Both path allowlists - `wallet_api_get`'s and `wallet_api_write`'s - share a single canonicalisation
186
+ step (`src/http-path.ts`) that runs BEFORE any manifest is consulted, and both then match and request
187
+ the *same* canonical string. This is not a detail: a path that one layer validates and another layer
188
+ re-parses differently is an allowlist that allows something other than what it approved. `fetch`
189
+ re-parses whatever it is given with the WHATWG URL parser, which resolves `.` and `..` (percent-encoded
190
+ forms included), normalises `\` to `/`, and truncates at `#` - so a regex allowlist that treats those as
191
+ ordinary characters and then rebuilds the request from the caller's raw string is checking one route and
192
+ calling another.
193
+
194
+ So a caller-supplied path is refused unless it is *canonical*: absolute, never beginning `//`, no
195
+ fragment, no empty segment, every segment drawn from unreserved/sub-delim characters or well-formed
196
+ `%XX` escapes that do not decode - through any number of layers - to a separator, a fragment marker or a
197
+ `.`/`..` traversal, and finally a string the URL parser reproduces byte for byte. A `{param}` in a
198
+ manifest template compiles to that same charset, so a path parameter structurally cannot hold a
199
+ separator. The denylists are enforced as refusals rather than as mere omissions, so a denylisted literal
200
+ cannot come back through a parameterised sibling that happens to match it. `src/api-client.ts` re-checks
201
+ the same property immediately before calling `fetch`, as a second layer for callers other than these
202
+ two. A percent-encoded character that is genuinely part of an id (`%3A` for a CAIP-style `:`) passes and
203
+ is transmitted verbatim - never decoded, never re-encoded.
204
+
205
+ ### `wallet_sign_request`: the typed-intent lane
206
+
207
+ It is the write primitive this package has had the longest, and it covers every owner-authorized action
208
+ other than a send (see the tools table above and "Lane arbitration" below for why `wallet_send` stays
209
+ separate). Read closely, it does not weaken the argument above - it satisfies every part of it:
210
+
211
+ - **Typed intent.** `wallet_sign_request` takes a `kind` and a `params` object, never a path and a raw
212
+ body. The backend validates `params` server-side against that kind's own DTO (one shape per kind,
213
+ `SIGN_KIND_DTOS` in the contract) before anything is stored; an unregistered kind, or a body that does
214
+ not match the registered one, is refused before the owner is involved at all. Call `list_sign_kinds`
215
+ first to see every kind currently registered and exactly what it takes.
216
+ - **Review panel.** This tool never builds calldata and never signs. What it returns is a URL - the
217
+ SAME `/sign` handoff `wallet_send`'s browser lane already uses - that the OWNER opens in their own
218
+ browser, where the page builds the operation, simulates it, and asks for their signature. A model
219
+ calling this tool can make the owner see a request; it cannot make anything happen without them.
220
+ - **Lane arbitration.** `wallet_sign_request` has none to weaken, because it has no agent lane to
221
+ arbitrate into: it is the browser handoff, always, for every action it covers. `wallet_send` keeps its
222
+ own arbitration (agent-vs-owner routing) untouched, because send is the one action where that routing
223
+ is load-bearing - see "Lane arbitration" below.
224
+
225
+ So the worst a compromised model achieves through `wallet_sign_request` is ASKING the owner to sign
226
+ something - the same authority a stranger with the owner's phone number has.
227
+
228
+ ### `wallet_api_write`: plain session writes that need no signature
229
+
230
+ Everything left over from the two primitives above is administrative bookkeeping with no fund movement
231
+ and no authority grant of its own: claim an agent, mark a notification read, rename an account, edit a
232
+ pay-sh allowlist, manage conversations. `wallet_api_write` reaches exactly that set, and the property
233
+ that keeps it there is mechanical, not a promise: **every authority action in this backend returns an
234
+ UNSIGNED build for the owner to sign** (`build -> sign -> relay`, the invariant `botanary-be/CLAUDE.md`
235
+ states and `test/no-custody.spec.ts` pins). So a route can be excluded from `WRITE_ROUTES`
236
+ (`src/wallet/write-routes.manifest.ts`, generated by `pnpm generate:write-routes` - see "Development"
237
+ below) using nothing but the contract itself:
238
+
239
+ 1. **Build-shaped response.** Its success response resolves - directly, through an array's `items`, or
240
+ through any `oneOf`/`anyOf`/`allOf` branch - to a component schema named `*Build`: `UserOpBuild`,
241
+ `DelegationBuild`, `GrantBuild`, `ApiBudgetBuild`, and every other one. Freeze, initial account setup,
242
+ guardian install, add-signer, budget commit, delegation/grant creation - all named this way, all
243
+ excluded automatically.
244
+ 2. **`x-client-signed: true`.** The contract's own flag for "hands back or consumes something a client
245
+ must sign" - it catches real gaps signal 1 alone misses, because not every build-shaped response is
246
+ named `*Build` (`POST /account`'s `AccountDeployIntent` embeds an unsigned `deployUserOp`; pay.sh's
247
+ own withdraw-build response is named `PayShWithdrawBuildResponse`, breaking the naming convention by
248
+ one word) and it also catches every RELAY call (`POST /delegations`, pay.sh's own relay and
249
+ manual-sign lanes) - the same category `/userops` is in, just with nothing to *build* because there
250
+ is only something to *relay*.
251
+
252
+ A small explicit denylist covers what neither mechanical signal can see - it exists precisely BECAUSE the
253
+ two rules above are mechanical: a route can pass both (no `*Build` response, no `x-client-signed` flag)
254
+ and still hand back a live session credential, mint session authority for someone else, relay an
255
+ already-signed payload, or move funds through a custodial signer this backend holds itself (the Virtuals
256
+ agent-wallet withdraw/trade routes - the one deliberately custodial exception this backend carries). One
257
+ more entry (`/sign-requests`) is denylisted for a different reason: admitting it would let a caller
258
+ construct an arbitrary sign-request intent through an untyped path, duplicating `wallet_sign_request`'s own
259
+ typed-intent lane rather than going through it. Every entry is justified individually in
260
+ `scripts/generate-write-routes.mjs`'s own header comment and its `DENYLIST` object.
261
+
262
+ **The boundary is two mechanical rules plus this denylist, not one rule** - the numbers, read straight off
263
+ the frozen contract by `pnpm generate:write-routes` (`src/wallet/write-routes.manifest.ts`'s own generated
264
+ banner records them on every run):
265
+
266
+ | | Write operations (`POST`/`PUT`/`PATCH`/`DELETE`) |
267
+ |---|---|
268
+ | Total in the contract | 131 |
269
+ | Excluded - build-shaped response (`*Build`, signal 1) | 57 |
270
+ | Excluded - `x-client-signed: true`, not caught by signal 1 (signal 2) | 9 |
271
+ | Excluded - explicit denylist | 18 (across 17 distinct paths - `/auth/session` denylists both its `POST` and `DELETE`) |
272
+ | **Admitted into `WRITE_ROUTES`** | **47** |
273
+
274
+ The result: `wallet_api_write` can be talked into marking a notification read. It cannot be talked into
275
+ anything that moves a token or grants an agent, a signer, or a device new authority - not because the
276
+ tool refuses to *try*, but because the route to do so was never in its manifest to begin with.
277
+
278
+ **Two admitted routes still refuse at the HTTP layer, and that is a second layer working, not a manifest
279
+ gap.** `/pay-sh/accounts/{accountId}/allowlist` and `/pay-sh/accounts/{accountId}/settings` pass both
280
+ mechanical checks (neither is `*Build`-shaped nor `x-client-signed`) and so ARE in `WRITE_ROUTES` - but
281
+ `wallet_login` mints the same `kind: 'cli'` session `botanary login` does (this package and `botanary-cli`
282
+ share one identity and session store), and the backend's own `NoCliWritesGuard` 403s any write from a
283
+ `kind: 'cli'` session on the pay-sh and agent-wallet controllers, precisely because those routes move a
284
+ Privy-custodied balance or widen an agent wallet's spending fence with no owner signature anywhere in the
285
+ loop. The manifest is the first layer, not the only one; a route being admitted here says "shape-safe to
286
+ attempt," never "guaranteed to succeed."
162
287
 
163
288
  ## Why the advertised tool list changes
164
289
 
@@ -409,8 +534,18 @@ pnpm generate:routes # regenerate src/wallet/routes.manifest.ts from the siblin
409
534
  # checkout's openapi/botanary-v1.yaml - run this and commit the result whenever
410
535
  # the contract changes; it never runs automatically (a published install has no
411
536
  # sibling openapi/ directory to read it from)
537
+ pnpm generate:write-routes # same thing for src/wallet/write-routes.manifest.ts - see "The write
538
+ # boundary" above for the exclusion rule it applies while doing so
412
539
  ```
413
540
 
541
+ Neither generator runs from `test`, `build` or `prepare`, deliberately - a published `npx botanary-mcp`
542
+ install has no sibling `botanary-be` checkout to read the contract from. The cost of that correct
543
+ decision is that the committed output can rot silently, and it did (GET_ROUTES once sat nine routes
544
+ behind the contract, refusing routes the same branch had shipped). `test/manifest-freshness.spec.ts`
545
+ regenerates both manifests into a scratch directory and byte-compares - it never repairs the file it
546
+ checks - and skips, loudly and with a stated reason, where the sibling contract is absent. Same guard,
547
+ same reasoning as `botanary-fe/tests/api/schema-drift.test.ts`.
548
+
414
549
  `BOTANARY_MCP_HOME` overrides where the non-secret metadata file (and the file-fallback secret, if it's
415
550
  ever in use) live - defaults to `~/.botanary-mcp`. It exists mainly for tests and for running this
416
551
  server inside a container with an isolated home; a normal install never needs to set it.
@@ -435,8 +570,9 @@ What stays out on purpose:
435
570
  The chain is still the only real enforcement either way (see "The bound it reads is a convenience, not
436
571
  the check" above). The wallet lane's own `wallet_send` makes the SAME promise for the mandate lane it
437
572
  can take (see "Lane arbitration" above), and its browser-lane fallback is likewise never a shortcut:
438
- `wallet_api_get` is GET-only with no write counterpart, so every wallet-lane write terminates in the
439
- owner's own browser signature, not in anything this package executes on its own.
573
+ every fund-moving or authority-granting wallet-lane write terminates in the owner's own browser
574
+ signature, never in anything this package executes on its own. `wallet_api_write` reaches a small,
575
+ mechanically-excluded set of administrative writes only - see "The write boundary" above.
440
576
 
441
577
  `src/runtime.ts`'s `AgentRuntime` is the seam every tool in `src/tools.ts` is built on: it owns identity,
442
578
  signing (`store.sign()`/`store.signHash()`), a cached-with-retry agent session, and one method per
@@ -48,10 +48,18 @@ export declare class BotanaryApiClient {
48
48
  private readonly fetchImpl;
49
49
  constructor(baseUrl: string, fetchImpl?: typeof fetch);
50
50
  post<T>(path: string, body: unknown, token?: string): Promise<T>;
51
+ /** PUT request - used by wallet_api_write for the manifest's PUT-shaped administrative writes
52
+ * (e.g. `/agent-wallets/{agentId}/caps`). */
53
+ put<T>(path: string, body: unknown, token?: string): Promise<T>;
54
+ /** PATCH request - used by wallet_api_write for the manifest's PATCH-shaped administrative writes
55
+ * (e.g. `/accounts/{id}`). */
56
+ patch<T>(path: string, body: unknown, token?: string): Promise<T>;
51
57
  /** GET has no body - a query string, when one is needed, belongs in `path` itself (callers below never
52
58
  * put a token or anything secret there; see test/no-leak.spec.ts). */
53
59
  get<T>(path: string, token?: string): Promise<T>;
54
- /** DELETE request for logout and cleanup operations. No body, authenticated with optional token. */
60
+ /** DELETE request for logout and cleanup operations, and for the manifest's DELETE-shaped
61
+ * administrative writes (e.g. `/agent/conversations/{conversationId}`). No body, authenticated with
62
+ * optional token. */
55
63
  delete<T>(path: string, token?: string): Promise<T>;
56
64
  }
57
65
  //# sourceMappingURL=api-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/api-client.ts"],"names":[],"mappings":"AAAA;;;+DAG+D;AAC/D,qBAAa,gBAAiB,SAAQ,KAAK;IAGvC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAChD;;;;;;;OAOG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;gBAlBzC,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM;IACvB;;;;;;OAMG;IACM,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAW;IACvD;;;;;;;OAOG;IACM,iBAAiB,GAAE,MAAM,GAAG,IAAW;CAKnD;AAED;yFACyF;AACzF,qBAAa,iBAAiB;;IAE1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,OAAO,KAAa;IAG5C,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAItE;2EACuE;IACjE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAItD,oGAAoG;IAC9F,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CAyC1D"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/api-client.ts"],"names":[],"mappings":"AAEA;;;+DAG+D;AAC/D,qBAAa,gBAAiB,SAAQ,KAAK;IAGvC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAChD;;;;;;;OAOG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;gBAlBzC,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM;IACvB;;;;;;OAMG;IACM,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAW;IACvD;;;;;;;OAOG;IACM,iBAAiB,GAAE,MAAM,GAAG,IAAW;CAKnD;AAED;yFACyF;AACzF,qBAAa,iBAAiB;;IAE1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,OAAO,KAAa;IAG5C,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAItE;kDAC8C;IACxC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAIrE;mCAC+B;IACzB,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvE;2EACuE;IACjE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAItD;;0BAEsB;IAChB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CA0D1D"}
@@ -1,3 +1,4 @@
1
+ import { canonicalizeHttpPath, nonCanonicalPathMessage } from './http-path.js';
1
2
  /** Thrown for any non-2xx response, carrying the HTTP status alongside the SERVER's own message (never a
2
3
  * generic one - whoever built this agent needs the reason, §6-40 one layer up from the chain). The
3
4
  * status is what lets `AgentRuntime` tell "this session token is stale, re-mint and retry" (401) apart
@@ -43,17 +44,41 @@ export class BotanaryApiClient {
43
44
  async post(path, body, token) {
44
45
  return this.#request('POST', path, body, token);
45
46
  }
47
+ /** PUT request - used by wallet_api_write for the manifest's PUT-shaped administrative writes
48
+ * (e.g. `/agent-wallets/{agentId}/caps`). */
49
+ async put(path, body, token) {
50
+ return this.#request('PUT', path, body, token);
51
+ }
52
+ /** PATCH request - used by wallet_api_write for the manifest's PATCH-shaped administrative writes
53
+ * (e.g. `/accounts/{id}`). */
54
+ async patch(path, body, token) {
55
+ return this.#request('PATCH', path, body, token);
56
+ }
46
57
  /** GET has no body - a query string, when one is needed, belongs in `path` itself (callers below never
47
58
  * put a token or anything secret there; see test/no-leak.spec.ts). */
48
59
  async get(path, token) {
49
60
  return this.#request('GET', path, undefined, token);
50
61
  }
51
- /** DELETE request for logout and cleanup operations. No body, authenticated with optional token. */
62
+ /** DELETE request for logout and cleanup operations, and for the manifest's DELETE-shaped
63
+ * administrative writes (e.g. `/agent/conversations/{conversationId}`). No body, authenticated with
64
+ * optional token. */
52
65
  async delete(path, token) {
53
66
  return this.#request('DELETE', path, undefined, token);
54
67
  }
55
68
  async #request(method, path, body, token) {
56
- const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
69
+ // DEFENCE IN DEPTH - this is the layer that hands a string to `fetch`, and `fetch` re-parses it
70
+ // with the WHATWG URL parser, which resolves `..`, turns `\` into `/` and truncates at `#`. Every
71
+ // caller above is expected to have canonicalised already (wallet/api-get.ts and wallet/api-write.ts
72
+ // do, and it is the whole point of their allowlists), but "expected to" is what the original bug
73
+ // relied on: a path that survives one layer's regex and means something else to the URL parser
74
+ // reaches the network unless SOMETHING refuses it. This refuses it here too, so a future caller
75
+ // that interpolates an id into a template without thinking about it fails loudly instead of
76
+ // requesting a route nobody validated.
77
+ const canonical = canonicalizeHttpPath(path);
78
+ if (!canonical) {
79
+ throw new Error(nonCanonicalPathMessage(path, 'BotanaryApiClient'));
80
+ }
81
+ const res = await this.fetchImpl(`${this.baseUrl}${canonical.full}`, {
57
82
  method,
58
83
  headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
59
84
  ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/api-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAE/E;;;+DAG+D;AAC/D,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAG9B;IAQA;IASA;IAnBX,YACE,OAAe,EACN,MAAc;IACvB;;;;;;OAMG;IACM,UAA0C,IAAI;IACvD;;;;;;;OAOG;IACM,oBAAmC,IAAI;QAEhD,KAAK,CAAC,OAAO,CAAC,CAAC;QAnBN,WAAM,GAAN,MAAM,CAAQ;QAQd,YAAO,GAAP,OAAO,CAAuC;QAS9C,sBAAiB,GAAjB,iBAAiB,CAAsB;QAGhD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED;yFACyF;AACzF,MAAM,OAAO,iBAAiB;IAET;IACA;IAFnB,YACmB,OAAe,EACf,YAA0B,KAAK;QAD/B,YAAO,GAAP,OAAO,CAAQ;QACf,cAAS,GAAT,SAAS,CAAsB;IAC/C,CAAC;IAEJ,KAAK,CAAC,IAAI,CAAI,IAAY,EAAE,IAAa,EAAE,KAAc;QACvD,OAAO,IAAI,CAAC,QAAQ,CAAI,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACrD,CAAC;IAED;kDAC8C;IAC9C,KAAK,CAAC,GAAG,CAAI,IAAY,EAAE,IAAa,EAAE,KAAc;QACtD,OAAO,IAAI,CAAC,QAAQ,CAAI,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC;IAED;mCAC+B;IAC/B,KAAK,CAAC,KAAK,CAAI,IAAY,EAAE,IAAa,EAAE,KAAc;QACxD,OAAO,IAAI,CAAC,QAAQ,CAAI,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACtD,CAAC;IAED;2EACuE;IACvE,KAAK,CAAC,GAAG,CAAI,IAAY,EAAE,KAAc;QACvC,OAAO,IAAI,CAAC,QAAQ,CAAI,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACzD,CAAC;IAED;;0BAEsB;IACtB,KAAK,CAAC,MAAM,CAAI,IAAY,EAAE,KAAc;QAC1C,OAAO,IAAI,CAAC,QAAQ,CAAI,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,QAAQ,CACZ,MAAmD,EACnD,IAAY,EACZ,IAAa,EACb,KAAc;QAEd,gGAAgG;QAChG,kGAAkG;QAClG,oGAAoG;QACpG,iGAAiG;QACjG,+FAA+F;QAC/F,gGAAgG;QAChG,4FAA4F;QAC5F,uCAAuC;QACvC,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE;YACnE,MAAM;YACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;YACvG,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC,CAAC;QAEH,+FAA+F;QAC/F,gGAAgG;QAChG,6BAA6B;QAC7B,IAAI,IAA4F,CAAC;QACjG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,GAAG,EAAE,CAAC;YACpE,IAAI,CAAC;gBACH,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAEvB,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,qFAAqF;gBACrF,6CAA6C;gBAC7C,IAAI,GAAG,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,mGAAmG;YACnG,oEAAoE;YACpE,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YACrD,MAAM,iBAAiB,GAAG,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;YAClG,MAAM,IAAI,gBAAgB,CACxB,IAAI,EAAE,KAAK,EAAE,OAAO,IAAI,GAAG,IAAI,gBAAgB,GAAG,CAAC,MAAM,EAAE,EAC3D,GAAG,CAAC,MAAM,EACV,IAAI,EAAE,KAAK,EAAE,OAAO,IAAI,IAAI,EAC5B,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,IAAI,iBAAiB,IAAI,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CACxF,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,IAAI,IAAI,EAAE,CAAM,CAAC;IAC3B,CAAC;CACF"}
@@ -12,11 +12,26 @@ import type { HitlContext } from './context.js';
12
12
  * is indistinguishable from one that never dropped - a caller must not have to branch on how it got
13
13
  * its answer.
14
14
  *
15
- * `op` and `send` kinds await a terminal state and close the handle. `approval` does the same. Other
16
- * kinds (login, pair) are not implemented yet and report the flow is still live; Task B6 adds them
17
- * alongside the flows themselves. If the session is gone and this flow needs one (send, approval), this
18
- * function returns a default answer rather than throwing, because a handle outlives a logout - an action
19
- * already in flight deserves a status answer even when the wallet session is gone.
15
+ * `op`, `send`, `approval` and `pair` kinds await a terminal state and close the handle. If the session
16
+ * is gone and this flow needs one (send, approval), this function returns a default answer rather than
17
+ * throwing, because a handle outlives a logout - an action already in flight deserves a status answer
18
+ * even when the wallet session is gone.
19
+ *
20
+ * THERE IS DELIBERATELY NO `login` CASE. `HandleKind` still lists 'login' - `HandleStore` is what stores
21
+ * a login's poll secret, and `test/no-leak.spec.ts` pins that - but nothing in this package ever opens
22
+ * one: every `runtime.handles.open` call site is 'pair' (tools.ts), 'approval' (tools.ts), 'send'
23
+ * (tools.ts, wallet/send-orchestration.ts) or 'op' (wallet/send-orchestration.ts), and none passes the
24
+ * optional secret. A `login` case shipped here regardless, and it RETURNED `sessionToken` straight out
25
+ * of the poll response - into a tool result, which is to say into a model's context and the client's
26
+ * transcript. It was dead three times over (no opener, so `secretFor` could only answer null and take
27
+ * the not-resumable branch; and it polled `GET /v1/auth/status`, a route the backend does not have -
28
+ * the real login polls `GET /v1/auth/cli/{requestId}`, see wallet/login.ts), but a leak-shaped path in
29
+ * a published package is a leak waiting for its first caller, so it is gone rather than redacted.
30
+ *
31
+ * If a login ever does become resumable, it belongs on `/v1/auth/cli/{requestId}` and it must return
32
+ * what `wallet_login` itself returns - status, accountId, signerAddress - and NEVER the token, which
33
+ * belongs in the keychain and the Authorization header and nowhere else. `test/no-leak.spec.ts` has the
34
+ * regression guard.
20
35
  */
21
36
  export declare function resumeHandle(runtime: AgentRuntime, handle: PendingHandle, ctx: HitlContext): Promise<Record<string, unknown>>;
22
37
  //# sourceMappingURL=resume.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../../src/hitl/resume.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAuLlC"}
1
+ {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../../src/hitl/resume.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA6JlC"}
@@ -10,11 +10,26 @@ import { waitFor, pollSource, sseSource, withFallback } from './wait.js';
10
10
  * is indistinguishable from one that never dropped - a caller must not have to branch on how it got
11
11
  * its answer.
12
12
  *
13
- * `op` and `send` kinds await a terminal state and close the handle. `approval` does the same. Other
14
- * kinds (login, pair) are not implemented yet and report the flow is still live; Task B6 adds them
15
- * alongside the flows themselves. If the session is gone and this flow needs one (send, approval), this
16
- * function returns a default answer rather than throwing, because a handle outlives a logout - an action
17
- * already in flight deserves a status answer even when the wallet session is gone.
13
+ * `op`, `send`, `approval` and `pair` kinds await a terminal state and close the handle. If the session
14
+ * is gone and this flow needs one (send, approval), this function returns a default answer rather than
15
+ * throwing, because a handle outlives a logout - an action already in flight deserves a status answer
16
+ * even when the wallet session is gone.
17
+ *
18
+ * THERE IS DELIBERATELY NO `login` CASE. `HandleKind` still lists 'login' - `HandleStore` is what stores
19
+ * a login's poll secret, and `test/no-leak.spec.ts` pins that - but nothing in this package ever opens
20
+ * one: every `runtime.handles.open` call site is 'pair' (tools.ts), 'approval' (tools.ts), 'send'
21
+ * (tools.ts, wallet/send-orchestration.ts) or 'op' (wallet/send-orchestration.ts), and none passes the
22
+ * optional secret. A `login` case shipped here regardless, and it RETURNED `sessionToken` straight out
23
+ * of the poll response - into a tool result, which is to say into a model's context and the client's
24
+ * transcript. It was dead three times over (no opener, so `secretFor` could only answer null and take
25
+ * the not-resumable branch; and it polled `GET /v1/auth/status`, a route the backend does not have -
26
+ * the real login polls `GET /v1/auth/cli/{requestId}`, see wallet/login.ts), but a leak-shaped path in
27
+ * a published package is a leak waiting for its first caller, so it is gone rather than redacted.
28
+ *
29
+ * If a login ever does become resumable, it belongs on `/v1/auth/cli/{requestId}` and it must return
30
+ * what `wallet_login` itself returns - status, accountId, signerAddress - and NEVER the token, which
31
+ * belongs in the keychain and the Authorization header and nowhere else. `test/no-leak.spec.ts` has the
32
+ * regression guard.
18
33
  */
19
34
  export async function resumeHandle(runtime, handle, ctx) {
20
35
  switch (handle.kind) {
@@ -38,7 +53,16 @@ export async function resumeHandle(runtime, handle, ctx) {
38
53
  message: 'The wallet session has ended. Call wallet_login to reconnect and retry wallet_status to learn the outcome.',
39
54
  };
40
55
  }
41
- const TERMINAL_STATUSES = new Set(['success', 'failed', 'rejected']);
56
+ // MUST match the real sign-request status vocabulary (`SignRequestStatus` in the backend's
57
+ // `sign-request.dto.ts`: 'pending' | 'opened' | 'relayed' | 'rejected' | 'expired' | 'failed'),
58
+ // and `wallet/send.ts`'s own `TERMINAL_STATUSES` for the same reason that module's comment gives -
59
+ // 'opened' is deliberately NOT terminal (the owner's browser tab is open and loading is exactly
60
+ // the moment not to stop waiting). This previously read `['success', 'failed', 'rejected']`, a
61
+ // status vocabulary this route never emits - so a 'send' handle could never observe its own
62
+ // 'relayed' as terminal and would silently wait out the full deadline on every completed request.
63
+ // Invisible until now because nothing had ever opened a 'send' handle outside a test fixture;
64
+ // `wallet_sign_request` (tools.ts) is the first production caller of this branch.
65
+ const TERMINAL_STATUSES = new Set(['relayed', 'rejected', 'expired', 'failed']);
42
66
  const outcome = await waitFor(withFallback(sseSource(`${runtime.apiBaseUrl}/v1/sign-requests/${handle.ref.signRequestId}/stream`, walletToken, (d) => d), pollSource(() => runtime.api.get(`/v1/sign-requests/${handle.ref.signRequestId}`, walletToken), 2_000)), {
43
67
  isTerminal: (s) => TERMINAL_STATUSES.has(s.status),
44
68
  describe: (s) => `Request ${s.status}.`,
@@ -89,37 +113,6 @@ export async function resumeHandle(runtime, handle, ctx) {
89
113
  settled: outcome.terminal,
90
114
  };
91
115
  }
92
- case 'login': {
93
- const secret = await runtime.handles.secretFor(handle.id);
94
- if (!secret) {
95
- return {
96
- handle: handle.id,
97
- kind: handle.kind,
98
- resumable: false,
99
- message: 'This login cannot be rejoined - its poll credential is no longer on this machine. The ' +
100
- 'request may still be live in your browser; if it completes you are logged in. Run ' +
101
- 'wallet_login again if it does not.',
102
- };
103
- }
104
- // Re-poll the login with the same source and deadline
105
- const outcome = await waitFor(pollSource(() => runtime.api.get(`/v1/auth/status`, secret), 2_000), {
106
- isTerminal: (s) => s.status !== 'pending',
107
- describe: (s) => `Login status: ${s.status}.`,
108
- deadline: Date.parse(handle.expiresAt),
109
- ctx,
110
- });
111
- if (outcome.terminal && outcome.state.status === 'authenticated')
112
- await runtime.handles.close(handle.id);
113
- return {
114
- handle: handle.id,
115
- kind: handle.kind,
116
- label: handle.label,
117
- url: handle.url ?? null,
118
- status: outcome.state.status,
119
- sessionToken: outcome.state.sessionToken ?? null,
120
- settled: outcome.terminal,
121
- };
122
- }
123
116
  case 'pair': {
124
117
  // Re-poll the claim with the same source and deadline
125
118
  const code = handle.ref.code;
@@ -142,6 +135,8 @@ export async function resumeHandle(runtime, handle, ctx) {
142
135
  : `The code has not been claimed yet and this wait has ended. The code is still live until ${handle.expiresAt} - call botanary_wait to keep waiting, or regenerate_pairing_code for a fresh one.`,
143
136
  };
144
137
  }
138
+ // Where a 'login' handle lands, if one ever exists - see this function's header. The answer is
139
+ // honest (this build cannot rejoin it) and carries no credential of any kind.
145
140
  default:
146
141
  return {
147
142
  handle: handle.id,
@@ -1 +1 @@
1
- {"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/hitl/resume.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/hitl/resume.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAqB,EACrB,MAAqB,EACrB,GAAgB;IAEhB,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,IAAI,CAAC,CAAC,CAAC;YACV,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,mCAAmC;YACpF,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC;YACrF,IAAI,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,SAAS;gBAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACxF,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACvF,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,+FAA+F;YAC/F,+EAA+E;YAC/E,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC;YAC9C,MAAM,WAAW,GAAG,OAAO,EAAE,YAAY,CAAC;YAC1C,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO;oBACL,MAAM,EAAE,MAAM,CAAC,EAAE;oBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,OAAO,EAAE,4GAA4G;iBACtH,CAAC;YACJ,CAAC;YAED,2FAA2F;YAC3F,gGAAgG;YAChG,mGAAmG;YACnG,gGAAgG;YAChG,+FAA+F;YAC/F,4FAA4F;YAC5F,kGAAkG;YAClG,8FAA8F;YAC9F,kFAAkF;YAClF,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;YAMhF,MAAM,OAAO,GAAG,MAAM,OAAO,CAC3B,YAAY,CACV,SAAS,CACP,GAAG,OAAO,CAAC,UAAU,qBAAqB,MAAM,CAAC,GAAG,CAAC,aAAa,SAAS,EAC3E,WAAW,EACX,CAAC,CAAC,EAAE,EAAE,CAAC,CAAuB,CAC/B,EACD,UAAU,CACR,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAqB,qBAAqB,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,WAAW,CAAC,EACvG,KAAK,CACN,CACF,EACD;gBACE,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;gBAClD,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG;gBACvC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;gBACtC,GAAG;aACJ,CACF,CAAC;YACF,IAAI,OAAO,CAAC,QAAQ;gBAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC7D,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,EAAE;gBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,IAAI;gBACvB,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM;gBAC5B,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI;gBAChC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI;gBACpC,OAAO,EAAE,OAAO,CAAC,QAAQ;aAC1B,CAAC;QACJ,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC;YAC9C,MAAM,WAAW,GAAG,OAAO,EAAE,YAAY,CAAC;YAC1C,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO;oBACL,MAAM,EAAE,MAAM,CAAC,EAAE;oBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,OAAO,EAAE,4GAA4G;iBACtH,CAAC;YACJ,CAAC;YAMD,MAAM,MAAM,GAAG,CAAC,IAAuB,EAAE,EAAU,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YAEtF,MAAM,OAAO,GAAG,MAAM,OAAO,CAC3B,YAAY,CACV,SAAS,CACP,GAAG,OAAO,CAAC,UAAU,4BAA4B,EACjD,WAAW,EACX,CAAC,CAAC,EAAE,EAAE,CAAC,CAAsB,CAC9B,EACD,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,EAAgC,EAAE,KAAK,CAAC,CAC9E,EACD;gBACE,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;oBACnB,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;oBAC/C,OAAO,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC;gBACvD,CAAC;gBACD,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,oBAAoB,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,MAAM,IAAI,SAAS,GAAG;gBAClG,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;gBACtC,GAAG;aACJ,CACF,CAAC;YACF,IAAI,OAAO,CAAC,QAAQ;gBAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC7D,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,EAAE;gBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI;gBAC5D,OAAO,EAAE,OAAO,CAAC,QAAQ;aAC1B,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,sDAAsD;YACtD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAI7B,MAAM,OAAO,GAAG,MAAM,OAAO,CAC3B,YAAY,CACV,SAAS,CACP,GAAG,OAAO,CAAC,UAAU,sBAAsB,IAAI,SAAS,EACxD,CAAC,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC,EAC/B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC1B,EACD,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,CACxG,EACD;gBACE,UAAU,EAAE,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO;gBAC3C,QAAQ,EAAE,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,sCAAsC,IAAI,IAAI,SAAS,GAAG,CAAC;gBAC3H,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;gBACtC,GAAG;aACJ,CACF,CAAC;YACF,IAAI,OAAO,CAAC,QAAQ;gBAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC7D,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,EAAE;gBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,OAAO,EAAE,OAAO,CAAC,QAAQ;gBACzB,OAAO,EAAE,OAAO,CAAC,QAAQ;gBACzB,OAAO,EAAE,OAAO,CAAC,QAAQ;oBACvB,CAAC,CAAC,wCAAwC;oBAC1C,CAAC,CAAC,2FAA2F,MAAM,CAAC,SAAS,oFAAoF;aACpM,CAAC;QACJ,CAAC;QACD,+FAA+F;QAC/F,8EAA8E;QAC9E;YACE,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,EAAE;gBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,OAAO,EAAE,kEAAkE,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,KAAK,GAAG;aACzG,CAAC;IACN,CAAC;AACH,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * ONE PARSE. The string that is validated is the string that is requested.
3
+ *
4
+ * ------------------------------------------------------------------------------------------------
5
+ * THE BUG THIS EXISTS TO CLOSE
6
+ * ------------------------------------------------------------------------------------------------
7
+ * `wallet_api_get` and `wallet_api_write` both take a free-form, model-supplied `path` and check it
8
+ * against a generated manifest by compiling each manifest template into a regex, with a `{param}`
9
+ * segment becoming `[^/]+`. The compiled string was then re-assembled and handed to `fetch`, whose
10
+ * WHATWG URL parser re-parses it - and that parser does NOT agree with `[^/]+` about what a path
11
+ * segment is. It normalises `\` to `/`, resolves `.` and `..` (including their percent-encoded forms
12
+ * `%2e` / `%2e%2e`), and truncates everything from the first `#`. So:
13
+ *
14
+ * isKnownWritePath('/notifications/..\\userops#/read', 'POST') -> true
15
+ * ...and the request actually issued was POST /v1/userops
16
+ *
17
+ * Two different strings: the one the allowlist inspected, and the one that went on the wire. Every
18
+ * denylist entry in both manifests - the `/userops` relay boundary, `/approvals/{digest}/relay`,
19
+ * `/solana/transactions`, `/agent-wallets/partner-provision`, and on the read side `/auth/session`,
20
+ * which hands back the owner's live session token - was reachable through that gap.
21
+ *
22
+ * ------------------------------------------------------------------------------------------------
23
+ * WHY THIS CLOSES THE CLASS RATHER THAN THE INSTANCES
24
+ * ------------------------------------------------------------------------------------------------
25
+ * Blocklisting the tokens (`..`, `\`, `#`) loses: there is always another encoding (`%2e%2e`, `%5c`,
26
+ * `%23`, `%252e%252e`, and whatever the next parser quirk turns out to be). The root cause is not any
27
+ * particular character - it is that VALIDATION AND REQUEST OPERATED ON DIFFERENT STRINGS. So this
28
+ * module removes the difference instead of enumerating it:
29
+ *
30
+ * 1. The caller's path is canonicalised ONCE, up front, before anything looks at a manifest.
31
+ * 2. Canonicalisation is a FIXED-POINT test against the very parser `fetch` uses: the path is
32
+ * re-parsed with `new URL(...)`, and if `pathname + search` is not byte-identical to the input,
33
+ * the path is refused. Anything the URL parser would rewrite - a traversal in any encoding, a
34
+ * backslash, a fragment, a character it would percent-encode, a `//` that changes the host - is
35
+ * by definition not a fixed point, and is rejected without this module ever having to know that
36
+ * particular trick's name.
37
+ * 3. A structural charset allowlist runs first, per segment, so the fixed-point test is a second
38
+ * opinion rather than the only one: a segment may contain only unreserved/sub-delim characters
39
+ * and well-formed `%XX` triplets, and must not decode - through as many layers of encoding as it
40
+ * carries - to a separator, a fragment marker, or `.`/`..`.
41
+ * 4. The single canonical result is what the caller then matches AND requests. There is no second
42
+ * re-assembly step for the two to drift apart in.
43
+ *
44
+ * The `{param}` compilation is tightened to match: `PARAM_SEGMENT_PATTERN` is the same charset, so a
45
+ * parameter structurally cannot hold a separator or a fragment marker even before rule 2 speaks.
46
+ */
47
+ /**
48
+ * What a `{param}` in a manifest template compiles to. The old `[^/]+` was the bug: to a regex, `\`,
49
+ * `#` and `..` are ordinary characters, and to the URL parser they are separators. This charset holds
50
+ * no separator and no fragment marker in any form the URL parser recognises.
51
+ */
52
+ export declare const PARAM_SEGMENT_PATTERN = "(?:[A-Za-z0-9\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})+";
53
+ /** The canonical form of a caller-supplied path: one object, carried from validation to `fetch`. */
54
+ export interface CanonicalHttpPath {
55
+ /** The path, no query string. Byte-identical to what will be requested. */
56
+ readonly path: string;
57
+ /** The query string including its leading `?`, or `''`. Byte-identical to what will be requested. */
58
+ readonly query: string;
59
+ /** `path + query` - exactly the string that goes on the wire after the base URL. */
60
+ readonly full: string;
61
+ }
62
+ /**
63
+ * Canonicalise a caller-supplied request path, or return null if it is not canonical.
64
+ *
65
+ * "Canonical" means: an absolute path (never protocol-relative, which would change the HOST), every
66
+ * segment drawn from the safe charset and free of any encoded separator or traversal, an optional
67
+ * query drawn from its own charset, no fragment at all - and, as the final word, a string the WHATWG
68
+ * URL parser reproduces unchanged. Callers must use the returned strings, never their own input.
69
+ */
70
+ export declare function canonicalizeHttpPath(raw: unknown): CanonicalHttpPath | null;
71
+ /**
72
+ * Strip the `/v1` prefix, if present, so `/balance` and `/v1/balance` resolve to the same manifest
73
+ * entry. The OpenAPI contract declares paths WITHOUT the version prefix (`/v1` lives in
74
+ * `servers[].url`), so the generated manifests are un-prefixed - this is where the two conventions
75
+ * meet. Operates on an ALREADY-CANONICAL path; the old code did this to the raw input, which is how a
76
+ * `/v1`-prefixed bypass got a second bite.
77
+ */
78
+ export declare function stripVersionPrefix(canonicalPath: string): string;
79
+ /**
80
+ * Compile one manifest template (`/agents/{id}`) into a matcher for a concrete canonical path
81
+ * (`/agents/agt_1`). Shared by both allowlists so they cannot drift into two different ideas of what a
82
+ * path parameter may contain - which is how one of them ends up with the other's hole.
83
+ */
84
+ export declare function compileRoutePattern(template: string): RegExp;
85
+ /** Cap what a refusal echoes back, so a pathological input cannot flood a tool result. */
86
+ export declare function describePath(raw: unknown): string;
87
+ /**
88
+ * The refusal text every caller shares, so the reason a path was rejected reads the same wherever it
89
+ * is rejected - and names the real cause rather than the character that happened to trip it.
90
+ */
91
+ export declare function nonCanonicalPathMessage(raw: unknown, tool: string): string;
92
+ //# sourceMappingURL=http-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-path.d.ts","sourceRoot":"","sources":["../../src/http-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAaH;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,wDAAqB,CAAC;AAiDxD,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qGAAqG;IACrG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,GAAG,IAAI,CAmC3E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAU5D;AAED,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAGjD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAU1E"}