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