requestshield 0.1.0 → 0.1.3
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 +37 -6
- package/package.json +3 -1
- package/skills/requestshield/SKILL.md +302 -39
- package/skills/requestshield/assets/AGENTS.codex.md +62 -0
- package/skills/requestshield/references/backend-java-core.md +128 -0
- package/skills/requestshield/references/backend-spring-boot.md +145 -0
- package/skills/requestshield/references/browser-manual.md +210 -0
- package/skills/requestshield/references/browser-seamless.md +164 -0
- package/skills/requestshield/references/cli.md +182 -0
- package/skills/requestshield/references/integration-planning.md +389 -0
- package/skills/requestshield/references/troubleshooting.md +118 -0
- package/src/agent-detector.mjs +74 -0
- package/src/args.mjs +17 -13
- package/src/cli.mjs +1 -1
- package/src/commands/agent-setup.mjs +109 -15
- package/src/main.mjs +24 -24
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Backend SDK — Spring Boot 3 starter
|
|
2
|
+
|
|
3
|
+
The backend is where protection actually happens. Everything the browser does is
|
|
4
|
+
transport to deliver a token here; this file is where the request is allowed or refused.
|
|
5
|
+
|
|
6
|
+
This is one of two backend paths. It is the declarative one, and it removes the most
|
|
7
|
+
common hand-rolled mistakes, so prefer it whenever the stack allows. The other is the
|
|
8
|
+
framework-neutral core SDK → `backend-java-core.md`.
|
|
9
|
+
|
|
10
|
+
**Requires Spring Boot 3 with Spring MVC, and Java 17 or newer.** A Spring Boot 3 app
|
|
11
|
+
built on WebFlux is not Spring MVC and cannot use this path. Confirm the stack before
|
|
12
|
+
writing anything — the detection recipes are in `integration-planning.md`, under **Gate
|
|
13
|
+
-> Run the check**. Also needed: the same
|
|
14
|
+
App Key the browser uses, and the backend-only API Secret.
|
|
15
|
+
|
|
16
|
+
Confirm the baseline and supported languages with `requestshield contract` first. If the
|
|
17
|
+
customer's backend language is not listed, the honest answer is that the integration is
|
|
18
|
+
not supported yet, because a browser-only install protects nothing.
|
|
19
|
+
|
|
20
|
+
The browser mode does not decide this path. Either Seamless or Manual mode delivers the
|
|
21
|
+
token in `X-IntelliFend-Token`, and the starter reads that header without caring how it
|
|
22
|
+
got there.
|
|
23
|
+
|
|
24
|
+
Use exactly **one** verification path per request. A RequestShield token is used once, so
|
|
25
|
+
a handler covered by the annotation must not also call `verify()`.
|
|
26
|
+
|
|
27
|
+
## 1. Repository and dependency
|
|
28
|
+
|
|
29
|
+
```xml title="pom.xml"
|
|
30
|
+
<repositories>
|
|
31
|
+
<repository>
|
|
32
|
+
<id>intellifend-maven</id>
|
|
33
|
+
<url>https://sdk.intellifend.com/packages/maven</url>
|
|
34
|
+
</repository>
|
|
35
|
+
</repositories>
|
|
36
|
+
|
|
37
|
+
<dependency>
|
|
38
|
+
<groupId>com.intellifend.requestshield</groupId>
|
|
39
|
+
<artifactId>requestshield-spring-boot3-starter</artifactId>
|
|
40
|
+
<version>2.0.0</version>
|
|
41
|
+
</dependency>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```kotlin title="build.gradle.kts"
|
|
45
|
+
repositories {
|
|
46
|
+
maven { url = uri("https://sdk.intellifend.com/packages/maven") }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
dependencies {
|
|
50
|
+
implementation("com.intellifend.requestshield:requestshield-spring-boot3-starter:2.0.0")
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The starter includes `requestshield-backend-sdk:2.0.0` — do not declare both.
|
|
55
|
+
|
|
56
|
+
## 2. Configure
|
|
57
|
+
|
|
58
|
+
```yaml title="application.yml"
|
|
59
|
+
intellifend:
|
|
60
|
+
requestshield:
|
|
61
|
+
app-key: ${INTELLIFEND_REQUESTSHIELD_APP_KEY}
|
|
62
|
+
api-secret: ${INTELLIFEND_REQUESTSHIELD_API_SECRET}
|
|
63
|
+
challenge-server-url: https://challenge.intellifend.ai
|
|
64
|
+
mode: BLOCK
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Property | Required | Notes |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `app-key` | Yes | Must be the same value as the browser's `data-app-key`. |
|
|
70
|
+
| `api-secret` | Yes | Backend runtime secret storage. Pass the supplied value unchanged. |
|
|
71
|
+
| `challenge-server-url` | No | Defaults to the RequestShield service. |
|
|
72
|
+
| `mode` | No | Global mode, `BLOCK` or `MONITOR`. Defaults to `BLOCK`. |
|
|
73
|
+
|
|
74
|
+
Bind the secret with no fallback default, so an environment that fails to inject it
|
|
75
|
+
fails at startup. A backend that starts with a placeholder secret rejects every real
|
|
76
|
+
token, which surfaces days later as "RequestShield is blocking all our users" — a
|
|
77
|
+
startup failure is far cheaper to diagnose than that.
|
|
78
|
+
|
|
79
|
+
## 3. Annotate the protected method
|
|
80
|
+
|
|
81
|
+
```java
|
|
82
|
+
import com.intellifend.requestshield.spring.boot.ProtectionMode;
|
|
83
|
+
import com.intellifend.requestshield.spring.boot.RequestShieldProtected;
|
|
84
|
+
|
|
85
|
+
@PostMapping("/api/register")
|
|
86
|
+
@RequestShieldProtected(mode = ProtectionMode.DEFAULT)
|
|
87
|
+
public RegisterResponse register(@RequestBody RegisterRequest request) {
|
|
88
|
+
return accountService.create(request);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The annotation applies to methods. Put it on the handler that performs the protected
|
|
93
|
+
mutation — not on a wrapper or a read-only route in front of it, where the mutation
|
|
94
|
+
could still be reached another way.
|
|
95
|
+
|
|
96
|
+
The annotation declares no path, action, domain, or browser URL list. It creates no
|
|
97
|
+
browser configuration.
|
|
98
|
+
|
|
99
|
+
## 4. Enforcement mode
|
|
100
|
+
|
|
101
|
+
| Mode | Behaviour |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `BLOCK` | Enforce the decision before the method runs. |
|
|
104
|
+
| `MONITOR` | Evaluate the request while allowing the method to continue. |
|
|
105
|
+
| `DEFAULT` | Inherit the global mode. |
|
|
106
|
+
|
|
107
|
+
The global property accepts `BLOCK` or `MONITOR` and defaults to `BLOCK`. A method-level
|
|
108
|
+
`BLOCK` or `MONITOR` overrides the global setting.
|
|
109
|
+
|
|
110
|
+
`MONITOR` is the right first deploy on live traffic: it produces real decisions and
|
|
111
|
+
volume data with no risk of turning away genuine users, so you can confirm tokens are
|
|
112
|
+
arriving before switching to `BLOCK`. State clearly whenever an endpoint is in
|
|
113
|
+
`MONITOR` — it is protection-shaped and is not protection, and it is easy to forget.
|
|
114
|
+
|
|
115
|
+
## 5. Align the browser side
|
|
116
|
+
|
|
117
|
+
Configure exactly one browser mode for the same operation:
|
|
118
|
+
|
|
119
|
+
- Seamless mode → add the endpoint URL to `data-protect`. See `browser-seamless.md`.
|
|
120
|
+
- Manual mode → send the obtained token in `X-IntelliFend-Token`. See
|
|
121
|
+
`browser-manual.md`.
|
|
122
|
+
|
|
123
|
+
Browser configuration does not create backend protection, and the annotation does not
|
|
124
|
+
create browser configuration. Land both halves in the same change. For a cross-origin
|
|
125
|
+
API, configure CORS to allow the token header.
|
|
126
|
+
|
|
127
|
+
## Migrating from manual verification
|
|
128
|
+
|
|
129
|
+
For an endpoint already calling `RequestShieldClient.verify()`, remove that call and add
|
|
130
|
+
the annotation **in the same backend release**. Running both consumes the same
|
|
131
|
+
single-use token twice, so every request fails — and it fails only once both are
|
|
132
|
+
deployed, which makes it look like the annotation broke the endpoint.
|
|
133
|
+
|
|
134
|
+
Reason codes and what each means for debugging: `troubleshooting.md`.
|
|
135
|
+
|
|
136
|
+
## Security requirements
|
|
137
|
+
|
|
138
|
+
- The API Secret lives in backend runtime secret storage only — never in source, a
|
|
139
|
+
committed `.env`, an image layer, a client-visible response, or a log line.
|
|
140
|
+
- The App Key must be identical in browser and backend, or every token fails.
|
|
141
|
+
- One token per protected request, verified exactly once.
|
|
142
|
+
- Never log or persist raw tokens, decoded claims, or the secret. Reason codes exist so
|
|
143
|
+
diagnostics need none of that.
|
|
144
|
+
- Enforce the decision before the business mutation, not after — verification that runs
|
|
145
|
+
after the order is written is an audit log, not protection.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Browser SDK — Manual mode
|
|
2
|
+
|
|
3
|
+
Manual mode gives the application control over **when** a token is obtained and **how**
|
|
4
|
+
it is carried to the backend. Choose it when the request escapes Seamless interception,
|
|
5
|
+
or when the application must carry the token in an application-controlled header or
|
|
6
|
+
request-body field.
|
|
7
|
+
|
|
8
|
+
Prerequisites: a public App Key, a backend integration that extracts the chosen carrier,
|
|
9
|
+
and a protected operation that is **not** also configured for Seamless mode. One
|
|
10
|
+
operation uses one mode.
|
|
11
|
+
|
|
12
|
+
## 1. Load the hosted SDK
|
|
13
|
+
|
|
14
|
+
Omit `data-protect` when the page uses Manual mode only — its presence is what turns on
|
|
15
|
+
Seamless interception:
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<script
|
|
19
|
+
src="SCRIPT_URL_FROM_CONTRACT"
|
|
20
|
+
data-app-key="YOUR_APP_KEY"
|
|
21
|
+
defer
|
|
22
|
+
></script>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Attribute | Required | Notes |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `src` | Yes | Take it from `requestshield contract` → `browser.script_url`. Currently `https://static.intellifend.ai/intellifend.js`; read the contract rather than trusting this line. |
|
|
28
|
+
| `data-app-key` | Yes | Public App Key. Must match the backend's configured key. |
|
|
29
|
+
| `defer` | Recommended | Keeps HTML parsing unblocked. |
|
|
30
|
+
|
|
31
|
+
The SDK reads configuration from `document.currentScript`, so use **exactly one tag**.
|
|
32
|
+
A second tag re-initializes rather than merging, and the last one to run wins.
|
|
33
|
+
|
|
34
|
+
In templated or bundled apps, put the tag in the HTML entry point (`index.html`,
|
|
35
|
+
`_document.tsx`, the base Django/Rails/Thymeleaf layout) rather than injecting it from
|
|
36
|
+
component code, so it is present before any protected request can fire.
|
|
37
|
+
|
|
38
|
+
Applications with a custom script loader may configure the same setup in JavaScript
|
|
39
|
+
instead:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
IntelliFend.init({appKey: 'YOUR_APP_KEY'});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Use script attributes *or* explicit initialization for initial setup, not both.
|
|
46
|
+
|
|
47
|
+
## 2. Obtain a token immediately before the request
|
|
48
|
+
|
|
49
|
+
Call `getToken()` right before the protected operation, and attach the header **only
|
|
50
|
+
when the returned string is non-empty**:
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
async function createAccount(payload) {
|
|
54
|
+
const token = await IntelliFend.getToken();
|
|
55
|
+
const headers = {'Content-Type': 'application/json'};
|
|
56
|
+
|
|
57
|
+
if (token) {
|
|
58
|
+
headers['X-IntelliFend-Token'] = token;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return fetch('/api/register', {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers,
|
|
64
|
+
body: JSON.stringify(payload),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`getToken()` resolves to a string. A non-empty value is that request's token; an empty
|
|
70
|
+
value means the header is omitted and **the backend applies its configured policy**.
|
|
71
|
+
|
|
72
|
+
That last part is why the empty case needs no client-side handling. The decision about
|
|
73
|
+
what an absent token means already lives at the backend, so wrapping this in a retry
|
|
74
|
+
loop, blocking the submit, showing an error, or substituting a placeholder all move
|
|
75
|
+
policy into the browser — the one place it must not be. Attach it when present, omit it
|
|
76
|
+
when not, and let the backend decide.
|
|
77
|
+
|
|
78
|
+
### Optional action
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
const token = await IntelliFend.getToken({action: 'create-account'});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Case-sensitive. Use an action **only** when IntelliFend has supplied a corresponding
|
|
85
|
+
backend integration rule — a browser-supplied value is not backend configuration, and
|
|
86
|
+
inventing one has no effect on the decision.
|
|
87
|
+
|
|
88
|
+
## 3. Backend extracts the carrier
|
|
89
|
+
|
|
90
|
+
The Spring Boot starter reads `X-IntelliFend-Token` from the request itself, so no
|
|
91
|
+
extraction code is needed. With the Java core SDK, application code extracts the header
|
|
92
|
+
and passes the value unchanged to `verify()`.
|
|
93
|
+
|
|
94
|
+
A dedicated request-body field is available when the application contract requires it;
|
|
95
|
+
application code must then extract that field before verification. Prefer the header
|
|
96
|
+
unless there is a real constraint — it keeps the browser and backend halves symmetric
|
|
97
|
+
and works with the starter as-is.
|
|
98
|
+
|
|
99
|
+
See `backend-spring-boot.md` or `backend-java-core.md` for the extraction side.
|
|
100
|
+
|
|
101
|
+
## Framework call-site patterns
|
|
102
|
+
|
|
103
|
+
The recurring failure is attaching the token in one place while some requests reach the
|
|
104
|
+
endpoint through another. Enumerate every path to the endpoint before editing — and if
|
|
105
|
+
there are several, that is a strong signal Seamless mode is the better fit.
|
|
106
|
+
|
|
107
|
+
**Axios interceptor** — covers calls made through one Axios instance. Resolve both the
|
|
108
|
+
allow-list and each request to an origin-plus-pathname key. Pathname-only matching can
|
|
109
|
+
attach a token to the wrong origin, and resolving `config.url` against `location.origin`
|
|
110
|
+
ignores the instance's `baseURL`:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const protectedEndpointKeys = new Set(
|
|
114
|
+
PROTECTED_ENDPOINTS.map((endpoint) => {
|
|
115
|
+
const url = new URL(endpoint, location.origin);
|
|
116
|
+
return `${url.origin}${url.pathname}`;
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
api.interceptors.request.use(async (config) => {
|
|
121
|
+
let requestUrl;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
requestUrl = new URL(api.getUri(config), document.baseURI);
|
|
125
|
+
} catch {
|
|
126
|
+
// An unresolved destination must never receive a RequestShield token.
|
|
127
|
+
return config;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const requestKey = `${requestUrl.origin}${requestUrl.pathname}`;
|
|
131
|
+
if (protectedEndpointKeys.has(requestKey)) {
|
|
132
|
+
// Remove a token retained by a reused Axios request config.
|
|
133
|
+
for (const headerName of Object.keys(config.headers)) {
|
|
134
|
+
if (headerName.toLowerCase() === 'x-intellifend-token') {
|
|
135
|
+
delete config.headers[headerName];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const token = await IntelliFend.getToken();
|
|
139
|
+
if (token) {
|
|
140
|
+
config.headers['X-IntelliFend-Token'] = token;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return config;
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`api.getUri(config)` applies that Axios instance's request configuration, including
|
|
148
|
+
`baseURL`; resolving its result against `location.origin` also handles a root-relative
|
|
149
|
+
base URL. Query strings and fragments are deliberately absent from the comparison, but
|
|
150
|
+
the origin is mandatory.
|
|
151
|
+
|
|
152
|
+
Before shipping the interceptor, verify both directions against the actual instance:
|
|
153
|
+
|
|
154
|
+
- With `axios.create({baseURL: '/api'})`, a call to `post('/register')` resolves to the
|
|
155
|
+
current origin plus `/api/register` and matches that protected endpoint.
|
|
156
|
+
- A call to `https://other.example/api/register` does not match the current origin's
|
|
157
|
+
`/api/register`, does not call `getToken()`, and receives no RequestShield header.
|
|
158
|
+
|
|
159
|
+
**React / TanStack Query** — obtain the token inside the mutation function, never in a
|
|
160
|
+
`useEffect` or component state. A token held in state goes stale, gets reused across
|
|
161
|
+
retries, and comes back `token_replayed`.
|
|
162
|
+
|
|
163
|
+
**Retries** — any retry wrapper must call `getToken()` again on each attempt. Reusing
|
|
164
|
+
the first token is the most common cause of a retry being blocked while the original
|
|
165
|
+
attempt succeeded.
|
|
166
|
+
|
|
167
|
+
**Server-rendered forms** — a native form POST cannot carry a header. Either move the
|
|
168
|
+
submission to `fetch`, or agree a request-body carrier with IntelliFend. Never put the
|
|
169
|
+
token in a hidden field improvised on your own, and never in a query parameter.
|
|
170
|
+
|
|
171
|
+
## Cross-origin application APIs
|
|
172
|
+
|
|
173
|
+
When the protected endpoint is on another origin, its CORS response must allow the page
|
|
174
|
+
origin, the intended HTTP methods, and the `X-IntelliFend-Token` header. Without the
|
|
175
|
+
header in `Access-Control-Allow-Headers`, the browser strips it and the backend sees
|
|
176
|
+
every request as missing a token — which reads as a browser-integration bug but is a
|
|
177
|
+
CORS configuration bug.
|
|
178
|
+
|
|
179
|
+
## Content Security Policy
|
|
180
|
+
|
|
181
|
+
If the app sends a CSP, merge these sources into the existing directives — never replace
|
|
182
|
+
the policy, and keep every source the app already declares:
|
|
183
|
+
|
|
184
|
+
```http
|
|
185
|
+
Content-Security-Policy:
|
|
186
|
+
script-src 'self' https://static.intellifend.ai;
|
|
187
|
+
connect-src 'self' https://challenge.intellifend.ai;
|
|
188
|
+
worker-src 'self' blob:;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Confirm the hosts **and the directive list** against `requestshield contract` and the
|
|
192
|
+
customer deployment guide before editing a live policy — this skill is not the authority
|
|
193
|
+
on either. If `getToken()` returns an empty string on a page that sends a CSP, a blocked
|
|
194
|
+
directive is the first thing to check: the browser console names what it refused, which
|
|
195
|
+
beats guessing at the policy.
|
|
196
|
+
|
|
197
|
+
Applications that do not send a CSP need no header changes.
|
|
198
|
+
|
|
199
|
+
## Token handling rules
|
|
200
|
+
|
|
201
|
+
- One token for one protected request.
|
|
202
|
+
- A new token before retrying a protected operation.
|
|
203
|
+
- Forward a non-empty token unchanged; omit the header when it is empty.
|
|
204
|
+
- Never cache, persist, transform, or log a token.
|
|
205
|
+
- Never place a token in a URL path, query string, or fragment.
|
|
206
|
+
- Never verify the same request with both `@RequestShieldProtected` and a manual
|
|
207
|
+
`RequestShieldClient.verify()`.
|
|
208
|
+
- Exclude `X-IntelliFend-Token` from logs, analytics, and error reporting — check the
|
|
209
|
+
error-reporting SDK's header allowlist, which often captures request headers by
|
|
210
|
+
default.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Browser SDK — Seamless mode
|
|
2
|
+
|
|
3
|
+
Seamless mode is the recommended browser integration for applications that use `fetch`
|
|
4
|
+
or asynchronous `XMLHttpRequest`. Browser SDK 1.1 matches configured application
|
|
5
|
+
endpoints and adds `X-IntelliFend-Token` automatically, so no call site changes.
|
|
6
|
+
|
|
7
|
+
Prerequisites: a public App Key, the **exact** application API endpoints to protect, and
|
|
8
|
+
backend protection configured for the corresponding operations.
|
|
9
|
+
|
|
10
|
+
## 1. Load the hosted SDK
|
|
11
|
+
|
|
12
|
+
Place the script before any application bundle that can issue a protected request —
|
|
13
|
+
otherwise an early request leaves the page before interception is installed:
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<script
|
|
17
|
+
src="SCRIPT_URL_FROM_CONTRACT"
|
|
18
|
+
data-app-key="YOUR_APP_KEY"
|
|
19
|
+
data-protect='["/api/register"]'
|
|
20
|
+
defer
|
|
21
|
+
></script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`data-protect` is a **JSON array** — note the single quotes around the attribute so the
|
|
25
|
+
inner double quotes survive. Each entry is either a root-relative pathname beginning
|
|
26
|
+
with `/`, or an absolute URL without embedded credentials. Malformed JSON is a configuration
|
|
27
|
+
error — validate the attribute before shipping rather than assuming a parse failure will
|
|
28
|
+
announce itself.
|
|
29
|
+
|
|
30
|
+
Applications with a custom script loader can configure the same behaviour after the
|
|
31
|
+
hosted script loads:
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
IntelliFend.init({
|
|
35
|
+
appKey: 'YOUR_APP_KEY',
|
|
36
|
+
protect: ['/api/register'],
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use script attributes *or* explicit initialization for initial setup, not both.
|
|
41
|
+
|
|
42
|
+
## 2. Match application endpoints exactly
|
|
43
|
+
|
|
44
|
+
RequestShield compares the **exact origin and exact pathname**. This is where a Seamless
|
|
45
|
+
install either works or quietly does nothing, so resolve it before writing the attribute:
|
|
46
|
+
|
|
47
|
+
- Query strings and fragments are ignored on both sides.
|
|
48
|
+
- **Pathname case and trailing slash are significant.**
|
|
49
|
+
- Root-relative entries resolve against the page origin.
|
|
50
|
+
- Cross-origin entries use HTTPS, except for loopback development.
|
|
51
|
+
- The same endpoint rule applies to every HTTP method.
|
|
52
|
+
|
|
53
|
+
So `/api/register` matches `/api/register?source=campaign`, but it does **not** match
|
|
54
|
+
`/api/Register` or `/api/register/`.
|
|
55
|
+
|
|
56
|
+
The practical consequence: **list the URL the application passes to `fetch` or
|
|
57
|
+
asynchronous `XMLHttpRequest`.** With a normal development proxy, the application
|
|
58
|
+
requests `/api/register` on the frontend origin. The proxy then rewrites and forwards
|
|
59
|
+
that request to an upstream route such as `https://api.example.com/register`. The
|
|
60
|
+
rewrite happens outside the browser and does not change the URL matched by Seamless.
|
|
61
|
+
Configure `/api/register`, and ensure the proxy forwards `X-IntelliFend-Token`
|
|
62
|
+
unchanged.
|
|
63
|
+
|
|
64
|
+
If the application directly requests `https://api.example.com/register`, configure
|
|
65
|
+
that absolute URL instead and configure CORS to allow the page origin, request method,
|
|
66
|
+
and `X-IntelliFend-Token`.
|
|
67
|
+
|
|
68
|
+
A redirect is different from a proxy rewrite. With an HTTP redirect, the server returns
|
|
69
|
+
a `3xx` response and the browser follows its `Location`. Seamless matches the original
|
|
70
|
+
URL passed by application code; it does not separately match the browser's internal
|
|
71
|
+
redirect request. Do not configure only the redirect destination when the application
|
|
72
|
+
initially requests `/api/register`. Avoid redirects for protected mutation endpoints
|
|
73
|
+
when possible, or verify the method, header, and CORS behavior end to end.
|
|
74
|
+
|
|
75
|
+
A path built with an ID or slug (`/api/orders/42`) has no wildcard support — list every
|
|
76
|
+
concrete path, or use Manual mode for that operation.
|
|
77
|
+
|
|
78
|
+
## 3. Send requests normally
|
|
79
|
+
|
|
80
|
+
Application code does not call `getToken()` or construct the header for a
|
|
81
|
+
Seamless-mode endpoint:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
const response = await fetch('/api/register', {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {'Content-Type': 'application/json'},
|
|
87
|
+
body: JSON.stringify({email, password}),
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Browser configuration controls token attachment only. Configure the corresponding
|
|
92
|
+
backend operation independently — `data-protect` creates no backend protection.
|
|
93
|
+
|
|
94
|
+
## What Seamless covers
|
|
95
|
+
|
|
96
|
+
Supported: `fetch` (string, `URL`, and `Request` inputs) and asynchronous
|
|
97
|
+
`XMLHttpRequest`.
|
|
98
|
+
|
|
99
|
+
Do not rely on Seamless and a Manual call site coexisting on the same endpoint. One
|
|
100
|
+
protected operation uses one mode — see the prerequisites in `browser-manual.md`.
|
|
101
|
+
|
|
102
|
+
## What Seamless does not cover
|
|
103
|
+
|
|
104
|
+
Any of these means the request leaves without a token, with no error — which is why
|
|
105
|
+
they are worth checking *before* choosing the mode:
|
|
106
|
+
|
|
107
|
+
- Synchronous XHR.
|
|
108
|
+
- Native form navigation (a plain `<form>` submit).
|
|
109
|
+
- `navigator.sendBeacon`.
|
|
110
|
+
- WebSocket and EventSource.
|
|
111
|
+
- Service-worker-owned requests. Web workers and server-side rendering have their own
|
|
112
|
+
global scope too — in Next.js, Nuxt, Remix and similar, confirm the protected call
|
|
113
|
+
runs in the browser.
|
|
114
|
+
- A `fetch` using `no-cors`, which cannot carry a custom header at all.
|
|
115
|
+
|
|
116
|
+
For an operation on this list, use Manual mode **only if** the application can carry the
|
|
117
|
+
token in a supported header or request-body field. Otherwise the right answer is an
|
|
118
|
+
application-specific integration agreed with IntelliFend — say that plainly rather than
|
|
119
|
+
improvising a carrier.
|
|
120
|
+
|
|
121
|
+
## Cross-origin APIs
|
|
122
|
+
|
|
123
|
+
When the protected endpoint is on another origin, its CORS response must allow the page
|
|
124
|
+
origin, the intended methods, and the `X-IntelliFend-Token` header. Missing that header
|
|
125
|
+
in `Access-Control-Allow-Headers` means the browser strips it and every request reads as
|
|
126
|
+
missing a token.
|
|
127
|
+
|
|
128
|
+
## Verifying Seamless
|
|
129
|
+
|
|
130
|
+
Static checks:
|
|
131
|
+
|
|
132
|
+
1. Exactly one script tag, loaded before application bundles, with valid JSON in
|
|
133
|
+
`data-protect`.
|
|
134
|
+
2. Every listed entry matches a URL the browser actually requests — origin, case, and
|
|
135
|
+
trailing slash included.
|
|
136
|
+
3. Every protected endpoint reaches the network via `fetch` or async XHR from page
|
|
137
|
+
scope.
|
|
138
|
+
|
|
139
|
+
Runtime check: exercise the endpoint, confirm the network request carries a non-empty
|
|
140
|
+
`X-IntelliFend-Token`, then confirm the platform saw it. This proves the browser half is
|
|
141
|
+
firing; the negative test in `SKILL.md` proves backend enforcement.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
requestshield challenge volume <app-key> \
|
|
145
|
+
--from <start> \
|
|
146
|
+
--to <end> \
|
|
147
|
+
--granularity hour
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Zero volume with a correct-looking `data-protect` is nearly always a path mismatch
|
|
151
|
+
(trailing slash, case, a different origin than assumed) or one of the uncovered
|
|
152
|
+
transports above. Re-read the actual request URL before changing anything else.
|
|
153
|
+
|
|
154
|
+
## Expected result
|
|
155
|
+
|
|
156
|
+
Matching requests carry one non-empty `X-IntelliFend-Token` header; requests outside the
|
|
157
|
+
configured list are unchanged.
|
|
158
|
+
|
|
159
|
+
## CSP and token handling
|
|
160
|
+
|
|
161
|
+
Identical to Manual mode — see the CSP and token-handling sections of
|
|
162
|
+
`browser-manual.md`. The `worker-src 'self' blob:` directive matters just as much here:
|
|
163
|
+
without it, every intercepted request attaches an empty token and the backend blocks
|
|
164
|
+
traffic that looks correctly integrated.
|