@reclaimprotocol/agent 0.1.22 → 0.1.24

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 (163) hide show
  1. package/README.md +55 -20
  2. package/lib/authoring/backend.d.ts +13 -10
  3. package/lib/authoring/tools.js +106 -9
  4. package/lib/cdp/browser-proxy.d.ts +78 -0
  5. package/lib/cdp/browser-proxy.js +186 -0
  6. package/lib/cdp/capture-engine.js +8 -7
  7. package/lib/cdp/client.d.ts +10 -0
  8. package/lib/cdp/geo.d.ts +10 -0
  9. package/lib/cdp/geo.js +36 -0
  10. package/lib/cdp/index.d.ts +1 -0
  11. package/lib/cdp/index.js +1 -0
  12. package/lib/live/cloudflared.d.ts +15 -0
  13. package/lib/live/cloudflared.js +268 -0
  14. package/lib/live/page.d.ts +20 -0
  15. package/lib/live/page.js +134 -0
  16. package/lib/live/tunnel.d.ts +23 -0
  17. package/lib/live/tunnel.js +120 -0
  18. package/lib/live/viewer.d.ts +28 -0
  19. package/lib/live/viewer.js +232 -0
  20. package/lib/mcp/api/operations.gen.js +34 -26
  21. package/lib/mcp/api/to-zod.d.ts +1 -1
  22. package/lib/mcp/api/to-zod.js +1 -1
  23. package/lib/mcp/api/tools.js +1 -1
  24. package/lib/mcp/instructions.d.ts +15 -2
  25. package/lib/mcp/instructions.js +101 -38
  26. package/lib/mcp/start.js +4 -3
  27. package/lib/mcp/tools/agent/analyze.js +5 -1
  28. package/lib/mcp/tools/agent/attach.d.ts +8 -1
  29. package/lib/mcp/tools/agent/attach.js +166 -22
  30. package/lib/mcp/tools/agent/build.d.ts +13 -8
  31. package/lib/mcp/tools/agent/build.js +22 -11
  32. package/lib/mcp/tools/agent/capture.js +5 -1
  33. package/lib/mcp/tools/agent/eval.js +3 -1
  34. package/lib/mcp/tools/agent/find.js +6 -5
  35. package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
  36. package/lib/mcp/tools/agent/how-it-works.js +57 -17
  37. package/lib/mcp/tools/agent/live-view.d.ts +21 -0
  38. package/lib/mcp/tools/agent/live-view.js +153 -0
  39. package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
  40. package/lib/mcp/tools/agent/pod-proof.js +64 -0
  41. package/lib/mcp/tools/agent/propose.js +29 -3
  42. package/lib/mcp/tools/agent/proxy.d.ts +3 -0
  43. package/lib/mcp/tools/agent/proxy.js +199 -0
  44. package/lib/mcp/tools/agent/publish.js +49 -77
  45. package/lib/mcp/tools/agent/session.d.ts +2 -1
  46. package/lib/mcp/tools/agent/session.js +26 -7
  47. package/lib/mcp/tools/agent/shared.js +17 -8
  48. package/lib/mcp/tools/authenticate/authenticate.d.ts +11 -0
  49. package/lib/mcp/tools/authenticate/authenticate.js +143 -121
  50. package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -3
  51. package/lib/mcp/tools/authenticate/builder-access.js +8 -3
  52. package/lib/mcp/tools/mode/mode.d.ts +1 -1
  53. package/lib/mcp/tools/mode/mode.js +26 -7
  54. package/lib/old/client.d.ts +9 -0
  55. package/lib/old/client.js +19 -1
  56. package/lib/old/instructions.d.ts +9 -1
  57. package/lib/old/instructions.js +4 -6
  58. package/lib/old/login-cdp.d.ts +2 -2
  59. package/lib/old/login-cdp.js +46 -6
  60. package/lib/old/to-register.d.ts +20 -8
  61. package/lib/old/to-register.js +7 -1
  62. package/lib/old/tools/authenticate.js +9 -1
  63. package/lib/old/tools/get-provider-info.js +6 -5
  64. package/lib/old/tools/issue-app-credentials.js +3 -2
  65. package/lib/old/tools/me-providers.js +3 -1
  66. package/lib/old/tools/publish.d.ts +1 -1
  67. package/lib/old/tools/publish.js +132 -129
  68. package/lib/old/tools/update-provider-metadata.d.ts +1 -1
  69. package/lib/old/tools/update-provider-metadata.js +1 -1
  70. package/lib/old/tools/verify-sdk-setup.js +1 -1
  71. package/lib/proof/attestor.d.ts +3 -3
  72. package/lib/proof/debug-response-dump.js +4 -4
  73. package/lib/proof/index.d.ts +1 -0
  74. package/lib/proof/index.js +1 -0
  75. package/lib/proof/page-replay.d.ts +36 -0
  76. package/lib/proof/page-replay.js +76 -0
  77. package/lib/proof/pod-prover.d.ts +47 -0
  78. package/lib/proof/pod-prover.js +155 -0
  79. package/lib/proof/replay.d.ts +28 -0
  80. package/lib/proof/replay.js +39 -18
  81. package/lib/provider/dom-xpath.js +11 -9
  82. package/lib/provider/draft.d.ts +13 -8
  83. package/lib/provider/draft.js +47 -13
  84. package/lib/provider/injection-doc.d.ts +23 -1
  85. package/lib/provider/injection-doc.js +34 -21
  86. package/lib/provider/match-redaction-warning.d.ts +2 -2
  87. package/lib/provider/match-redaction-warning.js +3 -3
  88. package/lib/provider/matcher.js +122 -12
  89. package/lib/provider/secrets.d.ts +3 -3
  90. package/lib/provider/secrets.js +3 -3
  91. package/lib/provider/to-version.d.ts +12 -9
  92. package/lib/provider/to-version.js +16 -4
  93. package/lib/tools/registry.d.ts +5 -5
  94. package/lib/tools/registry.js +5 -5
  95. package/package.json +4 -3
  96. package/samples/demo-server/README.md +7 -6
  97. package/skill/SKILL.builder.md +356 -189
  98. package/skill/SKILL.common.md +314 -79
  99. package/skill/SKILL.old.md +340 -155
  100. package/skill/builder-mode.md +58 -17
  101. package/skill/hash-validation.md +170 -0
  102. package/skill/interception.md +235 -0
  103. package/skill/js-sdk-integration.md +13 -7
  104. package/skill/old-mode.md +53 -12
  105. package/skill/user-script.md +61 -0
  106. package/src/authoring/backend.ts +13 -10
  107. package/src/authoring/tools.ts +128 -9
  108. package/src/cdp/capture-engine.ts +8 -7
  109. package/src/cdp/client.ts +8 -0
  110. package/src/cdp/geo.ts +52 -0
  111. package/src/cdp/index.ts +1 -0
  112. package/src/consts.ts +1 -0
  113. package/src/live/cloudflared.ts +348 -0
  114. package/src/live/page.ts +145 -0
  115. package/src/live/tunnel.ts +157 -0
  116. package/src/live/viewer.ts +293 -0
  117. package/src/mcp/api/operations.gen.ts +34 -26
  118. package/src/mcp/api/to-zod.ts +1 -1
  119. package/src/mcp/api/tools.ts +1 -1
  120. package/src/mcp/instructions.ts +121 -48
  121. package/src/mcp/start.ts +4 -3
  122. package/src/mcp/tools/agent/analyze.ts +5 -1
  123. package/src/mcp/tools/agent/attach.ts +194 -22
  124. package/src/mcp/tools/agent/build.ts +26 -15
  125. package/src/mcp/tools/agent/capture.ts +5 -1
  126. package/src/mcp/tools/agent/eval.ts +4 -1
  127. package/src/mcp/tools/agent/find.ts +6 -5
  128. package/src/mcp/tools/agent/how-it-works.ts +62 -21
  129. package/src/mcp/tools/agent/live-view.ts +198 -0
  130. package/src/mcp/tools/agent/pod-proof.ts +90 -0
  131. package/src/mcp/tools/agent/propose.ts +31 -3
  132. package/src/mcp/tools/agent/publish.ts +54 -78
  133. package/src/mcp/tools/agent/session.ts +26 -5
  134. package/src/mcp/tools/agent/shared.ts +19 -8
  135. package/src/mcp/tools/authenticate/authenticate.ts +167 -165
  136. package/src/mcp/tools/authenticate/builder-access.ts +8 -3
  137. package/src/mcp/tools/mode/mode.ts +28 -7
  138. package/src/old/client.ts +30 -1
  139. package/src/old/instructions.ts +4 -10
  140. package/src/old/login-cdp.ts +50 -6
  141. package/src/old/to-register.ts +28 -8
  142. package/src/old/tools/authenticate.ts +9 -1
  143. package/src/old/tools/get-provider-info.ts +6 -5
  144. package/src/old/tools/issue-app-credentials.ts +3 -2
  145. package/src/old/tools/me-providers.ts +3 -1
  146. package/src/old/tools/publish.ts +140 -130
  147. package/src/old/tools/update-provider-metadata.ts +1 -1
  148. package/src/old/tools/verify-sdk-setup.ts +1 -1
  149. package/src/proof/attestor.ts +3 -3
  150. package/src/proof/debug-response-dump.ts +4 -4
  151. package/src/proof/index.ts +8 -0
  152. package/src/proof/page-replay.ts +131 -0
  153. package/src/proof/pod-prover.ts +231 -0
  154. package/src/proof/replay.ts +60 -19
  155. package/src/provider/dom-xpath.ts +11 -9
  156. package/src/provider/draft.ts +56 -13
  157. package/src/provider/injection-doc.ts +37 -24
  158. package/src/provider/match-redaction-warning.ts +3 -3
  159. package/src/provider/matcher.ts +151 -10
  160. package/src/provider/secrets.ts +3 -3
  161. package/src/provider/to-version.ts +30 -13
  162. package/src/tools/registry.ts +5 -5
  163. package/skill/injection-api.md +0 -335
@@ -1,335 +0,0 @@
1
- # Page injection: `jsUserScripts` / `customInjection`
2
-
3
- A provider can inject **plain JavaScript** that runs in the page before every
4
- page load. Builder stores it as `webSettings.jsUserScripts`; old-devtools stores
5
- it as `customInjection` (identical semantics). It is ordinary browser
6
- JavaScript — its job is to drive the page so the target API request(s) fire
7
- during a verification.
8
-
9
- ## What the script is for
10
-
11
- The verification client already **intercepts the page's network requests
12
- automatically** — the script does NOT necessarily ned to capture, forward, or "claim" them.
13
- Its primary job is to get the page into the state where those requests happen, using
14
- standard browser APIs:
15
-
16
- - **Navigate** with `window.location.href = '…'` / `location.assign(…)` / the
17
- history API.
18
- - **Interact**: select an element and `.click()` it, submit a form, dispatch
19
- events.
20
- - **Wait**: poll the DOM until login/mfa completes or content loads, then trigger
21
- the next step.
22
-
23
- Write it like any page script. It runs before EVERY page load, so keep it
24
- idempotent, cheap, and guarded against running at the wrong time.
25
-
26
- ## `window.Reclaim` — rarely needed, use sparingly
27
-
28
- `window.Reclaim` is an OPTIONAL bridge for a few edge cases only. Most providers
29
- never touch it. In particular it does **not** do navigation — use the standard
30
- browser APIs above for that, in plain `jsUserScripts` / `customInjection`.
31
-
32
- Do not reach for `window.Reclaim` by default. Only use it when an edge case
33
- genuinely requires it. Its full, authoritative API (every method, with exact
34
- signatures and semantics) is documented in
35
- `https://github.com/reclaimprotocol/provider-web-script-dev/blob/main/src/support/env.d.ts` — consult that file rather
36
- than guessing.
37
-
38
- > Both backends run the script: the Builder verification client installs
39
- > `jsUserScripts` to run before every page load (before navigating to the
40
- > provider's `initialUrl`), and old-devtools runs `customInjection` the same
41
- > way. (`window.Reclaim` itself is still only forwarded/available in the cases
42
- > noted above.)
43
-
44
- ## Interception mode: `injectionType` / `interceptorType`
45
-
46
- Separate from the script's CONTENT is HOW the client captures network traffic
47
- for it to consume — a per-version field alongside `customInjection`/
48
- `jsUserScripts`, not something you write into the script itself. Whichever
49
- mechanism is selected, the injected script consumes what it caught through
50
- the SAME API: `window.reclaimInterceptor.addResponseMiddleware(middleware,
51
- middlewareName?)`. That consumption API fires for any intercepted request
52
- under EVERY mechanism below, including `CDP` — it is not a HAWKEYE-specific
53
- thing.
54
-
55
- - **old-devtools** field: `injectionType`, one of `NONE | MSWJS | XHOOK | CDP |
56
- HAWKEYE`. Verified from the backend's Prisma schema: **the column default is
57
- `HAWKEYE`**, not `NONE` — every provider `create_provider_version_from_capture`
58
- registers gets `injectionType: HAWKEYE` implicitly, since neither this tool
59
- nor the old backend's register/config controllers ever set it explicitly.
60
- This is harmless for the normal capture→replay→prove flow: with no
61
- interceptor script installed, there's nothing calling
62
- `addResponseMiddleware`, so the standard flow behaves identically
63
- regardless of this field's value.
64
- - **builder** field: `webSettings.inapp.interceptorOptions.interceptorType`,
65
- one of `HAWKEYE | MSWJS | CDP` (no `NONE`/`XHOOK` in this schema).
66
- `create_provider_version_from_capture` (builder mode) never sets `inapp` at
67
- all, so this is a no-op for the standard flow there too.
68
-
69
- **The mechanisms, and why you'd pick one over plain replay:**
70
- - **`CDP`** — interception at the browser/DevTools-protocol level, below the
71
- page's own JS. Works uniformly for every request the page makes, INCLUDING
72
- its own document/navigation load — no caveats, "works best."
73
- - **`HAWKEYE` / `MSWJS` / `XHOOK`** — interception done IN the page's JS
74
- context (different underlying libraries; same idea). These see the page's
75
- ordinary sub-resource/API requests fine, but the page's OWN document
76
- request is a special case — see `disableRequestReplay` below.
77
- - Reach for ANY of the above (over the plain replay flow, `injectionType:
78
- NONE` / no `inapp`) only when the data genuinely can't be reproduced by an
79
- independent replay — e.g. it depends on one-time server-side state (a
80
- nonce, a websocket push) that asking again wouldn't give you back. For
81
- everything else, the standard flow is simpler and is what `propose_provider`
82
- / `run_proof` are built for.
83
-
84
- ## `disableRequestReplay` / `isDocumentRequestReplayEnabled`
85
-
86
- **This does NOT gate `addResponseMiddleware` in general** — that fires for
87
- any intercepted request regardless of this field. What it actually controls:
88
- whether the CURRENT PAGE'S OWN document/navigation request (the request that
89
- loads the page's HTML — not a sub-resource or API call) is sent for real
90
- over the network at all, for the non-CDP mechanisms above. And it only
91
- matters if the request you're proving *is* that document request — e.g. the
92
- target value is embedded straight in the page's own HTML rather than
93
- fetched via a separate API call.
94
-
95
- - `false` (old-devtools' schema default) — the document request IS sent for
96
- real (for `HAWKEYE`/`MSWJS`/`XHOOK`), so `addResponseMiddleware` sees it
97
- like any other intercepted request. This is what you need if you're
98
- proving data straight out of the current page's own HTML.
99
- - `true` — the document request is constructed/synthesized locally instead
100
- of actually being sent, so the interceptor never observes it (every OTHER
101
- request the page makes is still intercepted normally). This exists as a
102
- **compatibility escape hatch**: some websites misbehave when their own
103
- top-level navigation request gets pulled through a JS-level interceptor —
104
- flip this to `true` for those sites. It's not a privacy or architecture
105
- choice, it's a per-site workaround.
106
- - **`CDP` is exempt from all of this** — it intercepts at the browser level,
107
- so the document request is never a special case for it.
108
- `disableRequestReplay`/`isDocumentRequestReplayEnabled` is simply inert
109
- when `injectionType`/`interceptorType` is `CDP`.
110
-
111
- Field names, per backend:
112
- - **old-devtools**: `disableRequestReplay` (boolean, schema default `false`).
113
- The dashboard's own field hint (verbatim): *"When enabled (default), the
114
- main document HTTP request is actually sent to get the response. When
115
- disabled, the expected request and response are constructed locally
116
- without making the network call."* Note the double negative: the
117
- dashboard's checkbox is labeled "Enable Document Request Replay" and is
118
- CHECKED when `disableRequestReplay` is `false` — the checkbox and the field
119
- it writes are inverted from each other.
120
- - **builder**: `webSettings.inapp.interceptorOptions.isDocumentRequestReplayEnabled`
121
- (boolean, positive naming this time — `true` means the document request IS
122
- sent). Per its openapi description, the schema's own stated default is
123
- `false`. Don't assume the two backends' defaults line up just because the
124
- concept is the same — old effectively defaults to "sent", the builder
125
- schema's stated default is "not sent"; verify against whichever backend
126
- you're publishing to rather than carrying an assumption across.
127
-
128
- If you're not authoring a non-CDP interceptor script that specifically needs
129
- to observe the current page's own document request, leave this alone (the
130
- default already matches the standard flow on old-devtools; on builder, this
131
- field isn't touched by `create_provider_version_from_capture` at all).
132
-
133
- ## `bodySniff` — unrelated to the above
134
-
135
- A per-request field (sits next to `url`/`method` on each `requestData` entry,
136
- not a version-level setting): `{ enabled: boolean, template: string }`.
137
-
138
- **`bodySniff.enabled` should be `true` whenever the request HAS a body at
139
- all** — this isn't a special case reserved for dynamic bodies. Only skip it
140
- (leave `enabled: false` / template empty) when the request genuinely sends
141
- no body (typically a GET). Both publish tools in this package already set
142
- `enabled`/`template` for you from `provider.body` for a normal
143
- capture-and-replay provider — you don't need to touch it directly there.
144
- Read the rest of this section if you're authoring a claim via live
145
- interception (`window.Reclaim.requestClaim`) whose body isn't simply the
146
- one literal string you captured once — most commonly because some SMALL
147
- part of it is dynamic per user/run (e.g. a user id, not necessarily the
148
- whole payload).
149
-
150
- **What actually gets hashed.** Confirmed directly from attestor-core
151
- (`hashProviderParams` in `@reclaimprotocol/attestor-core`): the claim/spec
152
- hash is `keccak256(canonicalStringify({ url, method, body, responseMatches,
153
- responseRedactions }))`, where `body` is whatever plain string sits in
154
- `params.body` — attestor-core does **no** template resolution, substitution,
155
- or `{{}}`-stripping on it; it hashes exactly the string it's given, verbatim.
156
- `bodySniff` as a concept (the `{enabled, template}` wrapper) doesn't exist
157
- inside attestor-core at all — it's translated to a plain `body: string`
158
- one layer up (old-devtools' `providerUtils.ts`: `body: bodySniff.enabled ?
159
- bodySniff.template : ''`), and THAT literal string — including any
160
- unresolved `{{paramName}}` text still in it — is what reaches
161
- `hashProviderParams`.
162
-
163
- **The consequence — and why this differs from `templateParams`.** A live
164
- claim's own `body` field (submitted via `requestClaim`) can ALSO be set to
165
- a literal template string containing `{{paramName}}`, rather than the real
166
- substituted content, with the real value supplied separately as a witness
167
- param. Since attestor-core hashes `body` as an opaque string with no
168
- resolution, a claim whose `body` is the literal template `T` and a spec
169
- whose `body`/`bodySniff.template` is that SAME literal template `T` hash
170
- identically — regardless of what real value the `{{paramName}}` hole stood
171
- for. This is the OPPOSITE of `templateParams`/`${var}` (used in
172
- `responseMatches`/`responseRedactions`), which — per the JS SDK's
173
- `generateSpecsFromRequestSpecTemplate` — genuinely gets substituted with a
174
- real value at verify time before hashing. Don't conflate the two: `${var}`
175
- in a match/redaction template IS resolved before hashing; `{{var}}` in a
176
- body template is NEVER resolved before hashing, on either side.
177
-
178
- **Practical use**: check exactly which part of the body actually changes
179
- across users/runs — usually it's one small field (an id, a token, a short
180
- list), not the whole payload. Template ONLY that minimal changing part;
181
- leave everything else as fixed literal text. For a body like
182
- `{"request":1,"type":"user_info","id":"12345"}` where only `id` varies, the
183
- template is `{"request":1,"type":"user_info","id":"{{REQ_BODY_USER_ID}}"}`
184
- — NOT e.g. `{{whole_body}}` wrapping the entire object, which would throw
185
- away the ability to structurally pin down everything else about the
186
- request. Then:
187
- 1. In the claim you build for `requestClaim`, set `bodySniff: { enabled:
188
- true, template: '{"request":1,"type":"user_info","id":"{{REQ_BODY_USER_ID}}"}' }`
189
- and add a matching witness/extracted param
190
- `REQ_BODY_USER_ID: theRealUserId` so the real value is still
191
- independently verifiable, just not part of the hash.
192
- 2. In the corresponding `allowedInjectedRequestData` entry, set `body` to
193
- that EXACT SAME literal template string (byte-for-byte, same
194
- `{{REQ_BODY_USER_ID}}` placeholder, same everything else) — NOT a
195
- `${var}` substitution, since `body` doesn't go through that substitution
196
- step.
197
- 3. Before publishing, sanity-check by hand that
198
- `template.replace('{{REQ_BODY_USER_ID}}', 'a real sample id')`
199
- reproduces the real captured/expected request body byte-for-byte — if it
200
- doesn't (extra whitespace, different key order, a field that turned out
201
- to ALSO vary but wasn't templated, etc.), the hashes won't match either,
202
- since the SPEC side uses the literal template as-is and the real claim's
203
- body must independently be built to match that same shape when the
204
- placeholder(s) are filled in. If MULTIPLE fields vary independently,
205
- template each one with its own `{{placeholder}}` and its own witness
206
- param — you're not limited to one hole per template.
207
-
208
- **Fallback — whole-body placeholder.** If precise per-field templating
209
- keeps causing hash mismatches (e.g. more fields turn out to vary than
210
- expected, key order isn't stable, whitespace differs run-to-run) and you
211
- can't pin down every variable field individually, fall back to templating
212
- the ENTIRE body as one placeholder instead: `{{REQ_BODY_GRD}}`. The `GRD`
213
- suffix matters — per the response-matching template engine
214
- (`convertTemplateToRegex`), a placeholder name ending in `GRD` is treated as
215
- a GREEDY match (`(.*)`, matching everything including embedded delimiters)
216
- rather than the default non-greedy `(.*?)` (which stops at the first
217
- occurrence of whatever comes after it in the template) — for a placeholder
218
- covering the entire remaining body with nothing fixed after it, greedy is
219
- what you want. This is strictly a fallback: it means NOTHING about the
220
- body's structure is pinned down by the hash anymore (any body at all would
221
- hash the same), so prefer the minimal-placeholder approach above whenever
222
- you can actually identify which fields vary.
223
-
224
- The same opaque-string hashing (confirmed directly from the attestor-core
225
- bundle shipped in this repo's `node_modules`) also applies to fields beyond
226
- `body`. The caller that invokes it against a live submitted claim to gate
227
- proof acceptance is `@reclaimprotocol/js-sdk`'s `verifyProof` — the next
228
- section covers how that gate works and how to configure a provider that
229
- satisfies it.
230
-
231
- ## Provider-hash validation (`verifyProof(proof, { providerId })`) — how it gates, and how to configure a provider that passes it
232
-
233
- The caller for the hashing described above is `@reclaimprotocol/js-sdk`'s
234
- `verifyProof`. When called with `{ providerId }` (the SDK's own
235
- integration guide treats this as the normal case), traced directly from
236
- that package's source (`src/utils/proofValidationUtils.ts`,
237
- `src/utils/providerUtils.ts`, `src/witness.ts`):
238
-
239
- 1. `assertValidateProof` → `fetchProviderHashRequirementsBy(providerId, …)`
240
- fetches this provider's CURRENT PUBLISHED CONFIG and builds a list of
241
- "expected hashes" — one per entry in `requestData` PLUS one per entry in
242
- `allowedInjectedRequestData` (old-devtools) / `allowedJsRequests`
243
- (builder) — same concept, different field name per backend; don't
244
- assume a doc or error naming one applies verbatim to the other.
245
- 2. Each expected hash is `hashRequestSpec` → `hashProofClaimParams({ url,
246
- method, body, responseMatches, responseRedactions })` — the SAME
247
- opaque-string hashing described above for `body`, applied to the WHOLE
248
- spec at once.
249
- 3. The REAL submitted proof's own `claimData.parameters` is hashed the
250
- SAME way, with `isOptional`-driven 2^N combinatorial expansion over
251
- `responseMatches`/`responseRedactions` PAIRS so an optional field can be
252
- either present or absent (`getProviderParamsAsCanonicalizedString`) —
253
- but this expansion ONLY touches match/redaction pairs, never `url`.
254
- 4. `assertValidProofsByHash` requires the real proof's hash to exactly
255
- equal ONE expected hash. No match → `verifyProof` rejects, even for a
256
- perfectly valid, correctly-signed proof whose `responseMatches` are
257
- 100% correct in shape.
258
-
259
- **Key constraint: `url` is hashed VERBATIM, with ZERO template/regex
260
- resolution — even when `urlType` is `REGEX` or `TEMPLATE`.** Confirmed
261
- directly from `generateSpecsFromRequestSpecTemplate` (the function that DOES
262
- resolve `${var}` placeholders in `responseMatches`/`responseRedactions`
263
- before hashing): it spreads every other field from the template unchanged,
264
- and never touches `url`. `urlType` is consumed by a DIFFERENT validation
265
- path entirely (matching an incoming live request against this spec at
266
- capture time) — never by this hash computation.
267
-
268
- **Check this before populating `allowedInjectedRequestData`/
269
- `allowedJsRequests` for a `verifyProof({ providerId })` use case:** if the
270
- real captured request URL contains ANY component that varies between two
271
- otherwise-identical sessions — a tracking nonce, a CSRF token, a
272
- cache-busting timestamp, a per-request trace id — the hash computed from
273
- your declared spec can NEVER equal the hash of any real future proof, no
274
- matter how correct `responseMatches`/`responseRedactions`/`body` are. This
275
- is common on JS-heavy sites: e.g. Slack regenerates `_x_id`, `_x_csid`, and
276
- `fp` on every single API call, so a captured `users.profile.getSections`
277
- URL is never identical twice, even for the same user seconds apart.
278
- Populating this field for such a provider is actively worse than leaving
279
- it empty — it converts "no provider-hash check was attempted" into
280
- "`verifyProof({ providerId })` rejects every real proof from this
281
- provider, unconditionally."
282
-
283
- **Verify before wiring this up**: capture the same endpoint twice, in two
284
- separate sessions (ideally two different users, or the same user on two
285
- different days), and diff the full request URL byte-for-byte.
286
- - Identical every time → safe to populate with `urlType: CONSTANT` and the
287
- literal URL.
288
- - Not identical → do not populate this field expecting it to gate
289
- `verifyProof({ providerId })`. There is no way to make a session-unique
290
- URL satisfy this specific mechanism, on either backend — this is a
291
- property of the hashing itself, not something fixable by editing
292
- `responseMatches`, `responseRedactions`, or `body` more carefully. See
293
- the fallback below.
294
-
295
- Also note, independent of the `url` constraint above: even with a stable URL,
296
- `responseMatches`/`responseRedactions` must be an EXACT structural match —
297
- not just similar in spirit. A provider whose extracted field SET varies
298
- per user (e.g. optional profile fields present for some users, absent for
299
- others) needs every such field marked `isOptional: true` on BOTH the match
300
- and its paired redaction, so the 2^N expansion produces a valid hash for
301
- the "field absent" case too. A field that's sometimes-present but never
302
- marked optional breaks the hash for any user missing it — the same failure
303
- mode as the `url` constraint above, just scoped to one field instead of the
304
- whole spec.
305
-
306
- **Fallback if provider-hash validation keeps causing problems.** Whether
307
- it's an unstable `url`, an un-marked optional field, or any other
308
- persistent hash mismatch you can't pin down — the way out on the verifying
309
- side is `verifyProof(proof, { dangerouslyDisableContentValidation: true })`.
310
- That skips step 1-4 above entirely and trusts only the attestor's
311
- cryptographic signature over the proof; it does NOT check that the proof's
312
- `url`/`method`/`body`/`responseMatches`/`responseRedactions` match anything
313
- you declared. A caller-supplied `{ hashes: [...] }` computed some other way
314
- is the alternative when you still want hash-equality enforced, just not
315
- derived from this provider's published config. Reach for
316
- `dangerouslyDisableContentValidation` deliberately, not as a silent
317
- default — it trades away the guarantee that the proof is shaped the way
318
- this provider's spec says it should be.
319
-
320
- ## Recognizing this on a provider you didn't author
321
-
322
- `get_provider_version_config` (old-devtools) and `get_provider_info` (public
323
- SDK backend) both return the raw `providerConfig`. If you see
324
- `injectionType`/`interceptorType` set to anything other than the inert
325
- default AND a `customInjection`/`jsUserScripts` that calls
326
- `reclaimInterceptor.addResponseMiddleware` (or references `MSWJS`/`XHook`),
327
- that provider is on the live-interception path, not the standard replay
328
- flow — read the script before assuming you can safely carry it forward or
329
- edit around it. If, on top of that, the request being proven IS the login/
330
- landing page itself (its `url` matches `loginUrl`, i.e. it's the document
331
- request, not a separate API call) and `injectionType`/`interceptorType`
332
- isn't `CDP`, also check `disableRequestReplay`/
333
- `isDocumentRequestReplayEnabled` — `true` there means that request was
334
- deliberately routed around live interception for site-compatibility
335
- reasons, not an oversight.