@camada/node 0.2.0 → 0.3.1

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fastify.ts","../src/camada.ts","../src/env.ts","../src/challenge.ts","../package.json","../src/version.ts","../src/index.ts"],"sourcesContent":["// Fastify plugin, dependency-free (no fastify-plugin needed): the skip-override symbol keeps\n// the hook on the parent scope. Register:\n// import { camadaFastify } from '@camada/node/fastify';\n// await app.register(camadaFastify);\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Camada } from './camada.js';\nimport { getDefault } from './index.js';\n\ninterface Fastifyish {\n addHook(name: 'onRequest', hook: (request: { raw: IncomingMessage }, reply: { raw: ServerResponse; hijack(): void }, done: () => void) => void): void;\n}\n\nexport function camadaFastify(instance: Fastifyish, opts: { camada?: Camada }, done: (err?: Error) => void): void {\n const engine = opts.camada ?? getDefault();\n instance.addHook('onRequest', (request, reply, hookDone) => {\n if (engine.handle(request.raw, reply.raw)) reply.hijack(); // camada wrote the response on the raw socket\n hookDone();\n });\n done();\n}\n(camadaFastify as unknown as Record<symbol, boolean>)[Symbol.for('skip-override')] = true;\n","// The engine: node:http-level request handling every adapter delegates to.\n// camada.handle(req, res) -> true when camada fully answered the request (block or beacon\n// endpoint), false when the app should proceed. Everything runs inside the fail-open\n// envelope: a camada bug must never 5xx the customer (plan.md INT-2), and CAMADA_DISABLED=1\n// bypasses the SDK entirely.\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { randomUUID, webcrypto } from 'node:crypto'; // webcrypto handed to hashUserId: Node 18 has no global crypto.subtle\nimport {\n SnapshotClient, EventQueue, buildWireEvent, resolveClientIp, hashUserId, guarded, logRateLimited,\n challengePage, challengeCookie, safeReturnTo, wantsHtml, parseFormBody, CHALLENGE_COOKIE,\n TAP_NODE, type ChallengeKit, type SnapshotVersion, type TrustedProxyConfig, type WireEvent,\n} from '@camada/core';\nimport iife from '@camada/browser/iife-string';\nimport { resolveEnv, type ResolvedEnv } from './env.js';\nimport {\n CHALLENGE_PATH, nodeChallengeKit, readBody, isHttps, writeChallengePage, writeChallengeJson,\n} from './challenge.js';\nimport { SDK_ID } from './version.js';\n\nconst SESSION_COOKIE = '_sfp'; // same cookie as the edge collector: sid/ns comparable across taps\nconst SCRIPT_PATH = '/_cam/b.js';\nconst FP_PATH = '/_cam/fp';\nconst FP_MAX = 32 * 1024; // matches the server's /fp cap: never accept what ingest will 413\n\nexport interface CamadaOptions {\n env?: Record<string, string | undefined>;\n fetchImpl?: typeof fetch; // threaded into snapshot client, event queue, and the fp relay (tests)\n refreshMs?: number;\n scriptPath?: string;\n fpPath?: string;\n challenge?: boolean; // enforce `challenge` verdicts with the first-party page (default true; CAMADA_CHALLENGE=0 also switches it off)\n challengePath?: string; // where that page posts its solution (default /__camada/challenge)\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\ninterface CamadaRequest extends IncomingMessage {\n camada?: { rid: string; ip: string | null; sid: string };\n route?: { path?: string }; // Express fills this after routing\n camadaChallenged?: boolean; // serveChallenge() already shipped this request's event\n}\n\n/** The getter `header` conditions read (§D3). node:http lower-cases every incoming name and\n * the matcher always asks with a lower-cased one, so nothing has to be normalised here; a\n * header the client repeated arrives as an array and is joined the way the wire carried it. */\nconst headerReader = (req: IncomingMessage) => (name: string): string | null => {\n const v = req.headers[name];\n return v == null ? null : Array.isArray(v) ? v.join(', ') : v;\n};\n\nconst 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\nexport class Camada {\n readonly env: ResolvedEnv | null;\n readonly snap: SnapshotClient | null = null;\n readonly queue: EventQueue | null = null;\n private readonly fetchImpl: typeof fetch;\n private readonly scriptPath: string;\n private readonly fpPath: string;\n private readonly challengeOn: boolean;\n private readonly challengePath: string;\n private readonly kit: ChallengeKit | null = null;\n private readonly envSource: Record<string, string | undefined>;\n\n constructor(opts: CamadaOptions = {}) {\n this.envSource = opts.env ?? process.env;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.scriptPath = opts.scriptPath ?? SCRIPT_PATH;\n this.fpPath = opts.fpPath ?? FP_PATH;\n this.challengeOn = opts.challenge !== false && this.envSource.CAMADA_CHALLENGE !== '0';\n this.challengePath = opts.challengePath ?? CHALLENGE_PATH;\n this.env = resolveEnv(this.envSource);\n if (!this.env) return; // unconfigured: every entry point no-ops\n if (this.envSource.CAMADA_DISABLED === '1') return; // killed at boot: no poll timer, no exit hooks, truly silent\n this.snap = new SnapshotClient({\n url: this.env.snapshotUrl, token: this.env.snapToken,\n mode: this.env.serverless ? 'lazy' : 'timer',\n refreshMs: opts.refreshMs, snapshotVersion: opts.snapshotVersion, fetchImpl: this.fetchImpl, sdk: SDK_ID,\n });\n this.queue = new EventQueue({ url: this.env.ingestUrl, token: this.env.ingestToken, fetchImpl: this.fetchImpl, sdk: SDK_ID });\n this.kit = nodeChallengeKit(this.env.secret);\n this.snap.start();\n this.queue.installNodeExitFlush();\n }\n\n get disabled(): boolean { return this.envSource.CAMADA_DISABLED === '1' || !this.env; }\n\n private trustedProxy(): TrustedProxyConfig | null {\n return this.env?.trustedProxy ?? this.snap?.config?.trusted_proxy ?? null; // explicit local override wins\n }\n\n private beaconEnabled(): boolean { return this.snap?.config?.beacon !== false; }\n\n /** True when camada fully handled the request. Synchronous; never throws. */\n handle(req: IncomingMessage, res: ServerResponse): boolean {\n return guarded(() => this.handleInner(req as CamadaRequest, res), false);\n }\n\n private handleInner(req: CamadaRequest, res: ServerResponse): boolean {\n if (this.disabled || !this.snap || !this.queue || !this.env) return false;\n const t0 = Date.now();\n this.snap.ensureFresh();\n\n const rawUrl = req.url || '/';\n const qi = rawUrl.indexOf('?');\n const path = qi === -1 ? rawUrl : rawUrl.slice(0, qi);\n const query = qi === -1 ? '' : rawUrl.slice(qi);\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n\n // enforce before anything else, beacon endpoints included — fail open while cold. The custom\n // rules read the user agent and the request headers (§D3); without them every `ua` and\n // `header` condition is false.\n const v = this.snap.verdict({ ip, path, ua: req.headers['user-agent'], header: headerReader(req) });\n if (v.block) {\n const headers: Record<string, string> = { 'x-block-reason': v.reason ?? '', 'x-block-version': v.version ?? '', 'content-type': 'text/plain' };\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 res.writeHead(403, headers);\n res.end('Forbidden');\n const ev = this.buildEvent(req, path, query, ip, { rid: randomUUID(), sid: null, newSession: false });\n ev.st = 403; // blocked requests always ship: silent expiry makes blocks oscillate\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 this.queue.push(ev);\n return true;\n }\n // `warn` passes the request and only marks its event (below, on response-finish); a skip\n // passes with nothing stamped at all — it is the absence of enforcement.\n\n // A challenge needs a resolved client IP: the nonce and the `_cch` cookie are bound to it,\n // so without one a single solve would mint a cookie every unidentified client could\n // present. No ip -> no challenge (fail open), the same stance ip rules take.\n if (this.challengeOn && this.kit && ip) {\n // The verify endpoint answers first: a challenged client must be able to reach it.\n if (req.method === 'POST' && path === this.challengePath) { this.verifyChallenge(req, res, ip); return true; }\n if (v.challenge && !this.challengePassed(req, ip)) { this.serveChallengeInner(req, res, ip, path + query); return true; }\n }\n\n if (this.beaconEnabled()) {\n if (req.method === 'GET' && path === this.scriptPath) {\n res.writeHead(200, { 'content-type': 'application/javascript', 'cache-control': 'public, max-age=3600' });\n res.end(iife);\n return true;\n }\n if (req.method === 'POST' && path === this.fpPath) {\n this.relayBeacon(req, res, ip);\n return true;\n }\n }\n\n const rid = randomUUID();\n let sid = cookieValue((req.headers.cookie as string) || '', SESSION_COOKIE);\n const newSession = !sid;\n if (!sid) {\n sid = randomUUID();\n const https = (req.socket as { encrypted?: boolean }).encrypted || req.headers['x-forwarded-proto'] === 'https';\n res.setHeader('set-cookie', `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${https ? '; Secure' : ''}`);\n }\n req.camada = { rid, ip, sid };\n res.setHeader('x-rid', rid);\n\n const cfg = this.snap.config;\n const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));\n if (!excluded && Math.random() < (cfg?.sample ?? 1)) {\n res.on('finish', () => guarded(() => {\n // serveChallenge() may have answered from inside the app, and it already shipped the\n // `blk: \"challenge\"` row — one request, one event.\n if (req.camadaChallenged) return;\n const ev = this.buildEvent(req, path, query, ip, { rid, sid, newSession });\n ev.st = res.statusCode;\n ev.dur = Date.now() - t0;\n if (req.route?.path) ev.rt = String(req.route.path);\n if (v.warn && v.rule) ev.wrn = v.rule; // §D3: the warn rule that let this request through\n this.queue!.push(ev);\n }, undefined));\n }\n return false;\n }\n\n private buildEvent(req: CamadaRequest, path: string, query: string, ip: string | null, o: { rid: string; sid: string | null; newSession: boolean }): WireEvent {\n const raw = req.rawHeaders || [];\n const headers: Array<[string, string]> = [];\n for (let i = 0; i + 1 < raw.length; i += 2) headers.push([raw[i], raw[i + 1]]);\n return buildWireEvent(\n { method: req.method || 'GET', host: (req.headers.host as string) || '', path, query, headers, ip, httpVersion: req.httpVersion },\n { tap: TAP_NODE, rid: o.rid, sid: o.sid, newSession: o.newSession },\n );\n }\n\n /** Reads the beacon POST (≤32 KB), answers 204 immediately, and queues the beacon as a `sig: 1`\n * row with the trusted-proxy-resolved client IP: it rides the next event batch, so the analyst\n * sees one request per flush instead of one per page view. Junk bodies are dropped, never shipped. */\n private relayBeacon(req: IncomingMessage, res: ServerResponse, ip: string | null): void {\n const chunks: Buffer[] = [];\n let size = 0, dead = false;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size > FP_MAX) { dead = true; req.removeAllListeners('data'); res.writeHead(413).end(); return; }\n chunks.push(c);\n });\n req.on('end', () => guarded(() => {\n if (dead) return;\n res.writeHead(204, { 'cache-control': 'no-store' });\n res.end();\n let parsed: unknown;\n try { parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch { return; }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return;\n this.queue!.push({ ...(parsed as Record<string, unknown>), sig: 1, ip, tap: TAP_NODE });\n }, undefined));\n req.on('error', () => { try { res.destroy(); } catch { /* already gone */ } });\n }\n\n private challengePassed(req: IncomingMessage, ip: string | null): boolean {\n return !!this.kit?.tokenValid(ip, Date.now(), cookieValue((req.headers.cookie as string) || '', 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). */\n private serveChallengeInner(req: CamadaRequest, res: ServerResponse, ip: string, target: string): void {\n const to = safeReturnTo(target);\n if (wantsHtml((req.headers.accept as string) ?? null, (req.headers['sec-fetch-dest'] as string) ?? null)) {\n writeChallengePage(res, challengePage({ nonce: this.kit!.nonce(ip, Date.now()), action: this.challengePath, to }));\n } else {\n writeChallengeJson(res);\n }\n // The response is out; telemetry must never be able to undo that (a throw here would make\n // guarded() report the request as unhandled and let the app write to an ended response).\n guarded(() => {\n const qi = target.indexOf('?');\n const ev = this.buildEvent(req, qi === -1 ? target : target.slice(0, qi), qi === -1 ? '' : target.slice(qi), ip,\n { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.st = 403;\n ev.blk = 'challenge';\n this.queue!.push(ev);\n }, undefined);\n }\n\n /** The request's existing session, so challenge rows join the session that produced them. */\n private sessionOf(req: IncomingMessage): string | null {\n return cookieValue((req.headers.cookie as string) || '', SESSION_COOKIE);\n }\n\n /** POST from the challenge page: validate the nonce and the proof of work, set `_cch`, 302\n * back to the (sanitised, same-site) original URL, and ship `{ st: 200, ch: 1 }`. */\n private verifyChallenge(req: CamadaRequest, res: ServerResponse, ip: string): void {\n readBody(req, (body) => guarded(() => {\n const form = parseFormBody(body);\n const to = safeReturnTo(form.to);\n const now = Date.now();\n if (!this.kit!.verify(ip, now, form.nonce, form.solution)) {\n writeChallengePage(res, challengePage({ nonce: this.kit!.nonce(ip, now), action: this.challengePath, to }));\n return;\n }\n res.writeHead(302, {\n location: to,\n 'set-cookie': challengeCookie(this.kit!.issue(ip, now), isHttps(req)),\n 'cache-control': 'no-store',\n });\n res.end();\n const ev = this.buildEvent(req, this.challengePath, '', ip, { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.st = 200;\n ev.ch = 1; // challenge passed (contract §A3 ingest field)\n this.queue!.push(ev);\n }, undefined));\n }\n\n /** Serve the challenge for this request on demand — for a route the app wants to gate itself\n * (the example's /challenge-me). Returns false when the client already holds a valid `_cch`,\n * so the caller renders its own page. */\n serveChallenge(req: IncomingMessage, res: ServerResponse): boolean {\n return guarded(() => {\n if (this.disabled || !this.kit || !this.queue) return false;\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n if (!ip || this.challengePassed(req, ip)) return false; // unidentifiable client: fail open\n (req as CamadaRequest).camadaChallenged = true;\n this.serveChallengeInner(req as CamadaRequest, res, ip, req.url || '/');\n return true;\n }, false);\n }\n\n /** For HTML templates: the first-party beacon tag with the request's rid. */\n scriptTag(req: IncomingMessage): string {\n if (this.disabled || !this.beaconEnabled()) return '';\n const rid = (req as CamadaRequest).camada?.rid;\n return `<script src=\"${this.scriptPath}${rid ? `?r=${rid}` : ''}\" async></script>`;\n }\n\n /** App-context outcome events (login failed, signup, …). The identifier is HMAC-hashed\n * in-process; the raw value never reaches the queue. */\n track(req: IncomingMessage, event: string, data?: { user?: string }): void {\n guarded(() => {\n if (this.disabled || !this.queue || !this.env) return;\n const ctx = (req as CamadaRequest).camada;\n void (async () => {\n const uid = data?.user ? await hashUserId(data.user, this.env!.ingestToken, (globalThis.crypto ?? webcrypto).subtle) : null;\n this.queue!.push({ tap: TAP_NODE, et: event, uid, rid: ctx?.rid ?? null, sid: ctx?.sid ?? null, ip: ctx?.ip ?? null, ts: Date.now() });\n })().catch(logRateLimited);\n }, undefined);\n }\n\n /** Express/Connect middleware (also what NestJS on the default platform consumes). */\n express() {\n return (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void): void => {\n if (!this.handle(req, res)) next();\n };\n }\n\n stop(): void {\n this.snap?.stop();\n this.queue?.stop();\n }\n}\n\nexport function createCamada(opts: CamadaOptions = {}): Camada {\n const c = new Camada(opts);\n if (!c.env) logRateLimited(new Error('CAMADA_KEY (or CAMADA_TOKEN + CAMADA_SNAPSHOT_TOKEN) not set — camada is inactive'));\n return c;\n}\n","// Environment wiring. The two-line quickstart depends on this file doing the right thing:\n// CAMADA_KEY=<ingest_token>.<snap_token> (printed by `reconcile instructions` and seed)\n// CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL (dev: http://localhost:8787[/snapshot])\n// CAMADA_DISABLED=1 kill switch, checked per request\n// CAMADA_SERVERLESS=1 lazy snapshot mode (no interval timer)\n// CAMADA_TRUSTED_PROXY local override: none | vercel | hops:N | cidrs:a,b\nimport { parseKey, parseTrustedProxyEnv, type TrustedProxyConfig } from '@camada/core';\n\nexport { parseTrustedProxyEnv }; // re-export: this was @camada/node's public surface before it moved to core\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 serverless: boolean;\n trustedProxy: TrustedProxyConfig | null; // null = defer to server-delivered config\n}\n\n/** Returns null (SDK stays disabled, one log line) rather than throwing on bad config. */\nexport function resolveEnv(env: Record<string, string | undefined> = process.env): 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.dev').replace(/\\/$/, ''); // PLACEHOLDER default — confirm the production ingest domain before any npm publish\n return {\n ingestToken, snapToken, ingestUrl,\n secret: env.CAMADA_KEY || `${ingestToken}.${snapToken}`,\n snapshotUrl: env.CAMADA_SNAPSHOT_URL || `${ingestUrl}/snapshot`,\n serverless: env.CAMADA_SERVERLESS === '1',\n trustedProxy: parseTrustedProxyEnv(env.CAMADA_TRUSTED_PROXY),\n };\n}\n","// The challenge for @camada/node (SDK-04). node:crypto gives us a synchronous HMAC, so the\n// cookie check costs nothing on the hot path and Camada.handle() stays a synchronous boolean.\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createHash, createHmac } from 'node:crypto';\nimport { createChallenge, type ChallengeKit } from '@camada/core';\n\nexport const CHALLENGE_PATH = '/__camada/challenge';\nconst BODY_MAX = 4 * 1024; // the verify form is ~120 bytes; anything larger is not ours\n\nexport function nodeChallengeKit(secret: string): ChallengeKit {\n return createChallenge({\n secret,\n hmac: (s, m) => createHmac('sha256', s).update(m).digest('hex'),\n sha: (m) => createHash('sha256').update(m).digest('hex'),\n });\n}\n\n/** Reads at most BODY_MAX bytes; hands back '' when the client sends more (or errors). */\nexport function readBody(req: IncomingMessage, done: (body: string) => void): void {\n const chunks: Buffer[] = [];\n let size = 0, dead = false;\n req.on('data', (c: Buffer) => {\n if (dead) return;\n size += c.length;\n if (size > BODY_MAX) { dead = true; chunks.length = 0; return; }\n chunks.push(c);\n });\n req.on('end', () => done(dead ? '' : Buffer.concat(chunks).toString('utf8')));\n req.on('error', () => done(''));\n}\n\nexport const isHttps = (req: IncomingMessage): boolean =>\n !!(req.socket as { encrypted?: boolean } | undefined)?.encrypted || req.headers['x-forwarded-proto'] === 'https';\n\nexport function writeChallengePage(res: ServerResponse, html: string): void {\n res.writeHead(403, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-camada-challenge': '1' });\n res.end(html);\n}\n\nexport function writeChallengeJson(res: ServerResponse): void {\n res.writeHead(403, { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' });\n res.end('{\"error\":\"challenge_required\"}');\n}\n","{\n \"version\": \"0.2.0\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=18\"\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/node\",\n \"description\": \"camada backend SDK for Node: request capture, inline blocking, first-party beacon serving; Express/Fastify/Nest/Koa adapters\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./fastify\": {\n \"types\": \"./dist/fastify.d.ts\",\n \"import\": \"./dist/fastify.js\",\n \"require\": \"./dist/fastify.cjs\"\n },\n \"./nest\": {\n \"types\": \"./dist/nest.d.ts\",\n \"import\": \"./dist/nest.js\",\n \"require\": \"./dist/nest.cjs\"\n },\n \"./koa\": {\n \"types\": \"./dist/koa.d.ts\",\n \"import\": \"./dist/koa.js\",\n \"require\": \"./dist/koa.cjs\"\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/browser\": \"file:../camada-browser\",\n \"@camada/core\": \"file:../camada-core\"\n },\n \"devDependencies\": {\n \"@nestjs/common\": \"^11.0.0\",\n \"@nestjs/core\": \"^11.0.0\",\n \"@nestjs/platform-express\": \"^11.0.0\",\n \"@types/express\": \"^4.17.25\",\n \"@types/koa\": \"^2.15.2\",\n \"@types/node\": \"^22.10.0\",\n \"express\": \"^4.21.0\",\n \"fastify\": \"^5.2.0\",\n \"koa\": \"^2.15.0\",\n \"reflect-metadata\": \"^0.2.2\",\n \"rxjs\": \"^7.8.1\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"@nestjs/common\": \">=10\"\n },\n \"peerDependenciesMeta\": {\n \"@nestjs/common\": {\n \"optional\": true\n }\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","// Quickstart (Express / Connect / Nest-on-Express):\n// import camada from '@camada/node';\n// app.use(camada.express()); // env: CAMADA_KEY (+ CAMADA_INGEST_URL for dev)\n// The default export is a lazy singleton wired from the environment on first use.\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Camada, createCamada, type CamadaOptions } from './camada.js';\n\nexport { Camada, createCamada, type CamadaOptions } from './camada.js';\nexport { resolveEnv, parseTrustedProxyEnv, type ResolvedEnv } from './env.js';\n\nlet singleton: Camada | null = null;\nexport function getDefault(): Camada {\n return (singleton ??= createCamada());\n}\n/** Test/reset hook: replaces the singleton (stops the old one). */\nexport function configure(opts: CamadaOptions): Camada {\n singleton?.stop();\n singleton = createCamada(opts);\n return singleton;\n}\n\nexport default {\n express: () => (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => { if (!getDefault().handle(req, res)) next(); },\n handle: (req: IncomingMessage, res: ServerResponse) => getDefault().handle(req, res),\n scriptTag: (req: IncomingMessage) => getDefault().scriptTag(req),\n serveChallenge: (req: IncomingMessage, res: ServerResponse) => getDefault().serveChallenge(req, res),\n track: (req: IncomingMessage, event: string, data?: { user?: string }) => getDefault().track(req, event, data),\n configure,\n};\n"],"mappings":"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","names":["import_node_crypto","import_core","import_core","name","iife"]}
1
+ {"version":3,"sources":["../src/fastify.ts","../src/camada.ts","../src/env.ts","../src/challenge.ts","../package.json","../src/version.ts","../src/index.ts"],"sourcesContent":["// Fastify plugin, dependency-free (no fastify-plugin needed): the skip-override symbol keeps\n// the hook on the parent scope. Register:\n// import { camadaFastify } from '@camada/node/fastify';\n// await app.register(camadaFastify);\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Camada } from './camada.js';\nimport { getDefault } from './index.js';\n\ninterface Fastifyish {\n addHook(name: 'onRequest', hook: (request: { raw: IncomingMessage }, reply: { raw: ServerResponse; hijack(): void }, done: () => void) => void): void;\n}\n\nexport function camadaFastify(instance: Fastifyish, opts: { camada?: Camada }, done: (err?: Error) => void): void {\n const engine = opts.camada ?? getDefault();\n instance.addHook('onRequest', (request, reply, hookDone) => {\n if (engine.handle(request.raw, reply.raw)) reply.hijack(); // camada wrote the response on the raw socket\n hookDone();\n });\n done();\n}\n(camadaFastify as unknown as Record<symbol, boolean>)[Symbol.for('skip-override')] = true;\n","// The engine: node:http-level request handling every adapter delegates to.\n// camada.handle(req, res) -> true when camada fully answered the request (block or beacon\n// endpoint), false when the app should proceed. Everything runs inside the fail-open\n// envelope: a camada bug must never 5xx the customer (plan.md INT-2), and CAMADA_DISABLED=1\n// bypasses the SDK entirely.\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\nimport { randomUUID, webcrypto } from 'node:crypto'; // webcrypto handed to hashUserId explicitly, so the hash never depends on which global the host exposes\nimport {\n SnapshotClient, EventQueue, buildWireEvent, resolveClientIp, hashUserId, guarded, logRateLimited,\n challengePage, challengeCookie, safeReturnTo, wantsHtml, parseFormBody, CHALLENGE_COOKIE,\n TAP_NODE, type ChallengeKit, type SnapshotVersion, type TrustedProxyConfig, type WireEvent,\n} from '@camada/core';\nimport iife from '@camada/browser/iife-string';\nimport { resolveEnv, type ResolvedEnv } from './env.js';\nimport {\n CHALLENGE_PATH, nodeChallengeKit, readBody, isHttps, writeChallengePage, writeChallengeJson,\n} from './challenge.js';\nimport { SDK_ID } from './version.js';\n\nconst SESSION_COOKIE = '_sfp'; // same cookie as the edge collector: sid/ns comparable across taps\nconst SCRIPT_PATH = '/_cam/b.js';\nconst FP_PATH = '/_cam/fp';\nconst FP_MAX = 32 * 1024; // matches the server's /fp cap: never accept what ingest will 413\nconst ATTACHED = Symbol.for('camada.attached'); // one wrap per server, however often attach() is called\n\nexport interface CamadaOptions {\n env?: Record<string, string | undefined>;\n fetchImpl?: typeof fetch; // threaded into snapshot client, event queue, and the fp relay (tests)\n refreshMs?: number;\n scriptPath?: string;\n fpPath?: string;\n challenge?: boolean; // enforce `challenge` verdicts with the first-party page (default true; CAMADA_CHALLENGE=0 also switches it off)\n challengePath?: string; // where that page posts its solution (default /__camada/challenge)\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\ninterface CamadaRequest extends IncomingMessage {\n camada?: { rid: string; ip: string | null; sid: string };\n route?: { path?: string }; // Express fills this after routing\n camadaChallenged?: boolean; // serveChallenge() already shipped this request's event\n}\n\n/** The getter `header` conditions read (§D3). node:http lower-cases every incoming name and\n * the matcher always asks with a lower-cased one, so nothing has to be normalised here; a\n * header the client repeated arrives as an array and is joined the way the wire carried it. */\nconst headerReader = (req: IncomingMessage) => (name: string): string | null => {\n const v = req.headers[name];\n return v == null ? null : Array.isArray(v) ? v.join(', ') : v;\n};\n\nconst 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\n/** Sets a first visit's `_sfp` and keeps it there: an app that replaces Set-Cookie through\n * setHeader (or writeHead, setHeaders, which route through it once any header is set) gets its\n * own cookies plus the session's, unless it set `_sfp` itself. appendHeader already keeps it. */\nfunction keepSessionCookie(res: ServerResponse, cookie: string): void {\n const set = res.setHeader;\n res.setHeader = function (this: ServerResponse, name: string, value: number | string | readonly string[]) {\n if (typeof name === 'string' && name.toLowerCase() === 'set-cookie') {\n const list = Array.isArray(value) ? value : [String(value)];\n if (!list.some((c) => String(c).trimStart().startsWith(SESSION_COOKIE + '='))) value = [...list, cookie];\n }\n return set.call(this, name, value);\n } as ServerResponse['setHeader'];\n res.setHeader('set-cookie', cookie);\n}\n\nexport class Camada {\n readonly env: ResolvedEnv | null;\n readonly snap: SnapshotClient | null = null;\n readonly queue: EventQueue | null = null;\n private readonly fetchImpl: typeof fetch;\n private readonly scriptPath: string;\n private readonly fpPath: string;\n private readonly challengeOn: boolean;\n private readonly challengePath: string;\n private readonly kit: ChallengeKit | null = null;\n private readonly envSource: Record<string, string | undefined>;\n\n constructor(opts: CamadaOptions = {}) {\n this.envSource = opts.env ?? process.env;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.scriptPath = opts.scriptPath ?? SCRIPT_PATH;\n this.fpPath = opts.fpPath ?? FP_PATH;\n this.challengeOn = opts.challenge !== false && this.envSource.CAMADA_CHALLENGE !== '0';\n this.challengePath = opts.challengePath ?? CHALLENGE_PATH;\n this.env = resolveEnv(this.envSource);\n if (!this.env) return; // unconfigured: every entry point no-ops\n if (this.envSource.CAMADA_DISABLED === '1') return; // killed at boot: no poll timer, no exit hooks, truly silent\n this.snap = new SnapshotClient({\n url: this.env.snapshotUrl, token: this.env.snapToken,\n mode: this.env.serverless ? 'lazy' : 'timer',\n refreshMs: opts.refreshMs, snapshotVersion: opts.snapshotVersion, fetchImpl: this.fetchImpl, sdk: SDK_ID,\n });\n this.queue = new EventQueue({ url: this.env.ingestUrl, token: this.env.ingestToken, fetchImpl: this.fetchImpl, sdk: SDK_ID });\n this.kit = nodeChallengeKit(this.env.secret);\n this.snap.start();\n this.queue.installNodeExitFlush();\n }\n\n get disabled(): boolean { return this.envSource.CAMADA_DISABLED === '1' || !this.env; }\n\n private trustedProxy(): TrustedProxyConfig | null {\n return this.env?.trustedProxy ?? this.snap?.config?.trusted_proxy ?? null; // explicit local override wins\n }\n\n private beaconEnabled(): boolean { return this.snap?.config?.beacon !== false; }\n\n /** True when camada fully handled the request. Synchronous; never throws. */\n handle(req: IncomingMessage, res: ServerResponse): boolean {\n return guarded(() => this.handleInner(req as CamadaRequest, res), false);\n }\n\n private handleInner(req: CamadaRequest, res: ServerResponse): boolean {\n if (this.disabled || !this.snap || !this.queue || !this.env) return false;\n const t0 = Date.now();\n this.snap.ensureFresh();\n\n const rawUrl = req.url || '/';\n const qi = rawUrl.indexOf('?');\n const path = qi === -1 ? rawUrl : rawUrl.slice(0, qi);\n const query = qi === -1 ? '' : rawUrl.slice(qi);\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n\n // enforce before anything else, beacon endpoints included — fail open while cold. The custom\n // rules read the user agent and the request headers (§D3); without them every `ua` and\n // `header` condition is false.\n const v = this.snap.verdict({ ip, path, ua: req.headers['user-agent'], header: headerReader(req) });\n if (v.block) {\n const headers: Record<string, string> = { 'x-block-reason': v.reason ?? '', 'x-block-version': v.version ?? '', 'content-type': 'text/plain' };\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 res.writeHead(403, headers);\n res.end('Forbidden');\n const ev = this.buildEvent(req, path, query, ip, { rid: randomUUID(), sid: null, newSession: false });\n ev.st = 403; // blocked requests always ship: silent expiry makes blocks oscillate\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 this.queue.push(ev);\n return true;\n }\n // `warn` passes the request and only marks its event (below, on response-finish); a skip\n // passes with nothing stamped at all — it is the absence of enforcement.\n\n // A challenge needs a resolved client IP: the nonce and the `_cch` cookie are bound to it,\n // so without one a single solve would mint a cookie every unidentified client could\n // present. No ip -> no challenge (fail open), the same stance ip rules take.\n if (this.challengeOn && this.kit && ip) {\n // The verify endpoint answers first: a challenged client must be able to reach it.\n if (req.method === 'POST' && path === this.challengePath) { this.verifyChallenge(req, res, ip); return true; }\n if (v.challenge && !this.challengePassed(req, ip)) { this.serveChallengeInner(req, res, ip, path + query); return true; }\n }\n\n if (this.beaconEnabled()) {\n if (req.method === 'GET' && path === this.scriptPath) {\n res.writeHead(200, { 'content-type': 'application/javascript', 'cache-control': 'public, max-age=3600' });\n res.end(iife);\n return true;\n }\n if (req.method === 'POST' && path === this.fpPath) {\n this.relayBeacon(req, res, ip);\n return true;\n }\n }\n\n const rid = randomUUID();\n let sid = cookieValue((req.headers.cookie as string) || '', SESSION_COOKIE);\n const newSession = !sid;\n if (!sid) {\n sid = randomUUID();\n const https = (req.socket as { encrypted?: boolean }).encrypted || req.headers['x-forwarded-proto'] === 'https';\n keepSessionCookie(res, `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${https ? '; Secure' : ''}`);\n }\n req.camada = { rid, ip, sid };\n res.setHeader('x-rid', rid);\n\n const cfg = this.snap.config;\n const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));\n if (!excluded && Math.random() < (cfg?.sample ?? 1)) {\n // 'finish' when the response went out whole; 'close' alone when the client left first (an\n // aborted SSE or download), which ships the status set so far and dur up to the abort — the\n // fetch adapters' cancel semantics. 'close' also follows every 'finish': one event either way.\n let shipped = false;\n const ship = () => guarded(() => {\n // serveChallenge() may have answered from inside the app, and it already shipped the\n // `blk: \"challenge\"` row — one request, one event.\n if (shipped || req.camadaChallenged) return;\n shipped = true;\n const ev = this.buildEvent(req, path, query, ip, { rid, sid, newSession });\n ev.ts = t0; // the request start: the timeline draws [ts, ts + dur]\n ev.st = res.statusCode;\n ev.dur = Date.now() - t0;\n if (req.route?.path) ev.rt = String(req.route.path);\n if (v.warn && v.rule) ev.wrn = v.rule; // §D3: the warn rule that let this request through\n this.queue!.push(ev);\n }, undefined);\n res.once('finish', ship);\n res.once('close', ship);\n }\n return false;\n }\n\n private buildEvent(req: CamadaRequest, path: string, query: string, ip: string | null, o: { rid: string; sid: string | null; newSession: boolean }): WireEvent {\n const raw = req.rawHeaders || [];\n const headers: Array<[string, string]> = [];\n for (let i = 0; i + 1 < raw.length; i += 2) headers.push([raw[i], raw[i + 1]]);\n return buildWireEvent(\n { method: req.method || 'GET', host: (req.headers.host as string) || '', path, query, headers, ip, httpVersion: req.httpVersion },\n { tap: TAP_NODE, rid: o.rid, sid: o.sid, newSession: o.newSession },\n );\n }\n\n /** Reads the beacon POST (≤32 KB), answers 204 immediately, and queues the beacon as a `sig: 1`\n * row with the trusted-proxy-resolved client IP: it rides the next event batch, so the analyst\n * sees one request per flush instead of one per page view. Junk bodies are dropped, never shipped. */\n private relayBeacon(req: IncomingMessage, res: ServerResponse, ip: string | null): void {\n const chunks: Buffer[] = [];\n let size = 0, dead = false;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size > FP_MAX) { dead = true; req.removeAllListeners('data'); res.writeHead(413).end(); return; }\n chunks.push(c);\n });\n req.on('end', () => guarded(() => {\n if (dead) return;\n res.writeHead(204, { 'cache-control': 'no-store' });\n res.end();\n let parsed: unknown;\n try { parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch { return; }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return;\n this.queue!.push({ ...(parsed as Record<string, unknown>), sig: 1, ip, tap: TAP_NODE });\n }, undefined));\n req.on('error', () => { try { res.destroy(); } catch { /* already gone */ } });\n }\n\n private challengePassed(req: IncomingMessage, ip: string | null): boolean {\n return !!this.kit?.tokenValid(ip, Date.now(), cookieValue((req.headers.cookie as string) || '', 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). */\n private serveChallengeInner(req: CamadaRequest, res: ServerResponse, ip: string, target: string): void {\n const to = safeReturnTo(target);\n if (wantsHtml((req.headers.accept as string) ?? null, (req.headers['sec-fetch-dest'] as string) ?? null)) {\n writeChallengePage(res, challengePage({ nonce: this.kit!.nonce(ip, Date.now()), action: this.challengePath, to }));\n } else {\n writeChallengeJson(res);\n }\n // The response is out; telemetry must never be able to undo that (a throw here would make\n // guarded() report the request as unhandled and let the app write to an ended response).\n guarded(() => {\n const qi = target.indexOf('?');\n const ev = this.buildEvent(req, qi === -1 ? target : target.slice(0, qi), qi === -1 ? '' : target.slice(qi), ip,\n { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.st = 403;\n ev.blk = 'challenge';\n this.queue!.push(ev);\n }, undefined);\n }\n\n /** The request's existing session, so challenge rows join the session that produced them. */\n private sessionOf(req: IncomingMessage): string | null {\n return cookieValue((req.headers.cookie as string) || '', SESSION_COOKIE);\n }\n\n /** POST from the challenge page: validate the nonce and the proof of work, set `_cch`, 302\n * back to the (sanitised, same-site) original URL, and ship `{ st: 200, ch: 1 }`. */\n private verifyChallenge(req: CamadaRequest, res: ServerResponse, ip: string): void {\n readBody(req, (body) => guarded(() => {\n const form = parseFormBody(body);\n const to = safeReturnTo(form.to);\n const now = Date.now();\n if (!this.kit!.verify(ip, now, form.nonce, form.solution)) {\n writeChallengePage(res, challengePage({ nonce: this.kit!.nonce(ip, now), action: this.challengePath, to }));\n return;\n }\n res.writeHead(302, {\n location: to,\n 'set-cookie': challengeCookie(this.kit!.issue(ip, now), isHttps(req)),\n 'cache-control': 'no-store',\n });\n res.end();\n const ev = this.buildEvent(req, this.challengePath, '', ip, { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.st = 200;\n ev.ch = 1; // challenge passed (contract §A3 ingest field)\n this.queue!.push(ev);\n }, undefined));\n }\n\n /**\n * Records the upgrades (WebSocket handshakes) a node:http or node:https server hands to its\n * 'upgrade' listeners: Node never routes those through the request handler the middleware runs\n * in. Each one ships one event with `st: 101` once the app's listeners have run. Observe only:\n * the request is never blocked or challenged, nothing is written to the socket, no session\n * cookie is minted, and a server with no 'upgrade' listener of its own behaves exactly as before\n * (this wraps `server.emit` rather than adding a listener, which would claim every upgrade).\n * Idempotent; returns the server. A handshake the WebSocket library then rejects still ships 101.\n */\n attach<S extends Server>(server: S): S {\n guarded(() => {\n const s = server as S & { [ATTACHED]?: true };\n if (this.disabled || !this.queue || s[ATTACHED]) return;\n s[ATTACHED] = true;\n const emit = s.emit.bind(s) as (event: string | symbol, ...args: unknown[]) => boolean;\n s.emit = ((event: string | symbol, ...args: unknown[]) => {\n if (event !== 'upgrade') return emit(event, ...args);\n const t0 = Date.now();\n try {\n return emit(event, ...args);\n } finally {\n guarded(() => this.shipUpgrade(args[0] as CamadaRequest, t0), undefined);\n }\n }) as S['emit'];\n }, undefined);\n return server;\n }\n\n private shipUpgrade(req: CamadaRequest, t0: number): void {\n if (this.disabled || !this.queue) return;\n const rawUrl = req.url || '/';\n const qi = rawUrl.indexOf('?');\n const path = qi === -1 ? rawUrl : rawUrl.slice(0, qi);\n const cfg = this.snap?.config;\n if ((cfg?.exclude || []).some((x) => path.startsWith(x)) || Math.random() >= (cfg?.sample ?? 1)) return;\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n const ev = this.buildEvent(req, path, qi === -1 ? '' : rawUrl.slice(qi), ip, { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.ts = t0;\n ev.st = 101;\n ev.dur = Date.now() - t0;\n this.queue.push(ev);\n }\n\n /** Serve the challenge for this request on demand — for a route the app wants to gate itself\n * (the example's /challenge-me). Returns false when the client already holds a valid `_cch`,\n * so the caller renders its own page. */\n serveChallenge(req: IncomingMessage, res: ServerResponse): boolean {\n return guarded(() => {\n if (this.disabled || !this.kit || !this.queue) return false;\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n if (!ip || this.challengePassed(req, ip)) return false; // unidentifiable client: fail open\n (req as CamadaRequest).camadaChallenged = true;\n this.serveChallengeInner(req as CamadaRequest, res, ip, req.url || '/');\n return true;\n }, false);\n }\n\n /** For HTML templates: the first-party beacon tag with the request's rid. */\n scriptTag(req: IncomingMessage): string {\n if (this.disabled || !this.beaconEnabled()) return '';\n const rid = (req as CamadaRequest).camada?.rid;\n return `<script src=\"${this.scriptPath}${rid ? `?r=${rid}` : ''}\" async></script>`;\n }\n\n /** App-context outcome events (login failed, signup, …). The identifier is HMAC-hashed\n * in-process; the raw value never reaches the queue. */\n track(req: IncomingMessage, event: string, data?: { user?: string }): void {\n guarded(() => {\n if (this.disabled || !this.queue || !this.env) return;\n const ctx = (req as CamadaRequest).camada;\n void (async () => {\n const uid = data?.user ? await hashUserId(data.user, this.env!.ingestToken, (globalThis.crypto ?? webcrypto).subtle) : null;\n this.queue!.push({ tap: TAP_NODE, et: event, uid, rid: ctx?.rid ?? null, sid: ctx?.sid ?? null, ip: ctx?.ip ?? null, ts: Date.now() });\n })().catch(logRateLimited);\n }, undefined);\n }\n\n /** Express/Connect middleware (also what NestJS on the default platform consumes). */\n express() {\n return (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void): void => {\n if (!this.handle(req, res)) next();\n };\n }\n\n stop(): void {\n this.snap?.stop();\n this.queue?.stop();\n }\n}\n\nexport function createCamada(opts: CamadaOptions = {}): Camada {\n const c = new Camada(opts);\n if (!c.env) logRateLimited(new Error('CAMADA_KEY (or CAMADA_TOKEN + CAMADA_SNAPSHOT_TOKEN) not set — camada is inactive'));\n return c;\n}\n","// Environment wiring. The two-line quickstart depends on this file doing the right thing:\n// CAMADA_KEY=<ingest_token>.<snap_token> (printed by `reconcile instructions` and seed)\n// CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL (dev: http://localhost:8787[/snapshot])\n// CAMADA_DISABLED=1 kill switch, checked per request\n// CAMADA_SERVERLESS=1 lazy snapshot mode (no interval timer)\n// CAMADA_TRUSTED_PROXY local override: none | vercel | hops:N | cidrs:a,b\nimport { parseKey, parseTrustedProxyEnv, type TrustedProxyConfig } from '@camada/core';\n\nexport { parseTrustedProxyEnv }; // re-export: this was @camada/node's public surface before it moved to core\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 serverless: boolean;\n trustedProxy: TrustedProxyConfig | null; // null = defer to server-delivered config\n}\n\n/** Returns null (SDK stays disabled, one log line) rather than throwing on bad config. */\nexport function resolveEnv(env: Record<string, string | undefined> = process.env): 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 serverless: env.CAMADA_SERVERLESS === '1',\n trustedProxy: parseTrustedProxyEnv(env.CAMADA_TRUSTED_PROXY),\n };\n}\n","// The challenge for @camada/node (SDK-04). node:crypto gives us a synchronous HMAC, so the\n// cookie check costs nothing on the hot path and Camada.handle() stays a synchronous boolean.\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createHash, createHmac } from 'node:crypto';\nimport { createChallenge, type ChallengeKit } from '@camada/core';\n\nexport const CHALLENGE_PATH = '/__camada/challenge';\nconst BODY_MAX = 4 * 1024; // the verify form is ~120 bytes; anything larger is not ours\n\nexport function nodeChallengeKit(secret: string): ChallengeKit {\n return createChallenge({\n secret,\n hmac: (s, m) => createHmac('sha256', s).update(m).digest('hex'),\n sha: (m) => createHash('sha256').update(m).digest('hex'),\n });\n}\n\n/** Reads at most BODY_MAX bytes; hands back '' when the client sends more (or errors). */\nexport function readBody(req: IncomingMessage, done: (body: string) => void): void {\n const chunks: Buffer[] = [];\n let size = 0, dead = false;\n req.on('data', (c: Buffer) => {\n if (dead) return;\n size += c.length;\n if (size > BODY_MAX) { dead = true; chunks.length = 0; return; }\n chunks.push(c);\n });\n req.on('end', () => done(dead ? '' : Buffer.concat(chunks).toString('utf8')));\n req.on('error', () => done(''));\n}\n\nexport const isHttps = (req: IncomingMessage): boolean =>\n !!(req.socket as { encrypted?: boolean } | undefined)?.encrypted || req.headers['x-forwarded-proto'] === 'https';\n\nexport function writeChallengePage(res: ServerResponse, html: string): void {\n res.writeHead(403, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-camada-challenge': '1' });\n res.end(html);\n}\n\nexport function writeChallengeJson(res: ServerResponse): void {\n res.writeHead(403, { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' });\n res.end('{\"error\":\"challenge_required\"}');\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/node\",\n \"description\": \"camada backend SDK for Node: request capture, inline blocking, first-party beacon serving; Express/Fastify/Nest/Koa adapters\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./fastify\": {\n \"types\": \"./dist/fastify.d.ts\",\n \"import\": \"./dist/fastify.js\",\n \"require\": \"./dist/fastify.cjs\"\n },\n \"./nest\": {\n \"types\": \"./dist/nest.d.ts\",\n \"import\": \"./dist/nest.js\",\n \"require\": \"./dist/nest.cjs\"\n },\n \"./koa\": {\n \"types\": \"./dist/koa.d.ts\",\n \"import\": \"./dist/koa.js\",\n \"require\": \"./dist/koa.cjs\"\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/browser\": \"file:../camada-browser\",\n \"@camada/core\": \"file:../camada-core\"\n },\n \"devDependencies\": {\n \"@nestjs/common\": \"^11.0.0\",\n \"@nestjs/core\": \"^11.0.0\",\n \"@nestjs/platform-express\": \"^11.0.0\",\n \"@types/express\": \"^4.17.25\",\n \"@types/koa\": \"^2.15.2\",\n \"@types/node\": \"^22.10.0\",\n \"express\": \"^4.21.0\",\n \"fastify\": \"^5.2.0\",\n \"koa\": \"^2.15.0\",\n \"reflect-metadata\": \"^0.2.2\",\n \"rxjs\": \"^7.8.1\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"@nestjs/common\": \">=10\"\n },\n \"peerDependenciesMeta\": {\n \"@nestjs/common\": {\n \"optional\": true\n }\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","// Quickstart (Express / Connect / Nest-on-Express):\n// import camada from '@camada/node';\n// app.use(camada.express()); // env: CAMADA_KEY (+ CAMADA_INGEST_URL for dev)\n// The default export is a lazy singleton wired from the environment on first use.\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\nimport { Camada, createCamada, type CamadaOptions } from './camada.js';\n\nexport { Camada, createCamada, type CamadaOptions } from './camada.js';\nexport { resolveEnv, parseTrustedProxyEnv, type ResolvedEnv } from './env.js';\n\nlet singleton: Camada | null = null;\nexport function getDefault(): Camada {\n return (singleton ??= createCamada());\n}\n/** Test/reset hook: replaces the singleton (stops the old one). */\nexport function configure(opts: CamadaOptions): Camada {\n singleton?.stop();\n singleton = createCamada(opts);\n return singleton;\n}\n\nexport default {\n express: () => (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => { if (!getDefault().handle(req, res)) next(); },\n handle: (req: IncomingMessage, res: ServerResponse) => getDefault().handle(req, res),\n scriptTag: (req: IncomingMessage) => getDefault().scriptTag(req),\n serveChallenge: (req: IncomingMessage, res: ServerResponse) => getDefault().serveChallenge(req, res),\n track: (req: IncomingMessage, event: string, data?: { user?: string }) => getDefault().track(req, event, data),\n attach: <S extends Server>(server: S): S => getDefault().attach(server),\n configure,\n};\n"],"mappings":"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","names":["import_node_crypto","import_core","import_core","name","iife"]}
@@ -1,5 +1,5 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { C as Camada } from './camada-Cn5SJVIG.cjs';
2
+ import { C as Camada } from './camada-Dh_9D6Sx.cjs';
3
3
  import '@camada/core';
4
4
 
5
5
  interface Fastifyish {
package/dist/fastify.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { C as Camada } from './camada-Cn5SJVIG.js';
2
+ import { C as Camada } from './camada-Dh_9D6Sx.js';
3
3
  import '@camada/core';
4
4
 
5
5
  interface Fastifyish {
package/dist/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getDefault
3
- } from "./chunk-J7BQLOME.js";
3
+ } from "./chunk-PNJ4BVBD.js";
4
4
 
5
5
  // src/fastify.ts
6
6
  function camadaFastify(instance, opts, done) {
package/dist/index.cjs CHANGED
@@ -52,7 +52,7 @@ function resolveEnv(env = process.env) {
52
52
  const ingestToken = key?.ingestToken ?? env.CAMADA_TOKEN;
53
53
  const snapToken = key?.snapToken ?? env.CAMADA_SNAPSHOT_TOKEN;
54
54
  if (!ingestToken || !snapToken) return null;
55
- const ingestUrl = (env.CAMADA_INGEST_URL || "https://in.camada.dev").replace(/\/$/, "");
55
+ const ingestUrl = (env.CAMADA_INGEST_URL || "https://in.camada.app").replace(/\/$/, "");
56
56
  return {
57
57
  ingestToken,
58
58
  snapToken,
@@ -103,7 +103,7 @@ function writeChallengeJson(res) {
103
103
  }
104
104
 
105
105
  // package.json
106
- var version = "0.2.0";
106
+ var version = "0.3.1";
107
107
  var name = "@camada/node";
108
108
 
109
109
  // src/version.ts
@@ -114,6 +114,7 @@ var SESSION_COOKIE = "_sfp";
114
114
  var SCRIPT_PATH = "/_cam/b.js";
115
115
  var FP_PATH = "/_cam/fp";
116
116
  var FP_MAX = 32 * 1024;
117
+ var ATTACHED = /* @__PURE__ */ Symbol.for("camada.attached");
117
118
  var headerReader = (req) => (name2) => {
118
119
  const v = req.headers[name2];
119
120
  return v == null ? null : Array.isArray(v) ? v.join(", ") : v;
@@ -126,6 +127,17 @@ var cookieValue = (cookie, name2) => {
126
127
  const j = src.indexOf(";", start);
127
128
  return src.slice(start, j === -1 ? void 0 : j);
128
129
  };
130
+ function keepSessionCookie(res, cookie) {
131
+ const set = res.setHeader;
132
+ res.setHeader = function(name2, value) {
133
+ if (typeof name2 === "string" && name2.toLowerCase() === "set-cookie") {
134
+ const list = Array.isArray(value) ? value : [String(value)];
135
+ if (!list.some((c) => String(c).trimStart().startsWith(SESSION_COOKIE + "="))) value = [...list, cookie];
136
+ }
137
+ return set.call(this, name2, value);
138
+ };
139
+ res.setHeader("set-cookie", cookie);
140
+ }
129
141
  var Camada = class {
130
142
  env;
131
143
  snap = null;
@@ -223,22 +235,27 @@ var Camada = class {
223
235
  if (!sid) {
224
236
  sid = (0, import_node_crypto2.randomUUID)();
225
237
  const https = req.socket.encrypted || req.headers["x-forwarded-proto"] === "https";
226
- res.setHeader("set-cookie", `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${https ? "; Secure" : ""}`);
238
+ keepSessionCookie(res, `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${https ? "; Secure" : ""}`);
227
239
  }
228
240
  req.camada = { rid, ip, sid };
229
241
  res.setHeader("x-rid", rid);
230
242
  const cfg = this.snap.config;
231
243
  const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));
232
244
  if (!excluded && Math.random() < (cfg?.sample ?? 1)) {
233
- res.on("finish", () => (0, import_core3.guarded)(() => {
234
- if (req.camadaChallenged) return;
245
+ let shipped = false;
246
+ const ship = () => (0, import_core3.guarded)(() => {
247
+ if (shipped || req.camadaChallenged) return;
248
+ shipped = true;
235
249
  const ev = this.buildEvent(req, path, query, ip, { rid, sid, newSession });
250
+ ev.ts = t0;
236
251
  ev.st = res.statusCode;
237
252
  ev.dur = Date.now() - t0;
238
253
  if (req.route?.path) ev.rt = String(req.route.path);
239
254
  if (v.warn && v.rule) ev.wrn = v.rule;
240
255
  this.queue.push(ev);
241
- }, void 0));
256
+ }, void 0);
257
+ res.once("finish", ship);
258
+ res.once("close", ship);
242
259
  }
243
260
  return false;
244
261
  }
@@ -340,6 +357,47 @@ var Camada = class {
340
357
  this.queue.push(ev);
341
358
  }, void 0));
342
359
  }
360
+ /**
361
+ * Records the upgrades (WebSocket handshakes) a node:http or node:https server hands to its
362
+ * 'upgrade' listeners: Node never routes those through the request handler the middleware runs
363
+ * in. Each one ships one event with `st: 101` once the app's listeners have run. Observe only:
364
+ * the request is never blocked or challenged, nothing is written to the socket, no session
365
+ * cookie is minted, and a server with no 'upgrade' listener of its own behaves exactly as before
366
+ * (this wraps `server.emit` rather than adding a listener, which would claim every upgrade).
367
+ * Idempotent; returns the server. A handshake the WebSocket library then rejects still ships 101.
368
+ */
369
+ attach(server) {
370
+ (0, import_core3.guarded)(() => {
371
+ const s = server;
372
+ if (this.disabled || !this.queue || s[ATTACHED]) return;
373
+ s[ATTACHED] = true;
374
+ const emit = s.emit.bind(s);
375
+ s.emit = ((event, ...args) => {
376
+ if (event !== "upgrade") return emit(event, ...args);
377
+ const t0 = Date.now();
378
+ try {
379
+ return emit(event, ...args);
380
+ } finally {
381
+ (0, import_core3.guarded)(() => this.shipUpgrade(args[0], t0), void 0);
382
+ }
383
+ });
384
+ }, void 0);
385
+ return server;
386
+ }
387
+ shipUpgrade(req, t0) {
388
+ if (this.disabled || !this.queue) return;
389
+ const rawUrl = req.url || "/";
390
+ const qi = rawUrl.indexOf("?");
391
+ const path = qi === -1 ? rawUrl : rawUrl.slice(0, qi);
392
+ const cfg = this.snap?.config;
393
+ if ((cfg?.exclude || []).some((x) => path.startsWith(x)) || Math.random() >= (cfg?.sample ?? 1)) return;
394
+ const ip = (0, import_core3.resolveClientIp)(req.socket?.remoteAddress, req.headers["x-forwarded-for"], this.trustedProxy());
395
+ const ev = this.buildEvent(req, path, qi === -1 ? "" : rawUrl.slice(qi), ip, { rid: (0, import_node_crypto2.randomUUID)(), sid: this.sessionOf(req), newSession: false });
396
+ ev.ts = t0;
397
+ ev.st = 101;
398
+ ev.dur = Date.now() - t0;
399
+ this.queue.push(ev);
400
+ }
343
401
  /** Serve the challenge for this request on demand — for a route the app wants to gate itself
344
402
  * (the example's /challenge-me). Returns false when the client already holds a valid `_cch`,
345
403
  * so the caller renders its own page. */
@@ -406,6 +464,7 @@ var index_default = {
406
464
  scriptTag: (req) => getDefault().scriptTag(req),
407
465
  serveChallenge: (req, res) => getDefault().serveChallenge(req, res),
408
466
  track: (req, event, data) => getDefault().track(req, event, data),
467
+ attach: (server) => getDefault().attach(server),
409
468
  configure
410
469
  };
411
470
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/camada.ts","../src/env.ts","../src/challenge.ts","../package.json","../src/version.ts"],"sourcesContent":["// Quickstart (Express / Connect / Nest-on-Express):\n// import camada from '@camada/node';\n// app.use(camada.express()); // env: CAMADA_KEY (+ CAMADA_INGEST_URL for dev)\n// The default export is a lazy singleton wired from the environment on first use.\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Camada, createCamada, type CamadaOptions } from './camada.js';\n\nexport { Camada, createCamada, type CamadaOptions } from './camada.js';\nexport { resolveEnv, parseTrustedProxyEnv, type ResolvedEnv } from './env.js';\n\nlet singleton: Camada | null = null;\nexport function getDefault(): Camada {\n return (singleton ??= createCamada());\n}\n/** Test/reset hook: replaces the singleton (stops the old one). */\nexport function configure(opts: CamadaOptions): Camada {\n singleton?.stop();\n singleton = createCamada(opts);\n return singleton;\n}\n\nexport default {\n express: () => (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => { if (!getDefault().handle(req, res)) next(); },\n handle: (req: IncomingMessage, res: ServerResponse) => getDefault().handle(req, res),\n scriptTag: (req: IncomingMessage) => getDefault().scriptTag(req),\n serveChallenge: (req: IncomingMessage, res: ServerResponse) => getDefault().serveChallenge(req, res),\n track: (req: IncomingMessage, event: string, data?: { user?: string }) => getDefault().track(req, event, data),\n configure,\n};\n","// The engine: node:http-level request handling every adapter delegates to.\n// camada.handle(req, res) -> true when camada fully answered the request (block or beacon\n// endpoint), false when the app should proceed. Everything runs inside the fail-open\n// envelope: a camada bug must never 5xx the customer (plan.md INT-2), and CAMADA_DISABLED=1\n// bypasses the SDK entirely.\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { randomUUID, webcrypto } from 'node:crypto'; // webcrypto handed to hashUserId: Node 18 has no global crypto.subtle\nimport {\n SnapshotClient, EventQueue, buildWireEvent, resolveClientIp, hashUserId, guarded, logRateLimited,\n challengePage, challengeCookie, safeReturnTo, wantsHtml, parseFormBody, CHALLENGE_COOKIE,\n TAP_NODE, type ChallengeKit, type SnapshotVersion, type TrustedProxyConfig, type WireEvent,\n} from '@camada/core';\nimport iife from '@camada/browser/iife-string';\nimport { resolveEnv, type ResolvedEnv } from './env.js';\nimport {\n CHALLENGE_PATH, nodeChallengeKit, readBody, isHttps, writeChallengePage, writeChallengeJson,\n} from './challenge.js';\nimport { SDK_ID } from './version.js';\n\nconst SESSION_COOKIE = '_sfp'; // same cookie as the edge collector: sid/ns comparable across taps\nconst SCRIPT_PATH = '/_cam/b.js';\nconst FP_PATH = '/_cam/fp';\nconst FP_MAX = 32 * 1024; // matches the server's /fp cap: never accept what ingest will 413\n\nexport interface CamadaOptions {\n env?: Record<string, string | undefined>;\n fetchImpl?: typeof fetch; // threaded into snapshot client, event queue, and the fp relay (tests)\n refreshMs?: number;\n scriptPath?: string;\n fpPath?: string;\n challenge?: boolean; // enforce `challenge` verdicts with the first-party page (default true; CAMADA_CHALLENGE=0 also switches it off)\n challengePath?: string; // where that page posts its solution (default /__camada/challenge)\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\ninterface CamadaRequest extends IncomingMessage {\n camada?: { rid: string; ip: string | null; sid: string };\n route?: { path?: string }; // Express fills this after routing\n camadaChallenged?: boolean; // serveChallenge() already shipped this request's event\n}\n\n/** The getter `header` conditions read (§D3). node:http lower-cases every incoming name and\n * the matcher always asks with a lower-cased one, so nothing has to be normalised here; a\n * header the client repeated arrives as an array and is joined the way the wire carried it. */\nconst headerReader = (req: IncomingMessage) => (name: string): string | null => {\n const v = req.headers[name];\n return v == null ? null : Array.isArray(v) ? v.join(', ') : v;\n};\n\nconst 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\nexport class Camada {\n readonly env: ResolvedEnv | null;\n readonly snap: SnapshotClient | null = null;\n readonly queue: EventQueue | null = null;\n private readonly fetchImpl: typeof fetch;\n private readonly scriptPath: string;\n private readonly fpPath: string;\n private readonly challengeOn: boolean;\n private readonly challengePath: string;\n private readonly kit: ChallengeKit | null = null;\n private readonly envSource: Record<string, string | undefined>;\n\n constructor(opts: CamadaOptions = {}) {\n this.envSource = opts.env ?? process.env;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.scriptPath = opts.scriptPath ?? SCRIPT_PATH;\n this.fpPath = opts.fpPath ?? FP_PATH;\n this.challengeOn = opts.challenge !== false && this.envSource.CAMADA_CHALLENGE !== '0';\n this.challengePath = opts.challengePath ?? CHALLENGE_PATH;\n this.env = resolveEnv(this.envSource);\n if (!this.env) return; // unconfigured: every entry point no-ops\n if (this.envSource.CAMADA_DISABLED === '1') return; // killed at boot: no poll timer, no exit hooks, truly silent\n this.snap = new SnapshotClient({\n url: this.env.snapshotUrl, token: this.env.snapToken,\n mode: this.env.serverless ? 'lazy' : 'timer',\n refreshMs: opts.refreshMs, snapshotVersion: opts.snapshotVersion, fetchImpl: this.fetchImpl, sdk: SDK_ID,\n });\n this.queue = new EventQueue({ url: this.env.ingestUrl, token: this.env.ingestToken, fetchImpl: this.fetchImpl, sdk: SDK_ID });\n this.kit = nodeChallengeKit(this.env.secret);\n this.snap.start();\n this.queue.installNodeExitFlush();\n }\n\n get disabled(): boolean { return this.envSource.CAMADA_DISABLED === '1' || !this.env; }\n\n private trustedProxy(): TrustedProxyConfig | null {\n return this.env?.trustedProxy ?? this.snap?.config?.trusted_proxy ?? null; // explicit local override wins\n }\n\n private beaconEnabled(): boolean { return this.snap?.config?.beacon !== false; }\n\n /** True when camada fully handled the request. Synchronous; never throws. */\n handle(req: IncomingMessage, res: ServerResponse): boolean {\n return guarded(() => this.handleInner(req as CamadaRequest, res), false);\n }\n\n private handleInner(req: CamadaRequest, res: ServerResponse): boolean {\n if (this.disabled || !this.snap || !this.queue || !this.env) return false;\n const t0 = Date.now();\n this.snap.ensureFresh();\n\n const rawUrl = req.url || '/';\n const qi = rawUrl.indexOf('?');\n const path = qi === -1 ? rawUrl : rawUrl.slice(0, qi);\n const query = qi === -1 ? '' : rawUrl.slice(qi);\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n\n // enforce before anything else, beacon endpoints included — fail open while cold. The custom\n // rules read the user agent and the request headers (§D3); without them every `ua` and\n // `header` condition is false.\n const v = this.snap.verdict({ ip, path, ua: req.headers['user-agent'], header: headerReader(req) });\n if (v.block) {\n const headers: Record<string, string> = { 'x-block-reason': v.reason ?? '', 'x-block-version': v.version ?? '', 'content-type': 'text/plain' };\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 res.writeHead(403, headers);\n res.end('Forbidden');\n const ev = this.buildEvent(req, path, query, ip, { rid: randomUUID(), sid: null, newSession: false });\n ev.st = 403; // blocked requests always ship: silent expiry makes blocks oscillate\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 this.queue.push(ev);\n return true;\n }\n // `warn` passes the request and only marks its event (below, on response-finish); a skip\n // passes with nothing stamped at all — it is the absence of enforcement.\n\n // A challenge needs a resolved client IP: the nonce and the `_cch` cookie are bound to it,\n // so without one a single solve would mint a cookie every unidentified client could\n // present. No ip -> no challenge (fail open), the same stance ip rules take.\n if (this.challengeOn && this.kit && ip) {\n // The verify endpoint answers first: a challenged client must be able to reach it.\n if (req.method === 'POST' && path === this.challengePath) { this.verifyChallenge(req, res, ip); return true; }\n if (v.challenge && !this.challengePassed(req, ip)) { this.serveChallengeInner(req, res, ip, path + query); return true; }\n }\n\n if (this.beaconEnabled()) {\n if (req.method === 'GET' && path === this.scriptPath) {\n res.writeHead(200, { 'content-type': 'application/javascript', 'cache-control': 'public, max-age=3600' });\n res.end(iife);\n return true;\n }\n if (req.method === 'POST' && path === this.fpPath) {\n this.relayBeacon(req, res, ip);\n return true;\n }\n }\n\n const rid = randomUUID();\n let sid = cookieValue((req.headers.cookie as string) || '', SESSION_COOKIE);\n const newSession = !sid;\n if (!sid) {\n sid = randomUUID();\n const https = (req.socket as { encrypted?: boolean }).encrypted || req.headers['x-forwarded-proto'] === 'https';\n res.setHeader('set-cookie', `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${https ? '; Secure' : ''}`);\n }\n req.camada = { rid, ip, sid };\n res.setHeader('x-rid', rid);\n\n const cfg = this.snap.config;\n const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));\n if (!excluded && Math.random() < (cfg?.sample ?? 1)) {\n res.on('finish', () => guarded(() => {\n // serveChallenge() may have answered from inside the app, and it already shipped the\n // `blk: \"challenge\"` row — one request, one event.\n if (req.camadaChallenged) return;\n const ev = this.buildEvent(req, path, query, ip, { rid, sid, newSession });\n ev.st = res.statusCode;\n ev.dur = Date.now() - t0;\n if (req.route?.path) ev.rt = String(req.route.path);\n if (v.warn && v.rule) ev.wrn = v.rule; // §D3: the warn rule that let this request through\n this.queue!.push(ev);\n }, undefined));\n }\n return false;\n }\n\n private buildEvent(req: CamadaRequest, path: string, query: string, ip: string | null, o: { rid: string; sid: string | null; newSession: boolean }): WireEvent {\n const raw = req.rawHeaders || [];\n const headers: Array<[string, string]> = [];\n for (let i = 0; i + 1 < raw.length; i += 2) headers.push([raw[i], raw[i + 1]]);\n return buildWireEvent(\n { method: req.method || 'GET', host: (req.headers.host as string) || '', path, query, headers, ip, httpVersion: req.httpVersion },\n { tap: TAP_NODE, rid: o.rid, sid: o.sid, newSession: o.newSession },\n );\n }\n\n /** Reads the beacon POST (≤32 KB), answers 204 immediately, and queues the beacon as a `sig: 1`\n * row with the trusted-proxy-resolved client IP: it rides the next event batch, so the analyst\n * sees one request per flush instead of one per page view. Junk bodies are dropped, never shipped. */\n private relayBeacon(req: IncomingMessage, res: ServerResponse, ip: string | null): void {\n const chunks: Buffer[] = [];\n let size = 0, dead = false;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size > FP_MAX) { dead = true; req.removeAllListeners('data'); res.writeHead(413).end(); return; }\n chunks.push(c);\n });\n req.on('end', () => guarded(() => {\n if (dead) return;\n res.writeHead(204, { 'cache-control': 'no-store' });\n res.end();\n let parsed: unknown;\n try { parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch { return; }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return;\n this.queue!.push({ ...(parsed as Record<string, unknown>), sig: 1, ip, tap: TAP_NODE });\n }, undefined));\n req.on('error', () => { try { res.destroy(); } catch { /* already gone */ } });\n }\n\n private challengePassed(req: IncomingMessage, ip: string | null): boolean {\n return !!this.kit?.tokenValid(ip, Date.now(), cookieValue((req.headers.cookie as string) || '', 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). */\n private serveChallengeInner(req: CamadaRequest, res: ServerResponse, ip: string, target: string): void {\n const to = safeReturnTo(target);\n if (wantsHtml((req.headers.accept as string) ?? null, (req.headers['sec-fetch-dest'] as string) ?? null)) {\n writeChallengePage(res, challengePage({ nonce: this.kit!.nonce(ip, Date.now()), action: this.challengePath, to }));\n } else {\n writeChallengeJson(res);\n }\n // The response is out; telemetry must never be able to undo that (a throw here would make\n // guarded() report the request as unhandled and let the app write to an ended response).\n guarded(() => {\n const qi = target.indexOf('?');\n const ev = this.buildEvent(req, qi === -1 ? target : target.slice(0, qi), qi === -1 ? '' : target.slice(qi), ip,\n { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.st = 403;\n ev.blk = 'challenge';\n this.queue!.push(ev);\n }, undefined);\n }\n\n /** The request's existing session, so challenge rows join the session that produced them. */\n private sessionOf(req: IncomingMessage): string | null {\n return cookieValue((req.headers.cookie as string) || '', SESSION_COOKIE);\n }\n\n /** POST from the challenge page: validate the nonce and the proof of work, set `_cch`, 302\n * back to the (sanitised, same-site) original URL, and ship `{ st: 200, ch: 1 }`. */\n private verifyChallenge(req: CamadaRequest, res: ServerResponse, ip: string): void {\n readBody(req, (body) => guarded(() => {\n const form = parseFormBody(body);\n const to = safeReturnTo(form.to);\n const now = Date.now();\n if (!this.kit!.verify(ip, now, form.nonce, form.solution)) {\n writeChallengePage(res, challengePage({ nonce: this.kit!.nonce(ip, now), action: this.challengePath, to }));\n return;\n }\n res.writeHead(302, {\n location: to,\n 'set-cookie': challengeCookie(this.kit!.issue(ip, now), isHttps(req)),\n 'cache-control': 'no-store',\n });\n res.end();\n const ev = this.buildEvent(req, this.challengePath, '', ip, { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.st = 200;\n ev.ch = 1; // challenge passed (contract §A3 ingest field)\n this.queue!.push(ev);\n }, undefined));\n }\n\n /** Serve the challenge for this request on demand — for a route the app wants to gate itself\n * (the example's /challenge-me). Returns false when the client already holds a valid `_cch`,\n * so the caller renders its own page. */\n serveChallenge(req: IncomingMessage, res: ServerResponse): boolean {\n return guarded(() => {\n if (this.disabled || !this.kit || !this.queue) return false;\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n if (!ip || this.challengePassed(req, ip)) return false; // unidentifiable client: fail open\n (req as CamadaRequest).camadaChallenged = true;\n this.serveChallengeInner(req as CamadaRequest, res, ip, req.url || '/');\n return true;\n }, false);\n }\n\n /** For HTML templates: the first-party beacon tag with the request's rid. */\n scriptTag(req: IncomingMessage): string {\n if (this.disabled || !this.beaconEnabled()) return '';\n const rid = (req as CamadaRequest).camada?.rid;\n return `<script src=\"${this.scriptPath}${rid ? `?r=${rid}` : ''}\" async></script>`;\n }\n\n /** App-context outcome events (login failed, signup, …). The identifier is HMAC-hashed\n * in-process; the raw value never reaches the queue. */\n track(req: IncomingMessage, event: string, data?: { user?: string }): void {\n guarded(() => {\n if (this.disabled || !this.queue || !this.env) return;\n const ctx = (req as CamadaRequest).camada;\n void (async () => {\n const uid = data?.user ? await hashUserId(data.user, this.env!.ingestToken, (globalThis.crypto ?? webcrypto).subtle) : null;\n this.queue!.push({ tap: TAP_NODE, et: event, uid, rid: ctx?.rid ?? null, sid: ctx?.sid ?? null, ip: ctx?.ip ?? null, ts: Date.now() });\n })().catch(logRateLimited);\n }, undefined);\n }\n\n /** Express/Connect middleware (also what NestJS on the default platform consumes). */\n express() {\n return (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void): void => {\n if (!this.handle(req, res)) next();\n };\n }\n\n stop(): void {\n this.snap?.stop();\n this.queue?.stop();\n }\n}\n\nexport function createCamada(opts: CamadaOptions = {}): Camada {\n const c = new Camada(opts);\n if (!c.env) logRateLimited(new Error('CAMADA_KEY (or CAMADA_TOKEN + CAMADA_SNAPSHOT_TOKEN) not set — camada is inactive'));\n return c;\n}\n","// Environment wiring. The two-line quickstart depends on this file doing the right thing:\n// CAMADA_KEY=<ingest_token>.<snap_token> (printed by `reconcile instructions` and seed)\n// CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL (dev: http://localhost:8787[/snapshot])\n// CAMADA_DISABLED=1 kill switch, checked per request\n// CAMADA_SERVERLESS=1 lazy snapshot mode (no interval timer)\n// CAMADA_TRUSTED_PROXY local override: none | vercel | hops:N | cidrs:a,b\nimport { parseKey, parseTrustedProxyEnv, type TrustedProxyConfig } from '@camada/core';\n\nexport { parseTrustedProxyEnv }; // re-export: this was @camada/node's public surface before it moved to core\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 serverless: boolean;\n trustedProxy: TrustedProxyConfig | null; // null = defer to server-delivered config\n}\n\n/** Returns null (SDK stays disabled, one log line) rather than throwing on bad config. */\nexport function resolveEnv(env: Record<string, string | undefined> = process.env): 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.dev').replace(/\\/$/, ''); // PLACEHOLDER default — confirm the production ingest domain before any npm publish\n return {\n ingestToken, snapToken, ingestUrl,\n secret: env.CAMADA_KEY || `${ingestToken}.${snapToken}`,\n snapshotUrl: env.CAMADA_SNAPSHOT_URL || `${ingestUrl}/snapshot`,\n serverless: env.CAMADA_SERVERLESS === '1',\n trustedProxy: parseTrustedProxyEnv(env.CAMADA_TRUSTED_PROXY),\n };\n}\n","// The challenge for @camada/node (SDK-04). node:crypto gives us a synchronous HMAC, so the\n// cookie check costs nothing on the hot path and Camada.handle() stays a synchronous boolean.\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createHash, createHmac } from 'node:crypto';\nimport { createChallenge, type ChallengeKit } from '@camada/core';\n\nexport const CHALLENGE_PATH = '/__camada/challenge';\nconst BODY_MAX = 4 * 1024; // the verify form is ~120 bytes; anything larger is not ours\n\nexport function nodeChallengeKit(secret: string): ChallengeKit {\n return createChallenge({\n secret,\n hmac: (s, m) => createHmac('sha256', s).update(m).digest('hex'),\n sha: (m) => createHash('sha256').update(m).digest('hex'),\n });\n}\n\n/** Reads at most BODY_MAX bytes; hands back '' when the client sends more (or errors). */\nexport function readBody(req: IncomingMessage, done: (body: string) => void): void {\n const chunks: Buffer[] = [];\n let size = 0, dead = false;\n req.on('data', (c: Buffer) => {\n if (dead) return;\n size += c.length;\n if (size > BODY_MAX) { dead = true; chunks.length = 0; return; }\n chunks.push(c);\n });\n req.on('end', () => done(dead ? '' : Buffer.concat(chunks).toString('utf8')));\n req.on('error', () => done(''));\n}\n\nexport const isHttps = (req: IncomingMessage): boolean =>\n !!(req.socket as { encrypted?: boolean } | undefined)?.encrypted || req.headers['x-forwarded-proto'] === 'https';\n\nexport function writeChallengePage(res: ServerResponse, html: string): void {\n res.writeHead(403, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-camada-challenge': '1' });\n res.end(html);\n}\n\nexport function writeChallengeJson(res: ServerResponse): void {\n res.writeHead(403, { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' });\n res.end('{\"error\":\"challenge_required\"}');\n}\n","{\n \"version\": \"0.2.0\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=18\"\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/node\",\n \"description\": \"camada backend SDK for Node: request capture, inline blocking, first-party beacon serving; Express/Fastify/Nest/Koa adapters\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./fastify\": {\n \"types\": \"./dist/fastify.d.ts\",\n \"import\": \"./dist/fastify.js\",\n \"require\": \"./dist/fastify.cjs\"\n },\n \"./nest\": {\n \"types\": \"./dist/nest.d.ts\",\n \"import\": \"./dist/nest.js\",\n \"require\": \"./dist/nest.cjs\"\n },\n \"./koa\": {\n \"types\": \"./dist/koa.d.ts\",\n \"import\": \"./dist/koa.js\",\n \"require\": \"./dist/koa.cjs\"\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/browser\": \"file:../camada-browser\",\n \"@camada/core\": \"file:../camada-core\"\n },\n \"devDependencies\": {\n \"@nestjs/common\": \"^11.0.0\",\n \"@nestjs/core\": \"^11.0.0\",\n \"@nestjs/platform-express\": \"^11.0.0\",\n \"@types/express\": \"^4.17.25\",\n \"@types/koa\": \"^2.15.2\",\n \"@types/node\": \"^22.10.0\",\n \"express\": \"^4.21.0\",\n \"fastify\": \"^5.2.0\",\n \"koa\": \"^2.15.0\",\n \"reflect-metadata\": \"^0.2.2\",\n \"rxjs\": \"^7.8.1\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"@nestjs/common\": \">=10\"\n },\n \"peerDependenciesMeta\": {\n \"@nestjs/common\": {\n \"optional\": true\n }\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"],"mappings":"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","names":["import_node_crypto","import_core","import_core","name","iife"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/camada.ts","../src/env.ts","../src/challenge.ts","../package.json","../src/version.ts"],"sourcesContent":["// Quickstart (Express / Connect / Nest-on-Express):\n// import camada from '@camada/node';\n// app.use(camada.express()); // env: CAMADA_KEY (+ CAMADA_INGEST_URL for dev)\n// The default export is a lazy singleton wired from the environment on first use.\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\nimport { Camada, createCamada, type CamadaOptions } from './camada.js';\n\nexport { Camada, createCamada, type CamadaOptions } from './camada.js';\nexport { resolveEnv, parseTrustedProxyEnv, type ResolvedEnv } from './env.js';\n\nlet singleton: Camada | null = null;\nexport function getDefault(): Camada {\n return (singleton ??= createCamada());\n}\n/** Test/reset hook: replaces the singleton (stops the old one). */\nexport function configure(opts: CamadaOptions): Camada {\n singleton?.stop();\n singleton = createCamada(opts);\n return singleton;\n}\n\nexport default {\n express: () => (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => { if (!getDefault().handle(req, res)) next(); },\n handle: (req: IncomingMessage, res: ServerResponse) => getDefault().handle(req, res),\n scriptTag: (req: IncomingMessage) => getDefault().scriptTag(req),\n serveChallenge: (req: IncomingMessage, res: ServerResponse) => getDefault().serveChallenge(req, res),\n track: (req: IncomingMessage, event: string, data?: { user?: string }) => getDefault().track(req, event, data),\n attach: <S extends Server>(server: S): S => getDefault().attach(server),\n configure,\n};\n","// The engine: node:http-level request handling every adapter delegates to.\n// camada.handle(req, res) -> true when camada fully answered the request (block or beacon\n// endpoint), false when the app should proceed. Everything runs inside the fail-open\n// envelope: a camada bug must never 5xx the customer (plan.md INT-2), and CAMADA_DISABLED=1\n// bypasses the SDK entirely.\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\nimport { randomUUID, webcrypto } from 'node:crypto'; // webcrypto handed to hashUserId explicitly, so the hash never depends on which global the host exposes\nimport {\n SnapshotClient, EventQueue, buildWireEvent, resolveClientIp, hashUserId, guarded, logRateLimited,\n challengePage, challengeCookie, safeReturnTo, wantsHtml, parseFormBody, CHALLENGE_COOKIE,\n TAP_NODE, type ChallengeKit, type SnapshotVersion, type TrustedProxyConfig, type WireEvent,\n} from '@camada/core';\nimport iife from '@camada/browser/iife-string';\nimport { resolveEnv, type ResolvedEnv } from './env.js';\nimport {\n CHALLENGE_PATH, nodeChallengeKit, readBody, isHttps, writeChallengePage, writeChallengeJson,\n} from './challenge.js';\nimport { SDK_ID } from './version.js';\n\nconst SESSION_COOKIE = '_sfp'; // same cookie as the edge collector: sid/ns comparable across taps\nconst SCRIPT_PATH = '/_cam/b.js';\nconst FP_PATH = '/_cam/fp';\nconst FP_MAX = 32 * 1024; // matches the server's /fp cap: never accept what ingest will 413\nconst ATTACHED = Symbol.for('camada.attached'); // one wrap per server, however often attach() is called\n\nexport interface CamadaOptions {\n env?: Record<string, string | undefined>;\n fetchImpl?: typeof fetch; // threaded into snapshot client, event queue, and the fp relay (tests)\n refreshMs?: number;\n scriptPath?: string;\n fpPath?: string;\n challenge?: boolean; // enforce `challenge` verdicts with the first-party page (default true; CAMADA_CHALLENGE=0 also switches it off)\n challengePath?: string; // where that page posts its solution (default /__camada/challenge)\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\ninterface CamadaRequest extends IncomingMessage {\n camada?: { rid: string; ip: string | null; sid: string };\n route?: { path?: string }; // Express fills this after routing\n camadaChallenged?: boolean; // serveChallenge() already shipped this request's event\n}\n\n/** The getter `header` conditions read (§D3). node:http lower-cases every incoming name and\n * the matcher always asks with a lower-cased one, so nothing has to be normalised here; a\n * header the client repeated arrives as an array and is joined the way the wire carried it. */\nconst headerReader = (req: IncomingMessage) => (name: string): string | null => {\n const v = req.headers[name];\n return v == null ? null : Array.isArray(v) ? v.join(', ') : v;\n};\n\nconst 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\n/** Sets a first visit's `_sfp` and keeps it there: an app that replaces Set-Cookie through\n * setHeader (or writeHead, setHeaders, which route through it once any header is set) gets its\n * own cookies plus the session's, unless it set `_sfp` itself. appendHeader already keeps it. */\nfunction keepSessionCookie(res: ServerResponse, cookie: string): void {\n const set = res.setHeader;\n res.setHeader = function (this: ServerResponse, name: string, value: number | string | readonly string[]) {\n if (typeof name === 'string' && name.toLowerCase() === 'set-cookie') {\n const list = Array.isArray(value) ? value : [String(value)];\n if (!list.some((c) => String(c).trimStart().startsWith(SESSION_COOKIE + '='))) value = [...list, cookie];\n }\n return set.call(this, name, value);\n } as ServerResponse['setHeader'];\n res.setHeader('set-cookie', cookie);\n}\n\nexport class Camada {\n readonly env: ResolvedEnv | null;\n readonly snap: SnapshotClient | null = null;\n readonly queue: EventQueue | null = null;\n private readonly fetchImpl: typeof fetch;\n private readonly scriptPath: string;\n private readonly fpPath: string;\n private readonly challengeOn: boolean;\n private readonly challengePath: string;\n private readonly kit: ChallengeKit | null = null;\n private readonly envSource: Record<string, string | undefined>;\n\n constructor(opts: CamadaOptions = {}) {\n this.envSource = opts.env ?? process.env;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.scriptPath = opts.scriptPath ?? SCRIPT_PATH;\n this.fpPath = opts.fpPath ?? FP_PATH;\n this.challengeOn = opts.challenge !== false && this.envSource.CAMADA_CHALLENGE !== '0';\n this.challengePath = opts.challengePath ?? CHALLENGE_PATH;\n this.env = resolveEnv(this.envSource);\n if (!this.env) return; // unconfigured: every entry point no-ops\n if (this.envSource.CAMADA_DISABLED === '1') return; // killed at boot: no poll timer, no exit hooks, truly silent\n this.snap = new SnapshotClient({\n url: this.env.snapshotUrl, token: this.env.snapToken,\n mode: this.env.serverless ? 'lazy' : 'timer',\n refreshMs: opts.refreshMs, snapshotVersion: opts.snapshotVersion, fetchImpl: this.fetchImpl, sdk: SDK_ID,\n });\n this.queue = new EventQueue({ url: this.env.ingestUrl, token: this.env.ingestToken, fetchImpl: this.fetchImpl, sdk: SDK_ID });\n this.kit = nodeChallengeKit(this.env.secret);\n this.snap.start();\n this.queue.installNodeExitFlush();\n }\n\n get disabled(): boolean { return this.envSource.CAMADA_DISABLED === '1' || !this.env; }\n\n private trustedProxy(): TrustedProxyConfig | null {\n return this.env?.trustedProxy ?? this.snap?.config?.trusted_proxy ?? null; // explicit local override wins\n }\n\n private beaconEnabled(): boolean { return this.snap?.config?.beacon !== false; }\n\n /** True when camada fully handled the request. Synchronous; never throws. */\n handle(req: IncomingMessage, res: ServerResponse): boolean {\n return guarded(() => this.handleInner(req as CamadaRequest, res), false);\n }\n\n private handleInner(req: CamadaRequest, res: ServerResponse): boolean {\n if (this.disabled || !this.snap || !this.queue || !this.env) return false;\n const t0 = Date.now();\n this.snap.ensureFresh();\n\n const rawUrl = req.url || '/';\n const qi = rawUrl.indexOf('?');\n const path = qi === -1 ? rawUrl : rawUrl.slice(0, qi);\n const query = qi === -1 ? '' : rawUrl.slice(qi);\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n\n // enforce before anything else, beacon endpoints included — fail open while cold. The custom\n // rules read the user agent and the request headers (§D3); without them every `ua` and\n // `header` condition is false.\n const v = this.snap.verdict({ ip, path, ua: req.headers['user-agent'], header: headerReader(req) });\n if (v.block) {\n const headers: Record<string, string> = { 'x-block-reason': v.reason ?? '', 'x-block-version': v.version ?? '', 'content-type': 'text/plain' };\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 res.writeHead(403, headers);\n res.end('Forbidden');\n const ev = this.buildEvent(req, path, query, ip, { rid: randomUUID(), sid: null, newSession: false });\n ev.st = 403; // blocked requests always ship: silent expiry makes blocks oscillate\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 this.queue.push(ev);\n return true;\n }\n // `warn` passes the request and only marks its event (below, on response-finish); a skip\n // passes with nothing stamped at all — it is the absence of enforcement.\n\n // A challenge needs a resolved client IP: the nonce and the `_cch` cookie are bound to it,\n // so without one a single solve would mint a cookie every unidentified client could\n // present. No ip -> no challenge (fail open), the same stance ip rules take.\n if (this.challengeOn && this.kit && ip) {\n // The verify endpoint answers first: a challenged client must be able to reach it.\n if (req.method === 'POST' && path === this.challengePath) { this.verifyChallenge(req, res, ip); return true; }\n if (v.challenge && !this.challengePassed(req, ip)) { this.serveChallengeInner(req, res, ip, path + query); return true; }\n }\n\n if (this.beaconEnabled()) {\n if (req.method === 'GET' && path === this.scriptPath) {\n res.writeHead(200, { 'content-type': 'application/javascript', 'cache-control': 'public, max-age=3600' });\n res.end(iife);\n return true;\n }\n if (req.method === 'POST' && path === this.fpPath) {\n this.relayBeacon(req, res, ip);\n return true;\n }\n }\n\n const rid = randomUUID();\n let sid = cookieValue((req.headers.cookie as string) || '', SESSION_COOKIE);\n const newSession = !sid;\n if (!sid) {\n sid = randomUUID();\n const https = (req.socket as { encrypted?: boolean }).encrypted || req.headers['x-forwarded-proto'] === 'https';\n keepSessionCookie(res, `${SESSION_COOKIE}=${sid}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax${https ? '; Secure' : ''}`);\n }\n req.camada = { rid, ip, sid };\n res.setHeader('x-rid', rid);\n\n const cfg = this.snap.config;\n const excluded = (cfg?.exclude || []).some((x) => path.startsWith(x));\n if (!excluded && Math.random() < (cfg?.sample ?? 1)) {\n // 'finish' when the response went out whole; 'close' alone when the client left first (an\n // aborted SSE or download), which ships the status set so far and dur up to the abort — the\n // fetch adapters' cancel semantics. 'close' also follows every 'finish': one event either way.\n let shipped = false;\n const ship = () => guarded(() => {\n // serveChallenge() may have answered from inside the app, and it already shipped the\n // `blk: \"challenge\"` row — one request, one event.\n if (shipped || req.camadaChallenged) return;\n shipped = true;\n const ev = this.buildEvent(req, path, query, ip, { rid, sid, newSession });\n ev.ts = t0; // the request start: the timeline draws [ts, ts + dur]\n ev.st = res.statusCode;\n ev.dur = Date.now() - t0;\n if (req.route?.path) ev.rt = String(req.route.path);\n if (v.warn && v.rule) ev.wrn = v.rule; // §D3: the warn rule that let this request through\n this.queue!.push(ev);\n }, undefined);\n res.once('finish', ship);\n res.once('close', ship);\n }\n return false;\n }\n\n private buildEvent(req: CamadaRequest, path: string, query: string, ip: string | null, o: { rid: string; sid: string | null; newSession: boolean }): WireEvent {\n const raw = req.rawHeaders || [];\n const headers: Array<[string, string]> = [];\n for (let i = 0; i + 1 < raw.length; i += 2) headers.push([raw[i], raw[i + 1]]);\n return buildWireEvent(\n { method: req.method || 'GET', host: (req.headers.host as string) || '', path, query, headers, ip, httpVersion: req.httpVersion },\n { tap: TAP_NODE, rid: o.rid, sid: o.sid, newSession: o.newSession },\n );\n }\n\n /** Reads the beacon POST (≤32 KB), answers 204 immediately, and queues the beacon as a `sig: 1`\n * row with the trusted-proxy-resolved client IP: it rides the next event batch, so the analyst\n * sees one request per flush instead of one per page view. Junk bodies are dropped, never shipped. */\n private relayBeacon(req: IncomingMessage, res: ServerResponse, ip: string | null): void {\n const chunks: Buffer[] = [];\n let size = 0, dead = false;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size > FP_MAX) { dead = true; req.removeAllListeners('data'); res.writeHead(413).end(); return; }\n chunks.push(c);\n });\n req.on('end', () => guarded(() => {\n if (dead) return;\n res.writeHead(204, { 'cache-control': 'no-store' });\n res.end();\n let parsed: unknown;\n try { parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch { return; }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return;\n this.queue!.push({ ...(parsed as Record<string, unknown>), sig: 1, ip, tap: TAP_NODE });\n }, undefined));\n req.on('error', () => { try { res.destroy(); } catch { /* already gone */ } });\n }\n\n private challengePassed(req: IncomingMessage, ip: string | null): boolean {\n return !!this.kit?.tokenValid(ip, Date.now(), cookieValue((req.headers.cookie as string) || '', 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). */\n private serveChallengeInner(req: CamadaRequest, res: ServerResponse, ip: string, target: string): void {\n const to = safeReturnTo(target);\n if (wantsHtml((req.headers.accept as string) ?? null, (req.headers['sec-fetch-dest'] as string) ?? null)) {\n writeChallengePage(res, challengePage({ nonce: this.kit!.nonce(ip, Date.now()), action: this.challengePath, to }));\n } else {\n writeChallengeJson(res);\n }\n // The response is out; telemetry must never be able to undo that (a throw here would make\n // guarded() report the request as unhandled and let the app write to an ended response).\n guarded(() => {\n const qi = target.indexOf('?');\n const ev = this.buildEvent(req, qi === -1 ? target : target.slice(0, qi), qi === -1 ? '' : target.slice(qi), ip,\n { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.st = 403;\n ev.blk = 'challenge';\n this.queue!.push(ev);\n }, undefined);\n }\n\n /** The request's existing session, so challenge rows join the session that produced them. */\n private sessionOf(req: IncomingMessage): string | null {\n return cookieValue((req.headers.cookie as string) || '', SESSION_COOKIE);\n }\n\n /** POST from the challenge page: validate the nonce and the proof of work, set `_cch`, 302\n * back to the (sanitised, same-site) original URL, and ship `{ st: 200, ch: 1 }`. */\n private verifyChallenge(req: CamadaRequest, res: ServerResponse, ip: string): void {\n readBody(req, (body) => guarded(() => {\n const form = parseFormBody(body);\n const to = safeReturnTo(form.to);\n const now = Date.now();\n if (!this.kit!.verify(ip, now, form.nonce, form.solution)) {\n writeChallengePage(res, challengePage({ nonce: this.kit!.nonce(ip, now), action: this.challengePath, to }));\n return;\n }\n res.writeHead(302, {\n location: to,\n 'set-cookie': challengeCookie(this.kit!.issue(ip, now), isHttps(req)),\n 'cache-control': 'no-store',\n });\n res.end();\n const ev = this.buildEvent(req, this.challengePath, '', ip, { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.st = 200;\n ev.ch = 1; // challenge passed (contract §A3 ingest field)\n this.queue!.push(ev);\n }, undefined));\n }\n\n /**\n * Records the upgrades (WebSocket handshakes) a node:http or node:https server hands to its\n * 'upgrade' listeners: Node never routes those through the request handler the middleware runs\n * in. Each one ships one event with `st: 101` once the app's listeners have run. Observe only:\n * the request is never blocked or challenged, nothing is written to the socket, no session\n * cookie is minted, and a server with no 'upgrade' listener of its own behaves exactly as before\n * (this wraps `server.emit` rather than adding a listener, which would claim every upgrade).\n * Idempotent; returns the server. A handshake the WebSocket library then rejects still ships 101.\n */\n attach<S extends Server>(server: S): S {\n guarded(() => {\n const s = server as S & { [ATTACHED]?: true };\n if (this.disabled || !this.queue || s[ATTACHED]) return;\n s[ATTACHED] = true;\n const emit = s.emit.bind(s) as (event: string | symbol, ...args: unknown[]) => boolean;\n s.emit = ((event: string | symbol, ...args: unknown[]) => {\n if (event !== 'upgrade') return emit(event, ...args);\n const t0 = Date.now();\n try {\n return emit(event, ...args);\n } finally {\n guarded(() => this.shipUpgrade(args[0] as CamadaRequest, t0), undefined);\n }\n }) as S['emit'];\n }, undefined);\n return server;\n }\n\n private shipUpgrade(req: CamadaRequest, t0: number): void {\n if (this.disabled || !this.queue) return;\n const rawUrl = req.url || '/';\n const qi = rawUrl.indexOf('?');\n const path = qi === -1 ? rawUrl : rawUrl.slice(0, qi);\n const cfg = this.snap?.config;\n if ((cfg?.exclude || []).some((x) => path.startsWith(x)) || Math.random() >= (cfg?.sample ?? 1)) return;\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n const ev = this.buildEvent(req, path, qi === -1 ? '' : rawUrl.slice(qi), ip, { rid: randomUUID(), sid: this.sessionOf(req), newSession: false });\n ev.ts = t0;\n ev.st = 101;\n ev.dur = Date.now() - t0;\n this.queue.push(ev);\n }\n\n /** Serve the challenge for this request on demand — for a route the app wants to gate itself\n * (the example's /challenge-me). Returns false when the client already holds a valid `_cch`,\n * so the caller renders its own page. */\n serveChallenge(req: IncomingMessage, res: ServerResponse): boolean {\n return guarded(() => {\n if (this.disabled || !this.kit || !this.queue) return false;\n const ip = resolveClientIp(req.socket?.remoteAddress, req.headers['x-forwarded-for'] as string | undefined, this.trustedProxy());\n if (!ip || this.challengePassed(req, ip)) return false; // unidentifiable client: fail open\n (req as CamadaRequest).camadaChallenged = true;\n this.serveChallengeInner(req as CamadaRequest, res, ip, req.url || '/');\n return true;\n }, false);\n }\n\n /** For HTML templates: the first-party beacon tag with the request's rid. */\n scriptTag(req: IncomingMessage): string {\n if (this.disabled || !this.beaconEnabled()) return '';\n const rid = (req as CamadaRequest).camada?.rid;\n return `<script src=\"${this.scriptPath}${rid ? `?r=${rid}` : ''}\" async></script>`;\n }\n\n /** App-context outcome events (login failed, signup, …). The identifier is HMAC-hashed\n * in-process; the raw value never reaches the queue. */\n track(req: IncomingMessage, event: string, data?: { user?: string }): void {\n guarded(() => {\n if (this.disabled || !this.queue || !this.env) return;\n const ctx = (req as CamadaRequest).camada;\n void (async () => {\n const uid = data?.user ? await hashUserId(data.user, this.env!.ingestToken, (globalThis.crypto ?? webcrypto).subtle) : null;\n this.queue!.push({ tap: TAP_NODE, et: event, uid, rid: ctx?.rid ?? null, sid: ctx?.sid ?? null, ip: ctx?.ip ?? null, ts: Date.now() });\n })().catch(logRateLimited);\n }, undefined);\n }\n\n /** Express/Connect middleware (also what NestJS on the default platform consumes). */\n express() {\n return (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void): void => {\n if (!this.handle(req, res)) next();\n };\n }\n\n stop(): void {\n this.snap?.stop();\n this.queue?.stop();\n }\n}\n\nexport function createCamada(opts: CamadaOptions = {}): Camada {\n const c = new Camada(opts);\n if (!c.env) logRateLimited(new Error('CAMADA_KEY (or CAMADA_TOKEN + CAMADA_SNAPSHOT_TOKEN) not set — camada is inactive'));\n return c;\n}\n","// Environment wiring. The two-line quickstart depends on this file doing the right thing:\n// CAMADA_KEY=<ingest_token>.<snap_token> (printed by `reconcile instructions` and seed)\n// CAMADA_INGEST_URL / CAMADA_SNAPSHOT_URL (dev: http://localhost:8787[/snapshot])\n// CAMADA_DISABLED=1 kill switch, checked per request\n// CAMADA_SERVERLESS=1 lazy snapshot mode (no interval timer)\n// CAMADA_TRUSTED_PROXY local override: none | vercel | hops:N | cidrs:a,b\nimport { parseKey, parseTrustedProxyEnv, type TrustedProxyConfig } from '@camada/core';\n\nexport { parseTrustedProxyEnv }; // re-export: this was @camada/node's public surface before it moved to core\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 serverless: boolean;\n trustedProxy: TrustedProxyConfig | null; // null = defer to server-delivered config\n}\n\n/** Returns null (SDK stays disabled, one log line) rather than throwing on bad config. */\nexport function resolveEnv(env: Record<string, string | undefined> = process.env): 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 serverless: env.CAMADA_SERVERLESS === '1',\n trustedProxy: parseTrustedProxyEnv(env.CAMADA_TRUSTED_PROXY),\n };\n}\n","// The challenge for @camada/node (SDK-04). node:crypto gives us a synchronous HMAC, so the\n// cookie check costs nothing on the hot path and Camada.handle() stays a synchronous boolean.\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createHash, createHmac } from 'node:crypto';\nimport { createChallenge, type ChallengeKit } from '@camada/core';\n\nexport const CHALLENGE_PATH = '/__camada/challenge';\nconst BODY_MAX = 4 * 1024; // the verify form is ~120 bytes; anything larger is not ours\n\nexport function nodeChallengeKit(secret: string): ChallengeKit {\n return createChallenge({\n secret,\n hmac: (s, m) => createHmac('sha256', s).update(m).digest('hex'),\n sha: (m) => createHash('sha256').update(m).digest('hex'),\n });\n}\n\n/** Reads at most BODY_MAX bytes; hands back '' when the client sends more (or errors). */\nexport function readBody(req: IncomingMessage, done: (body: string) => void): void {\n const chunks: Buffer[] = [];\n let size = 0, dead = false;\n req.on('data', (c: Buffer) => {\n if (dead) return;\n size += c.length;\n if (size > BODY_MAX) { dead = true; chunks.length = 0; return; }\n chunks.push(c);\n });\n req.on('end', () => done(dead ? '' : Buffer.concat(chunks).toString('utf8')));\n req.on('error', () => done(''));\n}\n\nexport const isHttps = (req: IncomingMessage): boolean =>\n !!(req.socket as { encrypted?: boolean } | undefined)?.encrypted || req.headers['x-forwarded-proto'] === 'https';\n\nexport function writeChallengePage(res: ServerResponse, html: string): void {\n res.writeHead(403, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-camada-challenge': '1' });\n res.end(html);\n}\n\nexport function writeChallengeJson(res: ServerResponse): void {\n res.writeHead(403, { 'content-type': 'application/json', 'cache-control': 'no-store', 'x-camada-challenge': '1' });\n res.end('{\"error\":\"challenge_required\"}');\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/node\",\n \"description\": \"camada backend SDK for Node: request capture, inline blocking, first-party beacon serving; Express/Fastify/Nest/Koa adapters\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"./fastify\": {\n \"types\": \"./dist/fastify.d.ts\",\n \"import\": \"./dist/fastify.js\",\n \"require\": \"./dist/fastify.cjs\"\n },\n \"./nest\": {\n \"types\": \"./dist/nest.d.ts\",\n \"import\": \"./dist/nest.js\",\n \"require\": \"./dist/nest.cjs\"\n },\n \"./koa\": {\n \"types\": \"./dist/koa.d.ts\",\n \"import\": \"./dist/koa.js\",\n \"require\": \"./dist/koa.cjs\"\n }\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"dependencies\": {\n \"@camada/browser\": \"file:../camada-browser\",\n \"@camada/core\": \"file:../camada-core\"\n },\n \"devDependencies\": {\n \"@nestjs/common\": \"^11.0.0\",\n \"@nestjs/core\": \"^11.0.0\",\n \"@nestjs/platform-express\": \"^11.0.0\",\n \"@types/express\": \"^4.17.25\",\n \"@types/koa\": \"^2.15.2\",\n \"@types/node\": \"^22.10.0\",\n \"express\": \"^4.21.0\",\n \"fastify\": \"^5.2.0\",\n \"koa\": \"^2.15.0\",\n \"reflect-metadata\": \"^0.2.2\",\n \"rxjs\": \"^7.8.1\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"@nestjs/common\": \">=10\"\n },\n \"peerDependenciesMeta\": {\n \"@nestjs/common\": {\n \"optional\": true\n }\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"],"mappings":"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","names":["import_node_crypto","import_core","import_core","name","iife"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { a as CamadaOptions, C as Camada } from './camada-Cn5SJVIG.cjs';
3
- export { R as ResolvedEnv, c as createCamada, r as resolveEnv } from './camada-Cn5SJVIG.cjs';
1
+ import { IncomingMessage, ServerResponse, Server } from 'node:http';
2
+ import { a as CamadaOptions, C as Camada } from './camada-Dh_9D6Sx.cjs';
3
+ export { R as ResolvedEnv, c as createCamada, r as resolveEnv } from './camada-Dh_9D6Sx.cjs';
4
4
  export { parseTrustedProxyEnv } from '@camada/core';
5
5
 
6
6
  declare function getDefault(): Camada;
@@ -14,6 +14,7 @@ declare const _default: {
14
14
  track: (req: IncomingMessage, event: string, data?: {
15
15
  user?: string;
16
16
  }) => void;
17
+ attach: <S extends Server>(server: S) => S;
17
18
  configure: typeof configure;
18
19
  };
19
20
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { a as CamadaOptions, C as Camada } from './camada-Cn5SJVIG.js';
3
- export { R as ResolvedEnv, c as createCamada, r as resolveEnv } from './camada-Cn5SJVIG.js';
1
+ import { IncomingMessage, ServerResponse, Server } from 'node:http';
2
+ import { a as CamadaOptions, C as Camada } from './camada-Dh_9D6Sx.js';
3
+ export { R as ResolvedEnv, c as createCamada, r as resolveEnv } from './camada-Dh_9D6Sx.js';
4
4
  export { parseTrustedProxyEnv } from '@camada/core';
5
5
 
6
6
  declare function getDefault(): Camada;
@@ -14,6 +14,7 @@ declare const _default: {
14
14
  track: (req: IncomingMessage, event: string, data?: {
15
15
  user?: string;
16
16
  }) => void;
17
+ attach: <S extends Server>(server: S) => S;
17
18
  configure: typeof configure;
18
19
  };
19
20
 
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  index_default,
7
7
  parseTrustedProxyEnv,
8
8
  resolveEnv
9
- } from "./chunk-J7BQLOME.js";
9
+ } from "./chunk-PNJ4BVBD.js";
10
10
  export {
11
11
  Camada,
12
12
  configure,