@camada/next 0.3.1 → 0.4.0
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 +14 -4
- package/dist/index.cjs +46 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +49 -16
- package/dist/index.js.map +1 -1
- package/package.json +12 -7
package/README.md
CHANGED
|
@@ -51,8 +51,9 @@ rate-limited `console.error` says why.
|
|
|
51
51
|
`x-block-reason`/`x-block-version`/`x-block-rule`; blocked requests always ship,
|
|
52
52
|
unsampled), stamps `x-camada-rid` onto the forwarded request (dropping any the client sent)
|
|
53
53
|
and `x-rid` onto the response, and ships the wire event fire-and-forget with tap
|
|
54
|
-
`sdk-next`.
|
|
55
|
-
`
|
|
54
|
+
`sdk-next`. `x-vercel-ja4-digest` — a real TLS fingerprint — rides along as `ja4` only when the
|
|
55
|
+
`VERCEL` platform env var is set (elsewhere a client can set that header; so don't ship a
|
|
56
|
+
`vercel env pull` `.env.local`, which sets `VERCEL=1`, to a self-hosted deploy).
|
|
56
57
|
- **`camadaRoute()`** — `GET …/b.js` serves the `@camada/browser` IIFE first-party (no CSP
|
|
57
58
|
or ad-blocker friction); `POST …/fp` relays the beacon body to ingest with the
|
|
58
59
|
trusted-proxy-resolved client IP and `tap: 'sdk-next'` injected. Works on both runtimes.
|
|
@@ -129,7 +130,7 @@ rule carries one of four actions:
|
|
|
129
130
|
|---|---|---|
|
|
130
131
|
| `skip` | passes the request | nothing |
|
|
131
132
|
| `block` | `403` before your app | `blk: "rule"`, `rl: "<rule id>"` |
|
|
132
|
-
| `challenge` | serves the proof-of-work page (`CAMADA_CHALLENGE=0` opts out) | `blk: "challenge"` |
|
|
133
|
+
| `challenge` | serves the proof-of-work page to a page load or a Pages Router data request (`/_next/data`) (`CAMADA_CHALLENGE=0` opts out); any other request (fetch, XHR, API call) passes with `x-camada-challenge: 1` and `no-store` on `cache-control` and the CDN cache headers (`cdn-cache-control`, `surrogate-control`, and the Cloudflare, Vercel and Netlify variants). App Router soft navigations pass this way too, because Next hides the RSC header from middleware; the next document load meets the page | `blk: "challenge"` for the page, `chs: 1` for a passed request |
|
|
133
134
|
| `warn` | passes the request and marks it for the analyst | `wrn: "<rule id>"` |
|
|
134
135
|
|
|
135
136
|
A skip rule also carries a *record matches* flag, which only the analyst reads: a recorded skip
|
|
@@ -169,7 +170,16 @@ a real cold → blocked flow against the golden snapshot fixture.
|
|
|
169
170
|
no `CAMADA_TRUSTED_PROXY` (and no server-delivered tenant config) the IP resolves to
|
|
170
171
|
`null` and IP rules simply don't enforce — a spoofed `X-Forwarded-For` can never reach the
|
|
171
172
|
blocklist. When `VERCEL` is set and no override is given, the SDK defaults to
|
|
172
|
-
`{mode:'vercel'}` (Vercel overwrites XFF, so its rightmost entry is trustworthy).
|
|
173
|
+
`{mode:'vercel'}` (Vercel overwrites XFF, so its rightmost entry is trustworthy). Under
|
|
174
|
+
`VERCEL` that default also wins over the trusted-proxy setting in your dashboard: only
|
|
175
|
+
`CAMADA_TRUSTED_PROXY` overrides it.
|
|
176
|
+
- **No socket peer, so the X-Forwarded-For walk trusts your host's edge.** The SDKs that see
|
|
177
|
+
the socket (`@camada/node`, `@camada/bun`, `@camada/hono` off Workers) walk a `cidrs` config
|
|
178
|
+
only when the peer itself is a trusted proxy or loopback, and otherwise answer with the peer.
|
|
179
|
+
This adapter has no peer to check, so it keeps the plain walk: under `CAMADA_TRUSTED_PROXY` it
|
|
180
|
+
reads `X-Forwarded-For` as if the request came through your edge (Vercel, a load balancer, a
|
|
181
|
+
CDN). That holds when the edge is the only way in. If the origin is reachable directly, a
|
|
182
|
+
client can send its own `X-Forwarded-For`, so lock the origin to the edge.
|
|
173
183
|
- **Header order is alphabetical.** The edge runtime sorts request headers, so `hord` is
|
|
174
184
|
sorted at this tap (still shipped; the scorer's capability mask knows `sdk-next` lacks the
|
|
175
185
|
raw-wire-order signal `@camada/node` has).
|
package/dist/index.cjs
CHANGED
|
@@ -47,7 +47,7 @@ var import_core4 = require("@camada/core");
|
|
|
47
47
|
var import_core = require("@camada/core");
|
|
48
48
|
|
|
49
49
|
// package.json
|
|
50
|
-
var version = "0.
|
|
50
|
+
var version = "0.4.0";
|
|
51
51
|
var name = "@camada/next";
|
|
52
52
|
|
|
53
53
|
// src/version.ts
|
|
@@ -124,7 +124,8 @@ function buildEvent(req, path, ip, rid, sid, newSession) {
|
|
|
124
124
|
httpVersion: null
|
|
125
125
|
// not observable at this position
|
|
126
126
|
},
|
|
127
|
-
|
|
127
|
+
// ja4 only on Vercel (the server env, which a client cannot set): elsewhere x-vercel-ja4-digest is client-settable.
|
|
128
|
+
{ tap: import_core2.TAP_NEXT, rid, sid, newSession, ja4: envSource().VERCEL ? req.headers.get("x-vercel-ja4-digest") : null }
|
|
128
129
|
);
|
|
129
130
|
}
|
|
130
131
|
var MW_HEADER = "x-camada-mw";
|
|
@@ -189,22 +190,24 @@ async function serveChallenge(engine2, req, ip, target, waitUntil, ship = true)
|
|
|
189
190
|
const html = (0, import_core3.challengePage)({ nonce: await kitFor(engine2).nonce(ip, Date.now()), action: VERIFY_PATH, to: (0, import_core3.safeReturnTo)(target) });
|
|
190
191
|
return new Response(html, {
|
|
191
192
|
status: 403,
|
|
192
|
-
|
|
193
|
+
// the page's own policy (its inline solver and style by hash), never one the app sets (camada-all-mwpo)
|
|
194
|
+
headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "x-camada-challenge": "1", "content-security-policy": (0, import_core3.challengePageCsp)() }
|
|
193
195
|
});
|
|
194
196
|
}
|
|
195
197
|
async function verifyChallenge(engine2, req) {
|
|
196
198
|
const url = new URL(req.url);
|
|
197
199
|
const ip = clientIp(engine2, req);
|
|
198
200
|
const kit = kitFor(engine2);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (body.length > BODY_MAX) return new Response(null, { status: 413 });
|
|
201
|
+
const body = await (0, import_core3.readBodyCapped)(req, BODY_MAX);
|
|
202
|
+
if (body === null) return new Response(null, { status: 413 });
|
|
202
203
|
const form = (0, import_core3.parseFormBody)(body);
|
|
203
204
|
const to = (0, import_core3.safeReturnTo)(form.to);
|
|
204
205
|
const now = Date.now();
|
|
205
206
|
if (!ip || !await kit.verify(ip, now, form.nonce, form.solution)) {
|
|
206
207
|
const html = ip ? (0, import_core3.challengePage)({ nonce: await kit.nonce(ip, now), action: url.pathname, to }) : "Forbidden";
|
|
207
|
-
|
|
208
|
+
const headers = { "content-type": ip ? "text/html; charset=utf-8" : "text/plain", "cache-control": "no-store" };
|
|
209
|
+
if (ip) headers["content-security-policy"] = (0, import_core3.challengePageCsp)();
|
|
210
|
+
return new Response(html, { status: 403, headers });
|
|
208
211
|
}
|
|
209
212
|
const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get("cookie") || "", SESSION_COOKIE), false);
|
|
210
213
|
ev.st = 200;
|
|
@@ -220,27 +223,42 @@ async function verifyChallenge(engine2, req) {
|
|
|
220
223
|
}
|
|
221
224
|
});
|
|
222
225
|
}
|
|
226
|
+
function isChallengeable(req) {
|
|
227
|
+
return (0, import_core3.wantsHtml)(req.headers.get("accept"), req.headers.get("sec-fetch-dest")) || req.headers.get("x-nextjs-data") === "1";
|
|
228
|
+
}
|
|
223
229
|
async function challengeGate(req) {
|
|
224
230
|
if (isDisabled() || !challengeEnabled()) return null;
|
|
225
231
|
const engine2 = getEngine();
|
|
226
232
|
if (!engine2) return null;
|
|
233
|
+
const challengeable = isChallengeable(req);
|
|
227
234
|
const ip = clientIp(engine2, req);
|
|
228
235
|
if (!ip || await challengePassed(engine2, req, ip)) return null;
|
|
229
236
|
const url = new URL(req.url);
|
|
230
237
|
const shipped = await middlewareMarkValid(engine2.env.secret, req).catch(() => false);
|
|
238
|
+
if (!challengeable) {
|
|
239
|
+
if (!shipped) {
|
|
240
|
+
const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get("cookie") || "", SESSION_COOKIE), false);
|
|
241
|
+
ev.chs = 1;
|
|
242
|
+
engine2.queue.push(ev);
|
|
243
|
+
engine2.queue.flush();
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
231
247
|
return serveChallenge(engine2, req, ip, url.pathname + url.search, void 0, !shipped);
|
|
232
248
|
}
|
|
233
249
|
|
|
234
250
|
// src/middleware.ts
|
|
251
|
+
var NO_STORE_HEADERS = ["cache-control", "cdn-cache-control", "surrogate-control", "cloudflare-cdn-cache-control", "vercel-cdn-cache-control", "netlify-cdn-cache-control"];
|
|
235
252
|
var isBeaconRoute = (method, path) => method === "GET" && path === "/api/camada/b.js" || method === "POST" && path === "/api/camada/fp";
|
|
236
|
-
async function capture(engine2, req, path, ip, existingSid, warnRule, waitUntil) {
|
|
253
|
+
async function capture(engine2, req, path, ip, existingSid, warnRule, waitUntil, signalled = false) {
|
|
237
254
|
const rid = crypto.randomUUID();
|
|
238
255
|
const sid = existingSid ?? crypto.randomUUID();
|
|
239
256
|
const cfg = engine2.snap.config;
|
|
240
257
|
const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));
|
|
241
|
-
if (!excluded && Math.random() < (cfg?.sample ?? 1)) {
|
|
258
|
+
if (signalled || !excluded && Math.random() < (cfg?.sample ?? 1)) {
|
|
242
259
|
const ev = buildEvent(req, path, ip, rid, sid, !existingSid);
|
|
243
260
|
if (warnRule) ev.wrn = warnRule;
|
|
261
|
+
if (signalled) ev.chs = 1;
|
|
244
262
|
engine2.queue.push(ev);
|
|
245
263
|
engine2.queue.flush(waitUntil);
|
|
246
264
|
}
|
|
@@ -251,6 +269,10 @@ async function capture(engine2, req, path, ip, existingSid, warnRule, waitUntil)
|
|
|
251
269
|
if (mark) headers.set(MW_HEADER, mark);
|
|
252
270
|
const res = import_server.NextResponse.next({ request: { headers } });
|
|
253
271
|
res.headers.set("x-rid", rid);
|
|
272
|
+
if (signalled) {
|
|
273
|
+
res.headers.set("x-camada-challenge", "1");
|
|
274
|
+
for (const n of NO_STORE_HEADERS) res.headers.set(n, "no-store");
|
|
275
|
+
}
|
|
254
276
|
if (!existingSid) {
|
|
255
277
|
const secure = new URL(req.url).protocol === "https:" ? "; Secure" : "";
|
|
256
278
|
res.headers.append("set-cookie", `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${secure}`);
|
|
@@ -290,6 +312,7 @@ function camada(_options) {
|
|
|
290
312
|
engine2.queue.flush(waitUntil);
|
|
291
313
|
const headers = {
|
|
292
314
|
"content-type": "text/plain",
|
|
315
|
+
"cache-control": "no-store",
|
|
293
316
|
"x-block-reason": String(v.reason ?? ""),
|
|
294
317
|
"x-block-version": v.version ?? ""
|
|
295
318
|
};
|
|
@@ -298,7 +321,14 @@ function camada(_options) {
|
|
|
298
321
|
}
|
|
299
322
|
const warnRule = v.warn ? v.rule ?? null : null;
|
|
300
323
|
if (v.challenge && ip && challengeEnabled() && !isChallengeRoute(path)) {
|
|
301
|
-
|
|
324
|
+
const page = isChallengeable(req);
|
|
325
|
+
if (page || !isBeaconRoute(req.method, path)) {
|
|
326
|
+
return (0, import_core4.guardedAsync)(async () => {
|
|
327
|
+
const passed = await challengePassed(engine2, req, ip);
|
|
328
|
+
if (page && !passed) return serveChallenge(engine2, req, ip, path + new URL(req.url).search, waitUntil);
|
|
329
|
+
return capture(engine2, req, path, ip, existingSid, warnRule, waitUntil, !passed);
|
|
330
|
+
}, passThrough(req));
|
|
331
|
+
}
|
|
302
332
|
}
|
|
303
333
|
if (isBeaconRoute(req.method, path)) return passThrough(req);
|
|
304
334
|
return (0, import_core4.guardedAsync)(() => capture(engine2, req, path, ip, existingSid, warnRule, waitUntil), passThrough(req));
|
|
@@ -313,7 +343,6 @@ function camada(_options) {
|
|
|
313
343
|
var import_iife_string = __toESM(require("@camada/browser/iife-string"), 1);
|
|
314
344
|
var import_core5 = require("@camada/core");
|
|
315
345
|
var FP_MAX = 32 * 1024;
|
|
316
|
-
var encoder = new TextEncoder();
|
|
317
346
|
var notFound = () => new Response(null, { status: 404 });
|
|
318
347
|
var noContent = () => new Response(null, { status: 204, headers: { "cache-control": "no-store" } });
|
|
319
348
|
function blocked(engine2, req) {
|
|
@@ -327,7 +356,7 @@ function blocked(engine2, req) {
|
|
|
327
356
|
if (v.rule) ev.rl = v.rule;
|
|
328
357
|
engine2.queue.push(ev);
|
|
329
358
|
void engine2.queue.flush();
|
|
330
|
-
const headers = { "x-block-reason": String(v.reason ?? ""), "x-block-version": v.version ?? "" };
|
|
359
|
+
const headers = { "cache-control": "no-store", "x-block-reason": String(v.reason ?? ""), "x-block-version": v.version ?? "" };
|
|
331
360
|
if (v.rule) headers["x-block-rule"] = v.rule;
|
|
332
361
|
return new Response("Forbidden", { status: 403, headers });
|
|
333
362
|
}
|
|
@@ -364,14 +393,14 @@ function camadaRoute() {
|
|
|
364
393
|
return verifyChallenge(engine3, req);
|
|
365
394
|
}
|
|
366
395
|
if (lastSegment(req) !== "fp") return notFound();
|
|
367
|
-
const body = await req.text();
|
|
368
|
-
if (encoder.encode(body).byteLength > FP_MAX) return new Response(null, { status: 413 });
|
|
369
396
|
const engine2 = activeEngine();
|
|
370
397
|
if (engine2) {
|
|
371
398
|
engine2.snap.ensureFresh();
|
|
372
399
|
const deny = blocked(engine2, req);
|
|
373
400
|
if (deny) return deny;
|
|
374
401
|
if (beaconOff(engine2)) return notFound();
|
|
402
|
+
const body = await (0, import_core5.readBodyCapped)(req, FP_MAX);
|
|
403
|
+
if (body === null) return new Response(null, { status: 413 });
|
|
375
404
|
const ip = (0, import_core5.resolveClientIp)(null, req.headers.get("x-forwarded-for"), trustedProxy(engine2));
|
|
376
405
|
let parsed;
|
|
377
406
|
try {
|
|
@@ -380,7 +409,9 @@ function camadaRoute() {
|
|
|
380
409
|
return noContent();
|
|
381
410
|
}
|
|
382
411
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
383
|
-
|
|
412
|
+
const row = { ...parsed, sig: 1, ip, tap: import_core5.TAP_NEXT, rts: Date.now() };
|
|
413
|
+
for (const k of import_core5.REQUEST_ROW_KEYS) delete row[k];
|
|
414
|
+
engine2.queue.push(row);
|
|
384
415
|
void engine2.queue.flush();
|
|
385
416
|
}
|
|
386
417
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/engine.ts","../package.json","../src/version.ts","../src/challenge.ts","../src/event.ts","../src/route.ts","../src/beacon.tsx","../src/track.ts","../src/wait-until.ts","../src/with-camada.ts"],"sourcesContent":["// @camada/next — the three-line install:\n// middleware.ts / proxy.ts: export default camada();\n// app/api/camada/[...camada]/route.ts: export const { GET, POST } = camadaRoute();\n// app/layout.tsx <head>: <CamadaBeacon/>\n// Plus track() for app-context outcomes from server actions / route handlers, and withCamada(handler)\n// for a route handler whose event should carry the real status and dur.\nexport { camada, type CamadaMiddlewareOptions, type MiddlewareResult } from './middleware';\nexport { camadaRoute } from './route';\nexport { challengeGate } from './challenge';\nexport { CamadaBeacon, type CamadaBeaconProps } from './beacon';\nexport { track } from './track';\nexport { withCamada } from './with-camada';\n","// The Next.js middleware/proxy entry: enforce the blocklist inline, stamp x-camada-rid onto\n// the forwarded request (the <CamadaBeacon/> server component reads it back), ship the wire\n// event fire-and-forget. Everything runs inside a try/catch fail-open envelope — a camada\n// bug must never break the customer's app (plan.md INT-2).\n//\n// MUST stay edge-runtime-safe: Web APIs only (fetch/Headers/Request/Response/URL/crypto),\n// no node: imports anywhere in this entry's import graph. Proven by test/edge-safety.test.ts,\n// which bundles this file for a bare edge runtime and executes it inside @edge-runtime/vm.\n// NextRequest/NextFetchEvent are imported as TYPES only; NextResponse comes from\n// 'next/server', which is itself edge-safe.\nimport type { NextFetchEvent, NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport { resolveClientIp, logRateLimited, guarded, guardedAsync } from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { isChallengeRoute, challengePassed, serveChallenge } from './challenge';\nimport { buildEvent, cookieValue, middlewareMark, MW_HEADER, SESSION_COOKIE } from './event';\n\n/** camadaRoute()'s beacon endpoints (mounted at /api/camada/[...camada]). Camada's own, like core's scriptPath/fpPath:\n * after enforcement they are neither captured nor stamped with x-rid. */\nconst isBeaconRoute = (method: string, path: string): boolean =>\n (method === 'GET' && path === '/api/camada/b.js') || (method === 'POST' && path === '/api/camada/fp');\n\nexport interface CamadaMiddlewareOptions {\n // Reserved. The engine is configured via CAMADA_* environment variables.\n}\n\n/** Next accepts a promise here; the capture and challenge branches return one (they await WebCrypto). */\nexport type MiddlewareResult = Response | undefined | Promise<Response | undefined>;\n\n/** The ordinary path: ship one pre-response event, stamp the rid (and its proof for withCamada()),\n * mint the session cookie. `warnRule` is the id of the `warn` rule that let this request through, if one did (§D3). */\nasync function capture(engine: Engine, req: NextRequest, path: string, ip: string | null, existingSid: string | null, warnRule: string | null, waitUntil?: (p: Promise<unknown>) => void): Promise<Response> {\n const rid = crypto.randomUUID();\n const sid = existingSid ?? crypto.randomUUID();\n const cfg = engine.snap.config;\n const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));\n if (!excluded && Math.random() < (cfg?.sample ?? 1)) {\n // st stays null: middleware ships pre-response, like the edge collector's tap.\n const ev = buildEvent(req, path, ip, rid, sid, !existingSid);\n if (warnRule) ev.wrn = warnRule;\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n }\n\n const headers = new Headers(req.headers);\n headers.set('x-camada-rid', rid);\n // WebCrypto, the one await on this path; without it the request still goes through, just unproven (withCamada then ships too).\n // The headers are a copy of the client's: drop any mark it sent before stamping ours.\n headers.delete(MW_HEADER);\n const mark = await middlewareMark(engine.env.secret, req, rid).catch(() => null);\n if (mark) headers.set(MW_HEADER, mark);\n const res = NextResponse.next({ request: { headers } });\n res.headers.set('x-rid', rid);\n if (!existingSid) {\n const secure = new URL(req.url).protocol === 'https:' ? '; Secure' : '';\n res.headers.append('set-cookie', `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${secure}`);\n }\n return res;\n}\n\n/** Where camada lets a request through without capturing it: forward it without any x-camada-mw or x-camada-rid the\n * client sent, so the only mark and rid a route handler ever sees are ones this middleware made. Undefined (Next\n * continues untouched) when there is neither. */\nfunction passThrough(req: Request): Response | undefined {\n if (!req.headers.has(MW_HEADER) && !req.headers.has('x-camada-rid')) return undefined;\n const headers = new Headers(req.headers);\n headers.delete(MW_HEADER);\n headers.delete('x-camada-rid');\n return NextResponse.next({ request: { headers } });\n}\n\n/**\n * `export default camada();` from middleware.ts (Next ≤15) / proxy.ts (Next 16).\n * Returns undefined (Next continues) whenever camada is disabled, unconfigured, or broken; a client-sent\n * x-camada-mw or x-camada-rid is stripped on every path that forwards the request.\n */\nexport function camada(_options?: CamadaMiddlewareOptions): (req: NextRequest, event: NextFetchEvent) => MiddlewareResult {\n return function camadaMiddleware(req: NextRequest, event: NextFetchEvent): MiddlewareResult {\n try {\n if (isDisabled()) return passThrough(req);\n const engine = getEngine();\n if (!engine) return passThrough(req);\n const waitUntil = event?.waitUntil ? (p: Promise<unknown>) => event.waitUntil(p) : undefined;\n engine.snap.ensureFresh(waitUntil);\n\n // There is no socket peer at the edge: with no trusted-proxy config (or no XFF) the\n // ip resolves to null and ip rules simply don't enforce — a spoofed X-Forwarded-For\n // can never reach the blocklist. On Vercel the engine defaults to {mode:'vercel'}.\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n const path = new URL(req.url).pathname;\n\n // the same _sfp session as the collector and @camada/node: sid/ns comparable across taps\n const existingSid = cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE);\n\n // The custom rules read the user agent and the request headers (§D3); without them every\n // `ua` and `header` condition is false. `Headers.get` is case-insensitive, so the\n // lower-cased name the matcher asks with finds whatever spelling the client sent.\n const v = engine.snap.verdict({\n ip, path, ua: req.headers.get('user-agent'), header: (n) => req.headers.get(n),\n }); // cold start fails open ('cold')\n if (v.block) {\n const ev = buildEvent(req, path, ip, crypto.randomUUID(), existingSid, false);\n ev.st = 403; // blocked requests always ship, unsampled\n ev.blk = v.reason; // SDK-01: the reason rides the event so the analyst counts SDK blocks, not the app's own 403s ('rule' when a rule decided)\n if (v.rule) ev.rl = v.rule;\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n const headers: Record<string, string> = {\n 'content-type': 'text/plain',\n 'x-block-reason': String(v.reason ?? ''),\n 'x-block-version': v.version ?? '',\n };\n if (v.rule) headers['x-block-rule'] = v.rule; // a custom rule blocked: name it, so the customer knows which row to edit\n return new Response('Forbidden', { status: 403, headers });\n }\n // `warn` passes the request and only marks its event; a skip passes with nothing\n // stamped — it is the absence of enforcement.\n const warnRule = v.warn ? v.rule ?? null : null;\n\n // A challenge verdict: serve the proof-of-work page unless this client already passed.\n // The verify route answers its own endpoint, so never challenge that path. A challenge\n // needs a resolved ip (the nonce and `_cch` are bound to it) — without one, fail open,\n // the same stance ip rules take at this position.\n // A client that HAS passed falls through to the normal capture path: it keeps its rid,\n // its session cookie and its event, so an hour of `_cch` is not an hour of blindness.\n // guardedAsync covers the whole branch — the synchronous catch below cannot see a\n // rejection from these awaits, and WebCrypto is not guaranteed to exist.\n if (v.challenge && ip && challengeEnabled() && !isChallengeRoute(path)) {\n return guardedAsync(async () => (\n (await challengePassed(engine, req, ip))\n ? capture(engine, req, path, ip, existingSid, warnRule, waitUntil)\n : serveChallenge(engine, req, ip, path + new URL(req.url).search, waitUntil)\n ), passThrough(req));\n }\n\n if (isBeaconRoute(req.method, path)) return passThrough(req); // no row, no x-rid: the route handler answers it\n\n return guardedAsync(() => capture(engine, req, path, ip, existingSid, warnRule, waitUntil), passThrough(req));\n } catch (err) {\n logRateLimited(err); // fail open: the app proceeds as if camada were not installed\n return guarded(() => passThrough(req), undefined);\n }\n };\n}\n","// The lazy singleton engine behind every @camada/next entry point: one SnapshotClient in\n// 'lazy' mode (edge runtime — ensureFresh per request, no interval timers) plus one\n// EventQueue, wired from the environment on first use. Unconfigured -> engine is null, every\n// entry point stays inert, and one rate-limited console.error says why. CAMADA_DISABLED=1 is\n// checked per request (isDisabled), never memoized here.\n//\n// This module is part of the middleware's import graph and therefore MUST stay\n// edge-runtime-safe: Web APIs and process.env reads only, no node: imports.\nimport {\n SnapshotClient, EventQueue, parseKey, parseTrustedProxyEnv, logRateLimited,\n type SnapshotVersion, type TrustedProxyConfig,\n} from '@camada/core';\n\nimport { SDK_ID } from './version';\n\nexport { parseTrustedProxyEnv }; // re-export for callers that had reached into this module\n\nexport interface ResolvedEnv {\n ingestToken: string;\n snapToken: string;\n secret: string; // HMAC key for the challenge nonce/cookie — never leaves the process\n ingestUrl: string;\n snapshotUrl: string;\n trustedProxy: TrustedProxyConfig | null; // null = defer to server-delivered config\n}\n\n/** Returns null (SDK stays inert, one log line) rather than throwing on bad config. */\nexport function resolveEnv(env: Record<string, string | undefined>): ResolvedEnv | null {\n const key = parseKey(env.CAMADA_KEY);\n const ingestToken = key?.ingestToken ?? env.CAMADA_TOKEN;\n const snapToken = key?.snapToken ?? env.CAMADA_SNAPSHOT_TOKEN;\n if (!ingestToken || !snapToken) return null;\n const ingestUrl = (env.CAMADA_INGEST_URL || 'https://in.camada.app').replace(/\\/$/, '');\n return {\n ingestToken, snapToken, ingestUrl,\n secret: env.CAMADA_KEY || `${ingestToken}.${snapToken}`,\n snapshotUrl: env.CAMADA_SNAPSHOT_URL || `${ingestUrl}/snapshot`,\n // On Vercel the platform overwrites X-Forwarded-For, so its rightmost entry is\n // trustworthy: default to vercel mode there unless explicitly overridden.\n trustedProxy: parseTrustedProxyEnv(env.CAMADA_TRUSTED_PROXY) ?? (env.VERCEL ? { mode: 'vercel' } : null),\n };\n}\n\nexport interface Engine {\n env: ResolvedEnv;\n snap: SnapshotClient;\n queue: EventQueue;\n fetchImpl: typeof fetch; // for the beacon relay; snapshot/queue carry their own copy\n}\n\nexport interface ConfigureOptions {\n env?: Record<string, string | undefined>;\n fetchImpl?: typeof fetch;\n challenge?: boolean; // enforce `challenge` verdicts with the first-party page (default true)\n snapshotVersion?: SnapshotVersion; // 5 (default) also carries the tenant's ordered custom rules; 4 the allow/challenge sides only; 3 opts out of both\n}\n\nlet engine: Engine | null | undefined; // undefined = not built yet; null = unconfigured\nlet overrides: ConfigureOptions = {};\n\n/** Internal test/reset hook (mirrors @camada/node's configure): replaces the singleton. */\nexport function configure(opts: ConfigureOptions = {}): void {\n engine?.snap.stop();\n engine?.queue.stop();\n overrides = opts;\n engine = undefined;\n}\n\nexport function envSource(): Record<string, string | undefined> {\n return overrides.env ?? (typeof process !== 'undefined' && process.env ? process.env : {});\n}\n\n/** Kill switch, checked per request so an env flip disables camada without a code change. */\nexport function isDisabled(): boolean {\n return envSource().CAMADA_DISABLED === '1';\n}\n\n/** SDK-04 is on unless the app opts out in code or with CAMADA_CHALLENGE=0. */\nexport function challengeEnabled(): boolean {\n return overrides.challenge !== false && envSource().CAMADA_CHALLENGE !== '0';\n}\n\nexport function getEngine(): Engine | null {\n if (engine !== undefined) return engine;\n const env = resolveEnv(envSource());\n if (!env) {\n logRateLimited(new Error('CAMADA_KEY (or CAMADA_TOKEN + CAMADA_SNAPSHOT_TOKEN) not set — camada is inactive'));\n return (engine = null);\n }\n const fetchImpl = overrides.fetchImpl;\n const injected = fetchImpl ? { fetchImpl } : {}; // never pass an explicit undefined key\n engine = {\n env,\n fetchImpl: fetchImpl ?? fetch,\n // snapshotVersion left undefined takes core's default (5); the client drops undefined keys.\n snap: new SnapshotClient({ url: env.snapshotUrl, token: env.snapToken, mode: 'lazy', sdk: SDK_ID, snapshotVersion: overrides.snapshotVersion, ...injected }),\n queue: new EventQueue({ url: env.ingestUrl, token: env.ingestToken, sdk: SDK_ID, ...injected }),\n };\n return engine;\n}\n\n/** The trusted-proxy config in force: explicit local/env override wins, then the\n * server-delivered tenant config, then none (socket-less edge -> ip stays null). */\nexport function trustedProxy(e: Engine): TrustedProxyConfig | null {\n return e.env.trustedProxy ?? e.snap.config?.trusted_proxy ?? null;\n}\n","{\n \"version\": \"0.3.1\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"name\": \"@camada/next\",\n \"description\": \"camada backend SDK for Next.js: edge-safe middleware/proxy, /api/camada route handlers, <CamadaBeacon/> with the request rid\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\",\n \"@camada/react\": \"file:../camada-react\"\n },\n \"peerDependencies\": {\n \"next\": \">=15\",\n \"react\": \">=18\"\n },\n \"devDependencies\": {\n \"typescript\": \"^5.7.2\",\n \"tsup\": \"^8.3.5\",\n \"vitest\": \"^3.0.0\",\n \"next\": \"^16.0.0\",\n \"react\": \"^19.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"@edge-runtime/vm\": \"^5.0.0\",\n \"esbuild\": \"^0.24.0\",\n \"@types/node\": \"^22.10.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The challenge for @camada/next (SDK-04). Edge-runtime-safe: Web APIs and WebCrypto only, no\n// node: imports — this module sits in the middleware's import graph, which\n// test/edge-safety.test.ts bundles for a bare edge runtime.\n//\n// The page is served by the middleware (or by challengeGate() from a route the app gates\n// itself) and posts a form to POST /api/camada/challenge, handled by camadaRoute().\nimport {\n createChallengeAsync, challengePage, challengeCookie, safeReturnTo, wantsHtml, parseFormBody,\n resolveClientIp, CHALLENGE_COOKIE, type AsyncChallengeKit,\n} from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { buildEvent, cookieValue, middlewareMarkValid, SESSION_COOKIE } from './event';\n\n/** Where the page posts. The catch-all route handler must be mounted at /api/camada/[...camada]\n * (the documented install) — the served page hard-codes this action. */\nexport const VERIFY_PATH = '/api/camada/challenge';\n\nconst BODY_MAX = 4 * 1024; // the verify form is ~120 bytes; anything larger is not ours\n\n/** The app may mount the catch-all elsewhere, so the middleware matches on the tail. */\nexport const isChallengeRoute = (path: string): boolean => /\\/camada\\/challenge\\/?$/.test(path);\n\n// One kit per engine; the engine is replaced wholesale by configure(), so the map never grows.\nconst kits = new WeakMap<Engine, AsyncChallengeKit>();\nexport function kitFor(engine: Engine): AsyncChallengeKit {\n let k = kits.get(engine);\n if (!k) { k = createChallengeAsync({ secret: engine.env.secret }); kits.set(engine, k); }\n return k;\n}\n\nexport const clientIp = (engine: Engine, req: Request): string | null =>\n resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n\nexport function challengePassed(engine: Engine, req: Request, ip: string): Promise<boolean> {\n return kitFor(engine).tokenValid(ip, Date.now(), cookieValue(req.headers.get('cookie') || '', CHALLENGE_COOKIE));\n}\n\n/** 403 + the proof-of-work page (HTML navigations) or 403 JSON (everything else), plus the\n * `blk: \"challenge\"` event — a served challenge is reported like a block (contract §D2). */\nexport async function serveChallenge(engine: Engine, req: Request, ip: string, target: string, waitUntil?: (p: Promise<unknown>) => void, ship = true): Promise<Response> {\n if (ship) {\n const ev = buildEvent(req, new URL(req.url).pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 403;\n ev.blk = 'challenge';\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n }\n\n if (!wantsHtml(req.headers.get('accept'), req.headers.get('sec-fetch-dest'))) {\n return new Response('{\"error\":\"challenge_required\"}', {\n status: 403,\n headers: { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' },\n });\n }\n const html = challengePage({ nonce: await kitFor(engine).nonce(ip, Date.now()), action: VERIFY_PATH, to: safeReturnTo(target) });\n return new Response(html, {\n status: 403,\n headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-camada-challenge': '1' },\n });\n}\n\n/** POST handler for `…/camada/challenge`: validate, set `_cch`, 302 back, ship `{ st: 200, ch: 1 }`. */\nexport async function verifyChallenge(engine: Engine, req: Request): Promise<Response> {\n const url = new URL(req.url);\n const ip = clientIp(engine, req);\n const kit = kitFor(engine);\n // camada answers this path before the app runs, so it must not become a place to post\n // hundreds of megabytes at an unauthenticated endpoint.\n if (Number(req.headers.get('content-length')) > BODY_MAX) return new Response(null, { status: 413 });\n const body = await req.text();\n if (body.length > BODY_MAX) return new Response(null, { status: 413 });\n const form = parseFormBody(body);\n const to = safeReturnTo(form.to);\n const now = Date.now();\n if (!ip || !(await kit.verify(ip, now, form.nonce, form.solution))) {\n const html = ip ? challengePage({ nonce: await kit.nonce(ip, now), action: url.pathname, to }) : 'Forbidden';\n return new Response(html, { status: 403, headers: { 'content-type': ip ? 'text/html; charset=utf-8' : 'text/plain', 'cache-control': 'no-store' } });\n }\n const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 200;\n ev.ch = 1; // challenge passed (contract §A3 ingest field)\n engine.queue.push(ev);\n void engine.queue.flush();\n return new Response(null, {\n status: 302,\n headers: {\n location: to,\n 'set-cookie': challengeCookie(await kit.issue(ip, now), url.protocol === 'https:'),\n 'cache-control': 'no-store',\n },\n });\n}\n\n/** For a route that wants to gate itself (the example's /challenge-me): the challenge Response,\n * or null when this client already holds a valid `_cch` and the route should render normally.\n *\n * One request, one event. The middleware stamps `x-camada-rid` and its `x-camada-mw` proof on\n * the request it forwards, so a mark that verifies means the middleware already shipped this\n * request's row (a client-sent rid alone proves nothing); the gate then serves\n * the page WITHOUT a second row, which would double-count the request at the analyst. Exclude\n * the gated route from the middleware matcher and the gate ships the `blk: \"challenge\"` row\n * itself — that is the configuration to use when you want the challenge counted. */\nexport async function challengeGate(req: Request): Promise<Response | null> {\n if (isDisabled() || !challengeEnabled()) return null;\n const engine = getEngine();\n if (!engine) return null;\n const ip = clientIp(engine, req);\n if (!ip || await challengePassed(engine, req, ip)) return null; // unidentifiable client: fail open\n const url = new URL(req.url);\n const shipped = await middlewareMarkValid(engine.env.secret, req).catch(() => false);\n return serveChallenge(engine, req, ip, url.pathname + url.search, undefined, !shipped);\n}\n","// One wire-event builder for every @camada/next entry (middleware + beacon route). Web APIs\n// only: this sits in the middleware's edge-safe import graph.\nimport { buildWireEvent, hmacHex, safeEqual, TAP_NEXT, type WireEvent } from '@camada/core';\n\nexport function buildEvent(req: Request, path: string, ip: string | null, rid: string, sid: string | null, newSession: boolean): WireEvent {\n const url = new URL(req.url);\n return buildWireEvent(\n {\n method: req.method,\n host: req.headers.get('host') ?? url.host,\n path,\n query: url.search,\n // The edge runtime sorts header names, so hord is alphabetical at this tap — still\n // shipped; the scorer knows sdk-next lacks the raw-wire-order signal (capability mask).\n headers: [...req.headers.entries()],\n ip,\n httpVersion: null, // not observable at this position\n },\n { tap: TAP_NEXT, rid, sid, newSession, ja4: req.headers.get('x-vercel-ja4-digest') },\n );\n}\n\n/** Stamped by the middleware next to x-camada-rid on the request it forwards: proof, for withCamada(), that this request's\n * event already shipped. It only ever spares the wrapper a second event; enforcement never depends on it. */\nexport const MW_HEADER = 'x-camada-mw';\nexport const MW_WINDOW_MS = 30_000; // middleware → route handler takes milliseconds; 30 s covers a cold function start\nconst MW_SKEW_MS = 5_000; // the middleware's clock may run ahead of the function's (edge vs. Node on Vercel)\n\n/** What the mark binds: when it was made, the method, the path and query, the rid, and the client chain as forwarded.\n * JSON keeps the fields apart, since the rid and X-Forwarded-For are whatever the client sent where the middleware did not run. */\nconst markInput = (ts: number, req: Request, rid: string): string => {\n const url = new URL(req.url);\n return 'mw:' + JSON.stringify([ts, req.method, url.pathname + url.search, rid, req.headers.get('x-forwarded-for') ?? '']);\n};\n\n/** `<ms timestamp>.<HMAC-SHA256 hex>` under the server-only secret. */\nexport async function middlewareMark(secret: string, req: Request, rid: string, now = Date.now()): Promise<string> {\n return `${now}.${await hmacHex(secret, markInput(now, req, rid))}`;\n}\n\n/** True only for a mark this deployment's middleware made for this very request line and client, within the window.\n * A copied pair fails on another path, query, method or client, and on any request once the window has passed. */\nexport async function middlewareMarkValid(secret: string, req: Request, now = Date.now()): Promise<boolean> {\n const m = /^(\\d{13})\\.([0-9a-f]{64})$/.exec(req.headers.get(MW_HEADER) ?? '');\n const rid = req.headers.get('x-camada-rid');\n if (!m || !rid) return false;\n const ts = Number(m[1]);\n if (now - ts > MW_WINDOW_MS || ts - now > MW_SKEW_MS) return false;\n return safeEqual(m[2], await hmacHex(secret, markInput(ts, req, rid)));\n}\n\nexport const SESSION_COOKIE = '_sfp'; // the same session cookie as the edge collector and @camada/node: sid/ns comparable across taps\n\nexport const cookieValue = (cookie: string, name: string): string | null => {\n const src = '; ' + cookie;\n const i = src.indexOf('; ' + name + '=');\n if (i === -1) return null;\n const start = i + name.length + 3;\n const j = src.indexOf(';', start);\n return src.slice(start, j === -1 ? undefined : j);\n};\n","// Route handlers for app/api/camada/[...camada]/route.ts:\n// GET …/b.js -> the first-party beacon IIFE (its auto-init derives the fp endpoint from\n// the script URL's final path segment and the rid from ?r=)\n// POST …/fp -> queue the beacon as a sig:1 row with the trusted-proxy-resolved client IP\n// and tap 'sdk-next': it rides the event batch (one request per flush at the\n// analyst, not one per page view) — the mirror of @camada/node's /_cam/*.\n// Works on BOTH runtimes (edge and node): Web APIs only, no node: imports. Unconfigured or\n// disabled: GET 404s, POST answers 204 and drops — inert, never an error.\nimport iife from '@camada/browser/iife-string';\nimport { guardedAsync, resolveClientIp, TAP_NEXT } from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { verifyChallenge } from './challenge';\nimport { buildEvent, cookieValue, SESSION_COOKIE } from './event';\n\nconst FP_MAX = 32 * 1024; // matches the server's /fp cap: never accept what ingest will 413\nconst encoder = new TextEncoder();\n\nconst notFound = () => new Response(null, { status: 404 });\nconst noContent = () => new Response(null, { status: 204, headers: { 'cache-control': 'no-store' } });\n\n// The route handlers enforce too: a middleware matcher that excludes /api/ (a common pattern)\n// must not leave the beacon endpoints serving blocked clients.\nfunction blocked(engine: Engine, req: Request): Response | null {\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n const path = new URL(req.url).pathname;\n // §D3: `ua` and `header` rules need these; `Headers.get` already matches names case-insensitively\n const v = engine.snap.verdict({ ip, path, ua: req.headers.get('user-agent'), header: (n) => req.headers.get(n) });\n if (!v.block) return null;\n const ev = buildEvent(req, path, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 403; ev.blk = v.reason; // SDK-01: a beacon-route deny is a block like any other — it ships, unsampled\n if (v.rule) ev.rl = v.rule;\n engine.queue.push(ev);\n void engine.queue.flush();\n const headers: Record<string, string> = { 'x-block-reason': String(v.reason ?? ''), 'x-block-version': v.version ?? '' };\n if (v.rule) headers['x-block-rule'] = v.rule;\n return new Response('Forbidden', { status: 403, headers });\n}\n\nfunction lastSegment(req: Request): string {\n const parts = new URL(req.url).pathname.split('/');\n return parts[parts.length - 1];\n}\n\nfunction activeEngine(): Engine | null {\n return isDisabled() ? null : getEngine();\n}\n\n// tenant switch: both beacon endpoints stand down together (b.js and fp), as node's and hono's do\nconst beaconOff = (engine: Engine): boolean => engine.snap.config?.beacon === false;\n\nexport function camadaRoute(): {\n GET: (req: Request) => Promise<Response>;\n POST: (req: Request) => Promise<Response>;\n} {\n return {\n GET: (req) => guardedAsync(async () => {\n if (lastSegment(req) !== 'b.js') return notFound();\n const engine = activeEngine();\n if (!engine) return notFound();\n engine.snap.ensureFresh();\n const deny = blocked(engine, req);\n if (deny) return deny;\n if (beaconOff(engine)) return notFound();\n return new Response(iife, {\n status: 200,\n headers: { 'content-type': 'application/javascript', 'cache-control': 'public, max-age=3600' },\n });\n }, notFound()),\n\n POST: (req) => guardedAsync(async () => {\n if (lastSegment(req) === 'challenge') {\n const engine = activeEngine();\n if (!engine || !challengeEnabled()) return notFound();\n engine.snap.ensureFresh();\n const deny = blocked(engine, req); // a blocked client cannot buy its way past with a solution\n if (deny) return deny;\n return verifyChallenge(engine, req);\n }\n if (lastSegment(req) !== 'fp') return notFound();\n const body = await req.text();\n if (encoder.encode(body).byteLength > FP_MAX) return new Response(null, { status: 413 });\n const engine = activeEngine();\n if (engine) {\n engine.snap.ensureFresh();\n const deny = blocked(engine, req);\n if (deny) return deny;\n if (beaconOff(engine)) return notFound();\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n let parsed: unknown;\n try { parsed = JSON.parse(body); } catch { return noContent(); } // not a beacon: drop it, never ship junk\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n engine.queue.push({ ...(parsed as Record<string, unknown>), sig: 1, ip, tap: TAP_NEXT });\n void engine.queue.flush(); // fire-and-forget, as the relay was: a serverless runtime may freeze right after the response\n }\n }\n return noContent();\n }, noContent()),\n };\n}\n","// <CamadaBeacon/> — an async React Server Component for the app-router layout <head>: reads\n// the middleware-stamped x-camada-rid request header (so the beacon POST joins the server's\n// wire event) and renders the @camada/react beacon tag pointing at the camada route.\n// 'next/headers' is imported lazily inside the component so this module only touches it\n// server-side, in a request scope.\nimport { CamadaBeacon as Beacon } from '@camada/react';\n\nexport interface CamadaBeaconProps {\n /** Where camadaRoute() serves the beacon script. Default: '/api/camada/b.js'. */\n src?: string;\n}\n\nexport async function CamadaBeacon({ src = '/api/camada/b.js' }: CamadaBeaconProps = {}) {\n let rid: string | null = null;\n try {\n const { headers } = await import('next/headers');\n rid = (await headers()).get('x-camada-rid');\n } catch { /* outside a request scope (static render, tests): beacon ships without a rid */ }\n return <Beacon src={src} rid={rid} />;\n}\n","// track() — app-context outcome events recorded from a server action, route handler, or server\n// component, joined to the middleware's wire event. The name is free-form; the analyst's rules read\n// this vocabulary: login_failed, login_succeeded, signup, password_reset, mfa_failed, payment_failed,\n// payment_succeeded, coupon_failed (@camada/node README has the table). Inside withCamada() an\n// event joins on the wrapper's own rid and session; elsewhere on the x-camada-rid the middleware\n// stamps and the _sfp session cookie. The identifier is\n// HMAC-hashed in-process with the ingest token: the raw value never reaches the queue, and\n// the analyst drops anything that is not hash-shaped anyway.\n// next/headers is imported lazily inside the call (the beacon.tsx pattern) so this module\n// stays importable everywhere; calling track() outside a request scope is a no-op.\nimport { hashUserId, logRateLimited, resolveClientIp, TAP_NEXT } from '@camada/core';\nimport { getEngine, isDisabled, trustedProxy } from './engine';\nimport { requestWaitUntil } from './wait-until';\n\nconst SESSION_COOKIE = '_sfp';\n\ninterface Scope { rid: string; sid: string | null }\ninterface Als { run<R>(store: Scope, fn: () => R): R; getStore(): Scope | undefined }\nlet als: Als | null | undefined;\n/** Next puts AsyncLocalStorage on globalThis in both runtimes (node-environment-baseline, the edge sandbox). */\nconst scopes = (): Als | null => {\n if (als === undefined) {\n const Ctor = (globalThis as { AsyncLocalStorage?: new () => Als }).AsyncLocalStorage;\n als = Ctor ? new Ctor() : null; // ponytail: without it (never under Next) a wrapped route's track() falls back to the header\n }\n return als;\n};\n\n/** withCamada() runs its handler here, so track() inside it takes the wrapper's rid and never a\n * client-sent x-camada-rid: a wrapped route sits outside the matcher, where nothing strips it. */\nexport const withTrackScope = <R>(scope: Scope, fn: () => R): R => { const s = scopes(); return s ? s.run(scope, fn) : fn(); };\n\n/** Never throws, never blocks the response: a camada problem must not break a login. */\nexport async function track(event: string, data?: { user?: string }): Promise<void> {\n try {\n if (isDisabled()) return;\n const engine = getEngine();\n if (!engine) return;\n const { headers, cookies } = await import('next/headers');\n const [h, c] = await Promise.all([headers(), cookies()]);\n const scope = scopes()?.getStore();\n const uid = data?.user ? await hashUserId(data.user, engine.env.ingestToken) : null;\n engine.queue.push({\n tap: TAP_NEXT,\n et: event,\n uid,\n rid: scope ? scope.rid : h.get('x-camada-rid'),\n sid: scope?.sid ?? c.get(SESSION_COOKIE)?.value ?? null,\n ip: resolveClientIp(null, h.get('x-forwarded-for'), trustedProxy(engine)),\n ts: Date.now(),\n });\n void engine.queue.flush(requestWaitUntil()); // a serverless runtime may freeze right after the response: hold it open for the flush\n } catch (err) {\n logRateLimited(err); // outside a request scope, unconfigured, or a camada bug: swallow\n }\n}\n","// A waitUntil for code that runs inside a route handler or server action (withCamada, track()). A\n// serverless host (Vercel) may freeze the function once the response has gone out, and the event's\n// flush only starts then, when the body is done. Next's after() (15.1+) holds the function open\n// until its callback settles, but it must be called inside the request scope, which the body-done\n// callback is not. So the first promise handed in registers ONE after() callback; it runs once the\n// response has closed and drains every promise handed in, including any that arrive while it waits.\n// Without after() (Next 15.0, or outside a request scope) the host's request context waitUntil is\n// used, the one @vercel/functions reads; with neither, the promise is left to run, which a\n// long-lived `next start` process does anyway.\nimport * as nextServer from 'next/server';\n\ntype WaitUntil = (p: Promise<unknown>) => void;\ntype After = (task: () => Promise<unknown>) => void;\ntype RequestContext = { get?: () => { waitUntil?: WaitUntil } | undefined };\n\nfunction contextWaitUntil(): WaitUntil | undefined {\n const g = globalThis as Record<symbol, RequestContext | undefined>;\n for (const name of ['@next/request-context', '@vercel/request-context']) {\n const ctx = g[Symbol.for(name)]?.get?.();\n if (typeof ctx?.waitUntil === 'function') return (p) => ctx.waitUntil!(p);\n }\n return undefined;\n}\n\n/** Call once per request, inside its scope; hand the result to everything that flushes for it. Never throws. */\nexport function requestWaitUntil(): WaitUntil {\n const pending: Promise<unknown>[] = [];\n let hold: WaitUntil | null | undefined; // undefined = not decided yet; null = nothing to hold the host open with\n const drain = async () => {\n for (let i = 0; i < pending.length; i++) await pending[i].catch(() => {});\n };\n return (p) => {\n if (hold === undefined) {\n hold = null;\n try {\n const after = (nextServer as { after?: After }).after;\n if (typeof after !== 'function') throw new Error('no after()');\n after(drain); // throws outside a request scope\n hold = (q) => { pending.push(q); };\n } catch {\n try { hold = contextWaitUntil() ?? null; } catch { hold = null; }\n }\n }\n try { hold?.(p); } catch { /* a host that refuses the promise costs only the hold */ }\n };\n}\n","// withCamada(handler): the route-handler position, where the response is visible. The middleware\n// runs before the route and ships its event pre-response (st and dur null); a wrapped route\n// handler runs @camada/core's fetch pipeline itself (verdict, block, challenge, the session) and\n// ships one event with the real status and a dur up to the response (a text/event-stream body to its last byte).\n//\n// Threat model. Every request header can come from the client: the middleware does not run for a\n// route left out of its matcher, and Next offers no server-only channel from the middleware to the\n// route (on Vercel they run in different processes, often different regions). So:\n// - Enforcement here never depends on a header. Verdict, block and challenge run on every request.\n// - The one thing a header may decide is whether this request's event already shipped. Wrapped\n// routes belong outside the matcher; if the middleware ran anyway it stamps `x-camada-mw`, an\n// HMAC under the server-only CAMADA_KEY of a timestamp, the method, the path and query, the rid\n// and X-Forwarded-For. A mark that verifies within 30 s skips the second event and the second\n// session cookie, nothing else. A copied mark fails on another path, query, method or client\n// and after the window; within it, it can at most hide a replay's event, never pass a block.\n// - The middleware strips any x-camada-mw the client sent before forwarding.\n// - track() inside the handler takes this wrapper's rid and session (withTrackScope), never a\n// client-sent x-camada-rid; only a request the middleware already shipped keeps its stamped one.\nimport iife from '@camada/browser/iife-string';\nimport { logRateLimited, TAP_NEXT } from '@camada/core';\nimport { createFetchCamada, withSetCookie } from '@camada/core/fetch';\nimport { envSource, getEngine } from './engine';\nimport { VERIFY_PATH } from './challenge';\nimport { middlewareMarkValid } from './event';\nimport { requestWaitUntil } from './wait-until';\nimport { withTrackScope } from './track';\nimport { SDK_ID } from './version';\n\nconst cam = createFetchCamada({ tap: TAP_NEXT, sdk: SDK_ID, iife }, { challengePath: VERIFY_PATH }); // lazy mode: edge and serverless\n\nasync function middlewareShipped(req: Request): Promise<boolean> {\n const secret = getEngine()?.env.secret;\n return !!secret && (await middlewareMarkValid(secret, req));\n}\n\n/** Next's redirect() / notFound() throw an error whose digest ends in the status they answer with. */\nconst thrownStatus = (err: unknown): number =>\n Number(/;(\\d{3});?$/.exec(String((err as { digest?: unknown } | null)?.digest ?? ''))?.[1] ?? 500);\n\n/**\n * `export const GET = withCamada(async (req) => Response.json(...))` in a route.ts. Same\n * CAMADA_* env as the middleware; inert without a key or with CAMADA_DISABLED=1. Never breaks\n * the handler: a camada failure costs the event, not the response.\n */\nexport function withCamada<R extends Request, A extends unknown[]>(\n handler: (req: R, ...rest: A) => Response | Promise<Response>,\n): (req: R, ...rest: A) => Promise<Response> {\n return async (req, ...rest) => {\n const env = envSource();\n // On Vercel the platform overwrites X-Forwarded-For, so its rightmost entry is trustworthy: the middleware's default too.\n const r = await cam.before(req, {\n env: { ...env, CAMADA_TRUSTED_PROXY: env.CAMADA_TRUSTED_PROXY || (env.VERCEL ? 'vercel' : undefined) },\n waitUntil: requestWaitUntil(), // the event flushes after the body: hold a serverless function open for it\n });\n if (!r) return handler(req, ...rest);\n if (r.response) return r.response; // block, challenge: whatever headers the request carries\n let shipped = false;\n try { shipped = await middlewareShipped(req); } catch (err) { logRateLimited(err); }\n if (shipped) {\n logRateLimited(new Error(`withCamada: the middleware also matched ${new URL(req.url).pathname}, so its pre-response event stands and dur is not reported; exclude wrapped routes from the matcher`));\n return handler(req, ...rest); // and its _sfp cookie: no second session\n }\n let res: Response;\n try {\n res = await withTrackScope({ rid: r.vars.rid, sid: r.vars.sid }, () => handler(req, ...rest));\n } catch (err) {\n cam.after(req, r.vars, thrownStatus(err));\n throw err;\n }\n res = withRid(res, r.vars.rid);\n return cam.finish(req, r.vars, r.vars.sessionCookie ? withSetCookie(res, r.vars.sessionCookie) : res);\n };\n}\n\n/** x-rid, the rid of this request's row, the same header the middleware stamps. Set in place, or on a copy when the\n * headers are immutable (`Response.redirect()`, a `fetch()` result), as withSetCookie does: same status and body.\n * A 101 upgrade is left alone. Not on a request the middleware shipped: its own x-rid, that row's, reaches the client. */\nfunction withRid(res: Response, rid: string): Response {\n if (res.status === 101) return res;\n try {\n res.headers.set('x-rid', rid);\n return res;\n } catch {\n // ponytail: core's copyResponse is not exported; its Deno and @hono/node-server cases never reach a Next route.\n const out = new Response(res.body, res);\n out.headers.set('x-rid', rid);\n return out;\n }\n}\n"],"mappings":"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","names":["import_core","import_core","import_core","name","engine","engine","import_core","engine","iife","Beacon","import_core","name","after","SESSION_COOKIE","engine","import_iife_string","import_core","iife"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/engine.ts","../package.json","../src/version.ts","../src/challenge.ts","../src/event.ts","../src/route.ts","../src/beacon.tsx","../src/track.ts","../src/wait-until.ts","../src/with-camada.ts"],"sourcesContent":["// @camada/next — the three-line install:\n// middleware.ts / proxy.ts: export default camada();\n// app/api/camada/[...camada]/route.ts: export const { GET, POST } = camadaRoute();\n// app/layout.tsx <head>: <CamadaBeacon/>\n// Plus track() for app-context outcomes from server actions / route handlers, and withCamada(handler)\n// for a route handler whose event should carry the real status and dur.\nexport { camada, type CamadaMiddlewareOptions, type MiddlewareResult } from './middleware';\nexport { camadaRoute } from './route';\nexport { challengeGate } from './challenge';\nexport { CamadaBeacon, type CamadaBeaconProps } from './beacon';\nexport { track } from './track';\nexport { withCamada } from './with-camada';\n","// The Next.js middleware/proxy entry: enforce the blocklist inline, stamp x-camada-rid onto\n// the forwarded request (the <CamadaBeacon/> server component reads it back), ship the wire\n// event fire-and-forget. Everything runs inside a try/catch fail-open envelope — a camada\n// bug must never break the customer's app (plan.md INT-2).\n//\n// MUST stay edge-runtime-safe: Web APIs only (fetch/Headers/Request/Response/URL/crypto),\n// no node: imports anywhere in this entry's import graph. Proven by test/edge-safety.test.ts,\n// which bundles this file for a bare edge runtime and executes it inside @edge-runtime/vm.\n// NextRequest/NextFetchEvent are imported as TYPES only; NextResponse comes from\n// 'next/server', which is itself edge-safe.\nimport type { NextFetchEvent, NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport { resolveClientIp, logRateLimited, guarded, guardedAsync } from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { isChallengeRoute, challengePassed, serveChallenge, isChallengeable } from './challenge';\nimport { buildEvent, cookieValue, middlewareMark, MW_HEADER, SESSION_COOKIE } from './event';\n\n/** Per-client signal: no shared cache may keep the copy. CDNs read their own header ahead of cache-control. */\nconst NO_STORE_HEADERS = ['cache-control', 'cdn-cache-control', 'surrogate-control', 'cloudflare-cdn-cache-control', 'vercel-cdn-cache-control', 'netlify-cdn-cache-control'];\n\n/** camadaRoute()'s beacon endpoints (mounted at /api/camada/[...camada]). Camada's own, like core's scriptPath/fpPath:\n * after enforcement they are neither captured nor stamped with x-rid. */\nconst isBeaconRoute = (method: string, path: string): boolean =>\n (method === 'GET' && path === '/api/camada/b.js') || (method === 'POST' && path === '/api/camada/fp');\n\nexport interface CamadaMiddlewareOptions {\n // Reserved. The engine is configured via CAMADA_* environment variables.\n}\n\n/** Next accepts a promise here; the capture and challenge branches return one (they await WebCrypto). */\nexport type MiddlewareResult = Response | undefined | Promise<Response | undefined>;\n\n/** The ordinary path: ship one pre-response event, stamp the rid (and its proof for withCamada()),\n * mint the session cookie. `signalled`: a non-HTML request that passed under a challenge (82ar): `x-camada-challenge: 1` on the\n * response and `chs: 1` on the event, which then ships whatever sample and exclude say. `warnRule` is the id of the `warn` rule that let this request through, if one did (§D3). */\nasync function capture(engine: Engine, req: NextRequest, path: string, ip: string | null, existingSid: string | null, warnRule: string | null, waitUntil?: (p: Promise<unknown>) => void, signalled = false): Promise<Response> {\n const rid = crypto.randomUUID();\n const sid = existingSid ?? crypto.randomUUID();\n const cfg = engine.snap.config;\n const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));\n if (signalled || (!excluded && Math.random() < (cfg?.sample ?? 1))) {\n // st stays null: middleware ships pre-response, like the edge collector's tap.\n const ev = buildEvent(req, path, ip, rid, sid, !existingSid);\n if (warnRule) ev.wrn = warnRule;\n if (signalled) ev.chs = 1;\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n }\n\n const headers = new Headers(req.headers);\n headers.set('x-camada-rid', rid);\n // WebCrypto, the one await on this path; without it the request still goes through, just unproven (withCamada then ships too).\n // The headers are a copy of the client's: drop any mark it sent before stamping ours.\n headers.delete(MW_HEADER);\n const mark = await middlewareMark(engine.env.secret, req, rid).catch(() => null);\n if (mark) headers.set(MW_HEADER, mark);\n const res = NextResponse.next({ request: { headers } });\n res.headers.set('x-rid', rid);\n if (signalled) {\n res.headers.set('x-camada-challenge', '1');\n for (const n of NO_STORE_HEADERS) res.headers.set(n, 'no-store'); // the signal is per client: a shared cache must not hand it to others\n }\n if (!existingSid) {\n const secure = new URL(req.url).protocol === 'https:' ? '; Secure' : '';\n res.headers.append('set-cookie', `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${secure}`);\n }\n return res;\n}\n\n/** Where camada lets a request through without capturing it: forward it without any x-camada-mw or x-camada-rid the\n * client sent, so the only mark and rid a route handler ever sees are ones this middleware made. Undefined (Next\n * continues untouched) when there is neither. */\nfunction passThrough(req: Request): Response | undefined {\n if (!req.headers.has(MW_HEADER) && !req.headers.has('x-camada-rid')) return undefined;\n const headers = new Headers(req.headers);\n headers.delete(MW_HEADER);\n headers.delete('x-camada-rid');\n return NextResponse.next({ request: { headers } });\n}\n\n/**\n * `export default camada();` from middleware.ts (Next ≤15) / proxy.ts (Next 16).\n * Returns undefined (Next continues) whenever camada is disabled, unconfigured, or broken; a client-sent\n * x-camada-mw or x-camada-rid is stripped on every path that forwards the request.\n */\nexport function camada(_options?: CamadaMiddlewareOptions): (req: NextRequest, event: NextFetchEvent) => MiddlewareResult {\n return function camadaMiddleware(req: NextRequest, event: NextFetchEvent): MiddlewareResult {\n try {\n if (isDisabled()) return passThrough(req);\n const engine = getEngine();\n if (!engine) return passThrough(req);\n const waitUntil = event?.waitUntil ? (p: Promise<unknown>) => event.waitUntil(p) : undefined;\n engine.snap.ensureFresh(waitUntil);\n\n // There is no socket peer at the edge: with no trusted-proxy config (or no XFF) the\n // ip resolves to null and ip rules simply don't enforce — a spoofed X-Forwarded-For\n // can never reach the blocklist. On Vercel the engine defaults to {mode:'vercel'}.\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n const path = new URL(req.url).pathname;\n\n // the same _sfp session as the collector and @camada/node: sid/ns comparable across taps\n const existingSid = cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE);\n\n // The custom rules read the user agent and the request headers (§D3); without them every\n // `ua` and `header` condition is false. `Headers.get` is case-insensitive, so the\n // lower-cased name the matcher asks with finds whatever spelling the client sent.\n const v = engine.snap.verdict({\n ip, path, ua: req.headers.get('user-agent'), header: (n) => req.headers.get(n),\n }); // cold start fails open ('cold')\n if (v.block) {\n const ev = buildEvent(req, path, ip, crypto.randomUUID(), existingSid, false);\n ev.st = 403; // blocked requests always ship, unsampled\n ev.blk = v.reason; // SDK-01: the reason rides the event so the analyst counts SDK blocks, not the app's own 403s ('rule' when a rule decided)\n if (v.rule) ev.rl = v.rule;\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n const headers: Record<string, string> = {\n 'content-type': 'text/plain',\n 'cache-control': 'no-store',\n 'x-block-reason': String(v.reason ?? ''),\n 'x-block-version': v.version ?? '',\n };\n if (v.rule) headers['x-block-rule'] = v.rule; // a custom rule blocked: name it, so the customer knows which row to edit\n return new Response('Forbidden', { status: 403, headers });\n }\n // `warn` passes the request and only marks its event; a skip passes with nothing\n // stamped — it is the absence of enforcement.\n const warnRule = v.warn ? v.rule ?? null : null;\n\n // A challenge verdict: serve the proof-of-work page unless this client already passed.\n // The verify route answers its own endpoint, so never challenge that path. A challenge\n // needs a resolved ip (the nonce and `_cch` are bound to it) — without one, fail open,\n // the same stance ip rules take at this position.\n // A client that HAS passed falls through to the normal capture path: it keeps its rid,\n // its session cookie and its event, so an hour of `_cch` is not an hour of blindness.\n // guardedAsync covers the whole branch — the synchronous catch below cannot see a\n // rejection from these awaits, and WebCrypto is not guaranteed to exist.\n // Only a page or a Pages Router data request (`x-nextjs-data: 1`; Next turns a 403 into a document load) is challenged.\n // Any other request passes, signalled: x-camada-challenge: 1 and chs: 1 (82ar). That includes App Router soft navigations:\n // Next strips the RSC header before middleware runs, so they look like any fetch; the next document load meets the page.\n if (v.challenge && ip && challengeEnabled() && !isChallengeRoute(path)) {\n const page = isChallengeable(req);\n // A signalled beacon request falls through to the beacon branch below: camada's own answer carries no signal.\n if (page || !isBeaconRoute(req.method, path)) {\n return guardedAsync(async () => {\n const passed = await challengePassed(engine, req, ip);\n if (page && !passed) return serveChallenge(engine, req, ip, path + new URL(req.url).search, waitUntil);\n return capture(engine, req, path, ip, existingSid, warnRule, waitUntil, !passed);\n }, passThrough(req));\n }\n }\n\n if (isBeaconRoute(req.method, path)) return passThrough(req); // no row, no x-rid: the route handler answers it\n\n return guardedAsync(() => capture(engine, req, path, ip, existingSid, warnRule, waitUntil), passThrough(req));\n } catch (err) {\n logRateLimited(err); // fail open: the app proceeds as if camada were not installed\n return guarded(() => passThrough(req), undefined);\n }\n };\n}\n","// The lazy singleton engine behind every @camada/next entry point: one SnapshotClient in\n// 'lazy' mode (edge runtime — ensureFresh per request, no interval timers) plus one\n// EventQueue, wired from the environment on first use. Unconfigured -> engine is null, every\n// entry point stays inert, and one rate-limited console.error says why. CAMADA_DISABLED=1 is\n// checked per request (isDisabled), never memoized here.\n//\n// This module is part of the middleware's import graph and therefore MUST stay\n// edge-runtime-safe: Web APIs and process.env reads only, no node: imports.\nimport {\n SnapshotClient, EventQueue, parseKey, parseTrustedProxyEnv, logRateLimited,\n type SnapshotVersion, type TrustedProxyConfig,\n} from '@camada/core';\n\nimport { SDK_ID } from './version';\n\nexport { parseTrustedProxyEnv }; // re-export for callers that had reached into this module\n\nexport interface ResolvedEnv {\n ingestToken: string;\n snapToken: string;\n secret: string; // HMAC key for the challenge nonce/cookie — never leaves the process\n ingestUrl: string;\n snapshotUrl: string;\n trustedProxy: TrustedProxyConfig | null; // null = defer to server-delivered config\n}\n\n/** Returns null (SDK stays inert, one log line) rather than throwing on bad config. */\nexport function resolveEnv(env: Record<string, string | undefined>): ResolvedEnv | null {\n const key = parseKey(env.CAMADA_KEY);\n const ingestToken = key?.ingestToken ?? env.CAMADA_TOKEN;\n const snapToken = key?.snapToken ?? env.CAMADA_SNAPSHOT_TOKEN;\n if (!ingestToken || !snapToken) return null;\n const ingestUrl = (env.CAMADA_INGEST_URL || 'https://in.camada.app').replace(/\\/$/, '');\n return {\n ingestToken, snapToken, ingestUrl,\n secret: env.CAMADA_KEY || `${ingestToken}.${snapToken}`,\n snapshotUrl: env.CAMADA_SNAPSHOT_URL || `${ingestUrl}/snapshot`,\n // On Vercel the platform overwrites X-Forwarded-For, so its rightmost entry is\n // trustworthy: default to vercel mode there unless explicitly overridden.\n trustedProxy: parseTrustedProxyEnv(env.CAMADA_TRUSTED_PROXY) ?? (env.VERCEL ? { mode: 'vercel' } : null),\n };\n}\n\nexport interface Engine {\n env: ResolvedEnv;\n snap: SnapshotClient;\n queue: EventQueue;\n fetchImpl: typeof fetch; // for the beacon relay; snapshot/queue carry their own copy\n}\n\nexport interface ConfigureOptions {\n env?: Record<string, string | undefined>;\n fetchImpl?: typeof fetch;\n challenge?: boolean; // enforce `challenge` verdicts with the first-party page (default true)\n snapshotVersion?: SnapshotVersion; // 5 (default) also carries the tenant's ordered custom rules; 4 the allow/challenge sides only; 3 opts out of both\n}\n\nlet engine: Engine | null | undefined; // undefined = not built yet; null = unconfigured\nlet overrides: ConfigureOptions = {};\n\n/** Internal test/reset hook (mirrors @camada/node's configure): replaces the singleton. */\nexport function configure(opts: ConfigureOptions = {}): void {\n engine?.snap.stop();\n engine?.queue.stop();\n overrides = opts;\n engine = undefined;\n}\n\nexport function envSource(): Record<string, string | undefined> {\n return overrides.env ?? (typeof process !== 'undefined' && process.env ? process.env : {});\n}\n\n/** Kill switch, checked per request so an env flip disables camada without a code change. */\nexport function isDisabled(): boolean {\n return envSource().CAMADA_DISABLED === '1';\n}\n\n/** SDK-04 is on unless the app opts out in code or with CAMADA_CHALLENGE=0. */\nexport function challengeEnabled(): boolean {\n return overrides.challenge !== false && envSource().CAMADA_CHALLENGE !== '0';\n}\n\nexport function getEngine(): Engine | null {\n if (engine !== undefined) return engine;\n const env = resolveEnv(envSource());\n if (!env) {\n logRateLimited(new Error('CAMADA_KEY (or CAMADA_TOKEN + CAMADA_SNAPSHOT_TOKEN) not set — camada is inactive'));\n return (engine = null);\n }\n const fetchImpl = overrides.fetchImpl;\n const injected = fetchImpl ? { fetchImpl } : {}; // never pass an explicit undefined key\n engine = {\n env,\n fetchImpl: fetchImpl ?? fetch,\n // snapshotVersion left undefined takes core's default (5); the client drops undefined keys.\n snap: new SnapshotClient({ url: env.snapshotUrl, token: env.snapToken, mode: 'lazy', sdk: SDK_ID, snapshotVersion: overrides.snapshotVersion, ...injected }),\n queue: new EventQueue({ url: env.ingestUrl, token: env.ingestToken, sdk: SDK_ID, ...injected }),\n };\n return engine;\n}\n\n/** The trusted-proxy config in force: explicit local/env override wins, then the\n * server-delivered tenant config, then none (socket-less edge -> ip stays null). */\nexport function trustedProxy(e: Engine): TrustedProxyConfig | null {\n return e.env.trustedProxy ?? e.snap.config?.trusted_proxy ?? null;\n}\n","{\n \"version\": \"0.4.0\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"name\": \"@camada/next\",\n \"description\": \"camada backend SDK for Next.js: edge-safe middleware/proxy, /api/camada route handlers, <CamadaBeacon/> with the request rid\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\",\n \"@camada/react\": \"file:../camada-react\"\n },\n \"peerDependencies\": {\n \"next\": \">=15\",\n \"react\": \">=18\"\n },\n \"devDependencies\": {\n \"typescript\": \"^5.7.2\",\n \"tsup\": \"^8.3.5\",\n \"vitest\": \"^3.0.0\",\n \"next\": \"^16.0.0\",\n \"react\": \"^19.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"@edge-runtime/vm\": \"^5.0.0\",\n \"esbuild\": \"^0.24.0\",\n \"@types/node\": \"^22.10.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The challenge for @camada/next (SDK-04). Edge-runtime-safe: Web APIs and WebCrypto only, no\n// node: imports — this module sits in the middleware's import graph, which\n// test/edge-safety.test.ts bundles for a bare edge runtime.\n//\n// The page is served by the middleware (or by challengeGate() from a route the app gates\n// itself) and posts a form to POST /api/camada/challenge, handled by camadaRoute().\nimport {\n createChallengeAsync, challengePage, challengePageCsp, challengeCookie, safeReturnTo, wantsHtml, parseFormBody,\n resolveClientIp, readBodyCapped, CHALLENGE_COOKIE, type AsyncChallengeKit,\n} from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { buildEvent, cookieValue, middlewareMarkValid, SESSION_COOKIE } from './event';\n\n/** Where the page posts. The catch-all route handler must be mounted at /api/camada/[...camada]\n * (the documented install) — the served page hard-codes this action. */\nexport const VERIFY_PATH = '/api/camada/challenge';\n\nconst BODY_MAX = 4 * 1024; // the verify form is ~120 bytes; anything larger is not ours\n\n/** The app may mount the catch-all elsewhere, so the middleware matches on the tail. */\nexport const isChallengeRoute = (path: string): boolean => /\\/camada\\/challenge\\/?$/.test(path);\n\n// One kit per engine; the engine is replaced wholesale by configure(), so the map never grows.\nconst kits = new WeakMap<Engine, AsyncChallengeKit>();\nexport function kitFor(engine: Engine): AsyncChallengeKit {\n let k = kits.get(engine);\n if (!k) { k = createChallengeAsync({ secret: engine.env.secret }); kits.set(engine, k); }\n return k;\n}\n\nexport const clientIp = (engine: Engine, req: Request): string | null =>\n resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n\nexport function challengePassed(engine: Engine, req: Request, ip: string): Promise<boolean> {\n return kitFor(engine).tokenValid(ip, Date.now(), cookieValue(req.headers.get('cookie') || '', CHALLENGE_COOKIE));\n}\n\n/** 403 + the proof-of-work page (HTML navigations) or 403 JSON (a Pages Router data request), plus the\n * `blk: \"challenge\"` event — a served challenge is reported like a block (contract §D2). */\nexport async function serveChallenge(engine: Engine, req: Request, ip: string, target: string, waitUntil?: (p: Promise<unknown>) => void, ship = true): Promise<Response> {\n if (ship) {\n const ev = buildEvent(req, new URL(req.url).pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 403;\n ev.blk = 'challenge';\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n }\n\n // A page gets the proof-of-work page; the only other request served here is a Pages Router data request, which Next turns into a document load.\n if (!wantsHtml(req.headers.get('accept'), req.headers.get('sec-fetch-dest'))) {\n return new Response('{\"error\":\"challenge_required\"}', {\n status: 403,\n headers: { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' },\n });\n }\n const html = challengePage({ nonce: await kitFor(engine).nonce(ip, Date.now()), action: VERIFY_PATH, to: safeReturnTo(target) });\n return new Response(html, {\n status: 403,\n // the page's own policy (its inline solver and style by hash), never one the app sets (camada-all-mwpo)\n headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-camada-challenge': '1', 'content-security-policy': challengePageCsp() },\n });\n}\n\n/** POST handler for `…/camada/challenge`: validate, set `_cch`, 302 back, ship `{ st: 200, ch: 1 }`. */\nexport async function verifyChallenge(engine: Engine, req: Request): Promise<Response> {\n const url = new URL(req.url);\n const ip = clientIp(engine, req);\n const kit = kitFor(engine);\n // camada answers this path before the app runs, so it must not become a place to post\n // hundreds of megabytes at an unauthenticated endpoint.\n const body = await readBodyCapped(req, BODY_MAX);\n if (body === null) return new Response(null, { status: 413 });\n const form = parseFormBody(body);\n const to = safeReturnTo(form.to);\n const now = Date.now();\n if (!ip || !(await kit.verify(ip, now, form.nonce, form.solution))) {\n const html = ip ? challengePage({ nonce: await kit.nonce(ip, now), action: url.pathname, to }) : 'Forbidden';\n const headers: Record<string, string> = { 'content-type': ip ? 'text/html; charset=utf-8' : 'text/plain', 'cache-control': 'no-store' };\n if (ip) headers['content-security-policy'] = challengePageCsp();\n return new Response(html, { status: 403, headers });\n }\n const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 200;\n ev.ch = 1; // challenge passed (contract §A3 ingest field)\n engine.queue.push(ev);\n void engine.queue.flush();\n return new Response(null, {\n status: 302,\n headers: {\n location: to,\n 'set-cookie': challengeCookie(await kit.issue(ip, now), url.protocol === 'https:'),\n 'cache-control': 'no-store',\n },\n });\n}\n\n/** Only a page or a Pages Router data request (`x-nextjs-data: 1`; Next turns its non-ok answer into a hard navigation) is\n * challenged. Any other request passes, signalled (82ar). App Router soft navigations are among them: Next strips the RSC\n * header (and `_rsc`) before middleware runs, so they cannot be told from a fetch; the next document load meets the page. */\nexport function isChallengeable(req: Request): boolean {\n return wantsHtml(req.headers.get('accept'), req.headers.get('sec-fetch-dest')) || req.headers.get('x-nextjs-data') === '1';\n}\n\n/** For a route that wants to gate itself (the example's /challenge-me): the challenge Response,\n * or null when this client already holds a valid `_cch` and the route should render normally.\n *\n * One request, one event. The middleware stamps `x-camada-rid` and its `x-camada-mw` proof on\n * the request it forwards, so a mark that verifies means the middleware already shipped this\n * request's row (a client-sent rid alone proves nothing); the gate then serves\n * the page WITHOUT a second row, which would double-count the request at the analyst. Exclude\n * the gated route from the middleware matcher and the gate ships the `blk: \"challenge\"` row\n * itself — that is the configuration to use when you want the challenge counted.\n *\n * A request that is not challengeable returns null. The gate ships one `chs: 1` row only when there is no valid\n * middleware mark (the middleware did not see the request) and the ip has no valid `_cch`; with a valid mark it ships\n * nothing. A null return cannot set headers: the route sets `x-camada-challenge: 1` and `no-store` on `cache-control`\n * and the CDN cache headers (`cdn-cache-control`, `surrogate-control`, `cloudflare-`/`vercel-`/`netlify-cdn-cache-control`)\n * itself if it wants the frontend to see the signal. */\nexport async function challengeGate(req: Request): Promise<Response | null> {\n if (isDisabled() || !challengeEnabled()) return null;\n const engine = getEngine();\n if (!engine) return null;\n const challengeable = isChallengeable(req);\n const ip = clientIp(engine, req);\n if (!ip || await challengePassed(engine, req, ip)) return null; // unidentifiable client: fail open\n const url = new URL(req.url);\n const shipped = await middlewareMarkValid(engine.env.secret, req).catch(() => false);\n if (!challengeable) {\n // Not challenged (82ar): the route continues. Unless the middleware saw this request (valid mark), ship its chs: 1 event here.\n if (!shipped) {\n const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.chs = 1;\n engine.queue.push(ev);\n engine.queue.flush();\n }\n return null;\n }\n return serveChallenge(engine, req, ip, url.pathname + url.search, undefined, !shipped);\n}\n","// One wire-event builder for every @camada/next entry (middleware + beacon route). Web APIs\n// only: this sits in the middleware's edge-safe import graph.\nimport { buildWireEvent, hmacHex, safeEqual, TAP_NEXT, type WireEvent } from '@camada/core';\nimport { envSource } from './engine';\n\nexport function buildEvent(req: Request, path: string, ip: string | null, rid: string, sid: string | null, newSession: boolean): WireEvent {\n const url = new URL(req.url);\n return buildWireEvent(\n {\n method: req.method,\n host: req.headers.get('host') ?? url.host,\n path,\n query: url.search,\n // The edge runtime sorts header names, so hord is alphabetical at this tap — still\n // shipped; the scorer knows sdk-next lacks the raw-wire-order signal (capability mask).\n headers: [...req.headers.entries()],\n ip,\n httpVersion: null, // not observable at this position\n },\n // ja4 only on Vercel (the server env, which a client cannot set): elsewhere x-vercel-ja4-digest is client-settable.\n { tap: TAP_NEXT, rid, sid, newSession, ja4: envSource().VERCEL ? req.headers.get('x-vercel-ja4-digest') : null },\n );\n}\n\n/** Stamped by the middleware next to x-camada-rid on the request it forwards: proof, for withCamada(), that this request's\n * event already shipped. It only ever spares the wrapper a second event; enforcement never depends on it. */\nexport const MW_HEADER = 'x-camada-mw';\nexport const MW_WINDOW_MS = 30_000; // middleware → route handler takes milliseconds; 30 s covers a cold function start\nconst MW_SKEW_MS = 5_000; // the middleware's clock may run ahead of the function's (edge vs. Node on Vercel)\n\n/** What the mark binds: when it was made, the method, the path and query, the rid, and the client chain as forwarded.\n * JSON keeps the fields apart, since the rid and X-Forwarded-For are whatever the client sent where the middleware did not run. */\nconst markInput = (ts: number, req: Request, rid: string): string => {\n const url = new URL(req.url);\n return 'mw:' + JSON.stringify([ts, req.method, url.pathname + url.search, rid, req.headers.get('x-forwarded-for') ?? '']);\n};\n\n/** `<ms timestamp>.<HMAC-SHA256 hex>` under the server-only secret. */\nexport async function middlewareMark(secret: string, req: Request, rid: string, now = Date.now()): Promise<string> {\n return `${now}.${await hmacHex(secret, markInput(now, req, rid))}`;\n}\n\n/** True only for a mark this deployment's middleware made for this very request line and client, within the window.\n * A copied pair fails on another path, query, method or client, and on any request once the window has passed. */\nexport async function middlewareMarkValid(secret: string, req: Request, now = Date.now()): Promise<boolean> {\n const m = /^(\\d{13})\\.([0-9a-f]{64})$/.exec(req.headers.get(MW_HEADER) ?? '');\n const rid = req.headers.get('x-camada-rid');\n if (!m || !rid) return false;\n const ts = Number(m[1]);\n if (now - ts > MW_WINDOW_MS || ts - now > MW_SKEW_MS) return false;\n return safeEqual(m[2], await hmacHex(secret, markInput(ts, req, rid)));\n}\n\nexport const SESSION_COOKIE = '_sfp'; // the same session cookie as the edge collector and @camada/node: sid/ns comparable across taps\n\nexport const cookieValue = (cookie: string, name: string): string | null => {\n const src = '; ' + cookie;\n const i = src.indexOf('; ' + name + '=');\n if (i === -1) return null;\n const start = i + name.length + 3;\n const j = src.indexOf(';', start);\n return src.slice(start, j === -1 ? undefined : j);\n};\n","// Route handlers for app/api/camada/[...camada]/route.ts:\n// GET …/b.js -> the first-party beacon IIFE (its auto-init derives the fp endpoint from\n// the script URL's final path segment and the rid from ?r=)\n// POST …/fp -> queue the beacon as a sig:1 row with the trusted-proxy-resolved client IP\n// and tap 'sdk-next': it rides the event batch (one request per flush at the\n// analyst, not one per page view) — the mirror of @camada/node's /_cam/*.\n// Works on BOTH runtimes (edge and node): Web APIs only, no node: imports. Unconfigured or\n// disabled: GET 404s, POST answers 204 and drops — inert, never an error.\nimport iife from '@camada/browser/iife-string';\nimport { guardedAsync, resolveClientIp, readBodyCapped, TAP_NEXT, REQUEST_ROW_KEYS } from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { verifyChallenge } from './challenge';\nimport { buildEvent, cookieValue, SESSION_COOKIE } from './event';\n\nconst FP_MAX = 32 * 1024; // matches the server's /fp cap: never accept what ingest will 413\n\nconst notFound = () => new Response(null, { status: 404 });\nconst noContent = () => new Response(null, { status: 204, headers: { 'cache-control': 'no-store' } });\n\n// The route handlers enforce too: a middleware matcher that excludes /api/ (a common pattern)\n// must not leave the beacon endpoints serving blocked clients.\nfunction blocked(engine: Engine, req: Request): Response | null {\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n const path = new URL(req.url).pathname;\n // §D3: `ua` and `header` rules need these; `Headers.get` already matches names case-insensitively\n const v = engine.snap.verdict({ ip, path, ua: req.headers.get('user-agent'), header: (n) => req.headers.get(n) });\n if (!v.block) return null;\n const ev = buildEvent(req, path, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 403; ev.blk = v.reason; // SDK-01: a beacon-route deny is a block like any other — it ships, unsampled\n if (v.rule) ev.rl = v.rule;\n engine.queue.push(ev);\n void engine.queue.flush();\n const headers: Record<string, string> = { 'cache-control': 'no-store', 'x-block-reason': String(v.reason ?? ''), 'x-block-version': v.version ?? '' };\n if (v.rule) headers['x-block-rule'] = v.rule;\n return new Response('Forbidden', { status: 403, headers });\n}\n\nfunction lastSegment(req: Request): string {\n const parts = new URL(req.url).pathname.split('/');\n return parts[parts.length - 1];\n}\n\nfunction activeEngine(): Engine | null {\n return isDisabled() ? null : getEngine();\n}\n\n// tenant switch: both beacon endpoints stand down together (b.js and fp), as node's and hono's do\nconst beaconOff = (engine: Engine): boolean => engine.snap.config?.beacon === false;\n\nexport function camadaRoute(): {\n GET: (req: Request) => Promise<Response>;\n POST: (req: Request) => Promise<Response>;\n} {\n return {\n GET: (req) => guardedAsync(async () => {\n if (lastSegment(req) !== 'b.js') return notFound();\n const engine = activeEngine();\n if (!engine) return notFound();\n engine.snap.ensureFresh();\n const deny = blocked(engine, req);\n if (deny) return deny;\n if (beaconOff(engine)) return notFound();\n return new Response(iife, {\n status: 200,\n headers: { 'content-type': 'application/javascript', 'cache-control': 'public, max-age=3600' },\n });\n }, notFound()),\n\n POST: (req) => guardedAsync(async () => {\n if (lastSegment(req) === 'challenge') {\n const engine = activeEngine();\n if (!engine || !challengeEnabled()) return notFound();\n engine.snap.ensureFresh();\n const deny = blocked(engine, req); // a blocked client cannot buy its way past with a solution\n if (deny) return deny;\n return verifyChallenge(engine, req);\n }\n if (lastSegment(req) !== 'fp') return notFound();\n const engine = activeEngine();\n if (engine) {\n engine.snap.ensureFresh();\n // Decide before reading: a blocked client or a beacon the tenant turned off pulls no body.\n const deny = blocked(engine, req);\n if (deny) return deny;\n if (beaconOff(engine)) return notFound();\n const body = await readBodyCapped(req, FP_MAX); // streamed: a chunked body cannot buffer past the cap\n if (body === null) return new Response(null, { status: 413 });\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n let parsed: unknown;\n try { parsed = JSON.parse(body); } catch { return noContent(); } // not a beacon: drop it, never ship junk\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n const row: Record<string, unknown> = { ...(parsed as Record<string, unknown>), sig: 1, ip, tap: TAP_NEXT, rts: Date.now() }; // rts after the spread: the relay's clock, never the browser's\n for (const k of REQUEST_ROW_KEYS) delete row[k]; // SDK-attested keys never ride a visitor's beacon, as core's relay\n engine.queue.push(row);\n void engine.queue.flush(); // fire-and-forget, as the relay was: a serverless runtime may freeze right after the response\n }\n }\n return noContent();\n }, noContent()),\n };\n}\n","// <CamadaBeacon/> — an async React Server Component for the app-router layout <head>: reads\n// the middleware-stamped x-camada-rid request header (so the beacon POST joins the server's\n// wire event) and renders the @camada/react beacon tag pointing at the camada route.\n// 'next/headers' is imported lazily inside the component so this module only touches it\n// server-side, in a request scope.\nimport { CamadaBeacon as Beacon } from '@camada/react';\n\nexport interface CamadaBeaconProps {\n /** Where camadaRoute() serves the beacon script. Default: '/api/camada/b.js'. */\n src?: string;\n}\n\nexport async function CamadaBeacon({ src = '/api/camada/b.js' }: CamadaBeaconProps = {}) {\n let rid: string | null = null;\n try {\n const { headers } = await import('next/headers');\n rid = (await headers()).get('x-camada-rid');\n } catch { /* outside a request scope (static render, tests): beacon ships without a rid */ }\n return <Beacon src={src} rid={rid} />;\n}\n","// track() — app-context outcome events recorded from a server action, route handler, or server\n// component, joined to the middleware's wire event. The name is free-form; the analyst's rules read\n// this vocabulary: login_failed, login_succeeded, signup, password_reset, mfa_failed, payment_failed,\n// payment_succeeded, coupon_failed (@camada/node README has the table). Inside withCamada() an\n// event joins on the wrapper's own rid and session; elsewhere on the x-camada-rid the middleware\n// stamps and the _sfp session cookie. The identifier is\n// HMAC-hashed in-process with the ingest token: the raw value never reaches the queue, and\n// the analyst drops anything that is not hash-shaped anyway.\n// next/headers is imported lazily inside the call (the beacon.tsx pattern) so this module\n// stays importable everywhere; calling track() outside a request scope is a no-op.\nimport { hashUserId, logRateLimited, resolveClientIp, TAP_NEXT } from '@camada/core';\nimport { getEngine, isDisabled, trustedProxy } from './engine';\nimport { requestWaitUntil } from './wait-until';\n\nconst SESSION_COOKIE = '_sfp';\n\ninterface Scope { rid: string; sid: string | null }\ninterface Als { run<R>(store: Scope, fn: () => R): R; getStore(): Scope | undefined }\nlet als: Als | null | undefined;\n/** Next puts AsyncLocalStorage on globalThis in both runtimes (node-environment-baseline, the edge sandbox). */\nconst scopes = (): Als | null => {\n if (als === undefined) {\n const Ctor = (globalThis as { AsyncLocalStorage?: new () => Als }).AsyncLocalStorage;\n als = Ctor ? new Ctor() : null; // ponytail: without it (never under Next) a wrapped route's track() falls back to the header\n }\n return als;\n};\n\n/** withCamada() runs its handler here, so track() inside it takes the wrapper's rid and never a\n * client-sent x-camada-rid: a wrapped route sits outside the matcher, where nothing strips it. */\nexport const withTrackScope = <R>(scope: Scope, fn: () => R): R => { const s = scopes(); return s ? s.run(scope, fn) : fn(); };\n\n/** Never throws, never blocks the response: a camada problem must not break a login. */\nexport async function track(event: string, data?: { user?: string }): Promise<void> {\n try {\n if (isDisabled()) return;\n const engine = getEngine();\n if (!engine) return;\n const { headers, cookies } = await import('next/headers');\n const [h, c] = await Promise.all([headers(), cookies()]);\n const scope = scopes()?.getStore();\n const uid = data?.user ? await hashUserId(data.user, engine.env.ingestToken) : null;\n engine.queue.push({\n tap: TAP_NEXT,\n et: event,\n uid,\n rid: scope ? scope.rid : h.get('x-camada-rid'),\n sid: scope?.sid ?? c.get(SESSION_COOKIE)?.value ?? null,\n ip: resolveClientIp(null, h.get('x-forwarded-for'), trustedProxy(engine)),\n ts: Date.now(),\n });\n void engine.queue.flush(requestWaitUntil()); // a serverless runtime may freeze right after the response: hold it open for the flush\n } catch (err) {\n logRateLimited(err); // outside a request scope, unconfigured, or a camada bug: swallow\n }\n}\n","// A waitUntil for code that runs inside a route handler or server action (withCamada, track()). A\n// serverless host (Vercel) may freeze the function once the response has gone out, and the event's\n// flush only starts then, when the body is done. Next's after() (15.1+) holds the function open\n// until its callback settles, but it must be called inside the request scope, which the body-done\n// callback is not. So the first promise handed in registers ONE after() callback; it runs once the\n// response has closed and drains every promise handed in, including any that arrive while it waits.\n// Without after() (Next 15.0, or outside a request scope) the host's request context waitUntil is\n// used, the one @vercel/functions reads; with neither, the promise is left to run, which a\n// long-lived `next start` process does anyway.\nimport * as nextServer from 'next/server';\n\ntype WaitUntil = (p: Promise<unknown>) => void;\ntype After = (task: () => Promise<unknown>) => void;\ntype RequestContext = { get?: () => { waitUntil?: WaitUntil } | undefined };\n\nfunction contextWaitUntil(): WaitUntil | undefined {\n const g = globalThis as Record<symbol, RequestContext | undefined>;\n for (const name of ['@next/request-context', '@vercel/request-context']) {\n const ctx = g[Symbol.for(name)]?.get?.();\n if (typeof ctx?.waitUntil === 'function') return (p) => ctx.waitUntil!(p);\n }\n return undefined;\n}\n\n/** Call once per request, inside its scope; hand the result to everything that flushes for it. Never throws. */\nexport function requestWaitUntil(): WaitUntil {\n const pending: Promise<unknown>[] = [];\n let hold: WaitUntil | null | undefined; // undefined = not decided yet; null = nothing to hold the host open with\n const drain = async () => {\n for (let i = 0; i < pending.length; i++) await pending[i].catch(() => {});\n };\n return (p) => {\n if (hold === undefined) {\n hold = null;\n try {\n const after = (nextServer as { after?: After }).after;\n if (typeof after !== 'function') throw new Error('no after()');\n after(drain); // throws outside a request scope\n hold = (q) => { pending.push(q); };\n } catch {\n try { hold = contextWaitUntil() ?? null; } catch { hold = null; }\n }\n }\n try { hold?.(p); } catch { /* a host that refuses the promise costs only the hold */ }\n };\n}\n","// withCamada(handler): the route-handler position, where the response is visible. The middleware\n// runs before the route and ships its event pre-response (st and dur null); a wrapped route\n// handler runs @camada/core's fetch pipeline itself (verdict, block, challenge, the session) and\n// ships one event with the real status and a dur up to the response (a text/event-stream body to its last byte).\n//\n// Threat model. Every request header can come from the client: the middleware does not run for a\n// route left out of its matcher, and Next offers no server-only channel from the middleware to the\n// route (on Vercel they run in different processes, often different regions). So:\n// - Enforcement here never depends on a header. Verdict, block and challenge run on every request.\n// - The one thing a header may decide is whether this request's event already shipped. Wrapped\n// routes belong outside the matcher; if the middleware ran anyway it stamps `x-camada-mw`, an\n// HMAC under the server-only CAMADA_KEY of a timestamp, the method, the path and query, the rid\n// and X-Forwarded-For. A mark that verifies within 30 s skips the second event and the second\n// session cookie, nothing else. A copied mark fails on another path, query, method or client\n// and after the window; within it, it can at most hide a replay's event, never pass a block.\n// - The middleware strips any x-camada-mw the client sent before forwarding.\n// - track() inside the handler takes this wrapper's rid and session (withTrackScope), never a\n// client-sent x-camada-rid; only a request the middleware already shipped keeps its stamped one.\nimport iife from '@camada/browser/iife-string';\nimport { logRateLimited, TAP_NEXT } from '@camada/core';\nimport { createFetchCamada, withSetCookie } from '@camada/core/fetch';\nimport { envSource, getEngine } from './engine';\nimport { VERIFY_PATH } from './challenge';\nimport { middlewareMarkValid } from './event';\nimport { requestWaitUntil } from './wait-until';\nimport { withTrackScope } from './track';\nimport { SDK_ID } from './version';\n\nconst cam = createFetchCamada({ tap: TAP_NEXT, sdk: SDK_ID, iife }, { challengePath: VERIFY_PATH }); // lazy mode: edge and serverless\n\nasync function middlewareShipped(req: Request): Promise<boolean> {\n const secret = getEngine()?.env.secret;\n return !!secret && (await middlewareMarkValid(secret, req));\n}\n\n/** Next's redirect() / notFound() throw an error whose digest ends in the status they answer with. */\nconst thrownStatus = (err: unknown): number =>\n Number(/;(\\d{3});?$/.exec(String((err as { digest?: unknown } | null)?.digest ?? ''))?.[1] ?? 500);\n\n/**\n * `export const GET = withCamada(async (req) => Response.json(...))` in a route.ts. Same\n * CAMADA_* env as the middleware; inert without a key or with CAMADA_DISABLED=1. Never breaks\n * the handler: a camada failure costs the event, not the response.\n */\nexport function withCamada<R extends Request, A extends unknown[]>(\n handler: (req: R, ...rest: A) => Response | Promise<Response>,\n): (req: R, ...rest: A) => Promise<Response> {\n return async (req, ...rest) => {\n const env = envSource();\n // On Vercel the platform overwrites X-Forwarded-For, so its rightmost entry is trustworthy: the middleware's default too.\n const r = await cam.before(req, {\n env: { ...env, CAMADA_TRUSTED_PROXY: env.CAMADA_TRUSTED_PROXY || (env.VERCEL ? 'vercel' : undefined) },\n waitUntil: requestWaitUntil(), // the event flushes after the body: hold a serverless function open for it\n });\n if (!r) return handler(req, ...rest);\n if (r.response) return r.response; // block, challenge: whatever headers the request carries\n let shipped = false;\n try { shipped = await middlewareShipped(req); } catch (err) { logRateLimited(err); }\n if (shipped) {\n logRateLimited(new Error(`withCamada: the middleware also matched ${new URL(req.url).pathname}, so its pre-response event stands and dur is not reported; exclude wrapped routes from the matcher`));\n return handler(req, ...rest); // and its _sfp cookie: no second session\n }\n let res: Response;\n try {\n res = await withTrackScope({ rid: r.vars.rid, sid: r.vars.sid }, () => handler(req, ...rest));\n } catch (err) {\n cam.after(req, r.vars, thrownStatus(err));\n throw err;\n }\n res = withRid(res, r.vars.rid);\n return cam.finish(req, r.vars, r.vars.sessionCookie ? withSetCookie(res, r.vars.sessionCookie) : res);\n };\n}\n\n/** x-rid, the rid of this request's row, the same header the middleware stamps. Set in place, or on a copy when the\n * headers are immutable (`Response.redirect()`, a `fetch()` result), as withSetCookie does: same status and body.\n * A 101 upgrade is left alone. Not on a request the middleware shipped: its own x-rid, that row's, reaches the client. */\nfunction withRid(res: Response, rid: string): Response {\n if (res.status === 101) return res;\n try {\n res.headers.set('x-rid', rid);\n return res;\n } catch {\n // ponytail: core's copyResponse is not exported; its Deno and @hono/node-server cases never reach a Next route.\n const out = new Response(res.body, res);\n out.headers.set('x-rid', rid);\n return out;\n }\n}\n"],"mappings":"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","names":["import_core","import_core","import_core","name","engine","engine","import_core","engine","iife","Beacon","import_core","name","after","SESSION_COOKIE","engine","import_iife_string","import_core","iife"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -25,7 +25,13 @@ declare function camadaRoute(): {
|
|
|
25
25
|
* request's row (a client-sent rid alone proves nothing); the gate then serves
|
|
26
26
|
* the page WITHOUT a second row, which would double-count the request at the analyst. Exclude
|
|
27
27
|
* the gated route from the middleware matcher and the gate ships the `blk: "challenge"` row
|
|
28
|
-
* itself — that is the configuration to use when you want the challenge counted.
|
|
28
|
+
* itself — that is the configuration to use when you want the challenge counted.
|
|
29
|
+
*
|
|
30
|
+
* A request that is not challengeable returns null. The gate ships one `chs: 1` row only when there is no valid
|
|
31
|
+
* middleware mark (the middleware did not see the request) and the ip has no valid `_cch`; with a valid mark it ships
|
|
32
|
+
* nothing. A null return cannot set headers: the route sets `x-camada-challenge: 1` and `no-store` on `cache-control`
|
|
33
|
+
* and the CDN cache headers (`cdn-cache-control`, `surrogate-control`, `cloudflare-`/`vercel-`/`netlify-cdn-cache-control`)
|
|
34
|
+
* itself if it wants the frontend to see the signal. */
|
|
29
35
|
declare function challengeGate(req: Request): Promise<Response | null>;
|
|
30
36
|
|
|
31
37
|
interface CamadaBeaconProps {
|
package/dist/index.d.ts
CHANGED
|
@@ -25,7 +25,13 @@ declare function camadaRoute(): {
|
|
|
25
25
|
* request's row (a client-sent rid alone proves nothing); the gate then serves
|
|
26
26
|
* the page WITHOUT a second row, which would double-count the request at the analyst. Exclude
|
|
27
27
|
* the gated route from the middleware matcher and the gate ships the `blk: "challenge"` row
|
|
28
|
-
* itself — that is the configuration to use when you want the challenge counted.
|
|
28
|
+
* itself — that is the configuration to use when you want the challenge counted.
|
|
29
|
+
*
|
|
30
|
+
* A request that is not challengeable returns null. The gate ships one `chs: 1` row only when there is no valid
|
|
31
|
+
* middleware mark (the middleware did not see the request) and the ip has no valid `_cch`; with a valid mark it ships
|
|
32
|
+
* nothing. A null return cannot set headers: the route sets `x-camada-challenge: 1` and `no-store` on `cache-control`
|
|
33
|
+
* and the CDN cache headers (`cdn-cache-control`, `surrogate-control`, `cloudflare-`/`vercel-`/`netlify-cdn-cache-control`)
|
|
34
|
+
* itself if it wants the frontend to see the signal. */
|
|
29
35
|
declare function challengeGate(req: Request): Promise<Response | null>;
|
|
30
36
|
|
|
31
37
|
interface CamadaBeaconProps {
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "@camada/core";
|
|
13
13
|
|
|
14
14
|
// package.json
|
|
15
|
-
var version = "0.
|
|
15
|
+
var version = "0.4.0";
|
|
16
16
|
var name = "@camada/next";
|
|
17
17
|
|
|
18
18
|
// src/version.ts
|
|
@@ -73,11 +73,13 @@ function trustedProxy(e) {
|
|
|
73
73
|
import {
|
|
74
74
|
createChallengeAsync,
|
|
75
75
|
challengePage,
|
|
76
|
+
challengePageCsp,
|
|
76
77
|
challengeCookie,
|
|
77
78
|
safeReturnTo,
|
|
78
79
|
wantsHtml,
|
|
79
80
|
parseFormBody,
|
|
80
81
|
resolveClientIp,
|
|
82
|
+
readBodyCapped,
|
|
81
83
|
CHALLENGE_COOKIE
|
|
82
84
|
} from "@camada/core";
|
|
83
85
|
|
|
@@ -98,7 +100,8 @@ function buildEvent(req, path, ip, rid, sid, newSession) {
|
|
|
98
100
|
httpVersion: null
|
|
99
101
|
// not observable at this position
|
|
100
102
|
},
|
|
101
|
-
|
|
103
|
+
// ja4 only on Vercel (the server env, which a client cannot set): elsewhere x-vercel-ja4-digest is client-settable.
|
|
104
|
+
{ tap: TAP_NEXT, rid, sid, newSession, ja4: envSource().VERCEL ? req.headers.get("x-vercel-ja4-digest") : null }
|
|
102
105
|
);
|
|
103
106
|
}
|
|
104
107
|
var MW_HEADER = "x-camada-mw";
|
|
@@ -163,22 +166,24 @@ async function serveChallenge(engine2, req, ip, target, waitUntil, ship = true)
|
|
|
163
166
|
const html = challengePage({ nonce: await kitFor(engine2).nonce(ip, Date.now()), action: VERIFY_PATH, to: safeReturnTo(target) });
|
|
164
167
|
return new Response(html, {
|
|
165
168
|
status: 403,
|
|
166
|
-
|
|
169
|
+
// the page's own policy (its inline solver and style by hash), never one the app sets (camada-all-mwpo)
|
|
170
|
+
headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "x-camada-challenge": "1", "content-security-policy": challengePageCsp() }
|
|
167
171
|
});
|
|
168
172
|
}
|
|
169
173
|
async function verifyChallenge(engine2, req) {
|
|
170
174
|
const url = new URL(req.url);
|
|
171
175
|
const ip = clientIp(engine2, req);
|
|
172
176
|
const kit = kitFor(engine2);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (body.length > BODY_MAX) return new Response(null, { status: 413 });
|
|
177
|
+
const body = await readBodyCapped(req, BODY_MAX);
|
|
178
|
+
if (body === null) return new Response(null, { status: 413 });
|
|
176
179
|
const form = parseFormBody(body);
|
|
177
180
|
const to = safeReturnTo(form.to);
|
|
178
181
|
const now = Date.now();
|
|
179
182
|
if (!ip || !await kit.verify(ip, now, form.nonce, form.solution)) {
|
|
180
183
|
const html = ip ? challengePage({ nonce: await kit.nonce(ip, now), action: url.pathname, to }) : "Forbidden";
|
|
181
|
-
|
|
184
|
+
const headers = { "content-type": ip ? "text/html; charset=utf-8" : "text/plain", "cache-control": "no-store" };
|
|
185
|
+
if (ip) headers["content-security-policy"] = challengePageCsp();
|
|
186
|
+
return new Response(html, { status: 403, headers });
|
|
182
187
|
}
|
|
183
188
|
const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get("cookie") || "", SESSION_COOKIE), false);
|
|
184
189
|
ev.st = 200;
|
|
@@ -194,27 +199,42 @@ async function verifyChallenge(engine2, req) {
|
|
|
194
199
|
}
|
|
195
200
|
});
|
|
196
201
|
}
|
|
202
|
+
function isChallengeable(req) {
|
|
203
|
+
return wantsHtml(req.headers.get("accept"), req.headers.get("sec-fetch-dest")) || req.headers.get("x-nextjs-data") === "1";
|
|
204
|
+
}
|
|
197
205
|
async function challengeGate(req) {
|
|
198
206
|
if (isDisabled() || !challengeEnabled()) return null;
|
|
199
207
|
const engine2 = getEngine();
|
|
200
208
|
if (!engine2) return null;
|
|
209
|
+
const challengeable = isChallengeable(req);
|
|
201
210
|
const ip = clientIp(engine2, req);
|
|
202
211
|
if (!ip || await challengePassed(engine2, req, ip)) return null;
|
|
203
212
|
const url = new URL(req.url);
|
|
204
213
|
const shipped = await middlewareMarkValid(engine2.env.secret, req).catch(() => false);
|
|
214
|
+
if (!challengeable) {
|
|
215
|
+
if (!shipped) {
|
|
216
|
+
const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get("cookie") || "", SESSION_COOKIE), false);
|
|
217
|
+
ev.chs = 1;
|
|
218
|
+
engine2.queue.push(ev);
|
|
219
|
+
engine2.queue.flush();
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
205
223
|
return serveChallenge(engine2, req, ip, url.pathname + url.search, void 0, !shipped);
|
|
206
224
|
}
|
|
207
225
|
|
|
208
226
|
// src/middleware.ts
|
|
227
|
+
var NO_STORE_HEADERS = ["cache-control", "cdn-cache-control", "surrogate-control", "cloudflare-cdn-cache-control", "vercel-cdn-cache-control", "netlify-cdn-cache-control"];
|
|
209
228
|
var isBeaconRoute = (method, path) => method === "GET" && path === "/api/camada/b.js" || method === "POST" && path === "/api/camada/fp";
|
|
210
|
-
async function capture(engine2, req, path, ip, existingSid, warnRule, waitUntil) {
|
|
229
|
+
async function capture(engine2, req, path, ip, existingSid, warnRule, waitUntil, signalled = false) {
|
|
211
230
|
const rid = crypto.randomUUID();
|
|
212
231
|
const sid = existingSid ?? crypto.randomUUID();
|
|
213
232
|
const cfg = engine2.snap.config;
|
|
214
233
|
const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));
|
|
215
|
-
if (!excluded && Math.random() < (cfg?.sample ?? 1)) {
|
|
234
|
+
if (signalled || !excluded && Math.random() < (cfg?.sample ?? 1)) {
|
|
216
235
|
const ev = buildEvent(req, path, ip, rid, sid, !existingSid);
|
|
217
236
|
if (warnRule) ev.wrn = warnRule;
|
|
237
|
+
if (signalled) ev.chs = 1;
|
|
218
238
|
engine2.queue.push(ev);
|
|
219
239
|
engine2.queue.flush(waitUntil);
|
|
220
240
|
}
|
|
@@ -225,6 +245,10 @@ async function capture(engine2, req, path, ip, existingSid, warnRule, waitUntil)
|
|
|
225
245
|
if (mark) headers.set(MW_HEADER, mark);
|
|
226
246
|
const res = NextResponse.next({ request: { headers } });
|
|
227
247
|
res.headers.set("x-rid", rid);
|
|
248
|
+
if (signalled) {
|
|
249
|
+
res.headers.set("x-camada-challenge", "1");
|
|
250
|
+
for (const n of NO_STORE_HEADERS) res.headers.set(n, "no-store");
|
|
251
|
+
}
|
|
228
252
|
if (!existingSid) {
|
|
229
253
|
const secure = new URL(req.url).protocol === "https:" ? "; Secure" : "";
|
|
230
254
|
res.headers.append("set-cookie", `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${secure}`);
|
|
@@ -264,6 +288,7 @@ function camada(_options) {
|
|
|
264
288
|
engine2.queue.flush(waitUntil);
|
|
265
289
|
const headers = {
|
|
266
290
|
"content-type": "text/plain",
|
|
291
|
+
"cache-control": "no-store",
|
|
267
292
|
"x-block-reason": String(v.reason ?? ""),
|
|
268
293
|
"x-block-version": v.version ?? ""
|
|
269
294
|
};
|
|
@@ -272,7 +297,14 @@ function camada(_options) {
|
|
|
272
297
|
}
|
|
273
298
|
const warnRule = v.warn ? v.rule ?? null : null;
|
|
274
299
|
if (v.challenge && ip && challengeEnabled() && !isChallengeRoute(path)) {
|
|
275
|
-
|
|
300
|
+
const page = isChallengeable(req);
|
|
301
|
+
if (page || !isBeaconRoute(req.method, path)) {
|
|
302
|
+
return guardedAsync(async () => {
|
|
303
|
+
const passed = await challengePassed(engine2, req, ip);
|
|
304
|
+
if (page && !passed) return serveChallenge(engine2, req, ip, path + new URL(req.url).search, waitUntil);
|
|
305
|
+
return capture(engine2, req, path, ip, existingSid, warnRule, waitUntil, !passed);
|
|
306
|
+
}, passThrough(req));
|
|
307
|
+
}
|
|
276
308
|
}
|
|
277
309
|
if (isBeaconRoute(req.method, path)) return passThrough(req);
|
|
278
310
|
return guardedAsync(() => capture(engine2, req, path, ip, existingSid, warnRule, waitUntil), passThrough(req));
|
|
@@ -285,9 +317,8 @@ function camada(_options) {
|
|
|
285
317
|
|
|
286
318
|
// src/route.ts
|
|
287
319
|
import iife from "@camada/browser/iife-string";
|
|
288
|
-
import { guardedAsync as guardedAsync2, resolveClientIp as resolveClientIp3, TAP_NEXT as TAP_NEXT2 } from "@camada/core";
|
|
320
|
+
import { guardedAsync as guardedAsync2, resolveClientIp as resolveClientIp3, readBodyCapped as readBodyCapped2, TAP_NEXT as TAP_NEXT2, REQUEST_ROW_KEYS } from "@camada/core";
|
|
289
321
|
var FP_MAX = 32 * 1024;
|
|
290
|
-
var encoder = new TextEncoder();
|
|
291
322
|
var notFound = () => new Response(null, { status: 404 });
|
|
292
323
|
var noContent = () => new Response(null, { status: 204, headers: { "cache-control": "no-store" } });
|
|
293
324
|
function blocked(engine2, req) {
|
|
@@ -301,7 +332,7 @@ function blocked(engine2, req) {
|
|
|
301
332
|
if (v.rule) ev.rl = v.rule;
|
|
302
333
|
engine2.queue.push(ev);
|
|
303
334
|
void engine2.queue.flush();
|
|
304
|
-
const headers = { "x-block-reason": String(v.reason ?? ""), "x-block-version": v.version ?? "" };
|
|
335
|
+
const headers = { "cache-control": "no-store", "x-block-reason": String(v.reason ?? ""), "x-block-version": v.version ?? "" };
|
|
305
336
|
if (v.rule) headers["x-block-rule"] = v.rule;
|
|
306
337
|
return new Response("Forbidden", { status: 403, headers });
|
|
307
338
|
}
|
|
@@ -338,14 +369,14 @@ function camadaRoute() {
|
|
|
338
369
|
return verifyChallenge(engine3, req);
|
|
339
370
|
}
|
|
340
371
|
if (lastSegment(req) !== "fp") return notFound();
|
|
341
|
-
const body = await req.text();
|
|
342
|
-
if (encoder.encode(body).byteLength > FP_MAX) return new Response(null, { status: 413 });
|
|
343
372
|
const engine2 = activeEngine();
|
|
344
373
|
if (engine2) {
|
|
345
374
|
engine2.snap.ensureFresh();
|
|
346
375
|
const deny = blocked(engine2, req);
|
|
347
376
|
if (deny) return deny;
|
|
348
377
|
if (beaconOff(engine2)) return notFound();
|
|
378
|
+
const body = await readBodyCapped2(req, FP_MAX);
|
|
379
|
+
if (body === null) return new Response(null, { status: 413 });
|
|
349
380
|
const ip = resolveClientIp3(null, req.headers.get("x-forwarded-for"), trustedProxy(engine2));
|
|
350
381
|
let parsed;
|
|
351
382
|
try {
|
|
@@ -354,7 +385,9 @@ function camadaRoute() {
|
|
|
354
385
|
return noContent();
|
|
355
386
|
}
|
|
356
387
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
357
|
-
|
|
388
|
+
const row = { ...parsed, sig: 1, ip, tap: TAP_NEXT2, rts: Date.now() };
|
|
389
|
+
for (const k of REQUEST_ROW_KEYS) delete row[k];
|
|
390
|
+
engine2.queue.push(row);
|
|
358
391
|
void engine2.queue.flush();
|
|
359
392
|
}
|
|
360
393
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts","../src/engine.ts","../package.json","../src/version.ts","../src/challenge.ts","../src/event.ts","../src/route.ts","../src/beacon.tsx","../src/track.ts","../src/wait-until.ts","../src/with-camada.ts"],"sourcesContent":["// The Next.js middleware/proxy entry: enforce the blocklist inline, stamp x-camada-rid onto\n// the forwarded request (the <CamadaBeacon/> server component reads it back), ship the wire\n// event fire-and-forget. Everything runs inside a try/catch fail-open envelope — a camada\n// bug must never break the customer's app (plan.md INT-2).\n//\n// MUST stay edge-runtime-safe: Web APIs only (fetch/Headers/Request/Response/URL/crypto),\n// no node: imports anywhere in this entry's import graph. Proven by test/edge-safety.test.ts,\n// which bundles this file for a bare edge runtime and executes it inside @edge-runtime/vm.\n// NextRequest/NextFetchEvent are imported as TYPES only; NextResponse comes from\n// 'next/server', which is itself edge-safe.\nimport type { NextFetchEvent, NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport { resolveClientIp, logRateLimited, guarded, guardedAsync } from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { isChallengeRoute, challengePassed, serveChallenge } from './challenge';\nimport { buildEvent, cookieValue, middlewareMark, MW_HEADER, SESSION_COOKIE } from './event';\n\n/** camadaRoute()'s beacon endpoints (mounted at /api/camada/[...camada]). Camada's own, like core's scriptPath/fpPath:\n * after enforcement they are neither captured nor stamped with x-rid. */\nconst isBeaconRoute = (method: string, path: string): boolean =>\n (method === 'GET' && path === '/api/camada/b.js') || (method === 'POST' && path === '/api/camada/fp');\n\nexport interface CamadaMiddlewareOptions {\n // Reserved. The engine is configured via CAMADA_* environment variables.\n}\n\n/** Next accepts a promise here; the capture and challenge branches return one (they await WebCrypto). */\nexport type MiddlewareResult = Response | undefined | Promise<Response | undefined>;\n\n/** The ordinary path: ship one pre-response event, stamp the rid (and its proof for withCamada()),\n * mint the session cookie. `warnRule` is the id of the `warn` rule that let this request through, if one did (§D3). */\nasync function capture(engine: Engine, req: NextRequest, path: string, ip: string | null, existingSid: string | null, warnRule: string | null, waitUntil?: (p: Promise<unknown>) => void): Promise<Response> {\n const rid = crypto.randomUUID();\n const sid = existingSid ?? crypto.randomUUID();\n const cfg = engine.snap.config;\n const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));\n if (!excluded && Math.random() < (cfg?.sample ?? 1)) {\n // st stays null: middleware ships pre-response, like the edge collector's tap.\n const ev = buildEvent(req, path, ip, rid, sid, !existingSid);\n if (warnRule) ev.wrn = warnRule;\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n }\n\n const headers = new Headers(req.headers);\n headers.set('x-camada-rid', rid);\n // WebCrypto, the one await on this path; without it the request still goes through, just unproven (withCamada then ships too).\n // The headers are a copy of the client's: drop any mark it sent before stamping ours.\n headers.delete(MW_HEADER);\n const mark = await middlewareMark(engine.env.secret, req, rid).catch(() => null);\n if (mark) headers.set(MW_HEADER, mark);\n const res = NextResponse.next({ request: { headers } });\n res.headers.set('x-rid', rid);\n if (!existingSid) {\n const secure = new URL(req.url).protocol === 'https:' ? '; Secure' : '';\n res.headers.append('set-cookie', `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${secure}`);\n }\n return res;\n}\n\n/** Where camada lets a request through without capturing it: forward it without any x-camada-mw or x-camada-rid the\n * client sent, so the only mark and rid a route handler ever sees are ones this middleware made. Undefined (Next\n * continues untouched) when there is neither. */\nfunction passThrough(req: Request): Response | undefined {\n if (!req.headers.has(MW_HEADER) && !req.headers.has('x-camada-rid')) return undefined;\n const headers = new Headers(req.headers);\n headers.delete(MW_HEADER);\n headers.delete('x-camada-rid');\n return NextResponse.next({ request: { headers } });\n}\n\n/**\n * `export default camada();` from middleware.ts (Next ≤15) / proxy.ts (Next 16).\n * Returns undefined (Next continues) whenever camada is disabled, unconfigured, or broken; a client-sent\n * x-camada-mw or x-camada-rid is stripped on every path that forwards the request.\n */\nexport function camada(_options?: CamadaMiddlewareOptions): (req: NextRequest, event: NextFetchEvent) => MiddlewareResult {\n return function camadaMiddleware(req: NextRequest, event: NextFetchEvent): MiddlewareResult {\n try {\n if (isDisabled()) return passThrough(req);\n const engine = getEngine();\n if (!engine) return passThrough(req);\n const waitUntil = event?.waitUntil ? (p: Promise<unknown>) => event.waitUntil(p) : undefined;\n engine.snap.ensureFresh(waitUntil);\n\n // There is no socket peer at the edge: with no trusted-proxy config (or no XFF) the\n // ip resolves to null and ip rules simply don't enforce — a spoofed X-Forwarded-For\n // can never reach the blocklist. On Vercel the engine defaults to {mode:'vercel'}.\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n const path = new URL(req.url).pathname;\n\n // the same _sfp session as the collector and @camada/node: sid/ns comparable across taps\n const existingSid = cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE);\n\n // The custom rules read the user agent and the request headers (§D3); without them every\n // `ua` and `header` condition is false. `Headers.get` is case-insensitive, so the\n // lower-cased name the matcher asks with finds whatever spelling the client sent.\n const v = engine.snap.verdict({\n ip, path, ua: req.headers.get('user-agent'), header: (n) => req.headers.get(n),\n }); // cold start fails open ('cold')\n if (v.block) {\n const ev = buildEvent(req, path, ip, crypto.randomUUID(), existingSid, false);\n ev.st = 403; // blocked requests always ship, unsampled\n ev.blk = v.reason; // SDK-01: the reason rides the event so the analyst counts SDK blocks, not the app's own 403s ('rule' when a rule decided)\n if (v.rule) ev.rl = v.rule;\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n const headers: Record<string, string> = {\n 'content-type': 'text/plain',\n 'x-block-reason': String(v.reason ?? ''),\n 'x-block-version': v.version ?? '',\n };\n if (v.rule) headers['x-block-rule'] = v.rule; // a custom rule blocked: name it, so the customer knows which row to edit\n return new Response('Forbidden', { status: 403, headers });\n }\n // `warn` passes the request and only marks its event; a skip passes with nothing\n // stamped — it is the absence of enforcement.\n const warnRule = v.warn ? v.rule ?? null : null;\n\n // A challenge verdict: serve the proof-of-work page unless this client already passed.\n // The verify route answers its own endpoint, so never challenge that path. A challenge\n // needs a resolved ip (the nonce and `_cch` are bound to it) — without one, fail open,\n // the same stance ip rules take at this position.\n // A client that HAS passed falls through to the normal capture path: it keeps its rid,\n // its session cookie and its event, so an hour of `_cch` is not an hour of blindness.\n // guardedAsync covers the whole branch — the synchronous catch below cannot see a\n // rejection from these awaits, and WebCrypto is not guaranteed to exist.\n if (v.challenge && ip && challengeEnabled() && !isChallengeRoute(path)) {\n return guardedAsync(async () => (\n (await challengePassed(engine, req, ip))\n ? capture(engine, req, path, ip, existingSid, warnRule, waitUntil)\n : serveChallenge(engine, req, ip, path + new URL(req.url).search, waitUntil)\n ), passThrough(req));\n }\n\n if (isBeaconRoute(req.method, path)) return passThrough(req); // no row, no x-rid: the route handler answers it\n\n return guardedAsync(() => capture(engine, req, path, ip, existingSid, warnRule, waitUntil), passThrough(req));\n } catch (err) {\n logRateLimited(err); // fail open: the app proceeds as if camada were not installed\n return guarded(() => passThrough(req), undefined);\n }\n };\n}\n","// The lazy singleton engine behind every @camada/next entry point: one SnapshotClient in\n// 'lazy' mode (edge runtime — ensureFresh per request, no interval timers) plus one\n// EventQueue, wired from the environment on first use. Unconfigured -> engine is null, every\n// entry point stays inert, and one rate-limited console.error says why. CAMADA_DISABLED=1 is\n// checked per request (isDisabled), never memoized here.\n//\n// This module is part of the middleware's import graph and therefore MUST stay\n// edge-runtime-safe: Web APIs and process.env reads only, no node: imports.\nimport {\n SnapshotClient, EventQueue, parseKey, parseTrustedProxyEnv, logRateLimited,\n type SnapshotVersion, type TrustedProxyConfig,\n} from '@camada/core';\n\nimport { SDK_ID } from './version';\n\nexport { parseTrustedProxyEnv }; // re-export for callers that had reached into this module\n\nexport interface ResolvedEnv {\n ingestToken: string;\n snapToken: string;\n secret: string; // HMAC key for the challenge nonce/cookie — never leaves the process\n ingestUrl: string;\n snapshotUrl: string;\n trustedProxy: TrustedProxyConfig | null; // null = defer to server-delivered config\n}\n\n/** Returns null (SDK stays inert, one log line) rather than throwing on bad config. */\nexport function resolveEnv(env: Record<string, string | undefined>): ResolvedEnv | null {\n const key = parseKey(env.CAMADA_KEY);\n const ingestToken = key?.ingestToken ?? env.CAMADA_TOKEN;\n const snapToken = key?.snapToken ?? env.CAMADA_SNAPSHOT_TOKEN;\n if (!ingestToken || !snapToken) return null;\n const ingestUrl = (env.CAMADA_INGEST_URL || 'https://in.camada.app').replace(/\\/$/, '');\n return {\n ingestToken, snapToken, ingestUrl,\n secret: env.CAMADA_KEY || `${ingestToken}.${snapToken}`,\n snapshotUrl: env.CAMADA_SNAPSHOT_URL || `${ingestUrl}/snapshot`,\n // On Vercel the platform overwrites X-Forwarded-For, so its rightmost entry is\n // trustworthy: default to vercel mode there unless explicitly overridden.\n trustedProxy: parseTrustedProxyEnv(env.CAMADA_TRUSTED_PROXY) ?? (env.VERCEL ? { mode: 'vercel' } : null),\n };\n}\n\nexport interface Engine {\n env: ResolvedEnv;\n snap: SnapshotClient;\n queue: EventQueue;\n fetchImpl: typeof fetch; // for the beacon relay; snapshot/queue carry their own copy\n}\n\nexport interface ConfigureOptions {\n env?: Record<string, string | undefined>;\n fetchImpl?: typeof fetch;\n challenge?: boolean; // enforce `challenge` verdicts with the first-party page (default true)\n snapshotVersion?: SnapshotVersion; // 5 (default) also carries the tenant's ordered custom rules; 4 the allow/challenge sides only; 3 opts out of both\n}\n\nlet engine: Engine | null | undefined; // undefined = not built yet; null = unconfigured\nlet overrides: ConfigureOptions = {};\n\n/** Internal test/reset hook (mirrors @camada/node's configure): replaces the singleton. */\nexport function configure(opts: ConfigureOptions = {}): void {\n engine?.snap.stop();\n engine?.queue.stop();\n overrides = opts;\n engine = undefined;\n}\n\nexport function envSource(): Record<string, string | undefined> {\n return overrides.env ?? (typeof process !== 'undefined' && process.env ? process.env : {});\n}\n\n/** Kill switch, checked per request so an env flip disables camada without a code change. */\nexport function isDisabled(): boolean {\n return envSource().CAMADA_DISABLED === '1';\n}\n\n/** SDK-04 is on unless the app opts out in code or with CAMADA_CHALLENGE=0. */\nexport function challengeEnabled(): boolean {\n return overrides.challenge !== false && envSource().CAMADA_CHALLENGE !== '0';\n}\n\nexport function getEngine(): Engine | null {\n if (engine !== undefined) return engine;\n const env = resolveEnv(envSource());\n if (!env) {\n logRateLimited(new Error('CAMADA_KEY (or CAMADA_TOKEN + CAMADA_SNAPSHOT_TOKEN) not set — camada is inactive'));\n return (engine = null);\n }\n const fetchImpl = overrides.fetchImpl;\n const injected = fetchImpl ? { fetchImpl } : {}; // never pass an explicit undefined key\n engine = {\n env,\n fetchImpl: fetchImpl ?? fetch,\n // snapshotVersion left undefined takes core's default (5); the client drops undefined keys.\n snap: new SnapshotClient({ url: env.snapshotUrl, token: env.snapToken, mode: 'lazy', sdk: SDK_ID, snapshotVersion: overrides.snapshotVersion, ...injected }),\n queue: new EventQueue({ url: env.ingestUrl, token: env.ingestToken, sdk: SDK_ID, ...injected }),\n };\n return engine;\n}\n\n/** The trusted-proxy config in force: explicit local/env override wins, then the\n * server-delivered tenant config, then none (socket-less edge -> ip stays null). */\nexport function trustedProxy(e: Engine): TrustedProxyConfig | null {\n return e.env.trustedProxy ?? e.snap.config?.trusted_proxy ?? null;\n}\n","{\n \"version\": \"0.3.1\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"name\": \"@camada/next\",\n \"description\": \"camada backend SDK for Next.js: edge-safe middleware/proxy, /api/camada route handlers, <CamadaBeacon/> with the request rid\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\",\n \"@camada/react\": \"file:../camada-react\"\n },\n \"peerDependencies\": {\n \"next\": \">=15\",\n \"react\": \">=18\"\n },\n \"devDependencies\": {\n \"typescript\": \"^5.7.2\",\n \"tsup\": \"^8.3.5\",\n \"vitest\": \"^3.0.0\",\n \"next\": \"^16.0.0\",\n \"react\": \"^19.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"@edge-runtime/vm\": \"^5.0.0\",\n \"esbuild\": \"^0.24.0\",\n \"@types/node\": \"^22.10.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The challenge for @camada/next (SDK-04). Edge-runtime-safe: Web APIs and WebCrypto only, no\n// node: imports — this module sits in the middleware's import graph, which\n// test/edge-safety.test.ts bundles for a bare edge runtime.\n//\n// The page is served by the middleware (or by challengeGate() from a route the app gates\n// itself) and posts a form to POST /api/camada/challenge, handled by camadaRoute().\nimport {\n createChallengeAsync, challengePage, challengeCookie, safeReturnTo, wantsHtml, parseFormBody,\n resolveClientIp, CHALLENGE_COOKIE, type AsyncChallengeKit,\n} from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { buildEvent, cookieValue, middlewareMarkValid, SESSION_COOKIE } from './event';\n\n/** Where the page posts. The catch-all route handler must be mounted at /api/camada/[...camada]\n * (the documented install) — the served page hard-codes this action. */\nexport const VERIFY_PATH = '/api/camada/challenge';\n\nconst BODY_MAX = 4 * 1024; // the verify form is ~120 bytes; anything larger is not ours\n\n/** The app may mount the catch-all elsewhere, so the middleware matches on the tail. */\nexport const isChallengeRoute = (path: string): boolean => /\\/camada\\/challenge\\/?$/.test(path);\n\n// One kit per engine; the engine is replaced wholesale by configure(), so the map never grows.\nconst kits = new WeakMap<Engine, AsyncChallengeKit>();\nexport function kitFor(engine: Engine): AsyncChallengeKit {\n let k = kits.get(engine);\n if (!k) { k = createChallengeAsync({ secret: engine.env.secret }); kits.set(engine, k); }\n return k;\n}\n\nexport const clientIp = (engine: Engine, req: Request): string | null =>\n resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n\nexport function challengePassed(engine: Engine, req: Request, ip: string): Promise<boolean> {\n return kitFor(engine).tokenValid(ip, Date.now(), cookieValue(req.headers.get('cookie') || '', CHALLENGE_COOKIE));\n}\n\n/** 403 + the proof-of-work page (HTML navigations) or 403 JSON (everything else), plus the\n * `blk: \"challenge\"` event — a served challenge is reported like a block (contract §D2). */\nexport async function serveChallenge(engine: Engine, req: Request, ip: string, target: string, waitUntil?: (p: Promise<unknown>) => void, ship = true): Promise<Response> {\n if (ship) {\n const ev = buildEvent(req, new URL(req.url).pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 403;\n ev.blk = 'challenge';\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n }\n\n if (!wantsHtml(req.headers.get('accept'), req.headers.get('sec-fetch-dest'))) {\n return new Response('{\"error\":\"challenge_required\"}', {\n status: 403,\n headers: { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' },\n });\n }\n const html = challengePage({ nonce: await kitFor(engine).nonce(ip, Date.now()), action: VERIFY_PATH, to: safeReturnTo(target) });\n return new Response(html, {\n status: 403,\n headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-camada-challenge': '1' },\n });\n}\n\n/** POST handler for `…/camada/challenge`: validate, set `_cch`, 302 back, ship `{ st: 200, ch: 1 }`. */\nexport async function verifyChallenge(engine: Engine, req: Request): Promise<Response> {\n const url = new URL(req.url);\n const ip = clientIp(engine, req);\n const kit = kitFor(engine);\n // camada answers this path before the app runs, so it must not become a place to post\n // hundreds of megabytes at an unauthenticated endpoint.\n if (Number(req.headers.get('content-length')) > BODY_MAX) return new Response(null, { status: 413 });\n const body = await req.text();\n if (body.length > BODY_MAX) return new Response(null, { status: 413 });\n const form = parseFormBody(body);\n const to = safeReturnTo(form.to);\n const now = Date.now();\n if (!ip || !(await kit.verify(ip, now, form.nonce, form.solution))) {\n const html = ip ? challengePage({ nonce: await kit.nonce(ip, now), action: url.pathname, to }) : 'Forbidden';\n return new Response(html, { status: 403, headers: { 'content-type': ip ? 'text/html; charset=utf-8' : 'text/plain', 'cache-control': 'no-store' } });\n }\n const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 200;\n ev.ch = 1; // challenge passed (contract §A3 ingest field)\n engine.queue.push(ev);\n void engine.queue.flush();\n return new Response(null, {\n status: 302,\n headers: {\n location: to,\n 'set-cookie': challengeCookie(await kit.issue(ip, now), url.protocol === 'https:'),\n 'cache-control': 'no-store',\n },\n });\n}\n\n/** For a route that wants to gate itself (the example's /challenge-me): the challenge Response,\n * or null when this client already holds a valid `_cch` and the route should render normally.\n *\n * One request, one event. The middleware stamps `x-camada-rid` and its `x-camada-mw` proof on\n * the request it forwards, so a mark that verifies means the middleware already shipped this\n * request's row (a client-sent rid alone proves nothing); the gate then serves\n * the page WITHOUT a second row, which would double-count the request at the analyst. Exclude\n * the gated route from the middleware matcher and the gate ships the `blk: \"challenge\"` row\n * itself — that is the configuration to use when you want the challenge counted. */\nexport async function challengeGate(req: Request): Promise<Response | null> {\n if (isDisabled() || !challengeEnabled()) return null;\n const engine = getEngine();\n if (!engine) return null;\n const ip = clientIp(engine, req);\n if (!ip || await challengePassed(engine, req, ip)) return null; // unidentifiable client: fail open\n const url = new URL(req.url);\n const shipped = await middlewareMarkValid(engine.env.secret, req).catch(() => false);\n return serveChallenge(engine, req, ip, url.pathname + url.search, undefined, !shipped);\n}\n","// One wire-event builder for every @camada/next entry (middleware + beacon route). Web APIs\n// only: this sits in the middleware's edge-safe import graph.\nimport { buildWireEvent, hmacHex, safeEqual, TAP_NEXT, type WireEvent } from '@camada/core';\n\nexport function buildEvent(req: Request, path: string, ip: string | null, rid: string, sid: string | null, newSession: boolean): WireEvent {\n const url = new URL(req.url);\n return buildWireEvent(\n {\n method: req.method,\n host: req.headers.get('host') ?? url.host,\n path,\n query: url.search,\n // The edge runtime sorts header names, so hord is alphabetical at this tap — still\n // shipped; the scorer knows sdk-next lacks the raw-wire-order signal (capability mask).\n headers: [...req.headers.entries()],\n ip,\n httpVersion: null, // not observable at this position\n },\n { tap: TAP_NEXT, rid, sid, newSession, ja4: req.headers.get('x-vercel-ja4-digest') },\n );\n}\n\n/** Stamped by the middleware next to x-camada-rid on the request it forwards: proof, for withCamada(), that this request's\n * event already shipped. It only ever spares the wrapper a second event; enforcement never depends on it. */\nexport const MW_HEADER = 'x-camada-mw';\nexport const MW_WINDOW_MS = 30_000; // middleware → route handler takes milliseconds; 30 s covers a cold function start\nconst MW_SKEW_MS = 5_000; // the middleware's clock may run ahead of the function's (edge vs. Node on Vercel)\n\n/** What the mark binds: when it was made, the method, the path and query, the rid, and the client chain as forwarded.\n * JSON keeps the fields apart, since the rid and X-Forwarded-For are whatever the client sent where the middleware did not run. */\nconst markInput = (ts: number, req: Request, rid: string): string => {\n const url = new URL(req.url);\n return 'mw:' + JSON.stringify([ts, req.method, url.pathname + url.search, rid, req.headers.get('x-forwarded-for') ?? '']);\n};\n\n/** `<ms timestamp>.<HMAC-SHA256 hex>` under the server-only secret. */\nexport async function middlewareMark(secret: string, req: Request, rid: string, now = Date.now()): Promise<string> {\n return `${now}.${await hmacHex(secret, markInput(now, req, rid))}`;\n}\n\n/** True only for a mark this deployment's middleware made for this very request line and client, within the window.\n * A copied pair fails on another path, query, method or client, and on any request once the window has passed. */\nexport async function middlewareMarkValid(secret: string, req: Request, now = Date.now()): Promise<boolean> {\n const m = /^(\\d{13})\\.([0-9a-f]{64})$/.exec(req.headers.get(MW_HEADER) ?? '');\n const rid = req.headers.get('x-camada-rid');\n if (!m || !rid) return false;\n const ts = Number(m[1]);\n if (now - ts > MW_WINDOW_MS || ts - now > MW_SKEW_MS) return false;\n return safeEqual(m[2], await hmacHex(secret, markInput(ts, req, rid)));\n}\n\nexport const SESSION_COOKIE = '_sfp'; // the same session cookie as the edge collector and @camada/node: sid/ns comparable across taps\n\nexport const cookieValue = (cookie: string, name: string): string | null => {\n const src = '; ' + cookie;\n const i = src.indexOf('; ' + name + '=');\n if (i === -1) return null;\n const start = i + name.length + 3;\n const j = src.indexOf(';', start);\n return src.slice(start, j === -1 ? undefined : j);\n};\n","// Route handlers for app/api/camada/[...camada]/route.ts:\n// GET …/b.js -> the first-party beacon IIFE (its auto-init derives the fp endpoint from\n// the script URL's final path segment and the rid from ?r=)\n// POST …/fp -> queue the beacon as a sig:1 row with the trusted-proxy-resolved client IP\n// and tap 'sdk-next': it rides the event batch (one request per flush at the\n// analyst, not one per page view) — the mirror of @camada/node's /_cam/*.\n// Works on BOTH runtimes (edge and node): Web APIs only, no node: imports. Unconfigured or\n// disabled: GET 404s, POST answers 204 and drops — inert, never an error.\nimport iife from '@camada/browser/iife-string';\nimport { guardedAsync, resolveClientIp, TAP_NEXT } from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { verifyChallenge } from './challenge';\nimport { buildEvent, cookieValue, SESSION_COOKIE } from './event';\n\nconst FP_MAX = 32 * 1024; // matches the server's /fp cap: never accept what ingest will 413\nconst encoder = new TextEncoder();\n\nconst notFound = () => new Response(null, { status: 404 });\nconst noContent = () => new Response(null, { status: 204, headers: { 'cache-control': 'no-store' } });\n\n// The route handlers enforce too: a middleware matcher that excludes /api/ (a common pattern)\n// must not leave the beacon endpoints serving blocked clients.\nfunction blocked(engine: Engine, req: Request): Response | null {\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n const path = new URL(req.url).pathname;\n // §D3: `ua` and `header` rules need these; `Headers.get` already matches names case-insensitively\n const v = engine.snap.verdict({ ip, path, ua: req.headers.get('user-agent'), header: (n) => req.headers.get(n) });\n if (!v.block) return null;\n const ev = buildEvent(req, path, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 403; ev.blk = v.reason; // SDK-01: a beacon-route deny is a block like any other — it ships, unsampled\n if (v.rule) ev.rl = v.rule;\n engine.queue.push(ev);\n void engine.queue.flush();\n const headers: Record<string, string> = { 'x-block-reason': String(v.reason ?? ''), 'x-block-version': v.version ?? '' };\n if (v.rule) headers['x-block-rule'] = v.rule;\n return new Response('Forbidden', { status: 403, headers });\n}\n\nfunction lastSegment(req: Request): string {\n const parts = new URL(req.url).pathname.split('/');\n return parts[parts.length - 1];\n}\n\nfunction activeEngine(): Engine | null {\n return isDisabled() ? null : getEngine();\n}\n\n// tenant switch: both beacon endpoints stand down together (b.js and fp), as node's and hono's do\nconst beaconOff = (engine: Engine): boolean => engine.snap.config?.beacon === false;\n\nexport function camadaRoute(): {\n GET: (req: Request) => Promise<Response>;\n POST: (req: Request) => Promise<Response>;\n} {\n return {\n GET: (req) => guardedAsync(async () => {\n if (lastSegment(req) !== 'b.js') return notFound();\n const engine = activeEngine();\n if (!engine) return notFound();\n engine.snap.ensureFresh();\n const deny = blocked(engine, req);\n if (deny) return deny;\n if (beaconOff(engine)) return notFound();\n return new Response(iife, {\n status: 200,\n headers: { 'content-type': 'application/javascript', 'cache-control': 'public, max-age=3600' },\n });\n }, notFound()),\n\n POST: (req) => guardedAsync(async () => {\n if (lastSegment(req) === 'challenge') {\n const engine = activeEngine();\n if (!engine || !challengeEnabled()) return notFound();\n engine.snap.ensureFresh();\n const deny = blocked(engine, req); // a blocked client cannot buy its way past with a solution\n if (deny) return deny;\n return verifyChallenge(engine, req);\n }\n if (lastSegment(req) !== 'fp') return notFound();\n const body = await req.text();\n if (encoder.encode(body).byteLength > FP_MAX) return new Response(null, { status: 413 });\n const engine = activeEngine();\n if (engine) {\n engine.snap.ensureFresh();\n const deny = blocked(engine, req);\n if (deny) return deny;\n if (beaconOff(engine)) return notFound();\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n let parsed: unknown;\n try { parsed = JSON.parse(body); } catch { return noContent(); } // not a beacon: drop it, never ship junk\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n engine.queue.push({ ...(parsed as Record<string, unknown>), sig: 1, ip, tap: TAP_NEXT });\n void engine.queue.flush(); // fire-and-forget, as the relay was: a serverless runtime may freeze right after the response\n }\n }\n return noContent();\n }, noContent()),\n };\n}\n","// <CamadaBeacon/> — an async React Server Component for the app-router layout <head>: reads\n// the middleware-stamped x-camada-rid request header (so the beacon POST joins the server's\n// wire event) and renders the @camada/react beacon tag pointing at the camada route.\n// 'next/headers' is imported lazily inside the component so this module only touches it\n// server-side, in a request scope.\nimport { CamadaBeacon as Beacon } from '@camada/react';\n\nexport interface CamadaBeaconProps {\n /** Where camadaRoute() serves the beacon script. Default: '/api/camada/b.js'. */\n src?: string;\n}\n\nexport async function CamadaBeacon({ src = '/api/camada/b.js' }: CamadaBeaconProps = {}) {\n let rid: string | null = null;\n try {\n const { headers } = await import('next/headers');\n rid = (await headers()).get('x-camada-rid');\n } catch { /* outside a request scope (static render, tests): beacon ships without a rid */ }\n return <Beacon src={src} rid={rid} />;\n}\n","// track() — app-context outcome events recorded from a server action, route handler, or server\n// component, joined to the middleware's wire event. The name is free-form; the analyst's rules read\n// this vocabulary: login_failed, login_succeeded, signup, password_reset, mfa_failed, payment_failed,\n// payment_succeeded, coupon_failed (@camada/node README has the table). Inside withCamada() an\n// event joins on the wrapper's own rid and session; elsewhere on the x-camada-rid the middleware\n// stamps and the _sfp session cookie. The identifier is\n// HMAC-hashed in-process with the ingest token: the raw value never reaches the queue, and\n// the analyst drops anything that is not hash-shaped anyway.\n// next/headers is imported lazily inside the call (the beacon.tsx pattern) so this module\n// stays importable everywhere; calling track() outside a request scope is a no-op.\nimport { hashUserId, logRateLimited, resolveClientIp, TAP_NEXT } from '@camada/core';\nimport { getEngine, isDisabled, trustedProxy } from './engine';\nimport { requestWaitUntil } from './wait-until';\n\nconst SESSION_COOKIE = '_sfp';\n\ninterface Scope { rid: string; sid: string | null }\ninterface Als { run<R>(store: Scope, fn: () => R): R; getStore(): Scope | undefined }\nlet als: Als | null | undefined;\n/** Next puts AsyncLocalStorage on globalThis in both runtimes (node-environment-baseline, the edge sandbox). */\nconst scopes = (): Als | null => {\n if (als === undefined) {\n const Ctor = (globalThis as { AsyncLocalStorage?: new () => Als }).AsyncLocalStorage;\n als = Ctor ? new Ctor() : null; // ponytail: without it (never under Next) a wrapped route's track() falls back to the header\n }\n return als;\n};\n\n/** withCamada() runs its handler here, so track() inside it takes the wrapper's rid and never a\n * client-sent x-camada-rid: a wrapped route sits outside the matcher, where nothing strips it. */\nexport const withTrackScope = <R>(scope: Scope, fn: () => R): R => { const s = scopes(); return s ? s.run(scope, fn) : fn(); };\n\n/** Never throws, never blocks the response: a camada problem must not break a login. */\nexport async function track(event: string, data?: { user?: string }): Promise<void> {\n try {\n if (isDisabled()) return;\n const engine = getEngine();\n if (!engine) return;\n const { headers, cookies } = await import('next/headers');\n const [h, c] = await Promise.all([headers(), cookies()]);\n const scope = scopes()?.getStore();\n const uid = data?.user ? await hashUserId(data.user, engine.env.ingestToken) : null;\n engine.queue.push({\n tap: TAP_NEXT,\n et: event,\n uid,\n rid: scope ? scope.rid : h.get('x-camada-rid'),\n sid: scope?.sid ?? c.get(SESSION_COOKIE)?.value ?? null,\n ip: resolveClientIp(null, h.get('x-forwarded-for'), trustedProxy(engine)),\n ts: Date.now(),\n });\n void engine.queue.flush(requestWaitUntil()); // a serverless runtime may freeze right after the response: hold it open for the flush\n } catch (err) {\n logRateLimited(err); // outside a request scope, unconfigured, or a camada bug: swallow\n }\n}\n","// A waitUntil for code that runs inside a route handler or server action (withCamada, track()). A\n// serverless host (Vercel) may freeze the function once the response has gone out, and the event's\n// flush only starts then, when the body is done. Next's after() (15.1+) holds the function open\n// until its callback settles, but it must be called inside the request scope, which the body-done\n// callback is not. So the first promise handed in registers ONE after() callback; it runs once the\n// response has closed and drains every promise handed in, including any that arrive while it waits.\n// Without after() (Next 15.0, or outside a request scope) the host's request context waitUntil is\n// used, the one @vercel/functions reads; with neither, the promise is left to run, which a\n// long-lived `next start` process does anyway.\nimport * as nextServer from 'next/server';\n\ntype WaitUntil = (p: Promise<unknown>) => void;\ntype After = (task: () => Promise<unknown>) => void;\ntype RequestContext = { get?: () => { waitUntil?: WaitUntil } | undefined };\n\nfunction contextWaitUntil(): WaitUntil | undefined {\n const g = globalThis as Record<symbol, RequestContext | undefined>;\n for (const name of ['@next/request-context', '@vercel/request-context']) {\n const ctx = g[Symbol.for(name)]?.get?.();\n if (typeof ctx?.waitUntil === 'function') return (p) => ctx.waitUntil!(p);\n }\n return undefined;\n}\n\n/** Call once per request, inside its scope; hand the result to everything that flushes for it. Never throws. */\nexport function requestWaitUntil(): WaitUntil {\n const pending: Promise<unknown>[] = [];\n let hold: WaitUntil | null | undefined; // undefined = not decided yet; null = nothing to hold the host open with\n const drain = async () => {\n for (let i = 0; i < pending.length; i++) await pending[i].catch(() => {});\n };\n return (p) => {\n if (hold === undefined) {\n hold = null;\n try {\n const after = (nextServer as { after?: After }).after;\n if (typeof after !== 'function') throw new Error('no after()');\n after(drain); // throws outside a request scope\n hold = (q) => { pending.push(q); };\n } catch {\n try { hold = contextWaitUntil() ?? null; } catch { hold = null; }\n }\n }\n try { hold?.(p); } catch { /* a host that refuses the promise costs only the hold */ }\n };\n}\n","// withCamada(handler): the route-handler position, where the response is visible. The middleware\n// runs before the route and ships its event pre-response (st and dur null); a wrapped route\n// handler runs @camada/core's fetch pipeline itself (verdict, block, challenge, the session) and\n// ships one event with the real status and a dur up to the response (a text/event-stream body to its last byte).\n//\n// Threat model. Every request header can come from the client: the middleware does not run for a\n// route left out of its matcher, and Next offers no server-only channel from the middleware to the\n// route (on Vercel they run in different processes, often different regions). So:\n// - Enforcement here never depends on a header. Verdict, block and challenge run on every request.\n// - The one thing a header may decide is whether this request's event already shipped. Wrapped\n// routes belong outside the matcher; if the middleware ran anyway it stamps `x-camada-mw`, an\n// HMAC under the server-only CAMADA_KEY of a timestamp, the method, the path and query, the rid\n// and X-Forwarded-For. A mark that verifies within 30 s skips the second event and the second\n// session cookie, nothing else. A copied mark fails on another path, query, method or client\n// and after the window; within it, it can at most hide a replay's event, never pass a block.\n// - The middleware strips any x-camada-mw the client sent before forwarding.\n// - track() inside the handler takes this wrapper's rid and session (withTrackScope), never a\n// client-sent x-camada-rid; only a request the middleware already shipped keeps its stamped one.\nimport iife from '@camada/browser/iife-string';\nimport { logRateLimited, TAP_NEXT } from '@camada/core';\nimport { createFetchCamada, withSetCookie } from '@camada/core/fetch';\nimport { envSource, getEngine } from './engine';\nimport { VERIFY_PATH } from './challenge';\nimport { middlewareMarkValid } from './event';\nimport { requestWaitUntil } from './wait-until';\nimport { withTrackScope } from './track';\nimport { SDK_ID } from './version';\n\nconst cam = createFetchCamada({ tap: TAP_NEXT, sdk: SDK_ID, iife }, { challengePath: VERIFY_PATH }); // lazy mode: edge and serverless\n\nasync function middlewareShipped(req: Request): Promise<boolean> {\n const secret = getEngine()?.env.secret;\n return !!secret && (await middlewareMarkValid(secret, req));\n}\n\n/** Next's redirect() / notFound() throw an error whose digest ends in the status they answer with. */\nconst thrownStatus = (err: unknown): number =>\n Number(/;(\\d{3});?$/.exec(String((err as { digest?: unknown } | null)?.digest ?? ''))?.[1] ?? 500);\n\n/**\n * `export const GET = withCamada(async (req) => Response.json(...))` in a route.ts. Same\n * CAMADA_* env as the middleware; inert without a key or with CAMADA_DISABLED=1. Never breaks\n * the handler: a camada failure costs the event, not the response.\n */\nexport function withCamada<R extends Request, A extends unknown[]>(\n handler: (req: R, ...rest: A) => Response | Promise<Response>,\n): (req: R, ...rest: A) => Promise<Response> {\n return async (req, ...rest) => {\n const env = envSource();\n // On Vercel the platform overwrites X-Forwarded-For, so its rightmost entry is trustworthy: the middleware's default too.\n const r = await cam.before(req, {\n env: { ...env, CAMADA_TRUSTED_PROXY: env.CAMADA_TRUSTED_PROXY || (env.VERCEL ? 'vercel' : undefined) },\n waitUntil: requestWaitUntil(), // the event flushes after the body: hold a serverless function open for it\n });\n if (!r) return handler(req, ...rest);\n if (r.response) return r.response; // block, challenge: whatever headers the request carries\n let shipped = false;\n try { shipped = await middlewareShipped(req); } catch (err) { logRateLimited(err); }\n if (shipped) {\n logRateLimited(new Error(`withCamada: the middleware also matched ${new URL(req.url).pathname}, so its pre-response event stands and dur is not reported; exclude wrapped routes from the matcher`));\n return handler(req, ...rest); // and its _sfp cookie: no second session\n }\n let res: Response;\n try {\n res = await withTrackScope({ rid: r.vars.rid, sid: r.vars.sid }, () => handler(req, ...rest));\n } catch (err) {\n cam.after(req, r.vars, thrownStatus(err));\n throw err;\n }\n res = withRid(res, r.vars.rid);\n return cam.finish(req, r.vars, r.vars.sessionCookie ? withSetCookie(res, r.vars.sessionCookie) : res);\n };\n}\n\n/** x-rid, the rid of this request's row, the same header the middleware stamps. Set in place, or on a copy when the\n * headers are immutable (`Response.redirect()`, a `fetch()` result), as withSetCookie does: same status and body.\n * A 101 upgrade is left alone. Not on a request the middleware shipped: its own x-rid, that row's, reaches the client. */\nfunction withRid(res: Response, rid: string): Response {\n if (res.status === 101) return res;\n try {\n res.headers.set('x-rid', rid);\n return res;\n } catch {\n // ponytail: core's copyResponse is not exported; its Deno and @hono/node-server cases never reach a Next route.\n const out = new Response(res.body, res);\n out.headers.set('x-rid', rid);\n return out;\n }\n}\n"],"mappings":"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","names":["resolveClientIp","logRateLimited","name","engine","engine","resolveClientIp","logRateLimited","guardedAsync","resolveClientIp","TAP_NEXT","engine","resolveClientIp","guardedAsync","TAP_NEXT","logRateLimited","resolveClientIp","TAP_NEXT","name","after","SESSION_COOKIE","engine","TAP_NEXT","resolveClientIp","logRateLimited","iife","logRateLimited","TAP_NEXT","TAP_NEXT","iife","logRateLimited"]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts","../src/engine.ts","../package.json","../src/version.ts","../src/challenge.ts","../src/event.ts","../src/route.ts","../src/beacon.tsx","../src/track.ts","../src/wait-until.ts","../src/with-camada.ts"],"sourcesContent":["// The Next.js middleware/proxy entry: enforce the blocklist inline, stamp x-camada-rid onto\n// the forwarded request (the <CamadaBeacon/> server component reads it back), ship the wire\n// event fire-and-forget. Everything runs inside a try/catch fail-open envelope — a camada\n// bug must never break the customer's app (plan.md INT-2).\n//\n// MUST stay edge-runtime-safe: Web APIs only (fetch/Headers/Request/Response/URL/crypto),\n// no node: imports anywhere in this entry's import graph. Proven by test/edge-safety.test.ts,\n// which bundles this file for a bare edge runtime and executes it inside @edge-runtime/vm.\n// NextRequest/NextFetchEvent are imported as TYPES only; NextResponse comes from\n// 'next/server', which is itself edge-safe.\nimport type { NextFetchEvent, NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport { resolveClientIp, logRateLimited, guarded, guardedAsync } from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { isChallengeRoute, challengePassed, serveChallenge, isChallengeable } from './challenge';\nimport { buildEvent, cookieValue, middlewareMark, MW_HEADER, SESSION_COOKIE } from './event';\n\n/** Per-client signal: no shared cache may keep the copy. CDNs read their own header ahead of cache-control. */\nconst NO_STORE_HEADERS = ['cache-control', 'cdn-cache-control', 'surrogate-control', 'cloudflare-cdn-cache-control', 'vercel-cdn-cache-control', 'netlify-cdn-cache-control'];\n\n/** camadaRoute()'s beacon endpoints (mounted at /api/camada/[...camada]). Camada's own, like core's scriptPath/fpPath:\n * after enforcement they are neither captured nor stamped with x-rid. */\nconst isBeaconRoute = (method: string, path: string): boolean =>\n (method === 'GET' && path === '/api/camada/b.js') || (method === 'POST' && path === '/api/camada/fp');\n\nexport interface CamadaMiddlewareOptions {\n // Reserved. The engine is configured via CAMADA_* environment variables.\n}\n\n/** Next accepts a promise here; the capture and challenge branches return one (they await WebCrypto). */\nexport type MiddlewareResult = Response | undefined | Promise<Response | undefined>;\n\n/** The ordinary path: ship one pre-response event, stamp the rid (and its proof for withCamada()),\n * mint the session cookie. `signalled`: a non-HTML request that passed under a challenge (82ar): `x-camada-challenge: 1` on the\n * response and `chs: 1` on the event, which then ships whatever sample and exclude say. `warnRule` is the id of the `warn` rule that let this request through, if one did (§D3). */\nasync function capture(engine: Engine, req: NextRequest, path: string, ip: string | null, existingSid: string | null, warnRule: string | null, waitUntil?: (p: Promise<unknown>) => void, signalled = false): Promise<Response> {\n const rid = crypto.randomUUID();\n const sid = existingSid ?? crypto.randomUUID();\n const cfg = engine.snap.config;\n const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));\n if (signalled || (!excluded && Math.random() < (cfg?.sample ?? 1))) {\n // st stays null: middleware ships pre-response, like the edge collector's tap.\n const ev = buildEvent(req, path, ip, rid, sid, !existingSid);\n if (warnRule) ev.wrn = warnRule;\n if (signalled) ev.chs = 1;\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n }\n\n const headers = new Headers(req.headers);\n headers.set('x-camada-rid', rid);\n // WebCrypto, the one await on this path; without it the request still goes through, just unproven (withCamada then ships too).\n // The headers are a copy of the client's: drop any mark it sent before stamping ours.\n headers.delete(MW_HEADER);\n const mark = await middlewareMark(engine.env.secret, req, rid).catch(() => null);\n if (mark) headers.set(MW_HEADER, mark);\n const res = NextResponse.next({ request: { headers } });\n res.headers.set('x-rid', rid);\n if (signalled) {\n res.headers.set('x-camada-challenge', '1');\n for (const n of NO_STORE_HEADERS) res.headers.set(n, 'no-store'); // the signal is per client: a shared cache must not hand it to others\n }\n if (!existingSid) {\n const secure = new URL(req.url).protocol === 'https:' ? '; Secure' : '';\n res.headers.append('set-cookie', `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${secure}`);\n }\n return res;\n}\n\n/** Where camada lets a request through without capturing it: forward it without any x-camada-mw or x-camada-rid the\n * client sent, so the only mark and rid a route handler ever sees are ones this middleware made. Undefined (Next\n * continues untouched) when there is neither. */\nfunction passThrough(req: Request): Response | undefined {\n if (!req.headers.has(MW_HEADER) && !req.headers.has('x-camada-rid')) return undefined;\n const headers = new Headers(req.headers);\n headers.delete(MW_HEADER);\n headers.delete('x-camada-rid');\n return NextResponse.next({ request: { headers } });\n}\n\n/**\n * `export default camada();` from middleware.ts (Next ≤15) / proxy.ts (Next 16).\n * Returns undefined (Next continues) whenever camada is disabled, unconfigured, or broken; a client-sent\n * x-camada-mw or x-camada-rid is stripped on every path that forwards the request.\n */\nexport function camada(_options?: CamadaMiddlewareOptions): (req: NextRequest, event: NextFetchEvent) => MiddlewareResult {\n return function camadaMiddleware(req: NextRequest, event: NextFetchEvent): MiddlewareResult {\n try {\n if (isDisabled()) return passThrough(req);\n const engine = getEngine();\n if (!engine) return passThrough(req);\n const waitUntil = event?.waitUntil ? (p: Promise<unknown>) => event.waitUntil(p) : undefined;\n engine.snap.ensureFresh(waitUntil);\n\n // There is no socket peer at the edge: with no trusted-proxy config (or no XFF) the\n // ip resolves to null and ip rules simply don't enforce — a spoofed X-Forwarded-For\n // can never reach the blocklist. On Vercel the engine defaults to {mode:'vercel'}.\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n const path = new URL(req.url).pathname;\n\n // the same _sfp session as the collector and @camada/node: sid/ns comparable across taps\n const existingSid = cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE);\n\n // The custom rules read the user agent and the request headers (§D3); without them every\n // `ua` and `header` condition is false. `Headers.get` is case-insensitive, so the\n // lower-cased name the matcher asks with finds whatever spelling the client sent.\n const v = engine.snap.verdict({\n ip, path, ua: req.headers.get('user-agent'), header: (n) => req.headers.get(n),\n }); // cold start fails open ('cold')\n if (v.block) {\n const ev = buildEvent(req, path, ip, crypto.randomUUID(), existingSid, false);\n ev.st = 403; // blocked requests always ship, unsampled\n ev.blk = v.reason; // SDK-01: the reason rides the event so the analyst counts SDK blocks, not the app's own 403s ('rule' when a rule decided)\n if (v.rule) ev.rl = v.rule;\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n const headers: Record<string, string> = {\n 'content-type': 'text/plain',\n 'cache-control': 'no-store',\n 'x-block-reason': String(v.reason ?? ''),\n 'x-block-version': v.version ?? '',\n };\n if (v.rule) headers['x-block-rule'] = v.rule; // a custom rule blocked: name it, so the customer knows which row to edit\n return new Response('Forbidden', { status: 403, headers });\n }\n // `warn` passes the request and only marks its event; a skip passes with nothing\n // stamped — it is the absence of enforcement.\n const warnRule = v.warn ? v.rule ?? null : null;\n\n // A challenge verdict: serve the proof-of-work page unless this client already passed.\n // The verify route answers its own endpoint, so never challenge that path. A challenge\n // needs a resolved ip (the nonce and `_cch` are bound to it) — without one, fail open,\n // the same stance ip rules take at this position.\n // A client that HAS passed falls through to the normal capture path: it keeps its rid,\n // its session cookie and its event, so an hour of `_cch` is not an hour of blindness.\n // guardedAsync covers the whole branch — the synchronous catch below cannot see a\n // rejection from these awaits, and WebCrypto is not guaranteed to exist.\n // Only a page or a Pages Router data request (`x-nextjs-data: 1`; Next turns a 403 into a document load) is challenged.\n // Any other request passes, signalled: x-camada-challenge: 1 and chs: 1 (82ar). That includes App Router soft navigations:\n // Next strips the RSC header before middleware runs, so they look like any fetch; the next document load meets the page.\n if (v.challenge && ip && challengeEnabled() && !isChallengeRoute(path)) {\n const page = isChallengeable(req);\n // A signalled beacon request falls through to the beacon branch below: camada's own answer carries no signal.\n if (page || !isBeaconRoute(req.method, path)) {\n return guardedAsync(async () => {\n const passed = await challengePassed(engine, req, ip);\n if (page && !passed) return serveChallenge(engine, req, ip, path + new URL(req.url).search, waitUntil);\n return capture(engine, req, path, ip, existingSid, warnRule, waitUntil, !passed);\n }, passThrough(req));\n }\n }\n\n if (isBeaconRoute(req.method, path)) return passThrough(req); // no row, no x-rid: the route handler answers it\n\n return guardedAsync(() => capture(engine, req, path, ip, existingSid, warnRule, waitUntil), passThrough(req));\n } catch (err) {\n logRateLimited(err); // fail open: the app proceeds as if camada were not installed\n return guarded(() => passThrough(req), undefined);\n }\n };\n}\n","// The lazy singleton engine behind every @camada/next entry point: one SnapshotClient in\n// 'lazy' mode (edge runtime — ensureFresh per request, no interval timers) plus one\n// EventQueue, wired from the environment on first use. Unconfigured -> engine is null, every\n// entry point stays inert, and one rate-limited console.error says why. CAMADA_DISABLED=1 is\n// checked per request (isDisabled), never memoized here.\n//\n// This module is part of the middleware's import graph and therefore MUST stay\n// edge-runtime-safe: Web APIs and process.env reads only, no node: imports.\nimport {\n SnapshotClient, EventQueue, parseKey, parseTrustedProxyEnv, logRateLimited,\n type SnapshotVersion, type TrustedProxyConfig,\n} from '@camada/core';\n\nimport { SDK_ID } from './version';\n\nexport { parseTrustedProxyEnv }; // re-export for callers that had reached into this module\n\nexport interface ResolvedEnv {\n ingestToken: string;\n snapToken: string;\n secret: string; // HMAC key for the challenge nonce/cookie — never leaves the process\n ingestUrl: string;\n snapshotUrl: string;\n trustedProxy: TrustedProxyConfig | null; // null = defer to server-delivered config\n}\n\n/** Returns null (SDK stays inert, one log line) rather than throwing on bad config. */\nexport function resolveEnv(env: Record<string, string | undefined>): ResolvedEnv | null {\n const key = parseKey(env.CAMADA_KEY);\n const ingestToken = key?.ingestToken ?? env.CAMADA_TOKEN;\n const snapToken = key?.snapToken ?? env.CAMADA_SNAPSHOT_TOKEN;\n if (!ingestToken || !snapToken) return null;\n const ingestUrl = (env.CAMADA_INGEST_URL || 'https://in.camada.app').replace(/\\/$/, '');\n return {\n ingestToken, snapToken, ingestUrl,\n secret: env.CAMADA_KEY || `${ingestToken}.${snapToken}`,\n snapshotUrl: env.CAMADA_SNAPSHOT_URL || `${ingestUrl}/snapshot`,\n // On Vercel the platform overwrites X-Forwarded-For, so its rightmost entry is\n // trustworthy: default to vercel mode there unless explicitly overridden.\n trustedProxy: parseTrustedProxyEnv(env.CAMADA_TRUSTED_PROXY) ?? (env.VERCEL ? { mode: 'vercel' } : null),\n };\n}\n\nexport interface Engine {\n env: ResolvedEnv;\n snap: SnapshotClient;\n queue: EventQueue;\n fetchImpl: typeof fetch; // for the beacon relay; snapshot/queue carry their own copy\n}\n\nexport interface ConfigureOptions {\n env?: Record<string, string | undefined>;\n fetchImpl?: typeof fetch;\n challenge?: boolean; // enforce `challenge` verdicts with the first-party page (default true)\n snapshotVersion?: SnapshotVersion; // 5 (default) also carries the tenant's ordered custom rules; 4 the allow/challenge sides only; 3 opts out of both\n}\n\nlet engine: Engine | null | undefined; // undefined = not built yet; null = unconfigured\nlet overrides: ConfigureOptions = {};\n\n/** Internal test/reset hook (mirrors @camada/node's configure): replaces the singleton. */\nexport function configure(opts: ConfigureOptions = {}): void {\n engine?.snap.stop();\n engine?.queue.stop();\n overrides = opts;\n engine = undefined;\n}\n\nexport function envSource(): Record<string, string | undefined> {\n return overrides.env ?? (typeof process !== 'undefined' && process.env ? process.env : {});\n}\n\n/** Kill switch, checked per request so an env flip disables camada without a code change. */\nexport function isDisabled(): boolean {\n return envSource().CAMADA_DISABLED === '1';\n}\n\n/** SDK-04 is on unless the app opts out in code or with CAMADA_CHALLENGE=0. */\nexport function challengeEnabled(): boolean {\n return overrides.challenge !== false && envSource().CAMADA_CHALLENGE !== '0';\n}\n\nexport function getEngine(): Engine | null {\n if (engine !== undefined) return engine;\n const env = resolveEnv(envSource());\n if (!env) {\n logRateLimited(new Error('CAMADA_KEY (or CAMADA_TOKEN + CAMADA_SNAPSHOT_TOKEN) not set — camada is inactive'));\n return (engine = null);\n }\n const fetchImpl = overrides.fetchImpl;\n const injected = fetchImpl ? { fetchImpl } : {}; // never pass an explicit undefined key\n engine = {\n env,\n fetchImpl: fetchImpl ?? fetch,\n // snapshotVersion left undefined takes core's default (5); the client drops undefined keys.\n snap: new SnapshotClient({ url: env.snapshotUrl, token: env.snapToken, mode: 'lazy', sdk: SDK_ID, snapshotVersion: overrides.snapshotVersion, ...injected }),\n queue: new EventQueue({ url: env.ingestUrl, token: env.ingestToken, sdk: SDK_ID, ...injected }),\n };\n return engine;\n}\n\n/** The trusted-proxy config in force: explicit local/env override wins, then the\n * server-delivered tenant config, then none (socket-less edge -> ip stays null). */\nexport function trustedProxy(e: Engine): TrustedProxyConfig | null {\n return e.env.trustedProxy ?? e.snap.config?.trusted_proxy ?? null;\n}\n","{\n \"version\": \"0.4.0\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run\",\n \"check\": \"tsc --noEmit\"\n },\n \"name\": \"@camada/next\",\n \"description\": \"camada backend SDK for Next.js: edge-safe middleware/proxy, /api/camada route handlers, <CamadaBeacon/> with the request rid\",\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/core\": \"file:../camada-core\",\n \"@camada/browser\": \"file:../camada-browser\",\n \"@camada/react\": \"file:../camada-react\"\n },\n \"peerDependencies\": {\n \"next\": \">=15\",\n \"react\": \">=18\"\n },\n \"devDependencies\": {\n \"typescript\": \"^5.7.2\",\n \"tsup\": \"^8.3.5\",\n \"vitest\": \"^3.0.0\",\n \"next\": \"^16.0.0\",\n \"react\": \"^19.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"@edge-runtime/vm\": \"^5.0.0\",\n \"esbuild\": \"^0.24.0\",\n \"@types/node\": \"^22.10.0\"\n }\n}\n","// The SDK's wire identity (SDK-03): x-camada-sdk: <package>/<version>. Named JSON imports are\n// inlined and tree-shaken by tsup at build, so dist carries two literals — no runtime fs read.\nimport { name, version } from '../package.json';\nexport const SDK_ID = `${name}/${version}`;\n","// The challenge for @camada/next (SDK-04). Edge-runtime-safe: Web APIs and WebCrypto only, no\n// node: imports — this module sits in the middleware's import graph, which\n// test/edge-safety.test.ts bundles for a bare edge runtime.\n//\n// The page is served by the middleware (or by challengeGate() from a route the app gates\n// itself) and posts a form to POST /api/camada/challenge, handled by camadaRoute().\nimport {\n createChallengeAsync, challengePage, challengePageCsp, challengeCookie, safeReturnTo, wantsHtml, parseFormBody,\n resolveClientIp, readBodyCapped, CHALLENGE_COOKIE, type AsyncChallengeKit,\n} from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { buildEvent, cookieValue, middlewareMarkValid, SESSION_COOKIE } from './event';\n\n/** Where the page posts. The catch-all route handler must be mounted at /api/camada/[...camada]\n * (the documented install) — the served page hard-codes this action. */\nexport const VERIFY_PATH = '/api/camada/challenge';\n\nconst BODY_MAX = 4 * 1024; // the verify form is ~120 bytes; anything larger is not ours\n\n/** The app may mount the catch-all elsewhere, so the middleware matches on the tail. */\nexport const isChallengeRoute = (path: string): boolean => /\\/camada\\/challenge\\/?$/.test(path);\n\n// One kit per engine; the engine is replaced wholesale by configure(), so the map never grows.\nconst kits = new WeakMap<Engine, AsyncChallengeKit>();\nexport function kitFor(engine: Engine): AsyncChallengeKit {\n let k = kits.get(engine);\n if (!k) { k = createChallengeAsync({ secret: engine.env.secret }); kits.set(engine, k); }\n return k;\n}\n\nexport const clientIp = (engine: Engine, req: Request): string | null =>\n resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n\nexport function challengePassed(engine: Engine, req: Request, ip: string): Promise<boolean> {\n return kitFor(engine).tokenValid(ip, Date.now(), cookieValue(req.headers.get('cookie') || '', CHALLENGE_COOKIE));\n}\n\n/** 403 + the proof-of-work page (HTML navigations) or 403 JSON (a Pages Router data request), plus the\n * `blk: \"challenge\"` event — a served challenge is reported like a block (contract §D2). */\nexport async function serveChallenge(engine: Engine, req: Request, ip: string, target: string, waitUntil?: (p: Promise<unknown>) => void, ship = true): Promise<Response> {\n if (ship) {\n const ev = buildEvent(req, new URL(req.url).pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 403;\n ev.blk = 'challenge';\n engine.queue.push(ev);\n engine.queue.flush(waitUntil);\n }\n\n // A page gets the proof-of-work page; the only other request served here is a Pages Router data request, which Next turns into a document load.\n if (!wantsHtml(req.headers.get('accept'), req.headers.get('sec-fetch-dest'))) {\n return new Response('{\"error\":\"challenge_required\"}', {\n status: 403,\n headers: { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' },\n });\n }\n const html = challengePage({ nonce: await kitFor(engine).nonce(ip, Date.now()), action: VERIFY_PATH, to: safeReturnTo(target) });\n return new Response(html, {\n status: 403,\n // the page's own policy (its inline solver and style by hash), never one the app sets (camada-all-mwpo)\n headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-camada-challenge': '1', 'content-security-policy': challengePageCsp() },\n });\n}\n\n/** POST handler for `…/camada/challenge`: validate, set `_cch`, 302 back, ship `{ st: 200, ch: 1 }`. */\nexport async function verifyChallenge(engine: Engine, req: Request): Promise<Response> {\n const url = new URL(req.url);\n const ip = clientIp(engine, req);\n const kit = kitFor(engine);\n // camada answers this path before the app runs, so it must not become a place to post\n // hundreds of megabytes at an unauthenticated endpoint.\n const body = await readBodyCapped(req, BODY_MAX);\n if (body === null) return new Response(null, { status: 413 });\n const form = parseFormBody(body);\n const to = safeReturnTo(form.to);\n const now = Date.now();\n if (!ip || !(await kit.verify(ip, now, form.nonce, form.solution))) {\n const html = ip ? challengePage({ nonce: await kit.nonce(ip, now), action: url.pathname, to }) : 'Forbidden';\n const headers: Record<string, string> = { 'content-type': ip ? 'text/html; charset=utf-8' : 'text/plain', 'cache-control': 'no-store' };\n if (ip) headers['content-security-policy'] = challengePageCsp();\n return new Response(html, { status: 403, headers });\n }\n const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 200;\n ev.ch = 1; // challenge passed (contract §A3 ingest field)\n engine.queue.push(ev);\n void engine.queue.flush();\n return new Response(null, {\n status: 302,\n headers: {\n location: to,\n 'set-cookie': challengeCookie(await kit.issue(ip, now), url.protocol === 'https:'),\n 'cache-control': 'no-store',\n },\n });\n}\n\n/** Only a page or a Pages Router data request (`x-nextjs-data: 1`; Next turns its non-ok answer into a hard navigation) is\n * challenged. Any other request passes, signalled (82ar). App Router soft navigations are among them: Next strips the RSC\n * header (and `_rsc`) before middleware runs, so they cannot be told from a fetch; the next document load meets the page. */\nexport function isChallengeable(req: Request): boolean {\n return wantsHtml(req.headers.get('accept'), req.headers.get('sec-fetch-dest')) || req.headers.get('x-nextjs-data') === '1';\n}\n\n/** For a route that wants to gate itself (the example's /challenge-me): the challenge Response,\n * or null when this client already holds a valid `_cch` and the route should render normally.\n *\n * One request, one event. The middleware stamps `x-camada-rid` and its `x-camada-mw` proof on\n * the request it forwards, so a mark that verifies means the middleware already shipped this\n * request's row (a client-sent rid alone proves nothing); the gate then serves\n * the page WITHOUT a second row, which would double-count the request at the analyst. Exclude\n * the gated route from the middleware matcher and the gate ships the `blk: \"challenge\"` row\n * itself — that is the configuration to use when you want the challenge counted.\n *\n * A request that is not challengeable returns null. The gate ships one `chs: 1` row only when there is no valid\n * middleware mark (the middleware did not see the request) and the ip has no valid `_cch`; with a valid mark it ships\n * nothing. A null return cannot set headers: the route sets `x-camada-challenge: 1` and `no-store` on `cache-control`\n * and the CDN cache headers (`cdn-cache-control`, `surrogate-control`, `cloudflare-`/`vercel-`/`netlify-cdn-cache-control`)\n * itself if it wants the frontend to see the signal. */\nexport async function challengeGate(req: Request): Promise<Response | null> {\n if (isDisabled() || !challengeEnabled()) return null;\n const engine = getEngine();\n if (!engine) return null;\n const challengeable = isChallengeable(req);\n const ip = clientIp(engine, req);\n if (!ip || await challengePassed(engine, req, ip)) return null; // unidentifiable client: fail open\n const url = new URL(req.url);\n const shipped = await middlewareMarkValid(engine.env.secret, req).catch(() => false);\n if (!challengeable) {\n // Not challenged (82ar): the route continues. Unless the middleware saw this request (valid mark), ship its chs: 1 event here.\n if (!shipped) {\n const ev = buildEvent(req, url.pathname, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.chs = 1;\n engine.queue.push(ev);\n engine.queue.flush();\n }\n return null;\n }\n return serveChallenge(engine, req, ip, url.pathname + url.search, undefined, !shipped);\n}\n","// One wire-event builder for every @camada/next entry (middleware + beacon route). Web APIs\n// only: this sits in the middleware's edge-safe import graph.\nimport { buildWireEvent, hmacHex, safeEqual, TAP_NEXT, type WireEvent } from '@camada/core';\nimport { envSource } from './engine';\n\nexport function buildEvent(req: Request, path: string, ip: string | null, rid: string, sid: string | null, newSession: boolean): WireEvent {\n const url = new URL(req.url);\n return buildWireEvent(\n {\n method: req.method,\n host: req.headers.get('host') ?? url.host,\n path,\n query: url.search,\n // The edge runtime sorts header names, so hord is alphabetical at this tap — still\n // shipped; the scorer knows sdk-next lacks the raw-wire-order signal (capability mask).\n headers: [...req.headers.entries()],\n ip,\n httpVersion: null, // not observable at this position\n },\n // ja4 only on Vercel (the server env, which a client cannot set): elsewhere x-vercel-ja4-digest is client-settable.\n { tap: TAP_NEXT, rid, sid, newSession, ja4: envSource().VERCEL ? req.headers.get('x-vercel-ja4-digest') : null },\n );\n}\n\n/** Stamped by the middleware next to x-camada-rid on the request it forwards: proof, for withCamada(), that this request's\n * event already shipped. It only ever spares the wrapper a second event; enforcement never depends on it. */\nexport const MW_HEADER = 'x-camada-mw';\nexport const MW_WINDOW_MS = 30_000; // middleware → route handler takes milliseconds; 30 s covers a cold function start\nconst MW_SKEW_MS = 5_000; // the middleware's clock may run ahead of the function's (edge vs. Node on Vercel)\n\n/** What the mark binds: when it was made, the method, the path and query, the rid, and the client chain as forwarded.\n * JSON keeps the fields apart, since the rid and X-Forwarded-For are whatever the client sent where the middleware did not run. */\nconst markInput = (ts: number, req: Request, rid: string): string => {\n const url = new URL(req.url);\n return 'mw:' + JSON.stringify([ts, req.method, url.pathname + url.search, rid, req.headers.get('x-forwarded-for') ?? '']);\n};\n\n/** `<ms timestamp>.<HMAC-SHA256 hex>` under the server-only secret. */\nexport async function middlewareMark(secret: string, req: Request, rid: string, now = Date.now()): Promise<string> {\n return `${now}.${await hmacHex(secret, markInput(now, req, rid))}`;\n}\n\n/** True only for a mark this deployment's middleware made for this very request line and client, within the window.\n * A copied pair fails on another path, query, method or client, and on any request once the window has passed. */\nexport async function middlewareMarkValid(secret: string, req: Request, now = Date.now()): Promise<boolean> {\n const m = /^(\\d{13})\\.([0-9a-f]{64})$/.exec(req.headers.get(MW_HEADER) ?? '');\n const rid = req.headers.get('x-camada-rid');\n if (!m || !rid) return false;\n const ts = Number(m[1]);\n if (now - ts > MW_WINDOW_MS || ts - now > MW_SKEW_MS) return false;\n return safeEqual(m[2], await hmacHex(secret, markInput(ts, req, rid)));\n}\n\nexport const SESSION_COOKIE = '_sfp'; // the same session cookie as the edge collector and @camada/node: sid/ns comparable across taps\n\nexport const cookieValue = (cookie: string, name: string): string | null => {\n const src = '; ' + cookie;\n const i = src.indexOf('; ' + name + '=');\n if (i === -1) return null;\n const start = i + name.length + 3;\n const j = src.indexOf(';', start);\n return src.slice(start, j === -1 ? undefined : j);\n};\n","// Route handlers for app/api/camada/[...camada]/route.ts:\n// GET …/b.js -> the first-party beacon IIFE (its auto-init derives the fp endpoint from\n// the script URL's final path segment and the rid from ?r=)\n// POST …/fp -> queue the beacon as a sig:1 row with the trusted-proxy-resolved client IP\n// and tap 'sdk-next': it rides the event batch (one request per flush at the\n// analyst, not one per page view) — the mirror of @camada/node's /_cam/*.\n// Works on BOTH runtimes (edge and node): Web APIs only, no node: imports. Unconfigured or\n// disabled: GET 404s, POST answers 204 and drops — inert, never an error.\nimport iife from '@camada/browser/iife-string';\nimport { guardedAsync, resolveClientIp, readBodyCapped, TAP_NEXT, REQUEST_ROW_KEYS } from '@camada/core';\nimport { getEngine, isDisabled, challengeEnabled, trustedProxy, type Engine } from './engine';\nimport { verifyChallenge } from './challenge';\nimport { buildEvent, cookieValue, SESSION_COOKIE } from './event';\n\nconst FP_MAX = 32 * 1024; // matches the server's /fp cap: never accept what ingest will 413\n\nconst notFound = () => new Response(null, { status: 404 });\nconst noContent = () => new Response(null, { status: 204, headers: { 'cache-control': 'no-store' } });\n\n// The route handlers enforce too: a middleware matcher that excludes /api/ (a common pattern)\n// must not leave the beacon endpoints serving blocked clients.\nfunction blocked(engine: Engine, req: Request): Response | null {\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n const path = new URL(req.url).pathname;\n // §D3: `ua` and `header` rules need these; `Headers.get` already matches names case-insensitively\n const v = engine.snap.verdict({ ip, path, ua: req.headers.get('user-agent'), header: (n) => req.headers.get(n) });\n if (!v.block) return null;\n const ev = buildEvent(req, path, ip, crypto.randomUUID(), cookieValue(req.headers.get('cookie') || '', SESSION_COOKIE), false);\n ev.st = 403; ev.blk = v.reason; // SDK-01: a beacon-route deny is a block like any other — it ships, unsampled\n if (v.rule) ev.rl = v.rule;\n engine.queue.push(ev);\n void engine.queue.flush();\n const headers: Record<string, string> = { 'cache-control': 'no-store', 'x-block-reason': String(v.reason ?? ''), 'x-block-version': v.version ?? '' };\n if (v.rule) headers['x-block-rule'] = v.rule;\n return new Response('Forbidden', { status: 403, headers });\n}\n\nfunction lastSegment(req: Request): string {\n const parts = new URL(req.url).pathname.split('/');\n return parts[parts.length - 1];\n}\n\nfunction activeEngine(): Engine | null {\n return isDisabled() ? null : getEngine();\n}\n\n// tenant switch: both beacon endpoints stand down together (b.js and fp), as node's and hono's do\nconst beaconOff = (engine: Engine): boolean => engine.snap.config?.beacon === false;\n\nexport function camadaRoute(): {\n GET: (req: Request) => Promise<Response>;\n POST: (req: Request) => Promise<Response>;\n} {\n return {\n GET: (req) => guardedAsync(async () => {\n if (lastSegment(req) !== 'b.js') return notFound();\n const engine = activeEngine();\n if (!engine) return notFound();\n engine.snap.ensureFresh();\n const deny = blocked(engine, req);\n if (deny) return deny;\n if (beaconOff(engine)) return notFound();\n return new Response(iife, {\n status: 200,\n headers: { 'content-type': 'application/javascript', 'cache-control': 'public, max-age=3600' },\n });\n }, notFound()),\n\n POST: (req) => guardedAsync(async () => {\n if (lastSegment(req) === 'challenge') {\n const engine = activeEngine();\n if (!engine || !challengeEnabled()) return notFound();\n engine.snap.ensureFresh();\n const deny = blocked(engine, req); // a blocked client cannot buy its way past with a solution\n if (deny) return deny;\n return verifyChallenge(engine, req);\n }\n if (lastSegment(req) !== 'fp') return notFound();\n const engine = activeEngine();\n if (engine) {\n engine.snap.ensureFresh();\n // Decide before reading: a blocked client or a beacon the tenant turned off pulls no body.\n const deny = blocked(engine, req);\n if (deny) return deny;\n if (beaconOff(engine)) return notFound();\n const body = await readBodyCapped(req, FP_MAX); // streamed: a chunked body cannot buffer past the cap\n if (body === null) return new Response(null, { status: 413 });\n const ip = resolveClientIp(null, req.headers.get('x-forwarded-for'), trustedProxy(engine));\n let parsed: unknown;\n try { parsed = JSON.parse(body); } catch { return noContent(); } // not a beacon: drop it, never ship junk\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n const row: Record<string, unknown> = { ...(parsed as Record<string, unknown>), sig: 1, ip, tap: TAP_NEXT, rts: Date.now() }; // rts after the spread: the relay's clock, never the browser's\n for (const k of REQUEST_ROW_KEYS) delete row[k]; // SDK-attested keys never ride a visitor's beacon, as core's relay\n engine.queue.push(row);\n void engine.queue.flush(); // fire-and-forget, as the relay was: a serverless runtime may freeze right after the response\n }\n }\n return noContent();\n }, noContent()),\n };\n}\n","// <CamadaBeacon/> — an async React Server Component for the app-router layout <head>: reads\n// the middleware-stamped x-camada-rid request header (so the beacon POST joins the server's\n// wire event) and renders the @camada/react beacon tag pointing at the camada route.\n// 'next/headers' is imported lazily inside the component so this module only touches it\n// server-side, in a request scope.\nimport { CamadaBeacon as Beacon } from '@camada/react';\n\nexport interface CamadaBeaconProps {\n /** Where camadaRoute() serves the beacon script. Default: '/api/camada/b.js'. */\n src?: string;\n}\n\nexport async function CamadaBeacon({ src = '/api/camada/b.js' }: CamadaBeaconProps = {}) {\n let rid: string | null = null;\n try {\n const { headers } = await import('next/headers');\n rid = (await headers()).get('x-camada-rid');\n } catch { /* outside a request scope (static render, tests): beacon ships without a rid */ }\n return <Beacon src={src} rid={rid} />;\n}\n","// track() — app-context outcome events recorded from a server action, route handler, or server\n// component, joined to the middleware's wire event. The name is free-form; the analyst's rules read\n// this vocabulary: login_failed, login_succeeded, signup, password_reset, mfa_failed, payment_failed,\n// payment_succeeded, coupon_failed (@camada/node README has the table). Inside withCamada() an\n// event joins on the wrapper's own rid and session; elsewhere on the x-camada-rid the middleware\n// stamps and the _sfp session cookie. The identifier is\n// HMAC-hashed in-process with the ingest token: the raw value never reaches the queue, and\n// the analyst drops anything that is not hash-shaped anyway.\n// next/headers is imported lazily inside the call (the beacon.tsx pattern) so this module\n// stays importable everywhere; calling track() outside a request scope is a no-op.\nimport { hashUserId, logRateLimited, resolveClientIp, TAP_NEXT } from '@camada/core';\nimport { getEngine, isDisabled, trustedProxy } from './engine';\nimport { requestWaitUntil } from './wait-until';\n\nconst SESSION_COOKIE = '_sfp';\n\ninterface Scope { rid: string; sid: string | null }\ninterface Als { run<R>(store: Scope, fn: () => R): R; getStore(): Scope | undefined }\nlet als: Als | null | undefined;\n/** Next puts AsyncLocalStorage on globalThis in both runtimes (node-environment-baseline, the edge sandbox). */\nconst scopes = (): Als | null => {\n if (als === undefined) {\n const Ctor = (globalThis as { AsyncLocalStorage?: new () => Als }).AsyncLocalStorage;\n als = Ctor ? new Ctor() : null; // ponytail: without it (never under Next) a wrapped route's track() falls back to the header\n }\n return als;\n};\n\n/** withCamada() runs its handler here, so track() inside it takes the wrapper's rid and never a\n * client-sent x-camada-rid: a wrapped route sits outside the matcher, where nothing strips it. */\nexport const withTrackScope = <R>(scope: Scope, fn: () => R): R => { const s = scopes(); return s ? s.run(scope, fn) : fn(); };\n\n/** Never throws, never blocks the response: a camada problem must not break a login. */\nexport async function track(event: string, data?: { user?: string }): Promise<void> {\n try {\n if (isDisabled()) return;\n const engine = getEngine();\n if (!engine) return;\n const { headers, cookies } = await import('next/headers');\n const [h, c] = await Promise.all([headers(), cookies()]);\n const scope = scopes()?.getStore();\n const uid = data?.user ? await hashUserId(data.user, engine.env.ingestToken) : null;\n engine.queue.push({\n tap: TAP_NEXT,\n et: event,\n uid,\n rid: scope ? scope.rid : h.get('x-camada-rid'),\n sid: scope?.sid ?? c.get(SESSION_COOKIE)?.value ?? null,\n ip: resolveClientIp(null, h.get('x-forwarded-for'), trustedProxy(engine)),\n ts: Date.now(),\n });\n void engine.queue.flush(requestWaitUntil()); // a serverless runtime may freeze right after the response: hold it open for the flush\n } catch (err) {\n logRateLimited(err); // outside a request scope, unconfigured, or a camada bug: swallow\n }\n}\n","// A waitUntil for code that runs inside a route handler or server action (withCamada, track()). A\n// serverless host (Vercel) may freeze the function once the response has gone out, and the event's\n// flush only starts then, when the body is done. Next's after() (15.1+) holds the function open\n// until its callback settles, but it must be called inside the request scope, which the body-done\n// callback is not. So the first promise handed in registers ONE after() callback; it runs once the\n// response has closed and drains every promise handed in, including any that arrive while it waits.\n// Without after() (Next 15.0, or outside a request scope) the host's request context waitUntil is\n// used, the one @vercel/functions reads; with neither, the promise is left to run, which a\n// long-lived `next start` process does anyway.\nimport * as nextServer from 'next/server';\n\ntype WaitUntil = (p: Promise<unknown>) => void;\ntype After = (task: () => Promise<unknown>) => void;\ntype RequestContext = { get?: () => { waitUntil?: WaitUntil } | undefined };\n\nfunction contextWaitUntil(): WaitUntil | undefined {\n const g = globalThis as Record<symbol, RequestContext | undefined>;\n for (const name of ['@next/request-context', '@vercel/request-context']) {\n const ctx = g[Symbol.for(name)]?.get?.();\n if (typeof ctx?.waitUntil === 'function') return (p) => ctx.waitUntil!(p);\n }\n return undefined;\n}\n\n/** Call once per request, inside its scope; hand the result to everything that flushes for it. Never throws. */\nexport function requestWaitUntil(): WaitUntil {\n const pending: Promise<unknown>[] = [];\n let hold: WaitUntil | null | undefined; // undefined = not decided yet; null = nothing to hold the host open with\n const drain = async () => {\n for (let i = 0; i < pending.length; i++) await pending[i].catch(() => {});\n };\n return (p) => {\n if (hold === undefined) {\n hold = null;\n try {\n const after = (nextServer as { after?: After }).after;\n if (typeof after !== 'function') throw new Error('no after()');\n after(drain); // throws outside a request scope\n hold = (q) => { pending.push(q); };\n } catch {\n try { hold = contextWaitUntil() ?? null; } catch { hold = null; }\n }\n }\n try { hold?.(p); } catch { /* a host that refuses the promise costs only the hold */ }\n };\n}\n","// withCamada(handler): the route-handler position, where the response is visible. The middleware\n// runs before the route and ships its event pre-response (st and dur null); a wrapped route\n// handler runs @camada/core's fetch pipeline itself (verdict, block, challenge, the session) and\n// ships one event with the real status and a dur up to the response (a text/event-stream body to its last byte).\n//\n// Threat model. Every request header can come from the client: the middleware does not run for a\n// route left out of its matcher, and Next offers no server-only channel from the middleware to the\n// route (on Vercel they run in different processes, often different regions). So:\n// - Enforcement here never depends on a header. Verdict, block and challenge run on every request.\n// - The one thing a header may decide is whether this request's event already shipped. Wrapped\n// routes belong outside the matcher; if the middleware ran anyway it stamps `x-camada-mw`, an\n// HMAC under the server-only CAMADA_KEY of a timestamp, the method, the path and query, the rid\n// and X-Forwarded-For. A mark that verifies within 30 s skips the second event and the second\n// session cookie, nothing else. A copied mark fails on another path, query, method or client\n// and after the window; within it, it can at most hide a replay's event, never pass a block.\n// - The middleware strips any x-camada-mw the client sent before forwarding.\n// - track() inside the handler takes this wrapper's rid and session (withTrackScope), never a\n// client-sent x-camada-rid; only a request the middleware already shipped keeps its stamped one.\nimport iife from '@camada/browser/iife-string';\nimport { logRateLimited, TAP_NEXT } from '@camada/core';\nimport { createFetchCamada, withSetCookie } from '@camada/core/fetch';\nimport { envSource, getEngine } from './engine';\nimport { VERIFY_PATH } from './challenge';\nimport { middlewareMarkValid } from './event';\nimport { requestWaitUntil } from './wait-until';\nimport { withTrackScope } from './track';\nimport { SDK_ID } from './version';\n\nconst cam = createFetchCamada({ tap: TAP_NEXT, sdk: SDK_ID, iife }, { challengePath: VERIFY_PATH }); // lazy mode: edge and serverless\n\nasync function middlewareShipped(req: Request): Promise<boolean> {\n const secret = getEngine()?.env.secret;\n return !!secret && (await middlewareMarkValid(secret, req));\n}\n\n/** Next's redirect() / notFound() throw an error whose digest ends in the status they answer with. */\nconst thrownStatus = (err: unknown): number =>\n Number(/;(\\d{3});?$/.exec(String((err as { digest?: unknown } | null)?.digest ?? ''))?.[1] ?? 500);\n\n/**\n * `export const GET = withCamada(async (req) => Response.json(...))` in a route.ts. Same\n * CAMADA_* env as the middleware; inert without a key or with CAMADA_DISABLED=1. Never breaks\n * the handler: a camada failure costs the event, not the response.\n */\nexport function withCamada<R extends Request, A extends unknown[]>(\n handler: (req: R, ...rest: A) => Response | Promise<Response>,\n): (req: R, ...rest: A) => Promise<Response> {\n return async (req, ...rest) => {\n const env = envSource();\n // On Vercel the platform overwrites X-Forwarded-For, so its rightmost entry is trustworthy: the middleware's default too.\n const r = await cam.before(req, {\n env: { ...env, CAMADA_TRUSTED_PROXY: env.CAMADA_TRUSTED_PROXY || (env.VERCEL ? 'vercel' : undefined) },\n waitUntil: requestWaitUntil(), // the event flushes after the body: hold a serverless function open for it\n });\n if (!r) return handler(req, ...rest);\n if (r.response) return r.response; // block, challenge: whatever headers the request carries\n let shipped = false;\n try { shipped = await middlewareShipped(req); } catch (err) { logRateLimited(err); }\n if (shipped) {\n logRateLimited(new Error(`withCamada: the middleware also matched ${new URL(req.url).pathname}, so its pre-response event stands and dur is not reported; exclude wrapped routes from the matcher`));\n return handler(req, ...rest); // and its _sfp cookie: no second session\n }\n let res: Response;\n try {\n res = await withTrackScope({ rid: r.vars.rid, sid: r.vars.sid }, () => handler(req, ...rest));\n } catch (err) {\n cam.after(req, r.vars, thrownStatus(err));\n throw err;\n }\n res = withRid(res, r.vars.rid);\n return cam.finish(req, r.vars, r.vars.sessionCookie ? withSetCookie(res, r.vars.sessionCookie) : res);\n };\n}\n\n/** x-rid, the rid of this request's row, the same header the middleware stamps. Set in place, or on a copy when the\n * headers are immutable (`Response.redirect()`, a `fetch()` result), as withSetCookie does: same status and body.\n * A 101 upgrade is left alone. Not on a request the middleware shipped: its own x-rid, that row's, reaches the client. */\nfunction withRid(res: Response, rid: string): Response {\n if (res.status === 101) return res;\n try {\n res.headers.set('x-rid', rid);\n return res;\n } catch {\n // ponytail: core's copyResponse is not exported; its Deno and @hono/node-server cases never reach a Next route.\n const out = new Response(res.body, res);\n out.headers.set('x-rid', rid);\n return out;\n }\n}\n"],"mappings":"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","names":["resolveClientIp","logRateLimited","name","engine","engine","resolveClientIp","logRateLimited","guardedAsync","resolveClientIp","readBodyCapped","TAP_NEXT","engine","resolveClientIp","guardedAsync","readBodyCapped","TAP_NEXT","logRateLimited","resolveClientIp","TAP_NEXT","name","after","SESSION_COOKIE","engine","TAP_NEXT","resolveClientIp","logRateLimited","iife","logRateLimited","TAP_NEXT","TAP_NEXT","iife","logRateLimited"]}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.4.0",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
@@ -14,18 +14,23 @@
|
|
|
14
14
|
"description": "camada backend SDK for Next.js: edge-safe middleware/proxy, /api/camada route handlers, <CamadaBeacon/> with the request rid",
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"require": {
|
|
22
|
+
"types": "./dist/index.d.cts",
|
|
23
|
+
"default": "./dist/index.cjs"
|
|
24
|
+
}
|
|
20
25
|
}
|
|
21
26
|
},
|
|
22
27
|
"main": "./dist/index.cjs",
|
|
23
28
|
"module": "./dist/index.js",
|
|
24
29
|
"types": "./dist/index.d.ts",
|
|
25
30
|
"dependencies": {
|
|
26
|
-
"@camada/core": "^0.
|
|
27
|
-
"@camada/browser": "^0.
|
|
28
|
-
"@camada/react": "^0.
|
|
31
|
+
"@camada/core": "^0.6.0",
|
|
32
|
+
"@camada/browser": "^0.4.0",
|
|
33
|
+
"@camada/react": "^0.4.0"
|
|
29
34
|
},
|
|
30
35
|
"peerDependencies": {
|
|
31
36
|
"next": ">=15",
|