requestshield 0.0.0-stage → 1.0.0-stg.276

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 (54) hide show
  1. package/README.md +292 -2
  2. package/config/.env.stg +8 -0
  3. package/package.json +25 -4
  4. package/skills/requestshield/SKILL.md +240 -0
  5. package/skills/requestshield/assets/AGENTS.codex.md +107 -0
  6. package/skills/requestshield/references/backend-java-core.md +152 -0
  7. package/skills/requestshield/references/backend-spring-boot.md +158 -0
  8. package/skills/requestshield/references/browser-manual.md +244 -0
  9. package/skills/requestshield/references/browser-seamless.md +213 -0
  10. package/skills/requestshield/references/cli.md +314 -0
  11. package/skills/requestshield/references/integration-planning.md +357 -0
  12. package/skills/requestshield/references/troubleshooting.md +131 -0
  13. package/src/agent-detector.mjs +102 -0
  14. package/src/api-client.mjs +155 -0
  15. package/src/args.mjs +147 -0
  16. package/src/browser-opener.mjs +32 -0
  17. package/src/cli.mjs +115 -0
  18. package/src/command-completion.mjs +10 -0
  19. package/src/command-registry.mjs +151 -0
  20. package/src/commands/agent-setup.mjs +195 -0
  21. package/src/commands/agent-status.mjs +65 -0
  22. package/src/commands/application-mutations.mjs +37 -0
  23. package/src/commands/application-response.mjs +55 -0
  24. package/src/commands/apps-get.mjs +28 -0
  25. package/src/commands/apps-list.mjs +93 -0
  26. package/src/commands/auth-status.mjs +42 -0
  27. package/src/commands/billing.mjs +106 -0
  28. package/src/commands/contract.mjs +59 -0
  29. package/src/commands/keys-create.mjs +22 -0
  30. package/src/commands/mutation-support.mjs +135 -0
  31. package/src/commands/output.mjs +26 -0
  32. package/src/commands/secret-commands.mjs +66 -0
  33. package/src/commands/secret-output.mjs +52 -0
  34. package/src/commands/service-status.mjs +34 -0
  35. package/src/commands/signin.mjs +95 -0
  36. package/src/commands/signout.mjs +14 -0
  37. package/src/commands/update-check.mjs +205 -0
  38. package/src/commands/usage.mjs +72 -0
  39. package/src/config.mjs +200 -0
  40. package/src/entrypoint.mjs +44 -0
  41. package/src/errors.mjs +16 -0
  42. package/src/integration-contract-client.mjs +85 -0
  43. package/src/integration-contract.mjs +114 -0
  44. package/src/main.mjs +6 -0
  45. package/src/oauth-client.mjs +153 -0
  46. package/src/oauth-loopback.mjs +120 -0
  47. package/src/package-profile.mjs +24 -0
  48. package/src/private-path.mjs +33 -0
  49. package/src/secret-output.mjs +121 -0
  50. package/src/service-status-client.mjs +87 -0
  51. package/src/session-files.mjs +192 -0
  52. package/src/session-store.mjs +204 -0
  53. package/src/terminal.mjs +143 -0
  54. package/src/usage-time.mjs +26 -0
@@ -0,0 +1,357 @@
1
+ # Integration planning
2
+
3
+ Use this reference for sign-in when required by the calling workflow and before
4
+ writing or removing RequestShield integration code. It owns the planning steps that
5
+ are too detailed for `SKILL.md`.
6
+
7
+ ## Workflow by operation
8
+
9
+ Follow [SKILL.md — Classify the request](../SKILL.md#classify-the-request).
10
+ Use only the planning steps required by that intent.
11
+
12
+ ## Sign-in
13
+
14
+ 1. Run `requestshield auth status --json`. Status confirms the local session,
15
+ not online access.
16
+ - `valid`: return to the calling workflow; no new login is needed.
17
+ - `expired`: return to the calling workflow. The next authenticated command
18
+ attempts refresh automatically; expiry alone does not require browser sign-in.
19
+ Preserve that workflow's confirmation requirements before running its command.
20
+ - `configuration_error`: report the configuration failure and stop the dependent
21
+ action; browser sign-in does not fix CLI configuration.
22
+ - `signed_out`, `refresh_uncertain`, `config_mismatch`, or `invalid`: continue
23
+ with browser sign-in below.
24
+ If status fails or reports an unknown state, report the failure and follow the
25
+ CLI's recovery guidance rather than assuming login is required.
26
+ 2. When browser sign-in is required, explain that the CLI will start sign-in while the user completes the
27
+ browser flow. Ask for fresh confirmation through the host's interactive question
28
+ tool with **Yes** and **No**. Wait for an explicit **Yes**. If no permitted tool is
29
+ available, pause this step. Silence or a preselected option is not approval.
30
+ 3. Run `requestshield login` in an interactive PTY and keep it alive while the user
31
+ completes authentication in the browser. If an interactive PTY is unavailable,
32
+ ask the user to run `requestshield login` in their own terminal. Do not emulate
33
+ login or call authentication APIs manually.
34
+ 4. After login completes, run `requestshield auth status --json` again. Continue the
35
+ calling workflow only when it reports `state: "valid"`. Otherwise report the
36
+ failure and stop the dependent action. A browser launch, redirect, or user
37
+ confirmation alone does not establish sign-in.
38
+
39
+ If the calling workflow's authenticated command fails, follow the CLI's recovery
40
+ guidance. A sign-in-required error returns to step 2. A retryable refresh failure
41
+ (such as `SESSION_REFRESH_FAILED`) does not itself require browser sign-in; report
42
+ the failure and preserve the command's retry rules. Never blindly retry a mutation
43
+ whose result is uncertain; use the existing idempotent-retry guidance in
44
+ [the CLI reference](cli.md#key-and-secret-management).
45
+
46
+ Never request, read, copy, or enter passwords, passkeys, MFA codes, authentication
47
+ tokens, sign-in URLs, or callback URLs on the user's behalf.
48
+
49
+ ## Discover and select protected endpoints
50
+
51
+ This is the first Install planning step unless the user already supplied an explicit,
52
+ intentionally narrow set of exact HTTP method/path pairs. Discovery is read-only and
53
+ does not authorize integration changes.
54
+
55
+ ### Build the candidate inventory
56
+
57
+ Scan application source, excluding generated, vendored, dependency, fixture, and build
58
+ files. Find externally reachable browser operations that mutate state, authenticate,
59
+ expose abuse-sensitive capabilities, or trigger meaningful cost. Inspect browser call
60
+ sites, backend routes/handlers, actual effects, and existing protection.
61
+
62
+ Use focused framework-appropriate searches, starting with filenames. For example:
63
+
64
+ ```bash
65
+ rg -l "fetch\(|axios\.|XMLHttpRequest|\.request\(|<form|action=" \
66
+ -g "*.html" -g "*.js" -g "*.jsx" -g "*.ts" -g "*.tsx" \
67
+ -g "*.vue" -g "*.svelte" -g "!node_modules/**" -g "!**/build/**" .
68
+
69
+ rg -l "@(Post|Put|Patch|Delete|Request)Mapping|@(POST|PUT|PATCH|DELETE)|RouterFunctions|route\(" \
70
+ -g "*.java" -g "*.kt" -g "!**/build/**" -g "!**/target/**" .
71
+ ```
72
+
73
+ Resolve base URLs, route prefixes, parameters, and HTTP verbs to the exact method/path
74
+ sent by the browser. Pair each operation with its browser call site and backend handler.
75
+ Mark unresolved ownership or missing pairs **Needs clarification**, not install-ready.
76
+ Inspect relevant logic using the redaction rules in **Detect existing integration**;
77
+ never dump configuration or secret files.
78
+
79
+ Exclude health/metrics, static reads, internal jobs, and non-browser operations that
80
+ cannot receive a browser token. Judge actual behavior: an expensive GET may need
81
+ protection, while a POST is not automatically sensitive.
82
+
83
+ ### Rank candidates
84
+
85
+ Rank install-ready candidates using these factors in order:
86
+
87
+ 1. Business/security impact: account recovery, payments, privilege changes, destruction.
88
+ 2. Automation attractiveness: sign-in, registration, resets, checkout, submissions.
89
+ 3. Exposure and existing controls: public operations without equivalent protection.
90
+ 4. Operational cost and volume: messages, paid services, scarce resources, amplification.
91
+ 5. Trace confidence and supportability: exact browser/handler pairs and supported transport.
92
+
93
+ Use `Critical`, `High`, `Medium`, or `Low` with a concrete reason, not an invented
94
+ numeric score. Mark complete integrations **Already protected**; partial integrations
95
+ remain selectable and must identify the missing half.
96
+
97
+ ### Present a multi-select and wait
98
+
99
+ Present all actionable candidates using
100
+ [Interactive questions](../SKILL.md#interactive-questions). Each option shows priority,
101
+ exact HTTP method/path, reason, and current RequestShield state. Keep browser call-site
102
+ and backend-handler details available when useful. For Uninstall, omit priority and
103
+ use the protected-operation inventory specified in the skill.
104
+
105
+ Wait for the submitted selection, preserve its exact method/path set, and edit only
106
+ selected operations. Use the skill's Yes/No fallback when multi-select is unavailable.
107
+
108
+ ### Process the selected set
109
+
110
+ Apply the remaining planning and installation steps from `SKILL.md` to each selected
111
+ operation. Reuse contract metadata, scripts, and configuration only for compatible
112
+ operations in the same application; never combine evidence from different owners.
113
+ Group mode questions only when transport, backend owner, capabilities, and intended
114
+ choice match.
115
+
116
+ Add missing protection, repair only missing halves, and preserve complete integrations
117
+ without duplicate scripts or verification paths. Validate every selected operation.
118
+ Report unselected recommendations as opportunities, not incomplete authorized work.
119
+
120
+ ## Detect existing integration
121
+
122
+ Record each SDK's version and API separately from Seamless/Manual modes.
123
+ Preserve existing pins, API choices, credentials, and service base URLs.
124
+ Browser/Java 1.0.0 integrations use v1; newer documentation does not change
125
+ their browser, CSP, or Java requirements.
126
+
127
+ | SDK | API version |
128
+ | --- | --- |
129
+ | Browser 1.0.0 | v1, without an API-version option. |
130
+ | Browser 2.0.0 | v2 by default; `apiVersion: 'v1' \| 'v2'` or `data-api-version` selects it. |
131
+ | Java 1.0.0 | v1, without an API-version option. |
132
+ | Java 2.0.0 core and Spring Boot starter | v2 only; no builder or Spring API-version option. |
133
+
134
+ For new integrations, prefer matching browser and backend API versions. Mixed
135
+ versions can verify compatible tokens, and a token remains single-use across
136
+ both APIs. Backend availability checks observe only the API used by its SDK.
137
+ If the browser's other API cannot issue a token while the backend's API is
138
+ healthy, the backend can reject with `missing_token`. Check this behavior when
139
+ validating a mixed-version integration. Do not verify a token twice or add
140
+ automatic fallback from v2 to v1. Both APIs are available to active App Keys;
141
+ selecting v2 does not prevent another caller from obtaining a v1 challenge.
142
+
143
+ Search for existing RequestShield code before install, verify/troubleshoot, or uninstall. Use `rg` when available. Use `rg -l` for filename-only discovery.
144
+
145
+ Browser markers:
146
+
147
+ ```bash
148
+ rg -l "data-app-key|data-protect|IntelliFend|X-IntelliFend-Token" \
149
+ -g "*.html" -g "*.htm" -g "*.js" -g "*.jsx" -g "*.ts" -g "*.tsx" \
150
+ -g "*.vue" -g "*.svelte" -g "*.astro" \
151
+ -g "*.erb" -g "*.haml" -g "*.php" -g "*.jinja*" -g "*.j2" -g "*.twig" \
152
+ -g "*.hbs" -g "*.ejs" -g "*.pug" -g "*.blade.php" \
153
+ -g "!node_modules/**" -g "!**/skills/requestshield/**" -g "!**/.requestshield/**" .
154
+ ```
155
+
156
+ Include server-rendered layouts and any template extensions used by the application;
157
+ a scan limited to `.html`/`.js` can miss an existing SDK tag.
158
+
159
+ Backend markers:
160
+
161
+ ```bash
162
+ rg -l "RequestShieldClient|RequestShieldProtected|intellifend|requestshield" \
163
+ -g "*.java" -g "*.kt" -g "*.xml" -g "*.yml" -g "*.yaml" -g "*.properties" \
164
+ -g "*.gradle" -g "*.gradle.kts" \
165
+ -g "!**/skills/requestshield/**" -g "!**/.requestshield/**" .
166
+ ```
167
+
168
+ Inspect marker locations without exposing surrounding values:
169
+
170
+ ```bash
171
+ rg -n -o \
172
+ "data-app-key|data-protect|IntelliFend|getToken|X-IntelliFend-Token" \
173
+ <browser-candidate-file>
174
+
175
+ rg -n -o \
176
+ "RequestShieldClient|RequestShieldProtected|verify|isAllowed|intellifend|requestshield" \
177
+ <backend-candidate-file>
178
+
179
+ rg -n -o \
180
+ "(INTELLIFEND_)?REQUESTSHIELD_(APP_KEY|KEY|API_SECRET|SECRET)" \
181
+ <candidate-configuration-file>
182
+ ```
183
+ `-o` returns only marker or variable names. For context, use a secret-aware parser or
184
+ redacted view before output reaches the agent. Allow filenames, line numbers, config
185
+ keys, variable names, public App Keys when needed, and reference/literal classification.
186
+ Mask API Secrets, tokens, sessions, CSRF, authorization values, and other credentials.
187
+ Never follow filename discovery with unredacted `cat`, `sed`, full matching lines, or
188
+ configuration dumps. If safe inspection is unavailable, request a sanitized excerpt.
189
+ Exclude installed skill/reference directories so their examples do not count as an
190
+ application integration.
191
+
192
+ Report one of:
193
+
194
+ - **Not integrated**: no browser or backend markers.
195
+ - **Browser only**: tokens may be obtained or attached, but no backend enforcement was
196
+ found. This looks protected and is not.
197
+ - **Backend only**: enforcement exists, but no page sends a token. In BLOCK mode this can
198
+ reject legitimate users.
199
+ - **Fully integrated**: frontend and backend integration modes are both present. Name
200
+ both, including the backend mechanism. A supported mixed pair is fully integrated;
201
+ it does not require migration merely to make the mode names match.
202
+
203
+ Update an existing SDK tag rather than adding another: configuration from
204
+ `document.currentScript` is replaced, not merged, by a second tag.
205
+
206
+ ## Contract check
207
+
208
+ Run `requestshield contract --json` with the runner for the intended environment.
209
+ It requires no login. Use the returned URLs unchanged; never derive a versioned URL.
210
+ Confirm:
211
+
212
+ - Browser: `scriptUrlVersion`, `scriptUrlLatest`, matching integrity, `tokenHeader`,
213
+ `availableModes`, `cspAdditions`, and `sdkVersion` under `data.browser`.
214
+ - Service: `services.challengeUrl`.
215
+ - Backend: `sdkVersion`, `minJdk`, `mavenRepository`, `core`, and `springBoot3` coordinates.
216
+ - The three release documentation links.
217
+
218
+ Stop if the command or manifest is unavailable; do not fabricate values or substitute
219
+ an environment/release. Pinned browser metadata describes `scriptUrlVersion`, not
220
+ `scriptUrlLatest`. Use documentation for the selected release; never apply 2.x guidance
221
+ to a pinned 1.x release or infer latest capabilities from pinned metadata.
222
+
223
+ `browser.availableModes` describes frontend modes only. Establish backend capabilities
224
+ from SDK coordinates, release documentation, and the application stack. Preserve the
225
+ contract's JSON fields and apply mode preferences under **Choosing the modes**.
226
+
227
+ For a new Install without a selected supported URL, ask for `scriptUrlVersion` or
228
+ `scriptUrlLatest` using **Interactive questions** in `SKILL.md`. Preserve an existing
229
+ supported URL unless the user requests a change or the contract requires correction.
230
+ Do not query applications or credentials for this choice.
231
+
232
+ Use the chosen URL unchanged. Attach the contract's versioned integrity only to that
233
+ immutable URL; `latest` requires its own matching digest if SRI is used.
234
+
235
+ ## Gate
236
+
237
+ Establish capabilities before editing; choose modes afterwards under **Choosing the
238
+ modes**. A non-Spring-MVC backend limits backend Seamless, not frontend Seamless.
239
+
240
+ | Layer / mode | Requirement | Reference |
241
+ | --- | --- | --- |
242
+ | Backend Seamless | Spring Boot 3 MVC, Java 17+, released starter | `backend-spring-boot.md` |
243
+ | Backend Manual | Java 17+, released core SDK, application extraction/enforcement; also works on Boot 3 MVC | `backend-java-core.md` |
244
+ | Frontend Seamless | Released support and page-scope `fetch` / async XHR interception | `browser-seamless.md` |
245
+ | Frontend Manual | Released token acquisition and supported application-controlled carrier | `browser-manual.md` |
246
+
247
+ Both backend modes accept `X-IntelliFend-Token`; a documented body carrier requires
248
+ backend Manual extraction. Manual mode does not make unsupported transport valid.
249
+ Check the selected browser reference's coverage and carrier requirements.
250
+
251
+ ### Run the check
252
+
253
+ Locate the route's owning module, then inspect its build file. Repo-wide discovery
254
+ may find candidates, but never combine a route from one module with Java/Spring evidence
255
+ from another. If multiple services could own the endpoint, ask which one before editing.
256
+
257
+ ```bash
258
+ rg --files -g 'pom.xml' -g 'build.gradle*' -g '!**/node_modules/**' \
259
+ -g '!**/build/**' -g '!**/target/**'
260
+ rg -n "/api/register" -g "*.java" -g "*.kt" .
261
+ ```
262
+
263
+ #### Java target version
264
+
265
+ Read the project's target, not the machine's JDK:
266
+
267
+ - Maven: `maven.compiler.release`, compiler-plugin `<release>`, or inherited
268
+ Spring Boot `<java.version>`; inspect the parent POM where needed.
269
+ - Gradle: `java.toolchain.languageVersion`, `sourceCompatibility`, or Kotlin
270
+ `jvmToolchain`, including nested configuration blocks.
271
+ - `.java-version`, `.sdkmanrc`, `.tool-versions`, CI JDK settings, and Docker base
272
+ images are hints only. If build evidence is missing, ask; `java -version` does not
273
+ establish the project's target.
274
+
275
+ Java below 17 blocks both backend modes. Report the source and prerequisite upgrade;
276
+ do not substitute an older SDK or install only the browser half.
277
+
278
+ #### Spring Boot 3, and separately Spring MVC
279
+
280
+ Check Boot version in the parent POM, dependency BOM, or Gradle plugin, and separately
281
+ identify the stack serving the route:
282
+
283
+ - `spring-boot-starter-web` / Spring MVC on Boot 3 and Java 17+ supports the starter.
284
+ - WebFlux without MVC does not support the annotation path; use backend Manual.
285
+ - If both stacks are present, determine which owns the route; ask if unclear.
286
+ - Boot 2.x and other Java frameworks use backend Manual when Java 17+ is established.
287
+
288
+ Report Java target, backend stack, available modes per layer, existing modes, and
289
+ blockers with source evidence before proceeding.
290
+
291
+ ## Then act on what you found
292
+
293
+ Use this evidence to establish backend capabilities; do not select or migrate a mode
294
+ merely because the other option is available.
295
+
296
+ | Detected | Do this |
297
+ | --- | --- |
298
+ | Java < 17, or no Java backend | Stop before editing. Report the finding and its source; neither backend mode is supported. Name a supported Java backend as the prerequisite. |
299
+ | Java 17+, no Spring Boot 3 MVC | Backend Manual is the only option. State the capability limit; do not present backend Seamless as a choice. |
300
+ | Java 17+ and Spring Boot 3 MVC | Both backend modes are viable when their artifacts are released. Apply the selection rules below. |
301
+ | Ambiguous module, missing build file, version only in environment hints, or unclear MVC/WebFlux ownership | Ask, showing what you found. |
302
+
303
+ Report file/line evidence for the owning module, Java target, framework, and available
304
+ modes. Treat unknown values as clarification needs, not inferred compatibility.
305
+
306
+ ## Choosing the modes
307
+
308
+ Choose one supported mode per layer using the capabilities above and the selected
309
+ browser reference's transport/carrier limits. Apply these priorities in order:
310
+
311
+ 1. **Explicit preferences:** honor supported choices for each named layer. Explain
312
+ blockers and alternatives for unsupported choices; do not silently substitute.
313
+ 2. **Existing integration:** preserve supported modes, including mixed pairs, unless
314
+ the user requests migration or evidence requires correction. Verify/docs tasks
315
+ observe modes rather than reopen selection. During migration, replace the old path
316
+ with the new one in the same change and release; preserve the other layer.
317
+ 3. **One layer unselected:** recommend the matching mode if supported; otherwise a
318
+ compatible mixed pair. Do not change the selected layer just to match names.
319
+ 4. **Both layers new:** recommend Seamless/Seamless when all release, stack, transport,
320
+ and carrier requirements hold; otherwise Manual/Manual when supported. If neither
321
+ pair works, explain the supported mixed pair or blocker.
322
+
323
+ Recommendations require the selection step below before implementation.
324
+
325
+ ### Ask and wait before integration edits
326
+
327
+ For Install, ask about unselected layers using
328
+ [Interactive questions](../SKILL.md#interactive-questions). Present supported modes,
329
+ their differences, and the recommendation with its reason; even a single supported
330
+ option needs confirmation. Wait before integration edits, dependencies, or runtime
331
+ configuration changes; independent read-only discovery may continue.
332
+
333
+ An explicit supported preference already selects that layer. Preserve existing
334
+ supported modes. A general request to protect an endpoint, a recommendation, or silence
335
+ is not a choice. Do not repeat questions already answered.
336
+
337
+ The mode and script-URL questions may be combined, but each choice needs an answer;
338
+ choosing one does not choose the other.
339
+
340
+ Examples (frontend / backend):
341
+
342
+ | Situation | Recommendation |
343
+ | --- | --- |
344
+ | New Spring Boot 3 MVC + Java 17+, released Seamless support, page-scope `fetch` | Seamless / Seamless |
345
+ | New Java 17+ non-Boot or WebFlux backend; both layers otherwise unselected | Manual / Manual |
346
+ | User requests frontend Manual on Boot 3 MVC; backend not integrated | Manual / Manual |
347
+ | User requests frontend Seamless on a supported browser transport; backend is non-Boot Java 17+ | Seamless / Manual |
348
+ | Frontend Manual already paired with backend Seamless | Preserve Manual / Seamless |
349
+ | Frontend Seamless already paired with backend Manual | Preserve Seamless / Manual |
350
+
351
+ Different endpoints may use different pairs. A protected operation uses exactly one
352
+ frontend mode and one backend mode. Never combine `@RequestShieldProtected` and a
353
+ direct `RequestShieldClient.verify()` on one request: both consume the token, so the
354
+ second returns `token_replayed`. For the starter, state `BLOCK` or `MONITOR` separately
355
+ as the enforcement mode; integration-mode names are not values for that setting. For
356
+ direct core integration, describe the application's observed handling of `isAllowed()`
357
+ without inventing a starter-style configuration property.
@@ -0,0 +1,131 @@
1
+ # Troubleshooting
2
+
3
+ Work outward from the request, not from the code. Most reports of "RequestShield is
4
+ broken" are one of four things: the browser never sent a token, the configured carrier
5
+ never reached verification, the backend never verified it, or one token was verified twice.
6
+
7
+ Name the observed frontend and backend integration modes separately: each can be
8
+ Seamless or Manual where supported. Backend Seamless uses the Spring Boot 3 MVC starter;
9
+ backend Manual uses explicit core SDK verification. A mixed pair alone is not a fault
10
+ or a reason to migrate. Keep integration modes distinct from `BLOCK`/`MONITOR`
11
+ enforcement behavior.
12
+
13
+ ## First checks
14
+
15
+ Start with source inspection and runtime evidence already supplied by the user.
16
+ Browser requests and safe backend decision metrics provide evidence about runtime behavior;
17
+ collect new endpoint traffic only through the explicit Test flow.
18
+
19
+ For HTTP liveness, run `requestshield service status --json` without login or an
20
+ App Key. It checks Management API and Challenge Service at the caller's location
21
+ and time; it does not verify application protection or application integration.
22
+ Credential-status metadata remains unavailable; do not reveal a secret to check it.
23
+
24
+ If supplied evidence points to rejected backend credentials, explain the likely
25
+ configuration issue without reading secret values.
26
+ Credential management is a separate request.
27
+
28
+ ## Reason codes
29
+
30
+ `result.getReason().getCode()` returns a stable code, safe to log. Use codes to explain
31
+ a decision, never to amend one.
32
+
33
+ The table below is a working list, not the authoritative one — the customer
34
+ documentation owns that. Check a code against it before quoting a meaning to the user,
35
+ and treat a code that is not listed here as unrecognised rather than guessing from its
36
+ name.
37
+
38
+ | Code | Means | Usual cause |
39
+ | --- | --- | --- |
40
+ | `ok` | Verified and consumed | — |
41
+ | `missing_token` | No token reached backend verification | Script tag absent or loaded after the request; frontend Seamless path mismatch (case, trailing slash, wrong origin); an uncovered transport (form POST, sendBeacon, sync XHR, `no-cors`); a frontend Manual call site that never populates the configured carrier; backend extraction reading a different carrier; or CSP missing a `cspAdditions` entry, such as `workerSrc` or `'wasm-unsafe-eval'`, so `getToken()` returns empty. |
42
+ | `unknown_customer` | App Key not recognised | Typo, deactivated key, or browser and backend configured with different App Keys. |
43
+ | `token_tampered` | Token failed integrity checks | Something modified the value in transit — a proxy rewriting headers, or application code trimming or re-encoding it. Pass the value through unchanged. |
44
+ | `token_expired` | Token too old | The page held a token instead of obtaining a fresh one per request, or a very slow round trip. |
45
+ | `token_replayed` | Already consumed | The same token verified twice: a retry reusing the first token, or two verification paths on one request (`@RequestShieldProtected` plus a direct `verify()`, or a filter plus a controller). |
46
+ | `consume_unauthorized` | Backend credentials rejected | API Secret wrong, rotated, or not injected — explain the likely configuration issue from supplied evidence without reading the value. |
47
+ | `invalid_request` | Malformed verification request | Usually a mis-set `challenge-server-url`. |
48
+ | `not_found` | Token unknown to the platform | Cross-environment mismatch — a staging page's token verified against production, or the reverse. |
49
+ | `service_unavailable` | Platform unreachable | Check backend connectivity and safe health metrics. See degraded behaviour below. |
50
+
51
+ ## "It blocks everything"
52
+
53
+ Almost always `missing_token` or `unknown_customer`. Confirm in this order — each step is
54
+ cheap and rules out a whole class:
55
+
56
+ 1. Identify the carrier the backend actually reads: `X-IntelliFend-Token`, or a
57
+ documented dedicated body field for frontend Manual with backend Manual. Check
58
+ that carrier's presence and whether it is non-empty in the protected request;
59
+ do not record the token value. An absent header is expected for a body carrier.
60
+ 2. **Token absent from the configured carrier** → frontend. Seamless: compare the requested URL against
61
+ the exact origin and `data-protect` path segments; each `:name` must match one non-empty
62
+ segment, and literal case and trailing slash are significant. Also
63
+ check the transport is `fetch` or async XHR from page scope. Manual: find the call
64
+ site, and every retry path.
65
+ 3. **Header transport fails only across origins** → check CORS and preflight. The API's
66
+ `Access-Control-Allow-Headers` must include `X-IntelliFend-Token` when that header
67
+ is used, or the browser can block the request. This token-header requirement does
68
+ not apply to a body carrier; check CORS against that request's actual headers.
69
+ 4. **Configured carrier empty** → check token acquisition. Check the CSP
70
+ includes every `cspAdditions` entry from `requestshield contract --json`,
71
+ then check connectivity and safe backend health observations. For frontend Manual
72
+ using the header, an empty token means the header should be omitted, not sent empty
73
+ — see `browser-manual.md`.
74
+ 5. **Configured carrier populated** → backend half. Confirm extraction reads that
75
+ carrier unchanged, compare the App Key on both sides, then confirm runtime secret
76
+ injection by presence only.
77
+
78
+ ## "It blocks nothing"
79
+
80
+ Enumerate rather than assuming the browser is at fault:
81
+
82
+ - The endpoint uses `MONITOR` enforcement — evaluating and always continuing. Protection-shaped,
83
+ not protection.
84
+ - The decision is computed but only logged, never enforced.
85
+ - Verification happens after the mutation has already been performed.
86
+ - Application code overrides `isAllowed()` from a reason code — a separate decision table
87
+ in application code removes the protection.
88
+ - The backend never verifies at all: a browser-only install. Grep for the client or the
89
+ annotation before trusting a claim that the backend is wired up.
90
+ - Results are `isDegraded()` — the platform was unreachable and requests were allowed by
91
+ the availability policy.
92
+
93
+ ## Degraded results are allowed by design
94
+
95
+ When the Challenge Server cannot be reached, `verify()` returns a result that is
96
+ **allowed** and **degraded**. Availability is deliberately chosen over enforcement so a
97
+ platform outage cannot take down a customer's checkout flow.
98
+
99
+ Communicate the consequence clearly: during a degraded window, traffic passes without
100
+ real verification. Log the degraded rate as its own metric so it is visible, and check
101
+ backend connectivity and service health through established monitoring when it rises. Never "fix" a degraded result by overriding
102
+ `isAllowed()` — that converts a platform incident into a customer-facing outage.
103
+
104
+ ## Confirming traffic and enforcement
105
+
106
+ Troubleshooting reads evidence the user already has; exercising the endpoint belongs to
107
+ the explicit Test flow in `SKILL.md`. There, use browser network observations and
108
+ existing safe backend metrics in an authorized environment. Do not record raw tokens or secrets. `requestshield usage <app-key>`
109
+ provides approximate recent request totals, defaulting to 24 hours within seven-day
110
+ retention. Missing or delayed logs can hide traffic; a zero count does not prove
111
+ enforcement or lack of traffic. Application status is not a traffic measurement. Report static correctness,
112
+ observed requests and backend enforcement separately. Use the controlled negative
113
+ test in `SKILL.md` to demonstrate enforcement.
114
+
115
+ ## Secret exposure
116
+
117
+ If the API Secret appears in a repo, a log, a build artifact, or a chat transcript,
118
+ rotate it — deleting the line does not un-expose it. Follow the private-terminal
119
+ credential workflow in `SKILL.md`: explain the impact, warn that the replacement API
120
+ Secret is displayed only once, and obtain confirmation under
121
+ [Interactive questions](../SKILL.md#interactive-questions) before running:
122
+
123
+ ```bash
124
+ requestshield secret rotate <app-key> --idempotency-key <agent-generated-uuid> --yes
125
+ ```
126
+
127
+ Run that command only in a user-visible terminal whose output is unavailable to the
128
+ agent. The user stores the new secret privately and confirms only whether it was saved.
129
+ The App Key is unchanged, so no browser edit is needed. Rotation replaces the previous
130
+ secret, but changes may take a moment. A lost response can be recovered with explicit
131
+ reveal; an exposed secret requires rotation.
@@ -0,0 +1,102 @@
1
+ // @ts-check
2
+
3
+ import { constants } from "node:fs";
4
+ import { access } from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+
8
+ /**
9
+ * Detect supported agents from their executable or local configuration.
10
+ *
11
+ * @param {{ env?: NodeJS.ProcessEnv, homeDir?: string }} [options]
12
+ * @returns {Promise<Array<"codex" | "claude">>}
13
+ */
14
+ export async function detectAgents(options = {}) {
15
+
16
+ const env = options.env ??
17
+ process.env;
18
+
19
+ const homeDir = options.homeDir ??
20
+ os.homedir();
21
+
22
+ const codexHome = env.CODEX_HOME ??
23
+ path.join(homeDir, ".codex");
24
+
25
+ const codexConfigPath =
26
+ path.join(codexHome, "config.toml");
27
+
28
+ const claudeConfigPath =
29
+ path.join(homeDir, ".claude.json");
30
+
31
+ // Executables prove the CLI is on PATH. Configuration files cover desktop
32
+ // installs and agents that were previously configured but are not on PATH.
33
+ const [codexCommand, claudeCommand, codexConfig, claudeConfig, claudeState] =
34
+ await Promise.all([
35
+ commandExists("codex", env),
36
+ commandExists("claude", env),
37
+ pathExists(codexHome),
38
+ pathExists(path.join(homeDir, ".claude")),
39
+ pathExists(path.join(homeDir, ".claude.json")),
40
+ ]);
41
+
42
+ /** @type {Array<"codex" | "claude">} */
43
+ const detected = [];
44
+
45
+ if (codexCommand || codexConfig) {
46
+ detected.push("codex")
47
+ };
48
+
49
+ if (claudeCommand || claudeConfig || claudeState) {
50
+ detected.push("claude")
51
+ };
52
+ return detected;
53
+ }
54
+
55
+ /**
56
+ * Inspect PATH without starting the coding agent.
57
+ *
58
+ * @param {string} command
59
+ * @param {NodeJS.ProcessEnv} env
60
+ */
61
+ async function commandExists(command, env) {
62
+
63
+ const searchPath = env.PATH ??
64
+ env.Path ?? "";
65
+
66
+ const extensions =
67
+ process.platform === "win32"
68
+ ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM")
69
+ .split(";")
70
+ .filter(Boolean)
71
+ : [""];
72
+
73
+ for (const directory of searchPath.split(path.delimiter)) {
74
+ if (!directory) {
75
+ continue
76
+ };
77
+
78
+ for (const extension of extensions) {
79
+ try {
80
+ await access(
81
+ path.join(directory, `${command}${extension}`),
82
+ process.platform === "win32" ? constants.F_OK : constants.X_OK,
83
+ );
84
+
85
+ return true;
86
+ } catch {
87
+ // Continue through the remaining executable candidates.
88
+ }
89
+ }
90
+ }
91
+ return false;
92
+ }
93
+
94
+ /** @param {string} target */
95
+ async function pathExists(target) {
96
+ try {
97
+ await access(target);
98
+ return true;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }