requestshield 0.1.4 → 0.1.6

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 (37) hide show
  1. package/README.md +421 -85
  2. package/config/.env.prod +7 -0
  3. package/package.json +21 -12
  4. package/skills/requestshield/SKILL.md +299 -307
  5. package/skills/requestshield/assets/AGENTS.codex.md +62 -62
  6. package/skills/requestshield/references/backend-java-core.md +128 -128
  7. package/skills/requestshield/references/backend-spring-boot.md +145 -145
  8. package/skills/requestshield/references/browser-manual.md +210 -210
  9. package/skills/requestshield/references/browser-seamless.md +156 -164
  10. package/skills/requestshield/references/cli.md +107 -182
  11. package/skills/requestshield/references/integration-planning.md +362 -389
  12. package/skills/requestshield/references/troubleshooting.md +114 -118
  13. package/src/agent-detector.mjs +102 -74
  14. package/src/api-client.mjs +115 -79
  15. package/src/args.mjs +140 -80
  16. package/src/browser-opener.mjs +32 -0
  17. package/src/cli.mjs +277 -51
  18. package/src/commands/agent-setup.mjs +182 -185
  19. package/src/commands/application-mutations.mjs +33 -0
  20. package/src/commands/application-response.mjs +55 -0
  21. package/src/commands/apps-get.mjs +20 -0
  22. package/src/commands/apps-list.mjs +94 -0
  23. package/src/commands/auth-status.mjs +37 -0
  24. package/src/commands/keys-create.mjs +7 -38
  25. package/src/commands/mutation-support.mjs +110 -0
  26. package/src/commands/secret-commands.mjs +45 -0
  27. package/src/commands/signin.mjs +70 -57
  28. package/src/commands/signout.mjs +9 -0
  29. package/src/commands/update-check.mjs +12 -4
  30. package/src/config.mjs +150 -0
  31. package/src/entrypoint.mjs +24 -0
  32. package/src/errors.mjs +3 -1
  33. package/src/main.mjs +5 -24
  34. package/src/oauth-client.mjs +153 -0
  35. package/src/oauth-loopback.mjs +120 -0
  36. package/src/session-files.mjs +213 -0
  37. package/src/session-store.mjs +177 -64
@@ -1,118 +1,114 @@
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 attached a token, CORS stripped the
5
- header, the backend never verified it, or one token was verified twice.
6
-
7
- ## First three checks
8
-
9
- ```bash
10
- requestshield server # platform healthy or degraded?
11
- requestshield apps get <app-key> # status: ready (no traffic yet) or active?
12
- requestshield credentials status <app-key> # provisioned and active?
13
- ```
14
-
15
- `status: ready` means the platform has never seen traffic for that key — the integration
16
- is not wired up yet, so debugging backend enforcement logic is premature. Start at the
17
- browser.
18
-
19
- Run `credentials status` even when the pre-edit Credentials check in
20
- `integration-planning.md` passed during the install. That check greps the **local
21
- shell**; it says nothing about what the deployed backend actually receives, and a secret
22
- can be rotated, or missed by the deploy environment, after it passed. This is the check
23
- that answers `consume_unauthorized`.
24
-
25
- ## Reason codes
26
-
27
- `result.getReason().getCode()` returns a stable code, safe to log. Use codes to explain
28
- a decision, never to amend one.
29
-
30
- The table below is a working list, not the authoritative one the customer
31
- documentation owns that. Check a code against it before quoting a meaning to the user,
32
- and treat a code that is not listed here as unrecognised rather than guessing from its
33
- name.
34
-
35
- | Code | Means | Usual cause |
36
- | --- | --- | --- |
37
- | `ok` | Verified and consumed | |
38
- | `missing_token` | No token reached the backend | Script tag absent or loaded after the request; Seamless path mismatch (case, trailing slash, wrong origin); an uncovered transport (form POST, sendBeacon, sync XHR, `no-cors`); a Manual call site that never attaches the header; CORS not allowing `X-IntelliFend-Token`; or CSP missing `worker-src blob:` so `getToken()` returns empty. |
39
- | `unknown_customer` | App Key not recognised | Typo, deactivated key, or browser and backend configured with different App Keys. |
40
- | `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. |
41
- | `token_expired` | Token too old | The page held a token instead of obtaining a fresh one per request, or a very slow round trip. |
42
- | `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 manual `verify()`, or a filter plus a controller). |
43
- | `consume_unauthorized` | Backend credentials rejected | API Secret wrong, rotated, or not injected — check `credentials status` and the environment variable. |
44
- | `invalid_request` | Malformed verification request | Usually a mis-set `challenge-server-url`. |
45
- | `not_found` | Token unknown to the platform | Cross-environment mismatch a staging page's token verified against production, or the reverse. |
46
- | `service_unavailable` | Platform unreachable | Check `requestshield server`. See degraded behaviour below. |
47
-
48
- ## "It blocks everything"
49
-
50
- Almost always `missing_token` or `unknown_customer`. Confirm in this order — each step is
51
- cheap and rules out a whole class:
52
-
53
- 1. Open the network tab. Does the protected request carry `X-IntelliFend-Token` with a
54
- non-empty value?
55
- 2. **Header absent** browser half. Seamless: compare the requested URL against
56
- `data-protect` character for character case and trailing slash are significant — and
57
- check the transport is `fetch` or async XHR from page scope. Manual: find the call
58
- site, and every retry path.
59
- 3. **Header absent on a cross-origin request only** → CORS. The API's
60
- `Access-Control-Allow-Headers` must include `X-IntelliFend-Token`, or the browser
61
- strips it before it ever leaves.
62
- 4. **Header present but empty** → the SDK ran and could not obtain a token. Check the CSP
63
- includes `worker-src 'self' blob:` and that `connect-src` allows the challenge host,
64
- then check `requestshield server`. (In Manual mode an empty token means the header
65
- should be omitted, not sent empty see `browser-manual.md`.)
66
- 5. **Header present and populated** backend half. Compare the App Key on both sides
67
- character for character, then confirm the secret is actually injected at runtime.
68
-
69
- ## "It blocks nothing"
70
-
71
- Enumerate rather than assuming the browser is at fault:
72
-
73
- - The endpoint is in `MONITOR` mode — evaluating and always continuing. Protection-shaped,
74
- not protection.
75
- - The decision is computed but only logged, never enforced.
76
- - Verification happens after the mutation has already been performed.
77
- - Application code overrides `isAllowed()` from a reason code — a separate decision table
78
- in application code removes the protection.
79
- - The backend never verifies at all: a browser-only install. Grep for the client or the
80
- annotation before trusting a claim that the backend is wired up.
81
- - Results are `isDegraded()` — the platform was unreachable and requests were allowed by
82
- the availability policy.
83
-
84
- ## Degraded results are allowed by design
85
-
86
- When the Challenge Server cannot be reached, `verify()` returns a result that is
87
- **allowed** and **degraded**. Availability is deliberately chosen over enforcement so a
88
- platform outage cannot take down a customer's checkout flow.
89
-
90
- Communicate the consequence clearly: during a degraded window, traffic passes without
91
- real verification. Log the degraded rate as its own metric so it is visible, and check
92
- `requestshield server` when it rises. Never "fix" a degraded result by overriding
93
- `isAllowed()` — that converts a platform incident into a customer-facing outage.
94
-
95
- ## Confirming traffic reaches the platform
96
-
97
- ```bash
98
- requestshield challenge volume <app-key> --from 2026-08-01T00:00:00Z \
99
- --to 2026-08-27T23:59:59Z --granularity day
100
- ```
101
-
102
- Challenge volume is runtime evidence that the browser/platform half is active. It does
103
- not prove that the backend enforces a blocking decision. Report static correctness,
104
- platform traffic, and backend enforcement separately. Use the controlled negative test
105
- in `SKILL.md` to prove enforcement.
106
-
107
- ## Secret exposure
108
-
109
- If the API Secret appears in a repo, a log, a build artifact, or a chat transcript,
110
- rotate it — deleting the line does not un-expose it:
111
-
112
- ```bash
113
- requestshield app key rotate --app-key <app-key>
114
- ```
115
-
116
- The App Key is unchanged, so no browser edit is needed. Deploy the new secret to backend
117
- secret storage; the previous one is deactivated automatically. Warn the user that the new
118
- secret is displayed once.
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 attached a token, CORS stripped the
5
+ header, the backend never verified it, or one token was verified twice.
6
+
7
+ ## First checks
8
+
9
+ ```console
10
+ requestshield auth status --json
11
+ requestshield apps get <app-key>
12
+ ```
13
+
14
+ Session status is local only. Application status is configuration state, not
15
+ traffic: `pending`, `enabled`, `disabled`, `revoked` or `attention_required`.
16
+ It cannot prove that the browser sent a request or that a deployed backend has the
17
+ current secret. Inspect browser requests and safe backend decision metrics separately.
18
+ The CLI has no service-health or credential-status command.
19
+
20
+ Confirm runtime secret injection by presence only. A secret absent from the current
21
+ shell may still be supplied by the deployed environment; a rotated secret may not
22
+ have reached that backend yet. Never read or print the value to diagnose this.
23
+
24
+ ## Reason codes
25
+
26
+ `result.getReason().getCode()` returns a stable code, safe to log. Use codes to explain
27
+ a decision, never to amend one.
28
+
29
+ The table below is a working list, not the authoritative one — the customer
30
+ documentation owns that. Check a code against it before quoting a meaning to the user,
31
+ and treat a code that is not listed here as unrecognised rather than guessing from its
32
+ name.
33
+
34
+ | Code | Means | Usual cause |
35
+ | --- | --- | --- |
36
+ | `ok` | Verified and consumed | |
37
+ | `missing_token` | No token reached the backend | Script tag absent or loaded after the request; Seamless path mismatch (case, trailing slash, wrong origin); an uncovered transport (form POST, sendBeacon, sync XHR, `no-cors`); a Manual call site that never attaches the header; CORS not allowing `X-IntelliFend-Token`; or CSP missing `worker-src blob:` so `getToken()` returns empty. |
38
+ | `unknown_customer` | App Key not recognised | Typo, deactivated key, or browser and backend configured with different App Keys. |
39
+ | `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. |
40
+ | `token_expired` | Token too old | The page held a token instead of obtaining a fresh one per request, or a very slow round trip. |
41
+ | `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 manual `verify()`, or a filter plus a controller). |
42
+ | `consume_unauthorized` | Backend credentials rejected | API Secret wrong, rotated, or not injected confirm runtime secret injection and recent rotations without reading the value. |
43
+ | `invalid_request` | Malformed verification request | Usually a mis-set `challenge-server-url`. |
44
+ | `not_found` | Token unknown to the platform | Cross-environment mismatch — a staging page's token verified against production, or the reverse. |
45
+ | `service_unavailable` | Platform unreachable | Check backend connectivity and safe health metrics. See degraded behaviour below. |
46
+
47
+ ## "It blocks everything"
48
+
49
+ Almost always `missing_token` or `unknown_customer`. Confirm in this order — each step is
50
+ cheap and rules out a whole class:
51
+
52
+ 1. Open the network tab. Does the protected request carry `X-IntelliFend-Token` with a
53
+ non-empty value?
54
+ 2. **Header absent** → browser half. Seamless: compare the requested URL against
55
+ `data-protect` character for character case and trailing slash are significant — and
56
+ check the transport is `fetch` or async XHR from page scope. Manual: find the call
57
+ site, and every retry path.
58
+ 3. **Header absent on a cross-origin request only** → CORS. The API's
59
+ `Access-Control-Allow-Headers` must include `X-IntelliFend-Token`, or the browser
60
+ strips it before it ever leaves.
61
+ 4. **Header present but empty** → the SDK ran and could not obtain a token. Check the CSP
62
+ includes `worker-src 'self' blob:` and that `connect-src` allows the challenge host,
63
+ then check connectivity and safe backend health observations. (In Manual mode an empty token means the header
64
+ should be omitted, not sent empty see `browser-manual.md`.)
65
+ 5. **Header present and populated** backend half. Compare the App Key on both sides
66
+ character for character, then confirm the secret is actually injected at runtime.
67
+
68
+ ## "It blocks nothing"
69
+
70
+ Enumerate rather than assuming the browser is at fault:
71
+
72
+ - The endpoint is in `MONITOR` mode — evaluating and always continuing. Protection-shaped,
73
+ not protection.
74
+ - The decision is computed but only logged, never enforced.
75
+ - Verification happens after the mutation has already been performed.
76
+ - Application code overrides `isAllowed()` from a reason code — a separate decision table
77
+ in application code removes the protection.
78
+ - The backend never verifies at all: a browser-only install. Grep for the client or the
79
+ annotation before trusting a claim that the backend is wired up.
80
+ - Results are `isDegraded()` the platform was unreachable and requests were allowed by
81
+ the availability policy.
82
+
83
+ ## Degraded results are allowed by design
84
+
85
+ When the Challenge Server cannot be reached, `verify()` returns a result that is
86
+ **allowed** and **degraded**. Availability is deliberately chosen over enforcement so a
87
+ platform outage cannot take down a customer's checkout flow.
88
+
89
+ Communicate the consequence clearly: during a degraded window, traffic passes without
90
+ real verification. Log the degraded rate as its own metric so it is visible, and check
91
+ backend connectivity and service health through established monitoring when it rises. Never "fix" a degraded result by overriding
92
+ `isAllowed()` that converts a platform incident into a customer-facing outage.
93
+
94
+ ## Confirming traffic and enforcement
95
+
96
+ Use browser network observations and existing safe backend metrics in an authorized
97
+ environment. Do not record raw tokens or secrets. CLI challenge-volume analytics are
98
+ unavailable; application status is not a traffic measurement. Report static correctness,
99
+ observed requests and backend enforcement separately. Use the controlled negative
100
+ test in `SKILL.md` to demonstrate enforcement.
101
+
102
+ ## Secret exposure
103
+
104
+ If the API Secret appears in a repo, a log, a build artifact, or a chat transcript,
105
+ rotate it deleting the line does not un-expose it:
106
+
107
+ ```bash
108
+ requestshield keys rotate <app-key>
109
+ ```
110
+
111
+ The App Key is unchanged, so no browser edit is needed. Deploy the new secret to backend
112
+ secret storage. Rotation replaces the previous secret, but publication is asynchronous.
113
+ Warn the user that the new secret is displayed and must stay out of transcripts. A lost
114
+ response can be recovered with explicit reveal; an exposed secret requires rotation.
@@ -1,74 +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
- const env = options.env ?? process.env;
16
- const homeDir = options.homeDir ?? os.homedir();
17
- const codexHome = env.CODEX_HOME ?? path.join(homeDir, ".codex");
18
-
19
- // Executables prove the CLI is on PATH. Configuration files cover desktop
20
- // installs and agents that were previously configured but are not on PATH.
21
- const [codexCommand, claudeCommand, codexConfig, claudeConfig, claudeState] =
22
- await Promise.all([
23
- commandExists("codex", env),
24
- commandExists("claude", env),
25
- pathExists(codexHome),
26
- pathExists(path.join(homeDir, ".claude")),
27
- pathExists(path.join(homeDir, ".claude.json")),
28
- ]);
29
-
30
- /** @type {Array<"codex" | "claude">} */
31
- const detected = [];
32
- if (codexCommand || codexConfig) detected.push("codex");
33
- if (claudeCommand || claudeConfig || claudeState) detected.push("claude");
34
- return detected;
35
- }
36
-
37
- /**
38
- * Inspect PATH without starting the coding agent.
39
- *
40
- * @param {string} command
41
- * @param {NodeJS.ProcessEnv} env
42
- */
43
- async function commandExists(command, env) {
44
- const searchPath = env.PATH ?? env.Path ?? "";
45
- const extensions = process.platform === "win32"
46
- ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
47
- : [""];
48
-
49
- for (const directory of searchPath.split(path.delimiter)) {
50
- if (!directory) continue;
51
- for (const extension of extensions) {
52
- try {
53
- await access(
54
- path.join(directory, `${command}${extension}`),
55
- process.platform === "win32" ? constants.F_OK : constants.X_OK,
56
- );
57
- return true;
58
- } catch {
59
- // Continue through the remaining executable candidates.
60
- }
61
- }
62
- }
63
- return false;
64
- }
65
-
66
- /** @param {string} target */
67
- async function pathExists(target) {
68
- try {
69
- await access(target);
70
- return true;
71
- } catch {
72
- return false;
73
- }
74
- }
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
+ }