securenow 8.23.0 → 8.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL-API.md CHANGED
@@ -1,176 +1,168 @@
1
- # SecureNow SDK — Agent Skill
1
+ # SecureNow SDK — Agent Skill
2
2
 
3
3
  > **Node.js security:** Use a supported, patched runtime: Node.js 22.23.4+, 24.22.1+, or 26.12.1+ on its respective release line. Earlier patches are affected by [CVE-2026-111497](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases). Upgrading the SecureNow package does not patch Node.js; upgrade the runtime and restart every application process.
4
4
 
5
- Instrument any Node.js application with OpenTelemetry tracing, structured logging, request body capture, and a multi-layer IP firewall. Supports Express, Fastify, NestJS, Koa, Hapi, Next.js, Nuxt 3, Vite (browser), and raw `http.createServer` — with zero code changes for most setups.
5
+ The integration playbook for the `securenow` npm package. A coding agent that reads only this file can plug SecureNow into a Node.js app, turn on every feature, deploy it and prove it works. It describes 8.25.0; items marked **(8.25.0+)** are new in 8.25.0, so an older install needs `npm install securenow@latest` for them (check with `npx securenow version`). The terminal reference is [SKILL-CLI.md](./SKILL-CLI.md). Both files ship in `node_modules/securenow/`.
6
6
 
7
- **CLI parity:** every capability exposed below (redaction, CIDR matching, log/span emission, firewall preload, config inspection) has an equivalent `securenow` CLI command. See [SKILL-CLI.md](./SKILL-CLI.md) for the terminal surface.
7
+ ## Rules for the agent
8
8
 
9
- **MCP parity (v8.0+):** `npx securenow mcp` starts a local stdio MCP server for Codex, Claude, and other MCP clients. It reads admin/control-plane auth from `.securenow/admin.json` and SDK runtime app credentials from `.securenow/runtime.json`, with legacy `.securenow/credentials.json` still supported. Alert-rule operators can inspect notifications, read exact `metadata.matchedSubdetectors`, dry-run candidate SQL with `securenow_alert_rule_candidate_test`, and apply global system-rule query fixes with `securenow_alert_rule_query_update` only when the admin user/plan permits it.
9
+ 1. Never print, paste or commit `.securenow/admin.json`, `.securenow/runtime.json` or `.securenow/credentials*.json`: they hold the admin token and the runtime API key (`snk_live_...`). Mask keys in summaries.
10
+ 2. Start authentication only with `npx securenow login` from the project root, then wait for the person to finish the browser step. Never open an auth URL yourself (the CLI generates the callback and state).
11
+ 3. Configuration lives in `.securenow/runtime.json`. The SDK reads exactly two environment variables, `SECURENOW_CREDENTIALS` and `SECURENOW_DISABLED`. Every other `SECURENOW_*` variable (`SECURENOW_APPID`, `SECURENOW_API_KEY`, `SECURENOW_INSTANCE`, `SECURENOW_NO_UUID`, `SECURENOW_LOGGING_ENABLED`, `SECURENOW_CAPTURE_BODY`, ...) is ignored, whatever an older guide says. Do not create `.env` files for SecureNow.
12
+ 4. Ask the person before anything that changes enforcement in production: blocks, allowlist entries, rate limits, challenges, session revocations, rule promotion, blocking mode. CLI writes reach the running app within about 10 seconds; MCP writes need `confirm: true` and a reason.
13
+ 5. Put the preload on the command that actually starts the server process (start script, Dockerfile `CMD`, PM2 `node_args`, systemd `ExecStart`). Keep the project's existing middleware, instrumentation and scripts.
14
+ 6. Confirm any CLI command or flag with `npx securenow help <command>` (works offline). Don't invent URLs or ports: smoke-test only a URL the project or the person gives you.
10
15
 
11
- **Noisy alert-rule reviews:** prefer fixing a generic system-rule detector over creating customer-specific false positives. Dry-run candidate SQL first, preserve tenant scoping with `__USER_APP_KEYS__`, keep exploit-specific indicators, then save the shared query mapping only with an audit reason and explicit confirmation.
16
+ ## 1. Your stack → what to do
12
17
 
13
- ## Installation
18
+ | Your stack | Wire it with | Section |
19
+ |---|---|---|
20
+ | Express, Fastify, NestJS, Koa, Hapi, Hono (`@hono/node-server`), Polka, Feathers, h3, raw `http`/`https`/`http2`, any server on `node:http` | `node -r securenow/register <entry>`, the same flag for CommonJS, ESM and TypeScript | 3.0–3.11 |
21
+ | Next.js 15 or 16 | `npx securenow init` (writes `instrumentation.ts`, patches `next.config.*`) | 3.12 |
22
+ | Next.js 14 | `npx securenow init` **(8.25.0+)** | 3.13 |
23
+ | Nuxt 3 or 4 | `modules: ['securenow/nuxt']` in `nuxt.config`, then `npx securenow init` | 3.14 |
24
+ | Vite (`vite preview`, a Vite SSR server) | `node -r securenow/register-vite ...` or `-r securenow/register` | 3.15 |
25
+ | Bun 1.3, app served through `node:http` | `bun --preload securenow/register app.js` | 3.16 |
26
+ | IP blocking only, no telemetry | `node -r securenow/firewall-only <entry>` | 3.17 |
27
+ | PM2, Docker, Kubernetes, systemd, Railway/Render/Heroku/Fly, Vercel | the wiring above, plus production credentials | 6 |
28
+ | Deno, Cloudflare Workers, Vercel Edge / Next.js Edge runtime, `Bun.serve`, browser-only apps | not supported: SecureNow runs inside a Node.js process | 9 |
29
+ | AWS Lambda and other serverless functions | not supported or tested: don't promise it | 9 |
14
30
 
15
- ```bash
16
- npm install securenow@latest
17
- ```
31
+ ## 2. Setup checklist
18
32
 
19
- ### Install This Skill in Cursor
33
+ 1. **Install.** `npm install securenow@latest` (or the project's package manager), then `npx securenow version`.
34
+ 2. **Log in.** `npx securenow login` from the project root. The person picks or creates the app in the browser. It writes `.securenow/runtime.json` (app key, runtime API key, SDK config) and `.securenow/admin.json` (CLI/MCP auth) and adds both to `.gitignore` without ignoring the whole `.securenow/` folder. One lane at a time: `npx securenow app connect` (runtime only) or `npx securenow admin login` (admin only). Check with `npx securenow whoami`.
35
+ 3. **Init.** `npx securenow init` detects the framework (Next.js, Nuxt, Express, Fastify, Koa, Hapi, otherwise plain Node), fills `.securenow/runtime.json` with defaults and an explanation for each key (`deploymentEnvironment: "local"`; change it with `--env staging` etc.), writes `instrumentation.ts` and patches `next.config.*` for Next.js, prints the start-script change for plain Node, reminds you to add the module for Nuxt, and warns about a PM2 ecosystem file that uses a bare `-r securenow/register`. When an instrumentation file or config is too custom to patch, it prints an agent-ready prompt with the exact edits instead.
36
+ 4. **Wire.** Section 3 for the stack.
37
+ 5. **Deploy credentials.** `npx securenow credentials runtime --env production` writes `.securenow/credentials.production.json`. Ship it as `<app-root>/.securenow/credentials.json` (secret file or mount), or, on hosts without file mounts, print it as one variable with `--format env` and store `SECURENOW_CREDENTIALS` as a secret (section 6).
38
+ 6. **Verify.** `npx securenow whoami`, `npx securenow doctor`, `npx securenow test-span`, start the app and read its `[securenow]` lines, send one real request, then `npx securenow status` and `npx securenow today` (section 7).
20
39
 
21
- Save this file as `.cursor/skills/securenow-api/SKILL.md` in your project. Your AI agent will auto-discover it whenever you ask about integrating securenow, configuring tracing, setting up the firewall, or instrumenting any framework.
40
+ ## 3. Wiring per framework
22
41
 
23
- ## Quick Start — Any Node.js Framework
42
+ Every server built on `node:http` is covered by one preload: tracing, logs, header and body capture, crash capture, the firewall, rate limits, challenges and, **(8.25.0+)**, the zero-code session guard. No application code changes.
24
43
 
25
- ### 1. Install, Login, And Init
26
-
27
- ```bash
28
- npm install securenow@latest
29
- node -p "require('./node_modules/securenow/package.json').version"
30
- npx securenow version
31
- npx securenow login
32
- npx securenow admin login # admin/control-plane CLI + MCP auth only
33
- npx securenow app connect # SDK runtime app + runtime API key only
34
- npx securenow init
44
+ ```json
45
+ {
46
+ "scripts": {
47
+ "start": "node -r securenow/register src/index.js",
48
+ "dev": "node -r securenow/register --watch src/index.js"
49
+ }
50
+ }
35
51
  ```
36
52
 
37
- Use `securenow@8.0.0` or newer for split admin/runtime credentials and runtime-authenticated ingestion. Start authentication with `npx securenow login`; do not manually open auth URLs, because the CLI generates the callback/state values. The friendly login flow can connect both lanes, while `npx securenow admin login` only writes `.securenow/admin.json` and `npx securenow app connect` only writes `.securenow/runtime.json`. `init` scaffolds the framework integration.
38
-
39
- ### 2. Run With Instrumentation
53
+ Other ways to load the same preload:
40
54
 
41
- **Option A — CLI (recommended):**
55
+ - `npx securenow run src/index.js`: spawns Node with the preload (and the ESM hook for ESM apps) and passes Node flags through (`--watch`, `--inspect`). `npx securenow src/index.js` is a shorthand.
56
+ - `NODE_OPTIONS="-r securenow/register" npm start`, when the start command can't be edited. Every Node process started with that variable loads SecureNow, npm itself included, so prefer the flag in the script.
57
+ - CommonJS only: `require('securenow/register');` as the very first line of the entry file, before any other `require`.
42
58
 
43
- ```bash
44
- npx securenow run src/index.js
45
- ```
59
+ ### 3.0 CommonJS, ESM and TypeScript
46
60
 
47
- **Option B — Node preload flag:**
48
-
49
- ```bash
50
- node -r securenow/register src/index.js
51
- ```
61
+ - **CommonJS:** the `-r` flag, or the first-line `require`.
62
+ - **ESM** (`"type": "module"` or an `.mjs` entry): use the `-r` flag. It registers the OpenTelemetry ESM hook through `module.register()` (Node 20.6+) and prints `[securenow] ESM loader hook auto-registered (module.register)`. Don't `import 'securenow/register'` inside the app: static imports load before it runs, so Express, pg and the rest are not instrumented.
63
+ - **TypeScript:** `node -r securenow/register --import tsx src/index.ts`, or `node -r securenow/register -r ts-node/register src/index.ts` (SecureNow first), or run the compiled output: `node -r securenow/register dist/index.js`. Types ship for `securenow/events`, `securenow/sessions`, `securenow/tracing`, `securenow/nextjs`, `securenow/nuxt`.
52
64
 
53
- **Option C — Auto-setup for Next.js:**
65
+ ### 3.1 Express
54
66
 
55
- ```bash
56
- npx securenow init
67
+ ```js
68
+ // src/index.js: no SecureNow code
69
+ const express = require('express');
70
+ const app = express();
71
+ app.use(express.json()); // SecureNow records the body the app reads
72
+ app.post('/api/login', (req, res) => res.json({ ok: true }));
73
+ app.listen(3000);
57
74
  ```
58
75
 
59
- That's it. Traces, logs, request body capture, multipart metadata capture, and firewall enforcement are on by default. No code changes for Express, Fastify, NestJS, Koa, Hapi, and raw Node.
76
+ Start with `node -r securenow/register src/index.js`. Records `http.route` (`/users/:id`). One span per middleware is off by default (`config.otel.middlewareSpans`). Tested on Express 4.22 and 5.3 (CommonJS, ESM, tsx, `NODE_OPTIONS`, `securenow run`).
60
77
 
61
- Metrics export is off by default. SecureNow sets `OTEL_METRICS_EXPORTER=none` only when the app has not explicitly configured a metrics exporter, so `@opentelemetry/sdk-node` will not start a default metrics reader that retries `localhost:4318`.
78
+ ### 3.2 Fastify
62
79
 
63
- ### 3. Firewall Is Enabled by Default
80
+ Start with `node -r securenow/register server.js`. The route template (`http.route`, span name `GET /users/:id`) and handler exceptions come from Fastify's `fastify.initialization` diagnostics channel. Tested on Fastify 5.12.
64
81
 
65
- Since v8.0, the app runtime flow connects the firewall automatically after
66
- the user picks or creates an app. The runtime API key lives in `.securenow/runtime.json`
67
- so admin CLI/MCP login cannot overwrite it:
82
+ ### 3.3 NestJS
68
83
 
69
84
  ```bash
70
- npx securenow app connect # pick/create app; runtime API key is minted automatically
71
- # or, if you already have one:
72
- npx securenow api-key set snk_live_abc123...
85
+ npm run build && node -r securenow/register dist/main.js # compiled (production)
86
+ node -r securenow/register -r ts-node/register src/main.ts # TypeScript directly
73
87
  ```
74
88
 
75
- Both paths write the key to `.securenow/runtime.json` and the firewall activates on next start. For production, run `npx securenow credentials runtime --env production` and mount/copy the tokenless file as `.securenow/credentials.json` or `.securenow/credentials.<env>.json`.
89
+ `-r securenow/register` goes before `-r ts-node/register`. Records `http.route`. Tested on NestJS 11.2 with the default Express adapter.
76
90
 
77
- The automatically created key is scoped to the selected app only. Its
78
- `runtime_app` scopes are `traces:write`, `logs:write`, `firewall:read`,
79
- `blocklist:read`, and `allowlist:read`. `securenow api-key create` uses the
80
- current app from `.securenow/runtime.json` by default, resolves that app UUID to
81
- the server app id, and stores the new one-time plaintext key back as `apiKey`.
91
+ ### 3.4 Koa
82
92
 
83
- The firewall syncs your blocklist and enforces it on every request — zero code changes.
93
+ Start with the preload. `http.route` needs `@koa/router`; body capture needs a body parser (`koa-bodyparser` or `@koa/bodyparser`), because SecureNow records the body as the app reads it. Tested on Koa 3.3 with `@koa/router`.
84
94
 
85
- Blocklist unblocks are audit-preserving: dashboard/API/CLI/MCP unblock actions
86
- mark the active block as `removed`, invalidate firewall sync, clear expiry to
87
- avoid TTL deletion, and retain block reports/history for future review or
88
- reblock context.
95
+ ### 3.5 Hapi
89
96
 
90
- For near-realtime propagation after a block/unblock, set
91
- `config.firewall.versionCheckInterval` to `1` or `2` in the protected app's
92
- `.securenow/runtime.json` or production runtime credentials. The SDK polls `/firewall/sync` with ETag/304, so
93
- unchanged checks are lightweight; keep `config.firewall.syncInterval` high as a
94
- safety-net full refresh.
97
+ Start with the preload. Records `http.route`. Tested on Hapi 21.4.
95
98
 
96
- Default automation is active for new and existing customers. The API
97
- idempotently provisions risk-score rules for all apps/environments:
98
- `riskScore >= 95` blocks for 7 days, `90-94` for 72h, and `85-89` for 24h.
99
- Run `securenow automation defaults --yes` or the API backfill script when an
100
- operator needs to ensure those defaults immediately.
99
+ ### 3.6 Hono (`@hono/node-server`)
101
100
 
102
- ---
101
+ ```js
102
+ // app.mjs
103
+ import { serve } from '@hono/node-server';
104
+ import { Hono } from 'hono';
103
105
 
104
- ## Import Map
106
+ const app = new Hono();
107
+ app.get('/users/:id', (c) => c.json({ id: c.req.param('id') }));
108
+ serve({ fetch: app.fetch, port: 3000 });
109
+ ```
105
110
 
106
- | Import | Purpose | Type |
107
- |--------|---------|------|
108
- | `securenow` / `securenow/register` | Auto-register tracing + firewall (side-effect) | Preload (`-r`) |
109
- | `securenow/tracing` | Core OTel SDK; exports `getLogger()`, `isLoggingEnabled()` | CJS |
110
- | `securenow/nextjs` | Next.js instrumentation; exports `registerSecureNow(options?)` | CJS |
111
- | `securenow/nextjs-webpack-config` | Next.js config wrapper; exports `withSecureNow()`, `getSecureNowWebpackConfig()`, `EXTERNAL_PACKAGES` | CJS |
112
- | `securenow/nextjs-middleware` | Edge middleware body capture; exports `middleware()`, `redactSensitiveData()`, `DEFAULT_SENSITIVE_FIELDS` | CJS |
113
- | `securenow/nextjs-wrapper` | Route handler wrappers; exports `withSecureNow()`, `withSecureNowAsync()`, `captureRequestBody()`, `redactSensitiveData()` | CJS |
114
- | `securenow/nextjs-auto-capture` | Auto-patch Next request for body capture; exports `patchNextRequest()`, `safeBodyCapture()`, `redactSensitiveData()`, `isBodyCaptureEnabled()` | CJS |
115
- | `securenow/nuxt` | Nuxt 3 module (add to `modules` array) | ESM |
116
- | `securenow/firewall` | Standalone firewall; exports `init()`, `shutdown()`, `getStats()`, `getMatcher()`, `getAllowlistMatcher()` | CJS |
117
- | `securenow/rate-limits` | Rate-limit remediation API helper; exports `parseRateLimitText()`, `createRateLimitFromText()`, `createRateLimit()`, `listRateLimits()` | CJS |
118
- | `securenow/firewall-only` | Preload: credentials-file firewall only, no tracing | Preload (`-r`) |
119
- | `securenow/cidr` | CIDR utilities; exports `createMatcher()`, `ipToInt()`, `parseCidr()`, `matchesCidr()` | CJS |
120
- | `securenow/resolve-ip` | IP resolution; exports `resolveClientIp()`, `resolveSocketIp()`, `isFromTrustedProxy()` | CJS |
121
- | `securenow/console-instrumentation` | Console→OTLP bridge; exports `originalConsole`, `restoreConsole()` | CJS |
122
- | `securenow/web-vite` | Browser OTel (document load, fetch, XHR, user interaction); default export `startSecurenowWeb()` | ESM |
123
- | `securenow/register-vite` | CJS bridge for Vite preload | CJS |
111
+ Start with `node -r securenow/register app.mjs`. Only Hono served by `@hono/node-server` is covered (not `Bun.serve`, Deno or Workers). `http.route` and handler exceptions (5xx, with their stack) on the request span **(8.25.0+)**; before, spans show the raw path. Tested on Hono 4.
124
112
 
125
- ---
113
+ ### 3.7 Polka
126
114
 
127
- ## Framework Integration Guides
115
+ Start with the preload. Polka has no body parser: add one for body capture. Pass errors to `next(err)`; a thrown error crashes Polka. `http.route` and `next(err)` exceptions on the span **(8.25.0+)**. Tested on Polka 0.5.
128
116
 
129
- ### Express / Fastify / NestJS / Koa / Hapi / Raw Node
117
+ ### 3.8 Feathers
130
118
 
131
- No code changes. Use the preload:
119
+ Feathers 5 with the Express (`@feathersjs/express`) or Koa transport: start with the preload. `http.route` and 5xx service errors on the span **(8.25.0+)**. Tested on Feathers 5.0.
132
120
 
133
- ```bash
134
- node -r securenow/register app.js
135
- ```
121
+ ### 3.9 h3
136
122
 
137
- Or with the CLI:
123
+ ```js
124
+ const http = require('http');
125
+ const { createApp, createRouter, defineEventHandler, toNodeListener } = require('h3');
138
126
 
139
- ```bash
140
- npx securenow run app.js
127
+ const app = createApp();
128
+ const router = createRouter().get('/users/:id', defineEventHandler((event) => ({ id: event.context.params.id })));
129
+ app.use(router);
130
+ http.createServer(toNodeListener(app)).listen(3000);
141
131
  ```
142
132
 
143
- **PM2:**
133
+ Start with the preload. `http.route` and handler exceptions on the span **(8.25.0+)**. Tested on h3 1.15. Nitro inside Nuxt: section 3.14.
144
134
 
145
- ```javascript
146
- // ecosystem.config.cjs
147
- module.exports = {
148
- apps: [{
149
- name: 'my-app',
150
- script: './app.js',
151
- instances: 4,
152
- node_args: '-r securenow/register',
153
- // Reads .securenow/runtime.json from the project root.
154
- }],
155
- };
156
- ```
135
+ ### 3.10 Raw `http`, `https`, `http2`
157
136
 
158
- **Docker:**
137
+ Start with the preload. `http.createServer`, `https.createServer` and the HTTP/2 compatibility API (the `'request'` event) are covered; an HTTP/2 server driven only by raw `'stream'` events is not. Spans carry the raw path (no route templates).
159
138
 
160
- ```dockerfile
161
- COPY .securenow/credentials.production.json ./.securenow/credentials.production.json
162
- CMD ["node", "-r", "securenow/register", "app.js"]
163
- ```
139
+ ### 3.11 Other servers on `node:http`
164
140
 
165
- ---
141
+ Any framework that ends in a `node:http` server gets traces, capture and the firewall from the preload. Only the frameworks above are tested, and others show the raw path instead of a route template.
166
142
 
167
- ### Next.js
143
+ ### 3.12 Next.js 15 and 16 (App Router and Pages Router)
168
144
 
169
- Run `npx securenow init` first. It creates the straightforward integration below when files are missing, and prints a Codex/Claude-ready merge prompt when existing files need careful edits.
145
+ Run `npx securenow init`. It writes `instrumentation.ts` (`.js` when there is no `tsconfig.json`):
170
146
 
171
- **1. `next.config.js`** (or `.mjs` / `.ts`):
147
+ ```ts
148
+ export async function register() {
149
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
150
+ try {
151
+ const securenowNext = await import('securenow/nextjs');
152
+ const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
153
+ registerSecureNow({ captureBody: true });
154
+ await import('securenow/nextjs-auto-capture');
155
+ } catch (error) {
156
+ console.error('[securenow] SecureNow did not start; the app is running without it:', error);
157
+ }
158
+ }
159
+ }
160
+ ```
172
161
 
173
- ```javascript
162
+ and patches `next.config.*` (or creates `next.config.mjs`):
163
+
164
+ ```js
165
+ /** @type {import('next').NextConfig} */
174
166
  const nextConfig = {
175
167
  serverExternalPackages: ['securenow'],
176
168
  outputFileTracingIncludes: {
@@ -181,544 +173,679 @@ const nextConfig = {
181
173
  export default nextConfig;
182
174
  ```
183
175
 
184
- For Next.js < 15, add `securenow` to `experimental.serverComponentsExternalPackages` instead and keep the same `outputFileTracingIncludes` block for standalone/self-hosted output.
185
-
186
- **2. `instrumentation.ts`** (or `.js`, can be in `src/`):
187
-
188
- ```typescript
189
- export async function register() {
190
- if (process.env.NEXT_RUNTIME !== 'nodejs') return;
191
-
192
- const securenowNext = await import(/* webpackIgnore: true */ 'securenow/nextjs');
193
- const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
194
- registerSecureNow({ captureBody: true });
195
- await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
196
- }
197
- ```
198
-
199
- `registerSecureNow(options?)` accepts:
176
+ - Keep the imports inside the `nodejs` block, with no `webpackIgnore` comment and no early `return`. Next.js then drops them from the Edge build and traces `securenow` and its dependencies into `output: 'standalone'`. The `try/catch` keeps the app serving if SecureNow can't start.
177
+ - **`src/` layout:** Next.js loads the instrumentation file from the folder that holds `app/` or `pages/`. With `src/app` or `src/pages`, `init` writes `src/instrumentation.ts` **(8.25.0+; before, it wrote the project root, which Next.js 14 and 15 never load there)** and moves a root file it generated earlier. `--src`/`--root` force the folder, `--ts`/`--js` the language, and `--force` replaces an existing file (the old one is kept as `.bak`).
178
+ - Re-running `init` replaces an instrumentation file that `init` generated before 8.24.0 (its `webpackIgnore` imports left OpenTelemetry out of standalone images and every request returned 500). A file you edited is left alone and `init` prints the merge prompt.
179
+ - `registerSecureNow(options?)` accepts `{ captureBody?, serviceName?, endpoint?, environment?, noUuid? }`. Leave `serviceName` and `noUuid` alone: the dashboard finds the app by `service.name`, which must equal the app key.
180
+ - Captured: HTTP server spans with client IP, redacted headers and bodies (JSON, form, GraphQL, multipart metadata), console logs, the firewall, rate limits and challenges. `http.route` on the HTTP span **(8.25.0+)**; before, Next.js 16 put it on Next's own span and Next.js 15 recorded `next.route`.
181
+ - The Edge runtime is not instrumented: `middleware.ts` on the Edge and routes with `export const runtime = 'edge'` get nothing. Node.js route handlers, server components and API routes are covered.
182
+ - Works with `next start` and `output: 'standalone'`, built with Turbopack or webpack. Standalone and Vercel: section 6.
183
+ - You don't need `securenow/nextjs-middleware` or `securenow/nextjs-wrapper`: since 8.24.0 bodies are captured at the HTTP layer, and `securenow/nextjs-auto-capture` (imported above) is only a fallback.
200
184
 
201
- ```typescript
202
- interface RegisterOptions {
203
- serviceName?: string; // override credentials app key/name
204
- endpoint?: string; // advanced OTLP endpoint override
205
- noUuid?: boolean; // override credentials config.runtime.noUuid
206
- captureBody?: boolean; // override credentials config.capture.body
207
- }
208
- ```
209
-
210
- On Vercel it uses `@vercel/otel`; self-hosted uses vanilla `@opentelemetry/sdk-node`.
185
+ ### 3.13 Next.js 14 (8.25.0+)
211
186
 
212
- **3. Credentials:**
187
+ `npx securenow init` writes the same instrumentation file. A new config is:
213
188
 
214
- ```json
215
- {
216
- "app": {
217
- "key": "<uuid>",
218
- "name": "my-nextjs-app",
219
- "instance": "https://ingest.securenow.ai"
189
+ ```js
190
+ /** @type {import('next').NextConfig} */
191
+ const nextConfig = {
192
+ experimental: {
193
+ instrumentationHook: true,
194
+ serverComponentsExternalPackages: ['securenow'],
195
+ outputFileTracingIncludes: {
196
+ '/*': ['./node_modules/securenow/**/*'],
197
+ },
220
198
  },
221
- "config": {
222
- "logging": { "enabled": true },
223
- "capture": { "body": true, "multipart": true, "maxBodySize": 10240 },
224
- "firewall": { "enabled": true }
225
- }
226
- }
227
- ```
228
-
229
- Local development and production do not need `.env.local`; `npx securenow app connect` and `npx securenow init` keep `.securenow/runtime.json` filled with runtime credentials and secure defaults. For production, run `npx securenow credentials runtime --env production` and mount/copy the generated JSON as `.securenow/credentials.json` or `.securenow/credentials.production.json`.
230
-
231
- #### Next.js Body Capture
232
-
233
- **Option A — Auto-capture (recommended):**
234
-
235
- Add to your `instrumentation.ts`:
236
-
237
- ```typescript
238
- export async function register() {
239
- if (process.env.NEXT_RUNTIME !== 'nodejs') return;
240
-
241
- const securenowNext = await import(/* webpackIgnore: true */ 'securenow/nextjs');
242
- const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
243
- registerSecureNow({ captureBody: true });
244
- await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
245
- }
246
- ```
247
-
248
- **Option B — Middleware:**
249
-
250
- ```typescript
251
- // middleware.ts
252
- export { middleware } from 'securenow/nextjs-middleware';
253
- export const config = { matcher: ['/api/:path*'] };
254
- ```
255
-
256
- **Option C — Per-route wrapper:**
257
-
258
- ```typescript
259
- import { withSecureNow } from 'securenow/nextjs-wrapper';
260
-
261
- export const POST = withSecureNow(async (req) => {
262
- // handler
263
- });
264
- ```
265
-
266
- #### Next.js with `securenow init`
199
+ };
267
200
 
268
- ```bash
269
- npx securenow app connect
270
- npx securenow init
201
+ export default nextConfig;
271
202
  ```
272
203
 
273
- Auto-detects Next.js, creates `instrumentation.ts`, adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, and reuses the app, instance, runtime API key, and secure defaults in `.securenow/runtime.json`. If files already exist, it prints an agent-ready prompt with the exact edits to propose.
204
+ With an existing config, `init` prints these exact edits (Next.js 14 keeps the three keys under `experimental`). Tested on Next.js 14.2 with `next start` and standalone output.
274
205
 
275
- ---
206
+ ### 3.14 Nuxt 3 and 4
276
207
 
277
- ### Nuxt 3
278
-
279
- **`nuxt.config.ts`:**
280
-
281
- ```typescript
208
+ ```ts
209
+ // nuxt.config.ts
282
210
  export default defineNuxtConfig({
283
211
  modules: ['securenow/nuxt'],
284
- securenow: {
285
- // optional overrides (defaults come from .securenow/runtime.json)
286
- },
287
212
  });
288
213
  ```
289
214
 
290
- The Nuxt module auto-configures Nitro externals, runtime config, and a server plugin that sets up OTel tracing + logging + firewall. Local app identity, runtime API key, and secure defaults come from `.securenow/runtime.json`; production can use tokenless `.securenow/credentials.<env>.json`.
215
+ Then run `npx securenow init` to fill `.securenow/runtime.json`.
291
216
 
292
- ---
217
+ - The module keeps `securenow` external (Nitro copies it, with its dependencies, into `.output/server/node_modules`) and adds a Nitro plugin that loads the same runtime as `-r securenow/register`, plus `http.route` from the matched route and handler errors as exceptions.
218
+ - No `securenow: { ... }` options in `nuxt.config`: they are ignored, with a build warning. Settings live in `.securenow/runtime.json`.
219
+ - Production: `node .output/server/index.mjs`, with `.securenow/credentials.json` in the folder you start it from, or `SECURENOW_CREDENTIALS`.
220
+ - `nuxt dev` is traced too. Its requests come through Nuxt's dev proxy: the client IP is known from 8.25.0 (before, it was unknown, so IP blocking only applied to the built server).
221
+ - Without the module: `node -r securenow/register .output/server/index.mjs`.
222
+ - Tested on Nuxt 3.21 and 4.6 (module, `.output` copied on its own as in a Docker image, `nuxt dev`, `-r` preload).
293
223
 
294
- ### Vite / Browser
224
+ ### 3.15 Vite
295
225
 
296
- ```javascript
297
- import startSecurenowWeb from 'securenow/web-vite';
226
+ Browser telemetry is not sent to SecureNow (browser ingestion is disabled since 8.0, and `securenow/web-vite` sends nothing). Instrument the Node process that serves the app:
298
227
 
299
- startSecurenowWeb({
300
- serviceName: 'my-frontend',
301
- endpoint: 'https://ingest.securenow.ai',
302
- });
228
+ ```bash
229
+ node -r securenow/register-vite node_modules/vite/bin/vite.js preview
230
+ node -r securenow/register server.js # a Vite SSR server (Express + Vite middleware, ...)
303
231
  ```
304
232
 
305
- Instruments document load, fetch, XMLHttpRequest, and user interactions with browser-side OpenTelemetry.
306
-
307
- ---
308
-
309
- ## Firewall — Multi-Layer IP Blocking
233
+ `securenow/register-vite` is the same runtime as `securenow/register`. A static build served by nginx or a CDN has no Node process to protect; wire SecureNow into its API server instead.
310
234
 
311
- The firewall auto-activates once a runtime API key is resolvable and the app firewall toggle is on. Since **v8.0**, `npx securenow app connect` enables the selected app firewall by default and writes the scoped runtime key to `.securenow/runtime.json`; `securenow api-key set` can still write/rotate the key later. Production should use the tokenless file generated by `securenow credentials runtime --env production`. Runtime resolution order is project `./.securenow/runtime.json` -> legacy project credentials -> project named runtime credentials -> global runtime/legacy/named runtime credentials. Admin auth lives separately in `admin.json`.
235
+ ### 3.16 Bun
312
236
 
313
- ```
314
- Layer 4: Cloud/Edge WAF → blocked at CDN (Cloudflare, AWS WAF, GCP Cloud Armor)
315
- Layer 3: OS Firewall → kernel-level DROP (iptables/nftables)
316
- Layer 2: TCP Socket → socket.destroy() before HTTP parsing
317
- Layer 1: HTTP Handler → 403 JSON response (always active)
237
+ ```bash
238
+ bun --preload securenow/register app.js
318
239
  ```
319
240
 
320
- ### Activate
241
+ For apps served through `node:http` (tested: Express 5 on Bun 1.3). `Bun.serve` is not traced or protected.
321
242
 
322
- ```bash
323
- # Zero-config runtime (recommended) - writes the key to .securenow/runtime.json
324
- npx securenow app connect # pick/create app; firewall connects automatically
325
- # or, if you already have a key:
326
- npx securenow api-key set snk_live_abc123...
243
+ ### 3.17 Firewall only
327
244
 
328
- # Production runtime file:
329
- npx securenow credentials runtime --env production
245
+ ```bash
246
+ node -r securenow/firewall-only src/index.js
247
+ npx securenow run --firewall-only src/index.js
330
248
  ```
331
249
 
332
- ### Scoped Blocklist Entries
250
+ Blocklist, allowlist, scoped blocks, rate limits, challenges and, **(8.25.0+)**, the zero-code session guard. No traces, no logs, no OpenTelemetry. It needs the runtime API key like the full preload.
251
+
252
+ ## 4. Configuration: `.securenow/runtime.json`
333
253
 
334
- Manual blocklist entries can target all routes or only matching request scopes.
335
- Use the dashboard Advanced section, the API, CLI, or MCP with the same fields:
254
+ ### File anatomy
336
255
 
337
256
  ```json
338
257
  {
339
- "ip": "203.0.113.42",
340
- "pathPattern": "/admin*",
341
- "pathMatchMode": "prefix",
342
- "method": "ALL",
343
- "appKey": "optional-app-key",
344
- "environment": "production",
345
- "reason": "Admin probing"
258
+ "apiKey": "snk_live_...",
259
+ "app": { "key": "<app-uuid>", "name": "my-api" },
260
+ "config": {
261
+ "runtime": { "deploymentEnvironment": "local" },
262
+ "logging": { "enabled": true },
263
+ "capture": { "body": true, "multipart": true, "maxBodySize": 10240, "sensitiveFields": [] },
264
+ "networking": { "trustedProxies": [] },
265
+ "firewall": { "failMode": "open" }
266
+ }
346
267
  }
347
268
  ```
348
269
 
349
- `pathMatchMode` accepts `prefix`, `exact`, or `regex`. Omit `pathPattern` and
350
- use `method: "ALL"` for a global IP/CIDR block.
270
+ - `apiKey`: the app-scoped runtime key (scopes `traces:write`, `logs:write`, `firewall:read`, `blocklist:read`, `allowlist:read`). Without it the ingest accepts nothing and the firewall does not start. Rotate it with `npx securenow api-key create` or `npx securenow api-key set snk_live_...`.
271
+ - `app.key`: the app's UUID. It becomes `service.name`, which the dashboard matches exactly.
272
+ - `_securenow`: a schema version and an explanation for every key, written by `init`. Leave it.
273
+ - A key you leave out takes its default. `app.instance` in older files is ignored (use `config.otel.endpoint`).
274
+
275
+ ### Where the SDK looks (first match wins, files are never merged)
276
+
277
+ 1. `SECURENOW_CREDENTIALS`: the same JSON, raw or base64.
278
+ 2. A project file. The SDK searches the working directory (and npm's `INIT_CWD`) and the entry script's folder, then each of their parent folders; under PM2 it searches the app folder PM2 recorded and the script's folder instead. In each folder the order is `.securenow/runtime.json`, `.securenow/credentials.json`, then `credentials.staging.json`, `credentials.production.json`, `credentials.preview.json`, `credentials.local.json`, `credentials.test.json`, `credentials.development.json`, `credentials.dev.json`, `credentials.prod.json`.
279
+ 3. The same names in `~/.securenow/`.
280
+
281
+ So a `runtime.json` left on a server beats the `credentials.production.json` next to it, and `credentials.staging.json` beats `credentials.production.json`. The startup line says which source won: `credentials=/srv/app/.securenow/credentials.json` or `credentials=env:SECURENOW_CREDENTIALS` (printed by `-r securenow/register` and Nuxt; Next.js prints `service.name` and `Environment` lines instead).
282
+
283
+ ### Every key (all under `config` except `apiKey` and `app`)
284
+
285
+ | Key | Default | Change it when |
286
+ |---|---|---|
287
+ | `logging.enabled` | `true` | You don't want `console.*` exported as logs (crash capture needs it on) |
288
+ | `logging.redact` | `true` | **(8.25.0+)** You need log lines exported exactly as written (redaction replaces credential fields and secret shapes with `[REDACTED]`) |
289
+ | `capture.body` | `true` | Bodies must never leave the process. Detections then lose body payloads |
290
+ | `capture.multipart` | `true` | You don't want multipart field values and file metadata (file content is never captured) |
291
+ | `capture.maxBodySize` | `10240` bytes (minimum 1024) | Your JSON bodies are larger: bigger bodies are recorded as `[TOO LARGE: <n> bytes]` |
292
+ | `capture.sensitiveFields` | `[]` | You have extra field names to redact (added to the built-ins; also applied to header names) |
293
+ | `otel.endpoint` | `null` = `https://ingest.securenow.ai` | Advanced: sending to your own OTLP collector (the SecureNow dashboard then sees nothing) |
294
+ | `otel.tracesEndpoint`, `otel.logsEndpoint` | `null` | Advanced: split collectors |
295
+ | `otel.headers` | `{}` | Extra OTLP headers. App key, auth and environment headers are added for you |
296
+ | `otel.logLevel` | `"error"` | `debug` while troubleshooting; `none` hides export errors |
297
+ | `otel.disableInstrumentations` | `[]` | You want to skip an instrumentation: full package names, e.g. `"@opentelemetry/instrumentation-dns"` |
298
+ | `otel.middlewareSpans` | `false` | You want one span per Express/Koa middleware per request (high volume, no detection uses them) |
299
+ | `runtime.deploymentEnvironment` | `"production"` (`init` writes `"local"`) | Every deployment: `production`, `staging`, `preview`, `local`, `test`... (`dev`/`development` become `local`, `prod` becomes `production`) |
300
+ | `runtime.noUuid` | `null` = automatic (one service name for every worker when `app.key` is set) | Never: `false` adds a per-process suffix and the dashboard no longer finds the app |
301
+ | `runtime.strict` | `false` | PM2/cluster workers should exit when no app identity resolves |
302
+ | `runtime.testSpan` | `false` | You want a `securenow.startup.smoke` span at every boot (`npx securenow test-span` is usually better) |
303
+ | `networking.trustedProxies` | `[]` | A load balancer, CDN or proxy with a public address sits in front of the app: IPv4/IPv6 addresses, CIDRs (IPv6 ranges 8.25.0+) or `"cloudflare"` |
304
+ | `firewall.failMode` | `"open"` | `"closed"`: every request gets 403 until the first sync succeeds |
305
+ | `firewall.statusCode` | `403` | You want another status for blocked requests |
306
+ | `firewall.log` | `true` | You want no firewall lines in the console (this also hides the startup `Firewall:` lines) |
307
+ | `firewall.tcp` | `false` | Layer 2: drop blocked clients at the TCP connection (direct connections only) |
308
+ | `firewall.iptables` | `false` | Layer 3: kernel DROP (Linux, root or `CAP_NET_ADMIN`) |
309
+ | `firewall.cloud` | `null` | Layer 4: `"cloudflare"`, `"aws"` or `"gcp"` |
310
+ | `firewall.cloudDryRun` | `false` | Log the WAF pushes without applying them |
311
+ | `firewall.cloudflare.apiToken`, `.accountId` | `null` | Layer 4 on Cloudflare (secret) |
312
+ | `firewall.aws.wafIpSetId`, `.wafIpSetName`, `.wafScope` | `null`, `"securenow-blocklist"`, `"REGIONAL"` | Layer 4 on AWS WAF (`npm i @aws-sdk/client-wafv2`; AWS credentials from the standard chain) |
313
+ | `firewall.gcp.projectId`, `.securityPolicy` | `null` | Layer 4 on Cloud Armor (`npm i @google-cloud/compute`) |
314
+ | `firewall.rateLimitStore` | `null` (each process counts alone) | Several workers or servers must share rate-limit counters: `"redis://host:6379"` or `rediss://...` |
315
+ | `firewall.versionCheckInterval` | `10` seconds | `1`–`2` for near-instant propagation of blocks (unchanged checks are a cheap 304) |
316
+ | `firewall.syncInterval` | `3600` seconds | Safety-net full resync; rarely changed |
317
+ | `firewall.apiUrl` | SecureNow | Leave unset |
318
+ | `firewall.enabled` | ignored | Deprecated. Turn the firewall on or off per app and environment with `npx securenow firewall enable` / `disable` |
319
+ | `sessions.guard` **(8.25.0+)** | `false` | Revoked sessions should get a 401 in any framework, with no code |
320
+ | `sessions.capture` **(8.25.0+)** | `false` | Requests with a session should send `session.seen` for session-theft detection |
321
+ | `sessions.cookieNames` **(8.25.0+)** | `[]` | Your session cookie is not one of the defaults (section 5.10) |
322
+
323
+ ### Environment variables
324
+
325
+ - `SECURENOW_CREDENTIALS`: the runtime credentials JSON, raw or base64, for hosts that can't mount a file. It wins over every file. `npx securenow credentials runtime --env production --format env` prints `SECURENOW_CREDENTIALS=<base64>`. A value that isn't credentials JSON is ignored with a warning.
326
+ - `SECURENOW_DISABLED=1` (or `true`, `yes`, `on`): the SDK stays off in that process, with no traces, logs, events, firewall or session sync; `guard()` just calls `next()`. It prints `[securenow] disabled by SECURENOW_DISABLED (register not started)`. For test runs: `SECURENOW_DISABLED=1 npm test`.
327
+ - Nothing else is read by SecureNow. OpenTelemetry's own `OTEL_*` variables still reach the OpenTelemetry SDK underneath, and you need none of them. SecureNow sets `OTEL_METRICS_EXPORTER=none` when it is unset, so no metrics reader retries `localhost:4318`.
328
+
329
+ ## 5. Features
330
+
331
+ ### 5.1 Traces
332
+
333
+ - **What:** one server span per request with method, route (`http.route` where section 3 says so), status code, client IP (`http.client_ip`, `http.client_ip.source`, `http.socket_ip`, `http.proxy.trusted`), redacted headers and body, plus child spans for outgoing HTTP and databases (Postgres, MySQL, MongoDB, Redis...). `service.name` is the app key; `deployment.environment` comes from the config.
334
+ - **On:** automatic once wired. To skip one instrumentation, list its package name in `config.otel.disableInstrumentations`.
335
+ - **Verify:** `npx securenow test-span` prints `Span accepted (HTTP 200)`. Send one request to the app, then `npx securenow traces --env <env> --limit 5` and `npx securenow traces show <traceId> --env <env>`.
336
+
337
+ ### 5.2 Logs
338
+
339
+ - **What:** `console.log`, `info`, `warn`, `error` and `debug` become log records (INFO, INFO, WARN, ERROR, DEBUG) linked to the active trace, and still print to the terminal. In code:
340
+
341
+ ```js
342
+ const { getLogger } = require('securenow/tracing');
343
+ const logger = getLogger('billing', '1.0.0'); // null when logging is disabled
344
+ logger?.emit({ severityNumber: 13, severityText: 'WARN', body: 'Card declined', attributes: { orderId: 'o_123' } });
345
+ ```
351
346
 
352
- ### Firewall-Only Mode (No Tracing Overhead)
347
+ **Redacted before export (8.25.0+):** credential fields (password, token, apiKey, authorization, cookie, OTP codes, card numbers...) in logged objects, JSON and `key=value` text, and secret shapes anywhere in the line (JWTs, Bearer/Basic credentials, `sk_`/`pk_`/`rk_`/`snk_` keys, card numbers, emails) become `[REDACTED]`. The terminal still shows the line as written, and attributes you pass to `getLogger().emit()` are exported as set. `config.logging.redact: false` turns it off. Redaction goes by field names and value shapes, so a secret in free text ("the new password is hunter2") still goes out: don't log secrets. Before 8.25.0, lines were exported as written.
348
+ - **On:** `config.logging.enabled` (default `true`); redaction `config.logging.redact` (default `true`).
349
+ - **Verify:** the startup line `Logging: ENABLED → https://ingest.securenow.ai/v1/logs`; `npx securenow logs --env <env> --since 15m`; `npx securenow logs errors --since 1h` (failures grouped by message). From a shell or CI: `npx securenow log send "Deploy done" --level info --attrs version=1.2.3`.
350
+
351
+ ### 5.3 Header and body capture, redaction
352
+
353
+ - **What:**
354
+ - `http.request.headers`: JSON of the request headers, each value cut at 500 characters, dropped if the whole set exceeds 8 KB.
355
+ - `http.request.body` for `application/json`, `application/x-www-form-urlencoded` and `application/graphql`, up to `maxBodySize`; a larger body is recorded as `[TOO LARGE: <n> bytes]`. Other content types (text, XML, binary) are not captured.
356
+ - `multipart/form-data` as metadata: text fields (up to 1000 characters each, 100 parts) and files as field, name, type and size, never their content.
357
+ - The body is observed while the app reads it, never consumed. A route that never reads its body (no body parser) records none.
358
+ - **Redaction**, before anything leaves the process:
359
+ - Header names: `cookie`, `set-cookie`, `authorization`, `proxy-authorization`, any name containing `token`, `secret`, `password`, `passwd`, `csrf`, `xsrf`, `session`, `api-key`, `apikey`, `api_key`, `credential`, `signature`, `private-key` or `x-auth`, and OTP names.
360
+ - Body field names, case-insensitive substring match (68 built-in names; the startup line counts them): passwords, secrets, tokens, `auth`, `authorization`, `bearer`, credentials, session ids, cookies, card, CVV, IBAN, account, routing and sort codes, tax ids, SSN, PIN, email, phone, mobile, date of birth, first/last/full name, address, postcode, zip, passport, license. OTP names (`code`, `otp`, `otpCode`, `oneTimeCode`, `verificationCode`, `passcode`, `mfaCode`, `totpCode`, `recoveryCode`, `backupCode`...) match exactly, so `status_code` and `error_code` stay readable.
361
+ - Values that look like secrets, under any field name: JWTs, `Bearer`/`Basic` credentials, `sk_`/`pk_`/`rk_`/`snk_` live and test keys, email addresses, card numbers that pass the Luhn check.
362
+ - URL query parameters with OTP names. Other query values are kept (attack payloads in URLs are what detections read), so keep secrets out of URLs.
363
+ - **On:** on by default. Tune `config.capture.body`, `multipart`, `maxBodySize` and `sensitiveFields` (extra names; they apply to header names too, ignoring case and punctuation).
364
+ - **Verify:** `npx securenow redact '{"email":"a@b.co","password":"x","otpCode":"123456","status_code":200}'` shows what is kept; after a real POST, `npx securenow traces show <traceId>` shows the redacted `http.request.body`.
365
+
366
+ ### 5.4 Client IP and trusted proxies
367
+
368
+ - **What:** the client IP is the connection's address unless the connection comes from a trusted proxy. Then it is the right-most untrusted address in `X-Forwarded-For`, or else `CF-Connecting-IP`, `True-Client-IP`, `X-Real-IP` or `X-Client-IP`. Trusted by default: private ranges (`10/8`, `172.16/12`, `192.168/16`, `100.64/10`, `169.254/16`, `fc00::/7`), loopback, the host's own addresses and, **(8.25.0+)**, Unix-socket and named-pipe connections (nginx `proxy_pass http://unix:/run/app.sock`, Nuxt's dev proxy). A client can't choose its IP through `X-Forwarded-For`. The firewall, rate limits, `session.seen` and traces all use this IP.
369
+ - **On:** `config.networking.trustedProxies` takes exact IPv4/IPv6 addresses, IPv4 CIDRs, IPv6 CIDRs **(8.25.0+)**, and `"cloudflare"` (Cloudflare's published edge ranges, IPv6 ones included from 8.25.0):
370
+
371
+ ```json
372
+ { "config": { "networking": { "trustedProxies": ["cloudflare", "203.0.113.10", "198.51.100.0/24"] } } }
373
+ ```
374
+
375
+ The proxy must forward the visitor, for nginx: `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`.
376
+ - **Verify:** in `npx securenow traces show <traceId>`, `http.client_ip` is the visitor and `http.client_ip.source` is `x-forwarded-for` (or `cf-connecting-ip`...), not `socket` with the proxy's address. `npx securenow protect status` says whether the app sees real client IPs: blocking mode is refused while more than 20% of its traffic comes through a proxy it doesn't trust.
377
+
378
+ ### 5.5 Firewall (layers 1 to 4)
379
+
380
+ - **What:** syncs the app's blocklist (yours plus SecureNow's known-bad IP feed, refreshed hourly), allowlist, scoped blocks, rate limits and challenges. It checks a version every `versionCheckInterval` (10 s, ETag/304) and resyncs fully every `syncInterval` (3600 s); matching happens in memory. It fails open by default, and a circuit breaker pauses polling for 2 minutes after 5 consecutive API errors. Each request goes through: allowlist (when one is active, an unlisted IP is blocked and a listed IP skips every other check), then blocklist, scoped blocks (route, method), challenge, rate limit.
381
+
382
+ | Layer | Turn on | What it does | Limits |
383
+ |---|---|---|---|
384
+ | 1 HTTP | always, once the runtime API key is present | 403 JSON `{"error":"Forbidden","ip":"..."}`, or an HTML page with the masked IP for browsers; status from `firewall.statusCode` | Works behind proxies (forwarding headers) |
385
+ | 2 TCP | `"tcp": true` | Destroys the socket before HTTP parsing, zero bytes sent | Sees only the connection's source IP; connections from trusted proxies are left to layer 1. Use 8.25.0+: before, it hooked `listen()` and missed the servers most apps start before the SDK has finished starting |
386
+ | 3 OS | `"iptables": true` | Kernel DROP: a `SECURENOW_BLOCK` chain in iptables (ip6tables for IPv6), or a `securenow` nft table where iptables is missing **(8.25.0+)** | Linux, root or `CAP_NET_ADMIN`, `iptables`/`nft` installed; direct client connections only. Use 8.25.0+: it validates every entry, runs without a shell and applies each sync as one batch, and an IPv6 entry no longer stops the sync |
387
+ | 4 Cloud WAF | `"cloud": "cloudflare"`, `"aws"` or `"gcp"` | Pushes the global blocklist to the edge, at most every 30 s | Cloudflare: fills an account IP list `securenow-blocklist` that your own WAF rule must reference. AWS: WAFv2 IP set. GCP: Cloud Armor rules at priority 2000, `deny(403)`. Use 8.25.0+ (IPv6 ranges, Cloud Armor's 10-range limit, stale rules fixed). Request format tested, not run against real accounts |
388
+
389
+ Layers 2 to 4 carry the global blocklist. Scoped blocks, rate limits and challenges are layer 1 only.
390
+ - **On:** automatic with the runtime API key. Each app and environment also has a remote switch, `npx securenow firewall enable|disable --app <key> --env production`, applied without a deploy. `config.firewall.enabled` is ignored.
391
+ - **Verify:** startup lines `Firewall: ENABLED`, `Firewall: environment=<env>`, `Firewall: Layer 1 (HTTP 403) active` and `Firewall: synced <n> global blocked IPs (<x> exact + <y> CIDR ranges) and <z> scoped block rules`. Then `npx securenow firewall status --env <env>`, `npx securenow firewall test-ip 203.0.113.7 --path /admin --env <env>` and `npx securenow firewall hits --since 1h`.
392
+
393
+ ### 5.6 Rate limits
394
+
395
+ - **What:** 429 with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Window` and `X-SecureNow-Rate-Limit-Rule` (JSON `{"error":"Too Many Requests","ip":"...","retryAfter":<s>}`, or a small HTML page for browsers). A rule matches a route (exact, prefix or regex), a method, optionally an IP or CIDR, an app and an environment, and counts per IP, globally, per request-header value (API key, tenant) or per cookie value (session); header and cookie values are hashed, and requests without them count per IP. Rules live in SecureNow, not in your code; running apps apply them within about 10 seconds.
396
+ - **On:** create from the CLI, MCP or dashboard:
397
+
398
+ ```bash
399
+ npx securenow ratelimit add --route /api/login --method POST --limit 5 --window 1m --env production --reason "login brute force"
400
+ npx securenow ratelimit add --route /api/ --limit 100 --window 1m --header x-api-key --env production # per API key
401
+ npx securenow ratelimit add --route /app --limit 300 --window 1m --cookie session --env production # per session cookie
402
+ npx securenow ratelimit from-text "5 per minute per IP on POST /api/login for 2h" --yes
403
+ ```
404
+
405
+ Counters live in each process. With PM2 cluster or several servers, each process counts on its own (the effective limit is multiplied) unless they share Redis: `"config": { "firewall": { "rateLimitStore": "redis://your-redis:6379" } }` (`rediss://`, `user:password@` and `/db` work). The firewall never waits on Redis; at most a request or two per process can slip past while a reply is in flight, and it falls back to in-process counting if Redis is unreachable.
406
+ - **Verify:** `npx securenow ratelimit test 203.0.113.42 --path /api/login --method POST --env production`; startup line `Firewall: synced <n> rate-limit rules` (and `shared rate-limit counters: connecting to ...` with Redis); `npx securenow firewall hits --action rate_limited --since 1h`. Rules are per environment and the CLI defaults to `production`: to try one against a local app, create it with `--env local`.
407
+
408
+ ### 5.7 Challenges (proof of work)
409
+
410
+ - **What:** a softer step than a block. Browsers (`Accept: text/html`) get a 403 interstitial that solves a proof of work by itself (14 bits by default, about a second or less) and receives a pass cookie bound to the IP and user agent, valid 30 minutes by default. Clients that don't accept HTML get a 403 JSON `{"error":"challenge_required","challenge":{...}}` with the header `X-SecureNow-Challenge: required`, so don't challenge API routes that non-browser clients call. A challenge can escalate to a block after repeated failures.
411
+ - **On:**
412
+
413
+ ```bash
414
+ npx securenow challenge add --route /login --clearance 30m --env production
415
+ npx securenow challenge add 203.0.113.0/24 --route /signup --difficulty 16 --escalate-to-block --fail-threshold 10 --block-ttl-hours 24
416
+ ```
353
417
 
354
- ```bash
355
- node -r securenow/firewall-only app.js
418
+ - **Verify:** `npx securenow challenge test 203.0.113.9 --path /login --method GET --env production`; `npx securenow firewall hits --action captcha_challenged --since 1h`.
356
419
 
357
- # Or via the CLI (same effect)
358
- securenow run --firewall-only app.js
359
- ```
420
+ ### 5.8 Blocklist, allowlist, trusted IPs
360
421
 
361
- Loads only the firewall using SecureNow runtime credentials. No OpenTelemetry, no tracing, no external packages needed.
422
+ - **Blocklist:** block an IPv4 address or CIDR everywhere or on a route, optionally with an expiry (the dashboard, CLI and API don't accept IPv6 entries yet). An unblock stops enforcement and keeps the history.
362
423
 
363
- ### Programmatic Firewall API
424
+ ```bash
425
+ npx securenow blocklist add 203.0.113.42 --env production --duration 24h --reason "credential stuffing"
426
+ npx securenow blocklist add 203.0.113.42 --route /admin --mode prefix --method ALL --reason "admin probing"
427
+ npx securenow blocklist unblock <id> --reason "false alarm after review"
428
+ ```
364
429
 
365
- ```javascript
366
- const firewall = require('securenow/firewall');
367
- const appConfig = require('securenow/app-config');
368
-
369
- await firewall.init({
370
- ...appConfig.resolveFirewallOptions(),
371
- apiUrl: 'https://ingest.securenow.ai',
372
- syncInterval: 3600, // safety-net full sync every hour
373
- versionCheckInterval: 10, // lightweight ETag check every 10s
374
- failMode: 'open', // 'open' or 'closed'
375
- statusCode: 403,
376
- log: true,
377
- tcp: false,
378
- iptables: false,
379
- cloud: null, // 'cloudflare' | 'aws' | 'gcp'
380
- });
430
+ - **Allowlist (dangerous):** deny-by-default. Once any active entry exists for an app and environment, only listed IPs get through and everyone else gets 403. Listed IPs also skip the blocklist, rate limits and challenges. Use it only to lock an app to known IPs, with the person's approval: `npx securenow allowlist add <ip> --app <key> --env production --label "Office" --reason "..."`.
431
+ - **Trusted IPs:** known-safe infrastructure (uptime monitors, office VPN, your own servers). Alerts on them are suppressed; they restrict nobody: `npx securenow trusted add <ip> --label "Uptime monitor"`.
432
+ - **Verify:** `npx securenow blocklist list --env production`, `npx securenow firewall test-ip <ip> --env production`, `npx securenow firewall hits --ip <ip>`. Blocks reach running apps within about 10 seconds.
433
+
434
+ ### 5.9 Custom security events: `track()`
381
435
 
382
- const stats = firewall.getStats();
383
- const matcher = firewall.getMatcher(); // (ip) => boolean
384
- const allowMatcher = firewall.getAllowlistMatcher();
436
+ - **What:** your app reports what only it knows (sign-ins, OTPs, password resets, MFA). Events feed brute-force, credential-stuffing and account-takeover detection, and alert rules can query them (`attributes_string['event.type']`, `['enduser.id']`, `['session.id']`, `['http.client_ip']`). `track()` is fire-and-forget: it buffers (1000 events at most, oldest dropped), sends batches of up to 100 every 2 seconds and never throws. It uses the runtime credentials, so it also works in a script without the preload; `flush()` returns a promise for scripts that exit right away.
437
+
438
+ ```js
439
+ const { track } = require('securenow/events'); // ESM: import { track } from 'securenow/events';
440
+ track('auth.login.failure', { userId, sessionId, ip, userAgent, severity: 'warn', attributes: { reason: 'bad_password' } });
441
+ ```
442
+
443
+ Properties: `userId`, `sessionId`, `ip`, `userAgent`, `severity` (`trace`, `debug`, `info`, `warn`, `error`, `fatal`), `ts` (epoch ms), `attributes` (strings, numbers, booleans).
444
+
445
+ - **Recommended auth events:**
446
+
447
+ | Event | Emit when |
448
+ |---|---|
449
+ | `auth.login.failure` | A sign-in fails (unknown user, wrong password) |
450
+ | `auth.login.success` | A sign-in succeeds |
451
+ | `auth.otp.send.requested` | Someone asks for a one-time code or magic link |
452
+ | `auth.otp.send.result` | The SMS/email provider accepted or refused the send |
453
+ | `auth.otp.verify.failure` | A wrong or expired code is entered |
454
+ | `auth.otp.verify.success` | A code is accepted |
455
+ | `auth.password_reset.requested` | A reset is requested |
456
+ | `auth.password_reset.completed` | A password is changed through a reset |
457
+ | `auth.mfa.challenge.failure` | A second factor fails |
458
+ | `auth.mfa.challenge.success` | A second factor succeeds |
459
+ | `auth.session.revoked` | Your app signs a user out everywhere or kills a session |
385
460
 
386
- await firewall.shutdown();
387
- ```
461
+ - **Login route example (Express):**
388
462
 
389
- ### Cloud WAF Providers
463
+ ```js
464
+ const { track } = require('securenow/events');
465
+ const { resolveClientIp } = require('securenow/resolve-ip');
390
466
 
391
- **Cloudflare:**
392
- ```json
393
- {
394
- "config": {
395
- "firewall": {
396
- "cloud": "cloudflare",
397
- "cloudflare": {
398
- "apiToken": "your-token",
399
- "accountId": "your-account-id"
400
- }
467
+ app.post('/api/login', async (req, res) => {
468
+ const ip = resolveClientIp(req); // the same trusted-proxy rules as the firewall
469
+ const userAgent = req.get('user-agent');
470
+ const user = await findUserByEmail(req.body.email);
471
+ if (!user || !(await verifyPassword(user, req.body.password))) {
472
+ track('auth.login.failure', {
473
+ userId: user ? user.id : undefined, // your internal id, never the password
474
+ ip, userAgent, severity: 'warn',
475
+ attributes: { reason: user ? 'bad_password' : 'unknown_user', method: 'password' },
476
+ });
477
+ return res.status(401).json({ error: 'Invalid email or password' });
401
478
  }
402
- }
403
- }
404
- ```
479
+ const session = await createSession(user);
480
+ track('auth.login.success', { userId: user.id, ip, userAgent, attributes: { method: 'password' } });
481
+ res.json({ ok: true });
482
+ });
483
+ ```
405
484
 
406
- **AWS WAF:**
407
- ```json
408
- {
409
- "config": {
410
- "firewall": {
411
- "cloud": "aws",
412
- "aws": {
413
- "wafIpSetId": "your-ip-set-id",
414
- "wafIpSetName": "your-ip-set-name",
415
- "wafScope": "REGIONAL"
416
- }
417
- }
418
- }
419
- }
420
- ```
421
- AWS authentication should use the standard AWS credential chain outside the
422
- SecureNow SDK config.
423
- Requires peer dep: `npm install @aws-sdk/client-wafv2`
485
+ If you also send `sessionId` and want it to match revocations made through the session guard, send what the guard computes, `require('securenow/sessions').sha256(<session cookie value exactly as sent>)`, or give the guard your own `getSessionId` (section 5.10).
486
+ - **Verify:** `npx securenow telemetry fields --since 1h --json` lists the custom event types observed for the app. From a shell or a non-JS service: `npx securenow event send auth.login.failure --user u_test --ip 203.0.113.10 --attrs reason=test` (add `--env local` to tag it local).
424
487
 
425
- **GCP Cloud Armor:**
426
- ```json
427
- {
428
- "config": {
429
- "firewall": {
430
- "cloud": "gcp",
431
- "gcp": {
432
- "projectId": "your-project",
433
- "securityPolicy": "your-policy-name"
434
- }
435
- }
436
- }
437
- }
438
- ```
439
- Requires peer dep: `npm install @google-cloud/compute`
488
+ ### 5.10 Session revocation
440
489
 
441
- **Dry-run (log only, no actual WAF changes):**
442
- ```json
443
- {
444
- "config": {
445
- "firewall": {
446
- "cloudDryRun": true
447
- }
448
- }
449
- }
490
+ Revoke a stolen session or every session of a user, from the CLI, MCP-capable automation or the dashboard; the SDK pulls active revocations every 30 seconds (outbound, ETag-cached) and rejects matching requests locally. Fail-open: an outage never blocks sign-ins.
491
+
492
+ ```bash
493
+ npx securenow revoke session <session-id> --reason "impossible travel" --duration 24h --app <key>
494
+ npx securenow revoke user <user-id> --reason "account takeover" # default expiry 7d
495
+ npx securenow revoke list
496
+ npx securenow revoke restore <revocation-id> --reason "verified with the user"
450
497
  ```
451
498
 
452
- ---
499
+ The session id is what the SDK computes: the SHA-256 of the Bearer token, or else of the first session cookie found. Cookies looked up, in order: your `sessions.cookieNames` **(8.25.0+)**, then `__Secure-next-auth.session-token`, `__Host-next-auth.session-token`, `next-auth.session-token`, `connect.sid`, `session`, `sid`. The user id is the `sub` claim of a JWT Bearer token. To get the id of one session for a test: `node -e "console.log(require('securenow/sessions').sha256(process.argv[1]))" '<cookie value>'`.
453
500
 
454
- ## Logging
501
+ Pick one way to enforce:
455
502
 
456
- Logging is enabled by default (`config.logging.enabled: true`). Logs are exported to your OTLP collector alongside traces.
503
+ 1. **Zero code, every framework (8.25.0+).** In `.securenow/runtime.json`:
457
504
 
458
- ### Telemetry contract for alert-rule agents
505
+ ```json
506
+ { "config": { "sessions": { "guard": true, "capture": true, "cookieNames": ["my_app_session"] } } }
507
+ ```
459
508
 
460
- The package exports a versioned, machine-readable description of the ClickHouse tables,
461
- timestamp types, map access, tenant scope, automatic fields, custom-event mapping, client-IP
462
- expression, and safe SQL templates:
509
+ `guard` answers a revoked session or user with 401 (body `Session revoked`) and clears the session cookies; `capture` sends `session.seen`, deduplicated per session and IP every 5 minutes, for session-theft detection (impossible travel, concurrent networks). It runs in the firewall's request hook, so it needs the runtime API key, and it works with `-r securenow/register`, Next.js, Nuxt and `securenow/firewall-only`.
463
510
 
464
- ```js
465
- const telemetrySchema = require('securenow/telemetry-schema');
466
- ```
511
+ 2. **Express-style middleware: `guard()`** (Express, Connect, Polka, NestJS on the Express adapter, Feathers with Express):
467
512
 
468
- The CLI exposes the same contract and can compare it with fields observed in the connected app:
513
+ ```js
514
+ const sessions = require('securenow/sessions');
515
+ app.use(sessions.guard()); // default extraction above
516
+ // or with your own ids (after your session middleware):
517
+ app.use(sessions.guard({ getSessionId: (req) => req.session?.id, getUserId: (req) => req.user?.id }));
518
+ ```
469
519
 
470
- ```bash
471
- securenow telemetry schema --json
472
- securenow telemetry fields --app <key> --env production --since 24h --json
473
- securenow alerts rules validate --sql @rule.sql --app <key>
474
- ```
520
+ A revoked request gets 401 with the session cookies cleared: JSON `{"error":"Session revoked. Please sign in again.","code":"session_revoked"}` where `res.json` exists (Express), plain `Session revoked` otherwise. Options: `getSessionId(req)`, `getUserId(req)`, `onRevoked(req, res, next, { sessionId, userId })`, `capture` (default `true`: also sends `session.seen`), `captureWindowMs` (default 300000), `syncIntervalMs` (default 30000). In NestJS, call `app.use(sessions.guard())` in `main.ts`.
475
521
 
476
- The first command works offline. Live discovery returns field names/types/counts and event names,
477
- never values. A coding agent should inspect the customer repository for routes and `track()` calls,
478
- use the local contract to construct SQL, use live discovery to verify emitted fields, then validate
479
- the candidate query. Full guidance is shipped in `TELEMETRY-SQL.md`.
522
+ 3. **Anything else: `check()` (8.25.0+).** `sessions.check(req, options?)` takes a Node request or a Web `Request` and returns `{ revoked, sessionId, userId }`; you answer 401 yourself. Options: `getSessionId`, `getUserId`, `capture` (default `true`), `ip` (for `session.seen` when `req` is a Web `Request`, which has no socket).
480
523
 
481
- ### Get a Logger
524
+ ```js
525
+ const sessions = require('securenow/sessions');
482
526
 
483
- ```javascript
484
- const { getLogger } = require('securenow/tracing');
527
+ // Fastify
528
+ fastify.addHook('onRequest', async (request, reply) => {
529
+ if (sessions.check(request.raw).revoked) return reply.code(401).send({ error: 'session_revoked' });
530
+ });
485
531
 
486
- const logger = getLogger('my-module', '1.0.0');
487
- if (logger) {
488
- logger.emit({ body: 'User login succeeded', severityText: 'INFO', attributes: { userId: '123' } });
489
- }
490
- ```
532
+ // Koa
533
+ app.use(async (ctx, next) => {
534
+ if (sessions.check(ctx.req).revoked) { ctx.status = 401; ctx.body = { error: 'session_revoked' }; return; }
535
+ await next();
536
+ });
491
537
 
492
- **CLI equivalent** (for shell scripts, cron, CI):
538
+ // Hono (c.req.raw is a Web Request)
539
+ app.use('*', async (c, next) => {
540
+ if (sessions.check(c.req.raw).revoked) return c.json({ error: 'session_revoked' }, 401);
541
+ await next();
542
+ });
493
543
 
494
- ```bash
495
- securenow log send "User login succeeded" --level info --attrs userId=123
496
- securenow test-span "ci.smoke-test" # emit a span without booting the SDK
497
- ```
544
+ // h3
545
+ app.use(defineEventHandler((event) => {
546
+ if (sessions.check(event.node.req).revoked) { setResponseStatus(event, 401); return { error: 'session_revoked' }; }
547
+ }));
548
+ ```
498
549
 
499
- ### Console Instrumentation
550
+ ```ts
551
+ // Next.js route handler (Node.js runtime), app/api/account/route.ts
552
+ import { check } from 'securenow/sessions';
500
553
 
501
- When tracing is active, `console.log/warn/error` are automatically patched to emit OTLP log records correlated with the active trace span. To access the original console:
554
+ export async function GET(request: Request) {
555
+ if (check(request).revoked) return Response.json({ error: 'session_revoked' }, { status: 401 });
556
+ return Response.json({ ok: true });
557
+ }
558
+ ```
502
559
 
503
- ```javascript
504
- const { originalConsole, restoreConsole } = require('securenow/console-instrumentation');
505
- originalConsole.log('This bypasses OTLP');
506
- restoreConsole(); // undo the patch
507
- ```
560
+ 4. **Lower level:** `sessions.isRevoked({ sessionId, userId })` returns a boolean. Call `sessions.start()` once at boot when this is all you use (`guard()`, `check()` and the zero-code guard start the sync themselves).
508
561
 
509
- ---
562
+ - **Verify:** revoke a test session, wait up to 30 seconds, then send a request with that cookie or token: it gets 401 and `Set-Cookie` headers that clear the session cookies. `npx securenow revoke list` shows the entry; `npx securenow revoke restore <id>` lifts it.
510
563
 
511
- ## IP Resolution Utilities
564
+ ### 5.11 Crash capture
512
565
 
513
- ```javascript
514
- const { resolveClientIp, resolveSocketIp, isFromTrustedProxy } = require('securenow/resolve-ip');
566
+ - **What:** an uncaught exception or unhandled promise rejection becomes a FATAL log record with `exception.type`, `exception.message` and `exception.stacktrace`. Logs and the spans that ended before the crash are flushed (up to 2 seconds), then the process exits as Node would (stack on stderr, exit code 1). If the app registered its own handler, the error is recorded as ERROR and the app decides. OTLP export errors never crash the app.
567
+ - **On:** automatic while `config.logging.enabled` is `true`.
568
+ - **Verify:** `npx securenow logs --level fatal --since 1h --env <env>`.
515
569
 
516
- // From an HTTP request (respects X-Forwarded-For from trusted proxies)
517
- const clientIp = resolveClientIp(req);
570
+ ### 5.12 Detection
518
571
 
519
- // Direct socket IP
520
- const socketIp = resolveSocketIp(req);
572
+ - **Starter protection:** when an app is created or first sends traffic it gets about 20 rules (SQL injection, XSS, traversal, command and template injection, Log4Shell, Spring4Shell, metadata SSRF, CRLF, prototype pollution, `.env`/`.git`/backup probes, exploit kits, attack-tool user agents, failed sign-ins, 404 scanning) in **monitor** mode: they alert and block nothing. In **block** mode the block-ready rules block the address for 6 hours on exploit signatures. Only a signed-in person can turn blocking on, and only when the app sees real client IPs (5.4).
521
573
 
522
- // Check if request comes from a trusted proxy
523
- const trusted = isFromTrustedProxy(req);
524
- ```
574
+ ```bash
575
+ npx securenow protect status # mode, rules, alerts, ready to block?
576
+ npx securenow protect block --app <key> # a person, after reviewing
577
+ npx securenow protect monitor --app <key> # back to alerts only
578
+ ```
525
579
 
526
- ### CIDR Matching
580
+ - **Alert rules from English:** SecureNow drafts an instant rule (each request, as it arrives) or a periodic one (SQL on a schedule) from your app's real data, checks it, and creates it in **test mode**: it alerts but nothing acts on it (no instant block, automation or agent action).
527
581
 
528
- ```javascript
529
- const { createMatcher, ipToInt, parseCidr, matchesCidr } = require('securenow/cidr');
582
+ ```bash
583
+ npx securenow alerts rules from-text "one IP failing 10 sign-ins in 5 minutes" --app <key> --draft-only # review
584
+ npx securenow alerts rules from-text "one IP failing 10 sign-ins in 5 minutes" --app <key> # create (asks to confirm)
585
+ npx securenow alerts rules test <rule-id> --wait # dry run on real data
586
+ npx securenow alerts rules test <rule-id> --mode live --wait # inserts 6 synthetic attack requests first
587
+ npx securenow alerts rules runs <rule-id> --since 1h # what each run saw, and why it did or didn't notify
588
+ npx securenow alerts rules why <rule-id> --ip 203.0.113.50 # why it did or didn't alert on one address
589
+ npx securenow alerts rules promote <rule-id> --yes # a person moves it to prod
590
+ ```
530
591
 
531
- const isBlocked = createMatcher(['10.0.0.0/8', '192.168.1.0/24']);
532
- isBlocked('10.0.0.5'); // true
533
- isBlocked('8.8.8.8'); // false
534
- ```
592
+ Agents don't promote: they ask with `npx securenow alerts rules request-promotion <rule-id> --reason "..." --evidence "..."`, and a person decides (`npx securenow human promotions`).
593
+ - **SQL rules:** read the contract first (`npx securenow telemetry schema --json`, offline, and `node_modules/securenow/TELEMETRY-SQL.md`), check fields really sent (`npx securenow telemetry fields --app <key> --since 24h --json`), dry-run (`npx securenow alerts rules validate --sql @rule.sql --app <key>`), then `alerts rules create --name "..." --sql @rule.sql --apps <key>`. Scope app keys with `__USER_APP_KEYS__` and return an `ip` column.
594
+ - **What fires:** `npx securenow today` (the Overview), `npx securenow attacker <ip>`, `npx securenow review` (rules that fired too wide), `npx securenow human` (decisions waiting for a person). Details: SKILL-CLI.md.
535
595
 
536
- **CLI equivalent:**
596
+ ### 5.13 MCP for coding agents
537
597
 
538
- ```bash
539
- securenow cidr match 10.0.0.5 10.0.0.0/8,192.168.1.0/24 # exit 0 = match, 2 = miss
540
- securenow cidr parse 10.0.0.0/8 # network/broadcast/mask/size
541
- ```
598
+ - **Local stdio server** (after `npx securenow login`):
542
599
 
543
- ---
600
+ ```bash
601
+ claude mcp add securenow -- npx securenow mcp
602
+ codex mcp add securenow -- npx securenow mcp
603
+ ```
544
604
 
545
- ## Sensitive Data Redaction
605
+ Cursor, `.cursor/mcp.json`: `{ "mcpServers": { "securenow": { "command": "npx", "args": ["securenow", "mcp"] } } }`.
606
+ - **Hosted server:** `https://api.securenow.ai/mcp` with OAuth sign-in (an API key as `Authorization: Bearer <key>` also works), e.g. `claude mcp add --transport http securenow https://api.securenow.ai/mcp`.
607
+ - Read tools are annotated read-only; every write tool refuses to run without `confirm: true` and a non-empty `reason`. `npx securenow mcp --profile investigator|detection|responder` serves fewer tools. Details and tool counts: SKILL-CLI.md.
546
608
 
547
- SecureNow 8.11.2 redacts sign-in credentials named `code`, `otpCode`, `oneTimeCode`, `verificationCode`, and `passcode` by default, including case and separator variants. JSON, form, GraphQL, and multipart text capture use the same rule; URL query parameters and captured headers are also protected. Credential names match exactly after normalization, so `status_code` and `error_code` remain available for investigation. Redaction changes exported telemetry only.
609
+ ## 6. Deployment
548
610
 
549
- Available from multiple entry points (`securenow/nextjs-middleware`, `securenow/nextjs-wrapper`, `securenow/nextjs-auto-capture`):
611
+ ### 6.1 Production credentials
550
612
 
551
- ```javascript
552
- const { redactSensitiveData, DEFAULT_SENSITIVE_FIELDS } = require('securenow/nextjs-middleware');
613
+ ```bash
614
+ npx securenow credentials runtime --env production # writes .securenow/credentials.production.json
615
+ npx securenow credentials runtime --env production --format env # prints SECURENOW_CREDENTIALS=<base64>
616
+ ```
617
+
618
+ The file has the app key, the runtime API key and the config, with `deploymentEnvironment` set, and never the admin token. Ship it as `<app-root>/.securenow/credentials.json` (the generated name also works when it is the only file), or store the printed line as a secret environment variable. Both contain the runtime API key: secret storage only, never the image registry or the repository. Keep `.securenow/runtime.json` and `admin.json` out of images with a `.dockerignore`:
553
619
 
554
- const safe = redactSensitiveData({ username: 'alice', password: 's3cret', token: 'abc' });
555
- // { username: 'alice', password: '[REDACTED]', token: '[REDACTED]' }
620
+ ```
621
+ .securenow/admin.json
622
+ .securenow/runtime.json
623
+ .securenow/credentials*.json
556
624
  ```
557
625
 
558
- **Auto-redacted fields:** `password`, `passwd`, `pwd`, `secret`, `token`, `api_key`, `apikey`, `access_token`, `auth`, `credentials`, `mysql_pwd`, `stripeToken`, `card`, `cardnumber`, `ccv`, `cvc`, `cvv`, `ssn`, `pin`, `code`, `otpCode`, `oneTimeCode`, `verificationCode`, `passcode`.
626
+ ### 6.2 Docker
559
627
 
560
- Add custom fields via `config.capture.sensitiveFields` in `.securenow/runtime.json`.
628
+ Plain Node:
561
629
 
562
- **CLI equivalent** (for piping, scripts, debugging what a payload looks like post-redaction):
630
+ ```dockerfile
631
+ FROM node:24-slim
632
+ WORKDIR /app
633
+ COPY package*.json ./
634
+ RUN npm ci --omit=dev
635
+ COPY . .
636
+ CMD ["node", "-r", "securenow/register", "src/index.js"]
637
+ ```
563
638
 
564
639
  ```bash
565
- securenow redact '{"user":"alice","password":"s3cret"}'
566
- securenow redact @request.json --fields internal_id,sessionHash
640
+ docker run -v "$PWD/.securenow/credentials.production.json:/app/.securenow/credentials.json:ro" my-api
641
+ # or, without a file:
642
+ npx securenow credentials runtime --env production --format env > securenow.env # secret: don't commit
643
+ docker run --env-file securenow.env my-api
567
644
  ```
568
645
 
569
- ---
646
+ Next.js standalone (`output: 'standalone'` in `next.config`):
570
647
 
571
- ## Credentials Configuration
572
-
573
- Local development uses `.securenow/runtime.json`; legacy `.securenow/credentials.json` is still read. Every setting below lives under `app` or `config`; `npx securenow credentials runtime --env production` creates a tokenless production file with the same structure. Since v7.7.2, the SDK also accepts named runtime files such as `.securenow/credentials.production.json` when the canonical `credentials.json` file is absent. Filename lookup is deterministic and does not read environment variables. Environment-variable fallbacks are not supported.
648
+ ```dockerfile
649
+ FROM node:24-slim AS build
650
+ WORKDIR /app
651
+ COPY package*.json ./
652
+ RUN npm ci
653
+ COPY . .
654
+ RUN npm run build
574
655
 
575
- | Credentials path | Purpose |
576
- |---|---|
577
- | `app.key` | App routing UUID. The SecureNow ingestion gateway routes telemetry by this key |
578
- | `app.name` | Human-readable app label |
579
- | `apiKey` | One-app scoped runtime API key (`snk_live_...`) for telemetry ingestion and firewall sync. Runtime scopes: `traces:write`, `logs:write`, `firewall:read`, `blocklist:read`, `allowlist:read` |
580
- | `config.otel.endpoint` | Optional OTLP base endpoint override |
581
- | `config.otel.tracesEndpoint` | Optional full traces endpoint |
582
- | `config.otel.logsEndpoint` | Optional full logs endpoint |
583
- | `config.otel.headers` | Extra OTLP headers as JSON object |
584
- | `config.otel.logLevel` | SDK diagnostics: `error`, `warn`, `info`, `debug`, or `none` |
585
- | `config.otel.disableInstrumentations` | OTel instrumentation package names to skip |
586
- | `config.logging.enabled` | Enable/disable OTLP log export |
587
- | `config.capture.body` | Capture HTTP request bodies |
588
- | `config.capture.maxBodySize` | Max body size in bytes |
589
- | `config.capture.multipart` | Capture multipart metadata, never file content |
590
- | `config.capture.sensitiveFields` | Extra redaction field fragments |
591
- | `config.runtime.deploymentEnvironment` | Sent as `deployment.environment` |
592
- | `config.runtime.noUuid` | Use exact app key as service name without UUID suffix |
593
- | `config.runtime.strict` | Exit clustered workers when no app identity resolves |
594
- | `config.runtime.testSpan` | Emit a startup test span |
595
- | `config.firewall.*` | Local firewall behavior, API URL, intervals, cloud WAF, and trusted proxy settings |
656
+ FROM node:24-slim
657
+ WORKDIR /app
658
+ ENV NODE_ENV=production
659
+ COPY --from=build /app/.next/standalone ./
660
+ COPY --from=build /app/.next/static ./.next/static
661
+ COPY --from=build /app/public ./public
662
+ # credentials: mount /app/.securenow/credentials.json (next to server.js) or pass SECURENOW_CREDENTIALS
663
+ CMD ["node", "server.js"]
664
+ ```
596
665
 
597
- **Resilience:** The firewall SDK includes a circuit breaker (opens after 5 consecutive errors, 2-min cooldown), in-flight request guards (prevents overlapping requests), 429 Retry-After support, and exponential backoff on both lightweight ETag checks and initial sync retries.
666
+ Nuxt (`.output`):
598
667
 
599
- ### Cloud WAF Provider Variables
668
+ ```dockerfile
669
+ FROM node:24-slim AS build
670
+ WORKDIR /app
671
+ COPY package*.json ./
672
+ RUN npm ci
673
+ COPY . .
674
+ RUN npm run build
600
675
 
601
- | Provider | Variables |
602
- |----------|-----------|
603
- | Cloudflare | `config.firewall.cloudflare.apiToken`, `config.firewall.cloudflare.accountId` |
604
- | AWS WAF | `config.firewall.aws.wafIpSetId`, `config.firewall.aws.wafIpSetName`, `config.firewall.aws.wafScope` |
605
- | GCP | `config.firewall.gcp.projectId`, `config.firewall.gcp.securityPolicy` |
676
+ FROM node:24-slim
677
+ WORKDIR /app
678
+ COPY --from=build /app/.output ./.output
679
+ # credentials: mount /app/.securenow/credentials.json or pass SECURENOW_CREDENTIALS
680
+ CMD ["node", ".output/server/index.mjs"]
681
+ ```
606
682
 
607
- ### Priority Order
683
+ Layer 3 (`firewall.iptables`) in a container needs `--cap-add NET_ADMIN`, a root process and the `iptables` package in the image. Layers 2 and 3 act on the connection's source address, so they only help when clients connect to the container directly, not through a load balancer or proxy (layer 1 handles those with forwarding headers).
608
684
 
609
- **Service name:** credentials `app.key` > credentials `app.name` > package name
685
+ ### 6.3 Kubernetes
610
686
 
611
- **Endpoint:** credentials `config.otel.tracesEndpoint` / `config.otel.endpoint` > default SecureNow ingestion gateway `https://ingest.securenow.ai`
687
+ As a file:
612
688
 
613
- ---
689
+ ```bash
690
+ kubectl create secret generic securenow-credentials --from-file=credentials.json=.securenow/credentials.production.json
691
+ ```
614
692
 
615
- ## Recipes for Agentic AI
693
+ ```yaml
694
+ containers:
695
+ - name: api
696
+ image: my-api
697
+ volumeMounts:
698
+ - name: securenow
699
+ mountPath: /app/.securenow/credentials.json
700
+ subPath: credentials.json
701
+ readOnly: true
702
+ volumes:
703
+ - name: securenow
704
+ secret:
705
+ secretName: securenow-credentials
706
+ ```
616
707
 
617
- ### Add Observability to an Existing Express App
708
+ Or as an environment variable:
618
709
 
619
710
  ```bash
620
- npm install securenow@latest
621
- npx securenow login
711
+ npx securenow credentials runtime --env production --format env > securenow.env
712
+ kubectl create secret generic securenow-env --from-env-file=securenow.env
622
713
  ```
623
714
 
624
- No `.env` is needed. `npx securenow app connect` writes app identity, runtime API key, and secure defaults to `.securenow/runtime.json`; the SDK uses the default SecureNow ingestion gateway and the gateway routes by `app.key` while authenticating with the runtime API key. `npx securenow init` makes sure the file has explanations and secure SDK defaults.
625
-
626
- Update `package.json`:
627
- ```json
628
- { "scripts": { "start": "node -r securenow/register src/index.js" } }
715
+ ```yaml
716
+ envFrom:
717
+ - secretRef:
718
+ name: securenow-env
629
719
  ```
630
720
 
631
- No code changes to the application needed.
632
-
633
- ### Add Observability + Firewall to a Next.js App
721
+ ### 6.4 PM2
634
722
 
635
- ```bash
636
- npm install securenow@latest
637
- npx securenow app connect # pick/create app; runtime API key is minted automatically
723
+ ```js
724
+ // ecosystem.config.cjs (.cjs so require.resolve works in "type": "module" projects too)
725
+ module.exports = {
726
+ apps: [{
727
+ name: 'api',
728
+ script: './src/index.js',
729
+ cwd: __dirname,
730
+ exec_mode: 'cluster', // or 'fork'
731
+ instances: 'max',
732
+ node_args: ['-r', require.resolve('securenow/register')],
733
+ }],
734
+ };
638
735
  ```
639
736
 
640
- `securenow app connect` enables the selected app's firewall toggle and writes app/runtime config plus the runtime API key to `.securenow/runtime.json`. Traces, logs, request body capture, multipart metadata capture, and firewall enforcement are enabled by default. Then run `npx securenow init`; it creates `instrumentation.ts`, patches `next.config.*` when safe, or prints exact Codex/Claude merge instructions for existing files.
641
-
642
- ### Enable Firewall With Zero Tracing Overhead
737
+ - Always the absolute path from `require.resolve`, computed when PM2 loads the file. In cluster mode Node resolves a bare `-r securenow/register` from the PM2 daemon's folder, so workers started from elsewhere (`pm2 resurrect` at boot) crash-loop with `Cannot find module 'securenow/register'`.
738
+ - Each process reads the `.securenow/` folder of the app PM2 started, wherever the daemon runs, and every worker reports under one service name. A PM2 process without credentials says so at startup.
739
+ - TypeScript: `node_args: ['-r', require.resolve('securenow/register'), '-r', require.resolve('ts-node/register')]`.
740
+ - Several workers count rate limits separately unless `firewall.rateLimitStore` points at Redis.
741
+ - After changing `node_args`: `pm2 delete api && pm2 start ecosystem.config.cjs && pm2 save`.
643
742
 
644
- For apps that only need IP blocking:
743
+ ### 6.5 systemd and plain VMs
645
744
 
646
- ```bash
647
- node -r securenow/firewall-only app.js
745
+ ```ini
746
+ [Service]
747
+ WorkingDirectory=/srv/my-api
748
+ ExecStart=/usr/bin/node -r securenow/register src/index.js
749
+ Restart=always
750
+ User=my-api
751
+ # credentials: /srv/my-api/.securenow/credentials.json (mode 600, owned by my-api)
752
+ # or: EnvironmentFile=/etc/my-api/securenow.env (the line printed by --format env)
648
753
  ```
649
754
 
650
- Make sure an API key is resolvable by running `npx securenow app connect` first. If you already have a key, `securenow api-key set snk_live_...` writes the runtime credentials file. For production, run `npx securenow credentials runtime --env production` and mount/copy it as `.securenow/credentials.json` or `.securenow/credentials.production.json`.
755
+ Credentials are searched from the working directory and the entry script's folder, so set `WorkingDirectory` to the app folder.
651
756
 
652
- ### Production Hardened Configuration
757
+ ### 6.6 Hosts without file mounts (Railway, Render, Heroku, Fly...)
653
758
 
654
- ```json
655
- {
656
- "apiKey": "snk_live_...",
657
- "app": {
658
- "key": "prod-api-uuid",
659
- "name": "prod-api",
660
- "instance": "https://collector.prod.internal:4318"
661
- },
662
- "config": {
663
- "runtime": { "noUuid": true, "strict": true, "deploymentEnvironment": "production" },
664
- "logging": { "enabled": true },
665
- "capture": { "body": true, "multipart": true },
666
- "firewall": { "enabled": true, "tcp": true, "syncInterval": 30, "failMode": "open" },
667
- "otel": { "logLevel": "error" }
668
- }
669
- }
670
- ```
759
+ Store the output of `npx securenow credentials runtime --env production --format env` as a secret variable named `SECURENOW_CREDENTIALS` (dashboard, `fly secrets set`, `heroku config:set`...) and keep the preload in the start command (`web: node -r securenow/register src/index.js` in a Procfile).
671
760
 
672
- ### Instrument a Docker Container
761
+ ### 6.7 Vercel
673
762
 
674
- ```dockerfile
675
- FROM node:24.22.1-slim
676
- WORKDIR /app
677
- COPY package*.json ./
678
- RUN npm ci --omit=dev
679
- COPY . .
680
- # Mount/copy secret file at runtime as /app/.securenow/credentials.json.
763
+ Next.js on Vercel goes through `@vercel/otel` (an optional dependency); set `SECURENOW_CREDENTIALS` in the project's environment variables. Not tested end to end, and the firewall is not verified on Vercel serverless functions. Edge functions and Edge middleware are not instrumented. AWS Lambda and other serverless platforms are not supported or tested.
681
764
 
682
- CMD ["node", "-r", "securenow/register", "src/index.js"]
683
- ```
765
+ ## 7. Verification checklist
684
766
 
685
- ### Kubernetes with Separate Trace/Log Collectors
767
+ | Step | Command | Good output |
768
+ |---|---|---|
769
+ | Version | `npx securenow version` | `securenow v8.24.0` or newer (8.25.0+ for the marked items) |
770
+ | Auth | `npx securenow whoami` | Admin auth and the runtime app both present |
771
+ | Config | `npx securenow env` | `Service name` = the app key UUID, the expected `Environment`, `Firewall enabled` (not `missing firewall API key`) |
772
+ | Connectivity | `npx securenow doctor` | `All checks passed.` (traces, logs, API, firewall sync) |
773
+ | Ingest | `npx securenow test-span` | `Span accepted (HTTP 200)` |
774
+ | App start | start the app | `[securenow] pid=... appId="<app-uuid>" instance=... apiKey=set credentials=<path or env:SECURENOW_CREDENTIALS> → service.name=<app-uuid> ...` (`apiKey=missing`: no runtime API key, so the gateway rejects telemetry and firewall sync; before 8.25.0 the field showed whether the app key was set), `OTel SDK started → https://ingest.securenow.ai/v1/traces`, `Logging: ENABLED`, `Request body capture: ENABLED (max: 10240 bytes, redacting 68 sensitive fields)`, `Firewall: ENABLED`, `Firewall: environment=<env>`, `Firewall: synced <n> global blocked IPs (...) and <n> scoped block rules`. ESM apps first print `ESM loader hook auto-registered (module.register)`. Next.js prints `Next.js App -> service.name=<app-uuid> apiKey=set credentials=<...>` (the `apiKey` and `credentials` fields 8.25.0+), `Environment: <env>`, `OpenTelemetry started for Next.js -> ...` and the same Firewall lines |
775
+ | Traffic | one real request, then `npx securenow traces --env <env> --limit 5` | The request, with `http.client_ip` and (where supported) `http.route` |
776
+ | Overview | `npx securenow status --env <env>` and `npx securenow today` | The app covered: data flowing, firewall on, detection monitoring |
777
+ | Firewall | `npx securenow firewall status --env <env>`, `npx securenow firewall test-ip 203.0.113.7 --env <env>` | Firewall on for the app and environment; the verdict for the IP |
686
778
 
687
- ```json
688
- {
689
- "app": { "key": "k8s-service", "name": "k8s-service" },
690
- "config": {
691
- "otel": {
692
- "tracesEndpoint": "http://tempo:4318/v1/traces",
693
- "logsEndpoint": "http://loki:4318/v1/logs"
694
- },
695
- "logging": { "enabled": true },
696
- "runtime": { "noUuid": true }
697
- }
698
- }
699
- ```
779
+ Bad signs: no `[securenow]` line at all (the preload is not on the command that starts the server), `credentials=none`, `service.name=securenow-free-<uuid>` or `nextjs-app-<uuid>` (no app key found), `Firewall: initial sync failed` (key, network or environment), `disabled by SECURENOW_DISABLED`.
700
780
 
701
- ---
781
+ ## 8. Troubleshooting
702
782
 
703
- ## Verification
783
+ | Symptom | Cause | Fix |
784
+ |---|---|---|
785
+ | No data in the dashboard | The process that serves HTTP didn't load the SDK (no `[securenow]` lines) | Put the preload on the real start command (script, `CMD`, PM2 `node_args`, `ExecStart`) |
786
+ | | Looking at the wrong environment: `init` writes `local`, the CLI and dashboard default to `production` | `--env local` or `--env all`, or set `deploymentEnvironment` for that deployment |
787
+ | | Runtime API key missing (`npx securenow env` says `missing firewall API key`) | `npx securenow app connect` or `npx securenow api-key create` |
788
+ | | `service.name` isn't the app key (`noUuid: false`, a custom `serviceName`) | Remove the override |
789
+ | | `SECURENOW_DISABLED` set; outbound HTTPS to `ingest.securenow.ai` blocked; trial ended (HTTP 402) | Unset it; open egress; subscribe |
790
+ | Wrong credentials picked up | `SECURENOW_CREDENTIALS` in the environment, a stray `runtime.json` next to the production file, a `credentials.staging.json`, a parent folder's or `~/.securenow/` file | Read the `credentials=` startup line, then remove the stray source (order in section 4) |
791
+ | PM2 cluster crash-loop, `Cannot find module 'securenow/register'` | Bare `-r securenow/register` resolved from the PM2 daemon's folder | `node_args: ['-r', require.resolve('securenow/register')]`, then `pm2 delete <name> && pm2 start ecosystem.config.cjs && pm2 save` |
792
+ | Next.js standalone: every request 500, `Cannot find module '@opentelemetry/...'` | Instrumentation file from `init` before 8.24.0 (`webpackIgnore` imports) | Run `npx securenow init` again (it replaces that file) or remove `webpackIgnore`; rebuild |
793
+ | Next.js: no SecureNow lines at startup | Instrumentation file in the wrong folder (Next.js 14 and 15 ignore a root file in a `src/` layout), or Next.js 14 without `experimental.instrumentationHook` | Rerun `npx securenow init` (8.25.0+ writes or moves it to `src/`) or move it to `src/instrumentation.ts`; Next.js 14 config in section 3.13 |
794
+ | Body not captured | Content type other than JSON, form, GraphQL or multipart; the route never reads its body; body over `maxBodySize` (`[TOO LARGE: <n> bytes]`); `capture.body: false`; Edge runtime | Add a body parser, raise `maxBodySize`, check the config, use the Node.js runtime |
795
+ | Client IP is the proxy's address | The proxy has a public address SecureNow doesn't trust, or doesn't send `X-Forwarded-For` | Add it to `networking.trustedProxies` (IP, CIDR or `"cloudflare"`), make it forward the client; `http.client_ip.source` should read `x-forwarded-for` |
796
+ | Blocked IP still gets through | Firewall not running (no `Firewall: synced` line); block scoped to another app, environment or route; firewall switched off for that environment; the IP resolves to the proxy; the IP is on the allowlist (listed IPs skip the blocklist); not yet propagated; layer 2/3 behind a proxy | `npx securenow firewall test-ip <ip> --path <path> --env <env>`, `npx securenow firewall apps`, `npx securenow firewall hits --ip <ip>`; wait one `versionCheckInterval` (10 s) |
797
+ | ESM app: no `http.route`, no library spans | ESM hook not registered: SDK loaded with an `import` inside the app instead of `-r`, Node older than 20.6, or a wrapper that hides the entry script | `node -r securenow/register <entry>` directly; look for `ESM loader hook auto-registered`. Hono, h3, Feathers and Polka routes need 8.25.0+ |
798
+ | Rate limit not applied | Rule created for `production` while the app reports `local`; route or method mismatch; counted per worker | `npx securenow ratelimit test <ip> --path <path> --method <m> --env <env>`; Redis store for clusters |
799
+ | Revoked session still works | Sync not yet run (every 30 s); no enforcement wired (`sessions.guard` 8.25.0+, `guard()` or `check()`); different session id (SHA-256 of the exact cookie value) or a custom cookie name | Wait 30 s; wire one of 5.10; add `sessions.cookieNames` (8.25.0+) or a `getSessionId` |
800
+ | Every request 403 at boot | `firewall.failMode: "closed"` and the first sync failed | Fix connectivity, or keep `failMode: "open"` |
801
+ | Tests send data to the real app | Test runs load the project's `.securenow` credentials | `SECURENOW_DISABLED=1 npm test` |
704
802
 
705
- On startup, securenow logs its configuration:
803
+ ## 9. Tested on, and honest limits
706
804
 
707
- ```
708
- [securenow] app.key="my-app" → service.name=my-app
709
- [securenow] OTel SDK started → https://collector:4318/v1/traces
710
- [securenow] Logging: ENABLED → https://collector:4318/v1/logs
711
- [securenow] Request body capture: ENABLED (max: 10240 bytes)
712
- [securenow] Firewall: ENABLED
713
- [securenow] Firewall: synced 142 blocked IPs
714
- ```
805
+ **Tested end to end** (traces, client IP, body reaching the app, body capture with redaction, header redaction, 403 block, 429 rate limit, challenge, error span, exception, logs): Node 24.22 (CI: Node 24); Express 4.22 and 5.3; Fastify 5.12; NestJS 11.2; Koa 3.3; Hapi 21.4; Hono 4; Polka 0.5; Feathers 5.0; h3 1.15; Next.js 14.2 (`next start` and standalone), 15.5 and 16.4 (Turbopack and webpack, `next start` and standalone); Nuxt 3.21 and 4.6; Bun 1.3; PM2 5.4 (fork and cluster). Older majors are expected to work but are not tested: say "tested on", not "supports every version".
715
806
 
716
- Set `config.otel.logLevel` to `debug`, or temporarily run with `OTEL_LOG_LEVEL=debug`, and run `securenow doctor --json` to troubleshoot connectivity, duplicate OpenTelemetry API packages, and provider registration issues.
807
+ **Not supported:** Edge runtimes (Cloudflare Workers, Vercel Edge, Next.js Edge middleware and routes), Deno, `Bun.serve`, browser telemetry.
808
+ **Not tested:** Vercel (firewall unverified on its serverless functions), AWS Lambda and other serverless platforms.
809
+ **Layers 2 to 4:** turn them on with 8.25.0+ only (in 8.24.0 layer 2 dropped nothing for most apps and layers 3 and 4 mishandled IPv6). **Cloud WAF (layer 4):** request format tested, not run against real Cloudflare, AWS or GCP accounts; on Cloudflare your own WAF rule must reference the `securenow-blocklist` list.
810
+ **IPv6:** layer 1 and trusted proxies match IPv6 addresses and IPv6 CIDR ranges **(8.25.0+; before, IPv6 ranges never matched)**. The dashboard, CLI and API accept only IPv4 addresses and CIDRs in the blocklist, allowlist, trusted IPs, rate limits and challenges.
811
+ **`nuxt dev`:** before 8.25.0 the client IP was unknown, so IP blocking only applied to the built server.
812
+ **Log redaction (8.25.0+)** goes by field names and value shapes: a secret in free text with no recognizable shape is exported as written.
717
813
 
718
- **CLI equivalent** (works without booting the SDK — useful when the app won't start):
814
+ ## 10. Package entry points
719
815
 
720
- ```bash
721
- securenow env # show resolved app key, endpoints, and credentials fields
722
- securenow doctor # probe OTLP + API endpoints, exits 1 on failure
723
- securenow test-span # send a real span to the collector
816
+ | Import | Use |
817
+ |---|---|
818
+ | `securenow/register` (also `securenow`) | The preload: tracing, logs, capture, crash capture, firewall (`node -r securenow/register`) |
819
+ | `securenow/firewall-only` | Preload: firewall only, no OpenTelemetry |
820
+ | `securenow/register-vite` | Same runtime as `securenow/register`, for the Node process serving a Vite app |
821
+ | `securenow/nextjs` | `registerSecureNow(options?)`, called from `instrumentation.ts` |
822
+ | `securenow/nextjs-auto-capture` | Body-capture fallback imported by the generated instrumentation file |
823
+ | `securenow/nuxt` | Nuxt 3/4 module (`modules: ['securenow/nuxt']`) |
824
+ | `securenow/events` | `track(type, props)`, `flush()` |
825
+ | `securenow/sessions` | `guard()`, `capture()`, `isRevoked()`, `start()`, `sha256()`; `check()` **(8.25.0+)** |
826
+ | `securenow/tracing` | `getLogger(name, version)`, `isLoggingEnabled()` |
827
+ | `securenow/resolve-ip` | `resolveClientIp(req)`, `resolveClientIpWithDetails(req)`, `resolveSocketIp(socket)`, `isFromTrustedProxy(ip)` |
828
+ | `securenow/cidr` | `createMatcher(list)` returns `{ isBlocked(ip), stats() }` for IPv4 and IPv6 addresses and ranges (IPv6 ranges 8.25.0+); `parseCidr`, `parseCidr6`, `parseIp`, `ipToInt`, `matchesCidr` |
829
+ | `securenow/firewall` | `getStats()`, `getMatcher()`, `getAllowlistMatcher()`, `init()`/`shutdown()` for custom setups |
830
+ | `securenow/rate-limits` | API helpers: `parseRateLimitText()`, `createRateLimitFromText()`, `createRateLimit()`, `listRateLimits()` (use the CLI or MCP for one-off rules) |
831
+ | `securenow/telemetry-schema` | The ClickHouse table/field contract for alert SQL (also `npx securenow telemetry schema --json`) |
832
+ | `securenow/app-config` | The resolved configuration (`resolveAll()`, `resolveFirewallOptions()`...) |
833
+ | `securenow/nextjs-webpack-config`, `securenow/nextjs-middleware`, `securenow/nextjs-wrapper`, `securenow/console-instrumentation`, `securenow/web-vite` | Legacy: not needed for a new setup |
834
+
835
+ Examples:
836
+
837
+ ```js
838
+ // An internal health endpoint that shows the firewall is synced (the preload's own module instance)
839
+ const firewall = require('securenow/firewall');
840
+ app.get('/healthz/securenow', (req, res) => {
841
+ const s = firewall.getStats();
842
+ res.json({ initialized: s.initialized, blocked: s.blocked, rateLimited: s.rateLimited, circuit: s.circuitState });
843
+ });
844
+
845
+ // CIDR matching
846
+ const { createMatcher } = require('securenow/cidr');
847
+ const office = createMatcher(['10.0.0.0/8', '192.168.1.0/24']);
848
+ office.isBlocked('10.0.0.5'); // true
724
849
  ```
850
+
851
+ The same helpers exist in the CLI without writing Node: `npx securenow redact '<json>'`, `npx securenow cidr match <ip> <cidr,...>` (exit 0 on a match, 2 otherwise), `npx securenow cidr parse <cidr>`, `npx securenow env`, `npx securenow log send`, `npx securenow event send`, `npx securenow test-span`.