@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/builder-mode.md
CHANGED
|
@@ -1,17 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
Reclaim provider-authoring tools, running against the BUILDER backend. Three
|
|
2
|
+
tool layers: generated API tools (one per builder operation, snake_case),
|
|
3
|
+
credential/verification helpers, and browser agent tools (CDP capture,
|
|
4
|
+
synthesis, replay, proof).
|
|
5
|
+
|
|
6
|
+
**A `provider` is a recipe for extracting a data point from a site. A
|
|
7
|
+
`verification` is an end-user session that proves a data point using one. Never
|
|
8
|
+
call a provider a "verification."**
|
|
9
|
+
|
|
10
|
+
**Read the guide before authoring.** `how_it_works({ topic })` serves it, one
|
|
11
|
+
topic at a time — call `how_it_works({})` for the topic index. Topics:
|
|
12
|
+
`overview`, `auth`, `browser`, `capture`, `draft`, `params`, `prove`, `publish`,
|
|
13
|
+
`discover`, `user-script`, `interception`, `hash-validation`, `credentials`,
|
|
14
|
+
`never-do`, `troubleshooting`.
|
|
15
|
+
|
|
16
|
+
**Authentication:** try `get_me` first. On 401, `authenticate({})` returns a
|
|
17
|
+
device-pairing `code` + `attachUrl` — give the user the URL, then poll
|
|
18
|
+
`authenticate({ code })` until LINKED. Tokens cache ~2 days.
|
|
19
|
+
(→ `how_it_works({ topic: "auth" })`)
|
|
20
|
+
|
|
21
|
+
**Ask who is doing the signing in, then offer a browser.** "Yourself, or does
|
|
22
|
+
someone else need to log in?" decides half of `attach_browser`'s list. Doing it
|
|
23
|
+
themselves → `dedicated` or `attach`, both local, neither shareable. Someone
|
|
24
|
+
else, or a phone-only site → `container` (the Reclaim runtime in Docker, free,
|
|
25
|
+
shareable with `share_browser_view`) or `builder` (hosted, no install,
|
|
26
|
+
**chargeable**). Say which costs money, and say `container` exists at all:
|
|
27
|
+
nobody guesses it.
|
|
28
|
+
|
|
29
|
+
**The flow:** `attach_browser` (ask the developer which browser source — local
|
|
30
|
+
Chrome, the Reclaim runtime in Docker, a remote Builder browser, or a CDP URL
|
|
31
|
+
they supply) → `start_capture`
|
|
32
|
+
⇒ **`captureId`** (the handle every browser/inspect tool takes first) → drive
|
|
33
|
+
the page (`navigate`, `wait_for_page`, `eval_in_page`, `get_cookies`) → inspect
|
|
34
|
+
(`list_requests`, `get_request`, `find_requests_containing`) →
|
|
35
|
+
`propose_provider` ⇒ **`draftId`** (recipe + captured secrets, held server-side
|
|
36
|
+
— secrets never enter the transcript) → `replay_request(captureId, draftId)` →
|
|
37
|
+
`run_proof(captureId, draftId, ownerAddress)` → resolve a `providerId` →
|
|
38
|
+
`create_provider_version_from_capture` (lands on the draft branch) →
|
|
39
|
+
`publish_provider_version` (PRIVATE) or `submit_provider_version_for_review`
|
|
40
|
+
(PUBLIC).
|
|
41
|
+
|
|
42
|
+
**Reuse before create.** `create_provider_version_from_capture` needs a
|
|
43
|
+
`providerId`. Search your existing providers with `providers` (by domain / data
|
|
44
|
+
point) BEFORE calling `create_provider` — routing every run through
|
|
45
|
+
`create_provider` is what stacks duplicates in "My providers".
|
|
46
|
+
|
|
47
|
+
**Never prove a public endpoint.** After `replay_request` succeeds, re-run it
|
|
48
|
+
with `withoutSecrets: true`. If the value still extracts, the endpoint is public
|
|
49
|
+
and the proof binds to nobody — find an auth-bound request instead.
|
|
50
|
+
|
|
51
|
+
**Two hard constraints when drafting** (details in topics `draft` / `params`):
|
|
52
|
+
never `hash`/OPRF a value that also appears in the request URL; any param named
|
|
53
|
+
with `SECRET` may be used in a URL only within a tiny TOTAL character budget (2
|
|
54
|
+
under `key-update`, 12 under `zk`), checked at verification time.
|
|
55
|
+
|
|
56
|
+
**Backend mode:** this server can instead run against the devtools backend with
|
|
57
|
+
a reduced tool set. `get_devtools_mode` reports the active mode and why;
|
|
58
|
+
`set_devtools_mode` persists a switch to `~/.reclaim/config.json`, effective on
|
|
59
|
+
the NEXT server start. An explicit `USE_OLD_DEVTOOLS` env var wins over the
|
|
60
|
+
persisted choice.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Spec hashing: `bodySniff` and `verifyProof({ providerId })`
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- The one fact everything follows from
|
|
6
|
+
- `bodySniff` — when to enable it, how to template a partly dynamic body
|
|
7
|
+
- How `verifyProof(proof, { providerId })` gates a proof
|
|
8
|
+
- Why `url` is hashed verbatim, and when that makes
|
|
9
|
+
`allowedJsRequests`/`allowedInjectedRequestData` unusable
|
|
10
|
+
- Optional fields, and the `dangerouslyDisableContentValidation` fallback
|
|
11
|
+
- Related topics
|
|
12
|
+
|
|
13
|
+
Everything here follows from one fact, confirmed directly from
|
|
14
|
+
`hashProviderParams` in `@reclaimprotocol/attestor-core`:
|
|
15
|
+
|
|
16
|
+
> The claim/spec hash is
|
|
17
|
+
> `keccak256(canonicalStringify({ url, method, body, responseMatches, responseRedactions }))`,
|
|
18
|
+
> and attestor-core does **no** template resolution, substitution, or
|
|
19
|
+
> `{{}}`-stripping on any of it. It hashes exactly the strings it is given.
|
|
20
|
+
|
|
21
|
+
## `bodySniff`
|
|
22
|
+
|
|
23
|
+
A per-request field (it sits next to `url`/`method` on each `requestData` entry,
|
|
24
|
+
not a version-level setting): `{ enabled: boolean, template: string }`.
|
|
25
|
+
|
|
26
|
+
**`bodySniff.enabled` should be `true` whenever the request HAS a body at all**
|
|
27
|
+
— it isn't reserved for dynamic bodies. Only leave it off when the request
|
|
28
|
+
genuinely sends no body (typically a GET). Both publish tools in this package
|
|
29
|
+
already set `enabled`/`template` from `provider.body` for a normal
|
|
30
|
+
capture-and-replay provider, so you don't touch it there.
|
|
31
|
+
|
|
32
|
+
The rest of this section matters when authoring a claim through live
|
|
33
|
+
interception (`window.Reclaim.requestClaim`) whose body isn't the one literal
|
|
34
|
+
string you captured once — usually because a SMALL part is dynamic per
|
|
35
|
+
user/run.
|
|
36
|
+
|
|
37
|
+
`bodySniff` as a concept doesn't exist inside attestor-core at all: it's
|
|
38
|
+
translated one layer up (old-devtools' `providerUtils.ts`:
|
|
39
|
+
`body: bodySniff.enabled ? bodySniff.template : ''`), and THAT literal string —
|
|
40
|
+
including any unresolved `{{paramName}}` text still in it — is what reaches
|
|
41
|
+
`hashProviderParams`.
|
|
42
|
+
|
|
43
|
+
**The consequence, and why this differs from `templateParams`.** A live claim's
|
|
44
|
+
`body` can also be set to the literal template string containing
|
|
45
|
+
`{{paramName}}` rather than the substituted content, with the real value
|
|
46
|
+
supplied separately as a witness param. Since the body is hashed opaquely, a
|
|
47
|
+
claim whose `body` is template `T` and a spec whose `bodySniff.template` is that
|
|
48
|
+
same `T` hash identically, whatever the placeholder stood for. This is the
|
|
49
|
+
OPPOSITE of `templateParams`/`${var}` in `responseMatches`/`responseRedactions`,
|
|
50
|
+
which — per the JS SDK's `generateSpecsFromRequestSpecTemplate` — genuinely IS
|
|
51
|
+
substituted before hashing. Don't conflate them: `${var}` in a match/redaction
|
|
52
|
+
is resolved before hashing; `{{var}}` in a body is never resolved, on either
|
|
53
|
+
side.
|
|
54
|
+
|
|
55
|
+
**Practical use.** Work out exactly which part of the body varies across
|
|
56
|
+
users/runs — usually one small field, not the whole payload. Template ONLY that
|
|
57
|
+
part; leave everything else literal. For
|
|
58
|
+
`{"request":1,"type":"user_info","id":"12345"}` where only `id` varies, the
|
|
59
|
+
template is `{"request":1,"type":"user_info","id":"{{REQ_BODY_USER_ID}}"}` —
|
|
60
|
+
NOT `{{whole_body}}`, which throws away the ability to pin down everything else.
|
|
61
|
+
Then:
|
|
62
|
+
|
|
63
|
+
1. In the claim you build for `requestClaim`, set
|
|
64
|
+
`bodySniff: { enabled: true, template: '<that exact string>' }` and add a
|
|
65
|
+
matching witness/extracted param `REQ_BODY_USER_ID: <the real id>`, so the
|
|
66
|
+
real value stays independently verifiable while staying out of the hash.
|
|
67
|
+
2. In the corresponding `allowedInjectedRequestData` / `allowedJsRequests`
|
|
68
|
+
entry, set `body` to that EXACT SAME literal template, byte for byte — not a
|
|
69
|
+
`${var}` substitution.
|
|
70
|
+
3. Check by hand that
|
|
71
|
+
`template.replace('{{REQ_BODY_USER_ID}}', '<a real sample id>')` reproduces
|
|
72
|
+
the real request body byte for byte. Extra whitespace, different key order,
|
|
73
|
+
or a field that also varies but wasn't templated → the hashes won't match.
|
|
74
|
+
Multiple varying fields → one `{{placeholder}}` and one witness param each;
|
|
75
|
+
you're not limited to one hole.
|
|
76
|
+
|
|
77
|
+
**Fallback — whole-body placeholder.** If precise per-field templating keeps
|
|
78
|
+
mismatching and you can't pin every variable field down, template the ENTIRE
|
|
79
|
+
body as `{{REQ_BODY_GRD}}`. The `GRD` suffix matters: per the template engine
|
|
80
|
+
(`convertTemplateToRegex`), a placeholder name ending in `GRD` becomes a GREEDY
|
|
81
|
+
match (`(.*)`, including embedded delimiters) instead of the default non-greedy
|
|
82
|
+
`(.*?)`, which is what you want for a placeholder covering the whole remaining
|
|
83
|
+
body. Strictly a fallback — nothing about the body's structure is pinned by the
|
|
84
|
+
hash anymore (any body at all hashes the same).
|
|
85
|
+
|
|
86
|
+
## How `verifyProof(proof, { providerId })` gates
|
|
87
|
+
|
|
88
|
+
Traced from `@reclaimprotocol/js-sdk`
|
|
89
|
+
(`src/utils/proofValidationUtils.ts`, `src/utils/providerUtils.ts`,
|
|
90
|
+
`src/witness.ts`):
|
|
91
|
+
|
|
92
|
+
1. `assertValidateProof` → `fetchProviderHashRequirementsBy(providerId, …)`
|
|
93
|
+
fetches the provider's CURRENT PUBLISHED CONFIG and builds a list of expected
|
|
94
|
+
hashes — one per `requestData` entry PLUS one per
|
|
95
|
+
`allowedInjectedRequestData` (old-devtools) / `allowedJsRequests` (builder)
|
|
96
|
+
entry.
|
|
97
|
+
2. Each expected hash is `hashRequestSpec` → `hashProofClaimParams({ url,
|
|
98
|
+
method, body, responseMatches, responseRedactions })` — the same opaque
|
|
99
|
+
hashing as above, applied to the whole spec.
|
|
100
|
+
3. The real submitted proof's `claimData.parameters` is hashed the same way,
|
|
101
|
+
with `isOptional`-driven 2^N combinatorial expansion over
|
|
102
|
+
`responseMatches`/`responseRedactions` PAIRS
|
|
103
|
+
(`getProviderParamsAsCanonicalizedString`) so an optional field can be
|
|
104
|
+
present or absent. **That expansion only touches match/redaction pairs, never
|
|
105
|
+
`url`.**
|
|
106
|
+
4. `assertValidProofsByHash` requires the real proof's hash to equal exactly ONE
|
|
107
|
+
expected hash. No match → `verifyProof` rejects, even for a perfectly valid,
|
|
108
|
+
correctly signed proof whose `responseMatches` are 100% right.
|
|
109
|
+
|
|
110
|
+
### `url` is hashed VERBATIM — zero template/regex resolution
|
|
111
|
+
|
|
112
|
+
Even with `urlType: REGEX` or `TEMPLATE`. Confirmed from
|
|
113
|
+
`generateSpecsFromRequestSpecTemplate` (the function that DOES resolve `${var}`
|
|
114
|
+
in matches/redactions): it spreads every other field through unchanged and never
|
|
115
|
+
touches `url`. `urlType` is consumed by a different validation path entirely
|
|
116
|
+
(matching a live request against the spec at capture time), never by this hash.
|
|
117
|
+
|
|
118
|
+
**So, before populating `allowedInjectedRequestData`/`allowedJsRequests` for a
|
|
119
|
+
`verifyProof({ providerId })` use case:** if the real captured URL contains ANY
|
|
120
|
+
component that varies between two otherwise-identical sessions — a tracking
|
|
121
|
+
nonce, a CSRF token, a cache-busting timestamp, a per-request trace id — the
|
|
122
|
+
hash from your declared spec can NEVER equal any real future proof's, no matter
|
|
123
|
+
how correct everything else is. This is common on JS-heavy sites (Slack
|
|
124
|
+
regenerates `_x_id`, `_x_csid` and `fp` on every API call, so a captured
|
|
125
|
+
`users.profile.getSections` URL is never identical twice, even seconds apart).
|
|
126
|
+
|
|
127
|
+
Populating the field for such a provider is **actively worse than leaving it
|
|
128
|
+
empty**: it converts "no provider-hash check was attempted" into
|
|
129
|
+
"`verifyProof({ providerId })` rejects every real proof, unconditionally."
|
|
130
|
+
|
|
131
|
+
**Verify first:** capture the same endpoint twice in two separate sessions
|
|
132
|
+
(ideally two users, or the same user on two days) and diff the full URL byte for
|
|
133
|
+
byte.
|
|
134
|
+
|
|
135
|
+
- Identical every time → safe to populate (`urlType: CONSTANT`, literal URL).
|
|
136
|
+
- Not identical → do not populate it expecting it to gate `verifyProof`. No
|
|
137
|
+
amount of care with `responseMatches`/`responseRedactions`/`body` fixes it on
|
|
138
|
+
either backend; it's a property of the hashing itself.
|
|
139
|
+
|
|
140
|
+
### Optional fields
|
|
141
|
+
|
|
142
|
+
Independent of the `url` constraint: even with a stable URL,
|
|
143
|
+
`responseMatches`/`responseRedactions` must match EXACTLY, not just in spirit. A
|
|
144
|
+
provider whose extracted field SET varies per user (optional profile fields
|
|
145
|
+
present for some, absent for others) needs every such field marked
|
|
146
|
+
`isOptional: true` on BOTH the match and its paired redaction, so the 2^N
|
|
147
|
+
expansion produces a valid hash for the "absent" case. A sometimes-present field
|
|
148
|
+
that isn't marked optional breaks the hash for every user missing it — the same
|
|
149
|
+
failure mode as the `url` constraint, scoped to one field.
|
|
150
|
+
|
|
151
|
+
### Fallback
|
|
152
|
+
|
|
153
|
+
Whether it's an unstable `url`, an unmarked optional field, or any other
|
|
154
|
+
persistent mismatch, the way out is on the VERIFYING side:
|
|
155
|
+
`verifyProof(proof, { dangerouslyDisableContentValidation: true })`. That skips
|
|
156
|
+
steps 1–4 entirely and trusts only the attestor's cryptographic signature; it
|
|
157
|
+
does NOT check that the proof's url/method/body/matches/redactions match
|
|
158
|
+
anything you declared. A caller-supplied `{ hashes: [...] }` computed some other
|
|
159
|
+
way is the alternative when you still want hash-equality enforced, just not
|
|
160
|
+
derived from the published config. Reach for
|
|
161
|
+
`dangerouslyDisableContentValidation` deliberately, never as a silent default —
|
|
162
|
+
it trades away the guarantee that the proof is shaped the way the provider's
|
|
163
|
+
spec says.
|
|
164
|
+
|
|
165
|
+
## Related topics
|
|
166
|
+
|
|
167
|
+
- `user-script` — `allowedJsRequests` / `allowedInjectedRequestData`, the fields
|
|
168
|
+
this page's `url` constraint applies to.
|
|
169
|
+
- `prove` — `run_proof` and diagnosing a proof that fails before it ever reaches
|
|
170
|
+
`verifyProof`.
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Live interception: `injectionType` / `interceptorType`
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- The field, per backend
|
|
6
|
+
- Which mechanism to pick, and when to use any of them at all
|
|
7
|
+
- When a site won't load, or won't let the user sign in — ask which clients
|
|
8
|
+
ship it, bisect, then the remedies
|
|
9
|
+
- The document-replay flag, and why the two backends invert its polarity
|
|
10
|
+
- Recognizing live interception on a provider you didn't author
|
|
11
|
+
- Related topics
|
|
12
|
+
|
|
13
|
+
Separate from the user script's CONTENT is HOW the client captures network
|
|
14
|
+
traffic for it to consume. This is a per-version field alongside
|
|
15
|
+
`customInjection`/`jsUserScripts`, not something you write into the script.
|
|
16
|
+
|
|
17
|
+
Whichever mechanism is selected, the script consumes what was caught through the
|
|
18
|
+
SAME API: `window.reclaimInterceptor.addResponseMiddleware(middleware,
|
|
19
|
+
middlewareName?)`. That fires for any intercepted request under EVERY mechanism
|
|
20
|
+
below, `CDP` included — it is not HAWKEYE-specific.
|
|
21
|
+
|
|
22
|
+
## The field, per backend
|
|
23
|
+
|
|
24
|
+
- **old-devtools:** `injectionType`, one of
|
|
25
|
+
`NONE | MSWJS | XHOOK | CDP | HAWKEYE`. The backend's own column default is
|
|
26
|
+
**`HAWKEYE`, not `NONE`** — harmless for the standard flow, since with no
|
|
27
|
+
interceptor script installed nothing calls `addResponseMiddleware`.
|
|
28
|
+
`create_provider_version_from_capture` exposes it as an author-settable input
|
|
29
|
+
with NO forced default: omit it and the backend default applies; pass an
|
|
30
|
+
explicit value (including `NONE`) for certainty. When passed it's sent on both
|
|
31
|
+
register and add-version, and the tool's always-on add-version follow-up (see
|
|
32
|
+
`disableRequestReplay` below) reapplies it, so an explicit value is guaranteed
|
|
33
|
+
correct by the time that version lands.
|
|
34
|
+
- **builder:** `webSettings.inapp.interceptorOptions.interceptorType`, one of
|
|
35
|
+
`HAWKEYE | MSWJS | CDP`. There is no `NONE` here — **omitting
|
|
36
|
+
`interceptorOptions` entirely IS builder's no-interception signal**, and the
|
|
37
|
+
publish tool omits `inapp` rather than defaulting to `HAWKEYE`.
|
|
38
|
+
|
|
39
|
+
Don't assume a non-`NONE` `injectionType` on an existing provider means
|
|
40
|
+
something is broken — HAWKEYE-by-default is normal and inert for the standard
|
|
41
|
+
flow.
|
|
42
|
+
|
|
43
|
+
## Which mechanism, and when to use any of them at all
|
|
44
|
+
|
|
45
|
+
- **`CDP`** — interception at the browser/DevTools-protocol level, below the
|
|
46
|
+
page's own JS. Works uniformly for every request the page makes, including its
|
|
47
|
+
own document/navigation load. No caveats; works best.
|
|
48
|
+
- **`HAWKEYE` / `MSWJS` / `XHOOK`** — interception inside the page's JS context
|
|
49
|
+
(different libraries, same idea). Fine for ordinary sub-resource/API requests,
|
|
50
|
+
but the page's OWN document request is a special case — see
|
|
51
|
+
"The document-replay flag".
|
|
52
|
+
- **Reach for any of them only when the data genuinely can't be reproduced by
|
|
53
|
+
an independent replay** — for example, it depends on one-time server-side
|
|
54
|
+
state (a nonce, a websocket push) that asking again wouldn't return. For
|
|
55
|
+
everything else the standard capture → replay flow is simpler, and is what
|
|
56
|
+
`propose_provider` / `run_proof` are built for.
|
|
57
|
+
|
|
58
|
+
## When a site won't load, or won't let the user sign in
|
|
59
|
+
|
|
60
|
+
Three symptoms point the same way:
|
|
61
|
+
|
|
62
|
+
- the page loads blank, half-rendered, or spins forever;
|
|
63
|
+
- sign-in is refused, or the form submits and bounces straight back;
|
|
64
|
+
- login succeeds, then the next page breaks.
|
|
65
|
+
|
|
66
|
+
### First, ask which clients ship this provider
|
|
67
|
+
|
|
68
|
+
**Ask the author which verification clients their END USERS will run — don't
|
|
69
|
+
infer it.** It decides whether any of this section applies, and it is not
|
|
70
|
+
something the session can tell you: the client you author through is `builder`,
|
|
71
|
+
whereas production is whatever the author's own app integrates. A provider that
|
|
72
|
+
looks healthy here can fail for every real user, and the reverse.
|
|
73
|
+
|
|
74
|
+
Ask plainly, and offer the list: *"Which clients will run this in production —
|
|
75
|
+
the InApp SDK, the verifier app, the browser extension, portals, or something
|
|
76
|
+
else? I need it to tell whether interception is a plausible cause."* If they
|
|
77
|
+
ship more than one, the diagnosis has to hold for all of them, so treat the
|
|
78
|
+
strictest row below as the answer.
|
|
79
|
+
|
|
80
|
+
| Client the end user runs | Where it intercepts | Does this section apply? |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `inapp-sdk` | Inside the page's JS | **Yes** |
|
|
83
|
+
| `verifier-app` | Inside the page's JS | **Yes** |
|
|
84
|
+
| `reclaim-browser-extension` | Inside the page's JS | **Yes** |
|
|
85
|
+
| `portals` | Browser level, over CDP | No |
|
|
86
|
+
| `builder` | Server-side CDP, out of band | No |
|
|
87
|
+
|
|
88
|
+
`builder` is the row for authoring and for a session created with no
|
|
89
|
+
`verificationClientUrl`. Reaching it means you are diagnosing the authoring
|
|
90
|
+
tool, not what the author's users will hit — which is exactly why you ask
|
|
91
|
+
first.
|
|
92
|
+
|
|
93
|
+
**If production is only `portals` or `builder`, stop here and look elsewhere.**
|
|
94
|
+
CDP intercepts below the page's own JavaScript, so there is nothing for a site
|
|
95
|
+
to detect and nothing to disable — and `builder` never delivers intercepted
|
|
96
|
+
traffic to the page at all (topic `user-script`). A verification on those that
|
|
97
|
+
won't load or won't sign in is almost always a CAPTCHA or bot challenge
|
|
98
|
+
instead: check `analyze_request_constraints` for a `botChallenge` concern, and
|
|
99
|
+
treat it as a site that needs a human through a live view rather than a
|
|
100
|
+
provider to fix.
|
|
101
|
+
|
|
102
|
+
### Then bisect, cheapest change first
|
|
103
|
+
|
|
104
|
+
**For the three in-page clients, suspect interception before the recipe.** A
|
|
105
|
+
JS-level interceptor patches the page's own `fetch`/`XMLHttpRequest`, and some
|
|
106
|
+
sites treat that as tampering: anti-bot vendors probe for it directly (a
|
|
107
|
+
patched `fetch.toString()` is a well-known signal), and a few break on their
|
|
108
|
+
own without meaning to. None of that shows up while you author — the MCP agent
|
|
109
|
+
captures over CDP, out of band, so the site behaves normally right up until a
|
|
110
|
+
real verification runs.
|
|
111
|
+
|
|
112
|
+
Try document replay first and interception second. Often it is only
|
|
113
|
+
DOCUMENT REPLAY that breaks a site, not interception as a whole — the page's
|
|
114
|
+
own top-level navigation is the one request pulled through the interceptor, and
|
|
115
|
+
that is the one some sites refuse to survive. Turning it off is a much smaller
|
|
116
|
+
change than going interceptor-free, and it tells you which half is at fault.
|
|
117
|
+
Copy this checklist and work down it:
|
|
118
|
+
|
|
119
|
+
- [ ] 1. **Turn document replay OFF, keep the interceptor.** Old-devtools:
|
|
120
|
+
`disableRequestReplay: true`. Builder:
|
|
121
|
+
`interceptorOptions.isDocumentRequestReplayEnabled: false` (the two
|
|
122
|
+
backends name this with opposite polarity — see "The document-replay
|
|
123
|
+
flag"). Both publish tools already default it off, so a provider that
|
|
124
|
+
breaks here usually had it switched ON deliberately.
|
|
125
|
+
- [ ] 2. **Retry the verification.** Reload, then sign in as a real end-user
|
|
126
|
+
would. Works now → stop here. This is the cheapest fix and you keep the
|
|
127
|
+
interceptor.
|
|
128
|
+
- [ ] 3. **Still broken? Turn interception off entirely.** Old-devtools:
|
|
129
|
+
publish with `injectionType: NONE`. Builder: omit `interceptorOptions`
|
|
130
|
+
entirely — there is no `NONE` there, and omitting it IS the off switch.
|
|
131
|
+
- [ ] 4. **Retry again, and compare.** Loads and signs in now → interception
|
|
132
|
+
itself was the cause; pick a remedy below. Still broken → interception
|
|
133
|
+
is innocent; go back to the recipe (topic `prove`) or the browser
|
|
134
|
+
(topic `browser`).
|
|
135
|
+
|
|
136
|
+
If step 2 fixed it, you are done — the interceptor stays and only the document
|
|
137
|
+
request stops being replayed. Note that under `CDP` the flag is inert, so a
|
|
138
|
+
`CDP` provider that breaks is never a document-replay problem.
|
|
139
|
+
|
|
140
|
+
### Remedies
|
|
141
|
+
|
|
142
|
+
If it took step 3, take the first remedy that fits — in this order:
|
|
143
|
+
|
|
144
|
+
1. **Try a different interceptor.** They patch the page in different ways, so
|
|
145
|
+
a site that rejects one often accepts another — work along the row for your
|
|
146
|
+
backend before giving up on interception:
|
|
147
|
+
- old-devtools: `HAWKEYE` → `MSWJS` → `XHOOK` → `CDP`
|
|
148
|
+
- builder: `HAWKEYE` → `MSWJS` → `CDP`
|
|
149
|
+
|
|
150
|
+
`CDP` is the one that cannot be detected — it intercepts below the page's
|
|
151
|
+
JavaScript, so nothing in the page is patched. Prefer it when the target
|
|
152
|
+
client supports it; the in-page clients above may not, which is why the
|
|
153
|
+
JS-level mechanisms are worth cycling through first.
|
|
154
|
+
2. **Ship without it.** Most providers never needed an interceptor: the
|
|
155
|
+
standard capture-and-replay flow proves the data on its own. Leave it off
|
|
156
|
+
unless the value genuinely can't be reproduced by an independent replay
|
|
157
|
+
(see "Which mechanism, and when to use any of them at all").
|
|
158
|
+
3. **Drive the claim yourself.** Where the data only exists in a live page,
|
|
159
|
+
skip `reclaimInterceptor` and have the user script navigate to the page
|
|
160
|
+
that shows the value, read what the page already holds, and call
|
|
161
|
+
`window.Reclaim.requestClaim` with it. Declare anything the script fires in
|
|
162
|
+
`allowedJsRequests` / `allowedInjectedRequestData`, and read topic
|
|
163
|
+
`hash-validation` first — a session-unique URL there makes
|
|
164
|
+
`verifyProof({ providerId })` reject every real proof.
|
|
165
|
+
|
|
166
|
+
⚠️ **Remedy 3 needs a client that consumes `window.Reclaim`.** The InApp
|
|
167
|
+
SDK, the browser extension, and the portals do. Builder's own verification
|
|
168
|
+
client injects the bridge but never drains it, so `requestClaim` is inert
|
|
169
|
+
there — see topic `user-script`. Don't reach for remedy 3 on a Builder-run
|
|
170
|
+
verification.
|
|
171
|
+
|
|
172
|
+
## The document-replay flag
|
|
173
|
+
|
|
174
|
+
**Both publish tools default this to disabled, and both let the author override
|
|
175
|
+
it.**
|
|
176
|
+
|
|
177
|
+
- **old-devtools:** `disableRequestReplay`, schema default `false`. The publish
|
|
178
|
+
tool sends `true` by default, auto-following a fresh register with an
|
|
179
|
+
add-version call to apply it (the register endpoint hardcodes it `false`
|
|
180
|
+
regardless of what's sent). Pass `disableRequestReplay: false` explicitly for
|
|
181
|
+
real document replay.
|
|
182
|
+
- **builder:** `webSettings.clientOptions.inapp.interceptorOptions.isDocumentRequestReplayEnabled`
|
|
183
|
+
— positive naming, schema default `false`. The publish tool sets `false`
|
|
184
|
+
whenever you pass `interceptorOptions` at all. Pass
|
|
185
|
+
`interceptorOptions.isDocumentRequestReplayEnabled: true` to enable it.
|
|
186
|
+
|
|
187
|
+
The two backends' polarity is inverted, so don't carry an assumption across.
|
|
188
|
+
The old dashboard makes this worse: its checkbox is labeled "Enable Document
|
|
189
|
+
Request Replay" and is CHECKED when `disableRequestReplay` is `false`. Its field
|
|
190
|
+
hint, verbatim: *"When enabled (default), the main document HTTP request is
|
|
191
|
+
actually sent to get the response. When disabled, the expected request and
|
|
192
|
+
response are constructed locally without making the network call."*
|
|
193
|
+
|
|
194
|
+
**This does NOT gate `addResponseMiddleware` in general** — that fires for any
|
|
195
|
+
intercepted request regardless. What it actually controls is whether the CURRENT
|
|
196
|
+
PAGE'S OWN document/navigation request (the one that loads the page's HTML, not
|
|
197
|
+
a sub-resource or API call) is sent for real over the network, for the non-CDP
|
|
198
|
+
mechanisms. It only matters when the request you're proving *is* that document
|
|
199
|
+
request — that is, when the target value is embedded in the page's own HTML
|
|
200
|
+
rather than fetched by a separate API call.
|
|
201
|
+
|
|
202
|
+
- Document request genuinely sent → `addResponseMiddleware` sees it like any
|
|
203
|
+
other request. This is what you need to prove data straight out of the page's
|
|
204
|
+
HTML.
|
|
205
|
+
- Document request constructed locally → the interceptor never observes it
|
|
206
|
+
(every OTHER request is still intercepted normally). This is a **per-site
|
|
207
|
+
compatibility escape hatch**: some sites misbehave when their top-level
|
|
208
|
+
navigation is pulled through a JS-level interceptor. Not a privacy or
|
|
209
|
+
architecture choice.
|
|
210
|
+
- **`CDP` is exempt from all of it** — it intercepts at the browser level, so
|
|
211
|
+
the document request is never a special case and this flag is inert.
|
|
212
|
+
|
|
213
|
+
## Recognizing this on a provider you didn't author
|
|
214
|
+
|
|
215
|
+
`get_provider_info` (old-devtools mode; reads the public SDK backend) returns
|
|
216
|
+
the raw `providerConfig`. If `injectionType`/
|
|
217
|
+
`interceptorType` is set to something other than the inert default AND the
|
|
218
|
+
`customInjection`/`jsUserScripts` calls `reclaimInterceptor.addResponseMiddleware`
|
|
219
|
+
(or references `MSWJS`/`XHook`), that provider is on the live-interception path,
|
|
220
|
+
not the standard replay flow — read the script before carrying it forward or
|
|
221
|
+
editing around it.
|
|
222
|
+
|
|
223
|
+
If, on top of that, the request being proven IS the login/landing page itself
|
|
224
|
+
(its `url` matches `loginUrl`, so it's the document request) and the mechanism
|
|
225
|
+
isn't `CDP`, check the document-replay flag too: a value that routes it around
|
|
226
|
+
live interception was a deliberate site-compatibility decision, not an
|
|
227
|
+
oversight.
|
|
228
|
+
|
|
229
|
+
## Related topics
|
|
230
|
+
|
|
231
|
+
- `user-script` — what the script itself does with intercepted traffic, and why
|
|
232
|
+
`window.Reclaim` is inert on Builder's own client.
|
|
233
|
+
- `hash-validation` — declaring script-fired requests without breaking
|
|
234
|
+
`verifyProof({ providerId })`.
|
|
235
|
+
- `browser` — the modes with a live view (`container`, `builder`), for handing
|
|
236
|
+
a stuck sign-in to a human.
|
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
Keep `appSecret` on your BACKEND only. Everything below runs server-side;
|
|
4
4
|
the browser only ever sees the `requestUrl`.
|
|
5
5
|
|
|
6
|
+
## Contents
|
|
7
|
+
|
|
8
|
+
- Testing locally, without deploying anything
|
|
9
|
+
- Steps 1-4: install, create the proof request, open it, verify the callback
|
|
10
|
+
- Gotchas
|
|
11
|
+
|
|
6
12
|
## Testing locally, without deploying anything
|
|
7
13
|
|
|
8
14
|
- **providerId `"example"`**: a canonical built-in smoke-test provider —
|
|
9
|
-
no need to author
|
|
15
|
+
no need to author and publish your own provider to confirm the SDK
|
|
10
16
|
plumbing works end-to-end. Swap in your real providerId once this passes.
|
|
11
17
|
- **A public callback URL**: `setAppCallbackUrl` needs a URL Reclaim's
|
|
12
18
|
servers can reach over the public internet — `localhost` will never
|
|
@@ -15,9 +21,9 @@ the browser only ever sees the `requestUrl`.
|
|
|
15
21
|
alongside your local server, then set `PUBLIC_URL` to the
|
|
16
22
|
`https://<id>.ngrok-free.app` URL it prints. Zero-deploy, but the tunnel
|
|
17
23
|
URL is temporary.
|
|
18
|
-
- **A
|
|
19
|
-
one reachable from the internet,
|
|
20
|
-
skips the extra local tool.
|
|
24
|
+
- **A development or staging server you can deploy to quickly**: if you
|
|
25
|
+
already have one reachable from the internet, point `PUBLIC_URL` at it
|
|
26
|
+
instead — that skips the extra local tool.
|
|
21
27
|
Without either, only the frontend-side flow (opening `requestUrl` and
|
|
22
28
|
reading the result client-side) works — the async webhook never lands.
|
|
23
29
|
- `scaffold_reclaim_demo_server` defaults `providerId` to `"example"`; its
|
|
@@ -63,9 +69,9 @@ reclaimProofRequest.setContext(
|
|
|
63
69
|
const requestUrl = await reclaimProofRequest.getRequestUrl();
|
|
64
70
|
|
|
65
71
|
// Hand the requestUrl to the frontend to open in a new tab/window/webview.
|
|
66
|
-
// If you need to rehydrate this request object later (
|
|
67
|
-
// handler, in a different process), persist
|
|
68
|
-
// (sync) and rehydrate with:
|
|
72
|
+
// If you need to rehydrate this request object later (for example, in the
|
|
73
|
+
// webhook handler, in a different process), persist
|
|
74
|
+
// reclaimProofRequest.toJsonString() (sync) and rehydrate with:
|
|
69
75
|
// const req = await ReclaimProofRequest.fromJsonString(json); // ASYNC
|
|
70
76
|
```
|
|
71
77
|
|
package/skill/old-mode.md
CHANGED
|
@@ -1,21 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Reclaim provider-authoring tools, running against the OLD DEVTOOLS backend —
|
|
2
|
+
intentionally reduced: no generated API tools, no organizations, no public
|
|
3
|
+
review flow. Only GET/POST requests can be captured into providers.
|
|
4
|
+
|
|
5
|
+
**A `provider` is a recipe for extracting a data point from a site. A
|
|
6
|
+
`verification` is an end-user session that proves a data point using one. Never
|
|
7
|
+
call a provider a "verification."**
|
|
8
|
+
|
|
9
|
+
**Read the guide before authoring.** `how_it_works({ topic })` serves it, one
|
|
10
|
+
topic at a time — call `how_it_works({})` for the topic index. Topics:
|
|
11
|
+
`overview`, `auth`, `browser`, `capture`, `draft`, `params`, `prove`, `publish`,
|
|
12
|
+
`user-script`, `interception`, `hash-validation`, `credentials`, `never-do`,
|
|
13
|
+
`troubleshooting`.
|
|
14
|
+
|
|
15
|
+
**Ask who is doing the signing in, then offer a browser.** "Yourself, or does
|
|
16
|
+
someone else need to log in?" decides half of `attach_browser`'s list. Doing it
|
|
17
|
+
themselves → `dedicated` or `attach`, both local, neither shareable. Someone
|
|
18
|
+
else, or a phone-only site → `container` (the Reclaim runtime in Docker, free,
|
|
19
|
+
shareable with `share_browser_view`) or `builder` (hosted, no install,
|
|
20
|
+
**chargeable**). Say which costs money, and say `container` exists at all:
|
|
21
|
+
nobody guesses it.
|
|
22
|
+
|
|
23
|
+
**The flow:** `reclaim_authenticate` once → `attach_browser` (ask the developer
|
|
24
|
+
which browser source — local Chrome, the Reclaim runtime in Docker, a remote
|
|
25
|
+
Builder browser, or a CDP URL they supply) → `start_capture` ⇒ **`captureId`** →
|
|
26
|
+
drive the page (`navigate`,
|
|
27
|
+
`wait_for_page`, `eval_in_page`) → inspect (`list_requests`, `get_request`,
|
|
28
|
+
`find_requests_containing`) → `propose_provider` ⇒ **`draftId`** →
|
|
29
|
+
`replay_request(captureId, draftId)` → `run_proof(captureId, draftId,
|
|
30
|
+
ownerAddress)` → `create_provider_version_from_capture`.
|
|
31
|
+
|
|
32
|
+
**Publishing and ownership:** with NO `providerId`, publish REGISTERS A NEW
|
|
33
|
+
provider — re-running creates a duplicate. Pass a `providerId` from
|
|
34
|
+
`get_me_providers` (the only provider-listing tool here) to add a version to an
|
|
35
|
+
existing provider instead; you can only version a provider you own. For a
|
|
36
|
+
multi-request provider pass `providers: [draftA, draftB, …]` so each becomes a
|
|
37
|
+
parallel `requestData` entry on the one version.
|
|
38
|
+
|
|
39
|
+
**Two independent auth systems — don't confuse them.** `reclaim_authenticate`
|
|
40
|
+
authorizes everything in this mode; its Firebase token is short-lived (~1h), so
|
|
41
|
+
an auth error on publish or `get_me_providers` means re-run it.
|
|
42
|
+
`authenticate_builder` covers only two Builder-exclusive features: allocating a
|
|
43
|
+
remote browser (`attach_browser mode="builder"`) and
|
|
44
|
+
`list_builder_organizations`. It authorizes nothing else. `get_devtools_mode`
|
|
45
|
+
reports both.
|
|
46
|
+
|
|
47
|
+
**Never prove a public endpoint.** After `replay_request` succeeds, re-run it
|
|
48
|
+
with `withoutSecrets: true`. If the value still extracts, the endpoint is public
|
|
49
|
+
and the proof binds to nobody — find an auth-bound request instead.
|
|
50
|
+
|
|
51
|
+
**Templating:** bare `{{name}}` placeholders are extracted from the response.
|
|
52
|
+
Values the CONSUMER supplies at verification time use `{{context_<name>}}` —
|
|
53
|
+
this backend does NOT support the builder's dotted `{{context.<name>}}` form.
|
|
54
|
+
|
|
55
|
+
**Diagnosing a session:** `session_analytics_logs` fetches a verification
|
|
56
|
+
session's analytics logs by id (matches, claims, failures).
|
|
57
|
+
|
|
58
|
+
**Backend mode:** the BUILDER backend (generated API tools, orgs, verification
|
|
59
|
+
sessions, public review) is one switch away. `get_devtools_mode` reports the
|
|
60
|
+
active mode and why; `set_devtools_mode` persists a switch to
|
|
61
|
+
`~/.reclaim/config.json`, effective on the NEXT server start. An explicit
|
|
62
|
+
`USE_OLD_DEVTOOLS` env var wins over the persisted choice.
|