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/NPM_README.md CHANGED
@@ -1,25 +1,28 @@
1
- # SecureNow - Complete OpenTelemetry Observability for Node.js
1
+ # SecureNow - Complete OpenTelemetry Observability for Node.js
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
- OpenTelemetry instrumentation library for Node.js, Next.js, and Nuxt applications. Send distributed traces and logs to any OTLP-compatible observability backend.
5
+ OpenTelemetry instrumentation and an in-process firewall for Node.js, Next.js, and Nuxt applications. Traces, logs, crashes and security events go to the SecureNow ingest gateway (`https://ingest.securenow.ai`), where detection runs; an advanced setting can point the OpenTelemetry export at your own OTLP collector instead.
6
+
7
+ This is the long reference. The step-by-step integration playbook for coding agents is [`SKILL-API.md`](./SKILL-API.md); the CLI by task is [`SKILL-CLI.md`](./SKILL-CLI.md). Items marked **(8.25.0+)** need securenow 8.25.0 or newer.
6
8
 
7
9
  **Features:**
8
- - Zero-config automatic instrumentation
9
- - Distributed tracing for all popular frameworks
10
- - Automatic logging with console instrumentation
11
- - Built-in sensitive data redaction
12
- - Request body capture for debugging
13
- - Multi-layer firewall -- auto-blocks IPs from your SecureNow blocklist
10
+ - Zero-config automatic instrumentation: one `-r securenow/register` flag for CommonJS, ESM and TypeScript apps
11
+ - Distributed tracing for Express, Fastify, NestJS, Koa, Hapi, Hono, Polka, Feathers, h3, raw `http`, Next.js and Nuxt
12
+ - Automatic logging with console instrumentation, and crash capture
13
+ - Built-in sensitive data redaction (field names, OTP codes, secret-shaped values)
14
+ - Request header and body capture for detection and debugging
15
+ - Multi-layer firewall -- blocks IPs from your SecureNow blocklist, plus rate limits (429) and proof-of-work challenges
16
+ - Custom security events (`securenow/events`) and session revocation (`securenow/sessions`)
14
17
  - `securenow init` scaffolds Next.js instrumentation, safe `serverExternalPackages`, and standalone output tracing includes
15
18
  - `securenow/firewall-only` entry point for firewall without tracing overhead
16
19
  - Split local credentials: `.securenow/admin.json` for CLI/MCP admin auth and `.securenow/runtime.json` for SDK runtime
17
- - Single `-r securenow/register` flag -- works for both CJS and ESM apps
18
- - Native Nuxt 3 module (`securenow/nuxt`)
20
+ - Native Nuxt 3 and 4 module (`securenow/nuxt`)
21
+ - CLI and MCP server for coding agents
19
22
 
20
23
  ---
21
24
 
22
- > **v8.0 credential model:** admin/control-plane CLI and MCP auth lives in `.securenow/admin.json`; SDK runtime app config and the runtime API key live in `.securenow/runtime.json`. `npx securenow login` can run both lanes for onboarding, `npx securenow admin login` refreshes only admin auth, and `npx securenow app connect` refreshes only runtime app config. Legacy combined `.securenow/credentials.json` files are still read, and production runtime exports can still be mounted as `.securenow/credentials.json`.
25
+ > **v8.0 credential model:** admin/control-plane CLI and MCP auth lives in `.securenow/admin.json`; SDK runtime app config and the runtime API key live in `.securenow/runtime.json`. `npx securenow login` can run both lanes for onboarding, `npx securenow admin login` refreshes only admin auth, and `npx securenow app connect` refreshes only runtime app config. Legacy combined `.securenow/credentials.json` files are still read, and production runtime exports are mounted as `.securenow/credentials.json`.
23
26
 
24
27
  ---
25
28
 
@@ -27,20 +30,26 @@ OpenTelemetry instrumentation library for Node.js, Next.js, and Nuxt application
27
30
 
28
31
  - [Installation](#installation)
29
32
  - [Quick Start](#quick-start)
30
- - [CLI -- Command Line Interface](#cli--command-line-interface)
31
- - [MCP for Codex and Claude](#mcp-for-codex-and-claude)
33
+ - [CLI -- Command Line Interface](#cli----command-line-interface)
32
34
  - [Framework-Specific Setup](#framework-specific-setup)
33
35
  - [Express.js](#expressjs)
34
- - [Next.js](#nextjs)
35
- - [Nuxt 3](#nuxt-3)
36
36
  - [Fastify](#fastify)
37
- - [NestJS](#nestjs)
38
37
  - [Koa](#koa)
38
+ - [NestJS](#nestjs)
39
39
  - [Hapi](#hapi)
40
- - [Firewall -- Automatic IP Blocking](#firewall--automatic-ip-blocking)
41
- - [Environment Variables Reference](#environment-variables-reference)
40
+ - [h3, Polka, raw HTTP, Hono, Feathers](#h3-unjs)
41
+ - [Next.js](#nextjs)
42
+ - [Nuxt 3 and 4](#nuxt-3-and-4)
43
+ - [Vite and Bun](#vite)
44
+ - [Compatibility Matrix](#compatibility-matrix)
45
+ - [Firewall -- Automatic IP Blocking](#firewall----automatic-ip-blocking)
46
+ - [Credentials Configuration](#credentials-configuration)
47
+ - [Entry Points Reference](#entry-points-reference)
42
48
  - [Logging Setup](#logging-setup)
43
49
  - [Request Body Capture](#request-body-capture)
50
+ - [Custom Security Events](#custom-security-events)
51
+ - [Session Revocation](#session-revocation)
52
+ - [Deployment](#deployment)
44
53
  - [Advanced Configuration](#advanced-configuration)
45
54
  - [TypeScript Support](#typescript-support)
46
55
  - [Troubleshooting](#troubleshooting)
@@ -50,7 +59,7 @@ OpenTelemetry instrumentation library for Node.js, Next.js, and Nuxt application
50
59
  ## Installation
51
60
 
52
61
  ```bash
53
- npm install securenow
62
+ npm install securenow@latest
54
63
  ```
55
64
 
56
65
  Or with yarn:
@@ -71,7 +80,7 @@ Run login - it is a browser flow that picks or creates an app and connects the f
71
80
  npx securenow login
72
81
  ```
73
82
 
74
- During the browser step, the dashboard enables the selected app's firewall toggle, mints a one-time plaintext runtime API key scoped to that app only (scopes: `traces:write`, `logs:write`, `firewall:read`, `blocklist:read`, `allowlist:read`), and the CLI writes it as `apiKey` in `.securenow/runtime.json`. Admin auth, when part of the flow, is saved separately to `.securenow/admin.json`. Traces, logs, POST body capture, multipart metadata capture, and firewall protection are enabled by default. No env vars, no copy-pasting keys.
83
+ During the browser step, the dashboard enables the selected app's firewall toggle, mints a one-time plaintext runtime API key scoped to that app only (scopes: `traces:write`, `logs:write`, `firewall:read`, `blocklist:read`, `allowlist:read`), and the CLI writes it as `apiKey` in `.securenow/runtime.json`. Admin auth, when part of the flow, is saved separately to `.securenow/admin.json`. Both files are added to `.gitignore` (without ignoring the whole `.securenow/` folder). Traces, logs, POST body capture, multipart metadata capture, and firewall protection are enabled by default. No env vars, no copy-pasting keys.
75
84
 
76
85
  Metrics export is intentionally off by default. SecureNow sets `OTEL_METRICS_EXPORTER=none` when the app has not explicitly configured metrics, preventing the OpenTelemetry SDK from creating a default metrics reader that retries `localhost:4318`.
77
86
 
@@ -84,10 +93,11 @@ npx securenow init --key snk_live_abc123...
84
93
  ```
85
94
 
86
95
  This detects your framework and:
87
- - **Credentials**: Ensures `.securenow/runtime.json` has secure defaults and explanations
88
- - **Next.js**: Creates `instrumentation.ts`, adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, or prints a Codex/Claude-ready merge prompt for existing files
89
- - **Nuxt 3**: Suggests adding `securenow/nuxt` to modules
96
+ - **Credentials**: Ensures `.securenow/runtime.json` has secure defaults and explanations, with `config.runtime.deploymentEnvironment` set to `local` (change it with `--env staging`, etc.)
97
+ - **Next.js**: Creates `instrumentation.ts` (`src/instrumentation.ts` in a `src/` layout, 8.25.0+), adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, or prints a Codex/Claude-ready merge prompt for existing files. Next.js 14 gets the `experimental` keys **(8.25.0+)**
98
+ - **Nuxt 3 and 4**: Reminds you to add `securenow/nuxt` to modules
90
99
  - **Express / Node.js**: Shows how to add `-r securenow/register` to your start script
100
+ - **PM2**: Warns when an ecosystem file passes a bare `-r securenow/register`
91
101
  - **All**: Stores an optional `--key` in `.securenow/runtime.json`; no local `.env` file is needed. If the runtime API key is missing, run `npx securenow app connect` or `npx securenow api-key create` before sending telemetry.
92
102
 
93
103
  ### 2. Manual Setup
@@ -104,7 +114,7 @@ Add `-r securenow/register` to your start command -- that's the only change:
104
114
  node -r securenow/register app.js
105
115
  ```
106
116
 
107
- ESM and CJS apps are both handled automatically (Node >=20.6 auto-registers the ESM loader hook via `module.register()`).
117
+ ESM and CJS apps are both handled automatically: for an ESM app (`"type": "module"` or an `.mjs` entry) the preload registers the OpenTelemetry ESM loader hook with `module.register()` (Node >= 20.6) and prints `[securenow] ESM loader hook auto-registered (module.register)`. TypeScript: `node -r securenow/register --import tsx src/index.ts`, or `node -r securenow/register -r ts-node/register src/index.ts`.
108
118
 
109
119
  **package.json** example:
110
120
 
@@ -121,6 +131,14 @@ ESM and CJS apps are both handled automatically (Node >=20.6 auto-registers the
121
131
  NODE_OPTIONS="-r securenow/register" npm start
122
132
  ```
123
133
 
134
+ Every Node process started with that variable loads SecureNow, npm itself included, so the flag in the start script is cleaner when you can edit it.
135
+
136
+ **Alternative: the CLI wrapper**
137
+
138
+ ```bash
139
+ npx securenow run app.js # adds the preload (and the ESM hook for ESM apps), passes Node flags through
140
+ ```
141
+
124
142
  **CJS only: Code-based initialization**
125
143
 
126
144
  ```javascript
@@ -132,21 +150,31 @@ const app = express();
132
150
  // ...
133
151
  ```
134
152
 
153
+ In an ESM app, don't `import 'securenow/register'` inside your code: static imports load before it runs, so libraries are not instrumented. Use the `-r` flag.
154
+
135
155
  You'll see confirmation in the console:
136
156
 
137
157
  ```
138
- [securenow] OTel SDK started -> https://ingest.securenow.ai/v1/traces
158
+ [securenow] preload loaded pid=48211
159
+ [securenow] pid=48211 appId="<app-uuid>" instance="https://ingest.securenow.ai" apiKey=set credentials=/srv/app/.securenow/runtime.json → service.name=<app-uuid> instance.id=<app-uuid>-<uuid>
160
+ [securenow] OTel SDK started → https://ingest.securenow.ai/v1/traces
161
+ [securenow] Logging: ENABLED → https://ingest.securenow.ai/v1/logs
162
+ [securenow] Request body capture: ENABLED (max: 10240 bytes, redacting 68 sensitive fields)
139
163
  [securenow] Firewall: ENABLED
140
- [securenow] Firewall: synced 142 blocked IPs (138 exact + 4 CIDR ranges)
164
+ [securenow] Firewall: environment=local
165
+ [securenow] Firewall: Layer 1 (HTTP 403) active
166
+ [securenow] Firewall: synced 142 global blocked IPs (138 exact + 4 CIDR ranges) and 0 scoped block rules
141
167
  ```
142
168
 
169
+ `credentials=` names the file (or `env:SECURENOW_CREDENTIALS`) the settings came from; `service.name` must be your app key.
170
+
143
171
  ---
144
172
 
145
173
  ## CLI -- Command Line Interface
146
174
 
147
- The `securenow` CLI gives you full access to the SecureNow platform from the terminal -- no browser required for day-to-day workflows. Zero additional dependencies.
175
+ The `securenow` CLI gives you full access to the SecureNow platform from the terminal -- no browser required for day-to-day workflows. Zero additional dependencies. `npx securenow help` lists every command and `npx securenow help <command>` its subcommands and flags (offline). [`SKILL-CLI.md`](./SKILL-CLI.md) organizes the commands by task.
148
176
 
149
- **Full CLI/SDK parity:** every SDK export has a matching CLI command. `redactSensitiveData` -> `securenow redact`, `createMatcher` -> `securenow cidr match`, `getLogger().emit()` -> `securenow log send`, startup smoke spans -> `securenow test-span`, `node -r securenow/firewall-only` -> `securenow run --firewall-only`. False-positive triage (`fp create`, `fp ai-fill`, `fp mark`) works from the terminal without the web dashboard.
177
+ **CLI/SDK parity:** SDK helpers have matching CLI commands. `redactSensitiveData` -> `securenow redact`, `createMatcher` -> `securenow cidr match`, `getLogger().emit()` -> `securenow log send`, `track()` -> `securenow event send`, startup smoke spans -> `securenow test-span`, `node -r securenow/firewall-only` -> `securenow run --firewall-only`. False-positive triage (`fp create`, `fp ai-fill`, `fp mark`) works from the terminal without the web dashboard.
150
178
 
151
179
  ### Getting Started
152
180
 
@@ -156,7 +184,7 @@ npx securenow login
156
184
 
157
185
  # Admin/control-plane CLI and MCP auth only.
158
186
  npx securenow admin login
159
- npx securenow admin login --token <YOUR_JWT>
187
+ npx securenow admin login --token <YOUR_TOKEN>
160
188
 
161
189
  # App/runtime SDK config only.
162
190
  npx securenow app connect
@@ -164,7 +192,7 @@ npx securenow app connect
164
192
  # Save to ~/.securenow/ instead of this project
165
193
  npx securenow login --global
166
194
 
167
- # Check who you're logged in as (shows auth source)
195
+ # Check who you're logged in as (admin auth and runtime app)
168
196
  npx securenow whoami
169
197
 
170
198
  # Need or already have a runtime API key? Create or store it without re-running login:
@@ -182,9 +210,12 @@ npx securenow init
182
210
 
183
211
  # Optional: pass an existing runtime API key to store it in .securenow/runtime.json
184
212
  npx securenow init --key snk_live_abc123...
213
+
214
+ # Force the instrumentation file language
215
+ npx securenow init --typescript # or --javascript
185
216
  ```
186
217
 
187
- For Next.js projects, `init` creates `instrumentation.ts` (or `.js` if no TypeScript), adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, and prints exact merge instructions for Codex/Claude when existing files need judgment. For Nuxt, it suggests adding `securenow/nuxt` to your modules. For Express/Node, it shows the `-r securenow/register` flag.
218
+ For Next.js projects, `init` creates `instrumentation.ts` (or `.js` if no TypeScript), adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, and prints exact merge instructions for Codex/Claude when existing files need judgment. For Nuxt, it reminds you to add `securenow/nuxt` to your modules. For Express/Node, it shows the `-r securenow/register` flag.
188
219
 
189
220
  ### MCP for Codex and Claude
190
221
 
@@ -192,14 +223,17 @@ SecureNow includes a local stdio MCP server that resolves credentials by operati
192
223
 
193
224
  ```bash
194
225
  npx securenow login
226
+ claude mcp add securenow -- npx securenow mcp
195
227
  codex mcp add securenow -- npx securenow mcp
196
- # or
228
+ # or run the bin directly
197
229
  npx -p securenow securenow-mcp
198
230
  ```
199
231
 
200
- Admin/global tools use `.securenow/admin.json`. App-scoped runtime reads can use `.securenow/runtime.json` or an explicit app key. The MCP surface exposes tools for applications, traces, logs, firewall, IP intelligence, forensics, notifications, blocklist, allowlist, trusted IPs, rate-limit remediation, and docs-backed prompts/resources. Write actions require `confirm:true` and a reason. Use `securenow_blocklist_unblock` to stop firewall enforcement while keeping the block report/history; `securenow_blocklist_remove` is a compatibility alias.
232
+ Cursor uses `.cursor/mcp.json`: `{ "mcpServers": { "securenow": { "command": "npx", "args": ["securenow", "mcp"] } } }`.
201
233
 
202
- For hosted clients, SecureNow can expose the same surface at `https://api.securenow.ai/mcp`. The hosted endpoint uses the same API authentication and scope checks as the rest of SecureNow.
234
+ Admin/global tools use `.securenow/admin.json`. App-scoped runtime reads can use `.securenow/runtime.json` or an explicit app key. The MCP surface exposes tools for applications, traces, logs, firewall, IP intelligence, forensics, notifications, both review queues, alert rules, starter protection, blocklist, allowlist, trusted IPs, rate limits, challenges, automations, and docs-backed prompts/resources. Write actions require `confirm:true` and a reason. Use `securenow_blocklist_unblock` to stop firewall enforcement while keeping the block report/history; `securenow_blocklist_remove` is a compatibility alias. `npx securenow mcp --profile investigator|detection|responder` serves a smaller tool set. Session revocation has no MCP tool: use `securenow revoke`.
235
+
236
+ For hosted clients, the same surface is at `https://api.securenow.ai/mcp`, with OAuth sign-in (or `Authorization: Bearer <API key>`), e.g. `claude mcp add --transport http securenow https://api.securenow.ai/mcp`. The hosted endpoint uses the same API authentication and scope checks as the rest of SecureNow.
203
237
 
204
238
  ### Managing Applications
205
239
 
@@ -220,12 +254,23 @@ npx securenow apps default <app-key>
220
254
  npx securenow apps delete <app-id> --force
221
255
  ```
222
256
 
257
+ ### Overview and attackers
258
+
259
+ ```bash
260
+ # The dashboard's Overview: what happened, what SecureNow did, what needs you
261
+ npx securenow today
262
+ npx securenow today --since 7d --attackers 20
263
+
264
+ # Why an address was flagged: verdict, signals, traffic vs normal, detections, timeline
265
+ npx securenow attacker 203.0.113.42
266
+ ```
267
+
223
268
  ### Viewing Traces
224
269
 
225
270
  ```bash
226
271
  # List recent traces (uses default app, or specify --app)
227
272
  npx securenow traces
228
- npx securenow traces --app <key> --limit 50
273
+ npx securenow traces --app <key> --env production --limit 50
229
274
 
230
275
  # Show detailed spans for a trace
231
276
  npx securenow traces show <traceId>
@@ -239,10 +284,13 @@ npx securenow traces analyze <traceId>
239
284
  ```bash
240
285
  # List recent logs
241
286
  npx securenow logs
242
- npx securenow logs --app <key> --minutes 30 --level error
287
+ npx securenow logs --app <key> --since 30m --level error
243
288
 
244
289
  # Show logs for a specific trace
245
290
  npx securenow logs trace <traceId>
291
+
292
+ # What is failing: error lines grouped by message
293
+ npx securenow logs errors --since 6h
246
294
  ```
247
295
 
248
296
  ### Notifications
@@ -250,6 +298,7 @@ npx securenow logs trace <traceId>
250
298
  ```bash
251
299
  # List notifications
252
300
  npx securenow notifications
301
+ npx securenow notifications list --search "SQL injection" --severity high
253
302
 
254
303
  # Check unread count
255
304
  npx securenow notifications unread
@@ -259,30 +308,24 @@ npx securenow notifications read <id>
259
308
  npx securenow notifications read-all
260
309
  ```
261
310
 
262
- ### Alerting
311
+ The two review queues: `npx securenow review` (Alert Review: "is this rule right?") and `npx securenow human` (Requires Human: block, false positive or close). See SKILL-CLI.md.
263
312
 
264
- ### Revoke / kill sessions (new in 8.3)
313
+ ### Alerting
265
314
 
266
- ```js
267
- const securenow = require('securenow/sessions');
268
- // One line: emits session.seen (feeds impossible-travel / concurrent-session
269
- // detection) AND rejects revoked sessions. Auto-detects NextAuth/connect.sid/JWT.
270
- app.use(securenow.guard());
271
- // ...or check manually:
272
- if (securenow.isRevoked({ sessionId, userId })) return res.status(401).send('re-auth');
273
- // ...or emit-only (detection without enforcement):
274
- app.use(securenow.capture());
275
- ```
315
+ #### Revoke / kill sessions
276
316
 
277
- Revoke from the CLI (the SDK enforces within ~seconds, outbound-only, fail-open):
317
+ Revoke from the CLI; the SDK enforces it within 30 seconds (outbound sync, fail-open):
278
318
 
279
319
  ```bash
280
320
  npx securenow revoke session <session-id> --reason "impossible travel"
281
321
  npx securenow revoke user <user-id> --duration 7d # kill all of a user's sessions
282
322
  npx securenow revoke list
323
+ npx securenow revoke restore <revocation-id>
283
324
  ```
284
325
 
285
- ### Emit custom security events (new in 8.2)
326
+ How the app enforces it (zero-code config **(8.25.0+)**, `guard()` middleware, or `check()` **(8.25.0+)**): [Session Revocation](#session-revocation).
327
+
328
+ #### Emit custom security events
286
329
 
287
330
  ```js
288
331
  const { track } = require('securenow/events');
@@ -296,19 +339,19 @@ track('auth.login.success', {
296
339
  });
297
340
  ```
298
341
 
299
- Events become queryable by alert rules immediately (`attributes_string['event.type']`, `['enduser.id']`, `['session.id']`, `['http.client_ip']`). Non-JS apps emit the same thing with a plain POST to `/v1/events`:
342
+ Events become queryable by alert rules immediately (`attributes_string['event.type']`, `['enduser.id']`, `['session.id']`, `['http.client_ip']`). Non-JS apps emit the same thing with a plain POST to `/v1/events` on the ingest gateway:
300
343
 
301
344
  ```
302
- POST https://<your-ingest-host>/v1/events
345
+ POST https://ingest.securenow.ai/v1/events
303
346
  Authorization: Bearer snk_live_...
304
347
  X-SecureNow-App-Key: <app-uuid>
305
348
 
306
349
  { "events": [ { "type": "auth.login.success", "user_id": "u_1", "session_id": "s_1", "ip": "1.2.3.4" } ] }
307
350
  ```
308
351
 
309
- CLI: `npx securenow event send auth.login.failure --user u_1 --ip 1.2.3.4 --attrs reason=bad_token`
352
+ CLI: `npx securenow event send auth.login.failure --user u_1 --ip 1.2.3.4 --attrs reason=bad_token`. More in [Custom Security Events](#custom-security-events).
310
353
 
311
- ### Build alert SQL from the installed telemetry contract
354
+ #### Build alert SQL from the installed telemetry contract
312
355
 
313
356
  The NPM package ships the exact ClickHouse table/field contract used for alert-rule authoring.
314
357
  Agents should read it instead of guessing SQL columns:
@@ -338,13 +381,20 @@ Mark detections `NEEDS_INSTRUMENTATION` when the repository must add a `track()`
338
381
  verification plus SQL validation. See `node_modules/securenow/TELEMETRY-SQL.md` for tables,
339
382
  timestamp handling, privacy rules, templates, and the full test-to-production lifecycle.
340
383
 
384
+ #### Rules from English, SQL rules, runs
385
+
341
386
  ```bash
342
- # Describe a detection in English (new in 8.16): SecureNow picks an instant rule (every request,
387
+ # Describe a detection in English: SecureNow picks an instant rule (every request,
343
388
  # as it arrives) or a periodic one (SQL on a schedule), writes it from your app's real data,
344
389
  # checks it against that data, and creates it in test mode when you confirm
345
390
  npx securenow alerts rules from-text "a sign-in from a new address, then a password change within 5 minutes" --app <app-key>
391
+ npx securenow alerts rules from-text "one IP failing 10 sign-ins in 5 minutes" --app <app-key> --draft-only
392
+
393
+ # Test it: dry run on real data, or live with 6 synthetic attack requests
394
+ npx securenow alerts rules test <rule-id> --wait
395
+ npx securenow alerts rules test <rule-id> --mode live --wait
346
396
 
347
- # Create a custom detection rule from your own SQL (new in 8.1)
397
+ # Create a custom detection rule from your own SQL
348
398
  npx securenow alerts rules create \
349
399
  --name "Auth: magic-link brute force" \
350
400
  --sql @rule.sql \
@@ -352,11 +402,17 @@ npx securenow alerts rules create \
352
402
  --severity high \
353
403
  --nlp "single IP flooding /api/auth/signin or /api/auth/callback"
354
404
 
355
- # What each run did, run one now, and why a rule didn't alert on an address (new in 8.17)
405
+ # What each run did, run one now, and why a rule didn't alert on an address
356
406
  npx securenow alerts rules runs <rule-id>
357
407
  npx securenow alerts rules run <rule-id>
358
408
  npx securenow alerts rules why <rule-id> --ip 203.0.113.50 --since 2h
359
409
 
410
+ # Rewrite from English, check precision, promote (a person) or ask for promotion (an agent)
411
+ npx securenow alerts rules change <rule-id> "raise the threshold to 20"
412
+ npx securenow alerts rules quality <rule-id>
413
+ npx securenow alerts rules promote <rule-id> --yes
414
+ npx securenow alerts rules request-promotion <rule-id> --reason "Quiet for a week, caught the replayed attack"
415
+
360
416
  # View / manage alert rules, channels, and history
361
417
  npx securenow alerts rules
362
418
  npx securenow alerts rules show <rule-id>
@@ -368,7 +424,17 @@ npx securenow alerts channels
368
424
  npx securenow alerts history --limit 20
369
425
  ```
370
426
 
371
- Your detection SQL scopes app keys with the `__USER_APP_KEYS__` placeholder and selects an `ip` column for per-IP aggregation and remediation (SQL without one is refused: it would never notify). SecureNow stores the query, runs it once right away and then on the rule's schedule (default every 15 min), and routes matches to your in-app channel (override with `--channel <id>`). The throttle sends one alert per address every 15 minutes by default; other addresses still alert. Requires an API key with `alerts:write`.
427
+ Your detection SQL scopes app keys with the `__USER_APP_KEYS__` placeholder and selects an `ip` column for per-IP aggregation and remediation (SQL without one is refused: it would never notify). SecureNow stores the query, runs it once right away and then on the rule's schedule (default every 15 min), and routes matches to your in-app channel (override with `--channel <id>`). The throttle sends one alert per address every 15 minutes by default; other addresses still alert. New rules start in test mode (alerts only: no instant block, automation or agent action) until promoted. Requires an API key with `alerts:write`.
428
+
429
+ #### Starter protection
430
+
431
+ ```bash
432
+ npx securenow protect # mode, rules, alerts, ready to block?
433
+ npx securenow protect block --app <key> # a signed-in person turns blocking on
434
+ npx securenow protect monitor --app <key>
435
+ ```
436
+
437
+ Every app gets about 20 starter rules in monitor mode on first traffic. Blocking is refused while more than 20% of the app's traffic arrives through a proxy the SDK doesn't trust.
372
438
 
373
439
  ### IP Intelligence & Blocklist
374
440
 
@@ -382,6 +448,7 @@ npx securenow ip traces 203.0.113.42
382
448
  # Manage the blocklist
383
449
  npx securenow blocklist
384
450
  npx securenow blocklist add 203.0.113.42 --reason "Brute force scanner"
451
+ npx securenow blocklist add 203.0.113.42 --route /admin --mode prefix --duration 24h --reason "Admin probing"
385
452
  npx securenow blocklist unblock <id> --reason "Reviewed as safe"
386
453
  npx securenow blocklist remove <id> # compatibility alias
387
454
  npx securenow blocklist list --status removed
@@ -393,6 +460,25 @@ npx securenow trusted add 10.0.0.1 --label "Office VPN"
393
460
  npx securenow trusted remove <id>
394
461
  ```
395
462
 
463
+ ### Rate Limits & Challenges
464
+
465
+ ```bash
466
+ # Rate limits (429 + Retry-After), applied by running apps within about 10 seconds
467
+ npx securenow ratelimit add --route /api/login --method POST --limit 5 --window 1m --env production
468
+ npx securenow ratelimit add --route /api/ --limit 100 --window 1m --header x-api-key
469
+ npx securenow ratelimit from-text "5 per minute per IP on POST /api/login for 2h" --yes
470
+ npx securenow ratelimit test 203.0.113.42 --path /api/login --method POST
471
+ npx securenow ratelimit list
472
+
473
+ # Proof-of-work challenges (browsers solve it invisibly; a pass lasts 30 minutes by default)
474
+ npx securenow challenge add --route /login --clearance 30m
475
+ npx securenow challenge test 203.0.113.42 --path /login
476
+ npx securenow challenge list
477
+
478
+ # Who the firewall stopped
479
+ npx securenow firewall hits --since 1h
480
+ ```
481
+
396
482
  ### Forensic Queries
397
483
 
398
484
  Ask questions in plain English -- the AI translates them to SQL and runs them against your data.
@@ -402,20 +488,15 @@ Ask questions in plain English -- the AI translates them to SQL and runs them ag
402
488
  npx securenow forensics "show top 10 attacking IPs in the last hour"
403
489
  npx securenow forensics "which endpoints had 5xx errors today"
404
490
 
491
+ # Chat with memory: follow-ups see earlier answers
492
+ npx securenow forensics chat --app <key> "which accounts had failed logins then a success?"
493
+ npx securenow forensics chat --conversation <id> "what else did those IPs do?"
494
+ npx securenow forensics conversations
495
+
405
496
  # Browse the saved query library
406
497
  npx securenow forensics library
407
498
  ```
408
499
 
409
- ### API Map
410
-
411
- ```bash
412
- # View all discovered API endpoints
413
- npx securenow api-map
414
-
415
- # API map statistics
416
- npx securenow api-map stats
417
- ```
418
-
419
500
  ### Analytics & Dashboard
420
501
 
421
502
  ```bash
@@ -424,6 +505,7 @@ npx securenow analytics --app <key>
424
505
 
425
506
  # Full dashboard overview (apps, protection status, unread alerts)
426
507
  npx securenow status
508
+ npx securenow status --env all
427
509
  ```
428
510
 
429
511
  ### Instances
@@ -467,28 +549,31 @@ npx securenow fp edit <id> --active false
467
549
  npx securenow fp delete <id> --yes
468
550
  ```
469
551
 
470
- ### Telemetry -- Emit Logs and Spans From the Shell
552
+ ### Telemetry -- Emit Logs, Events and Spans From the Shell
471
553
 
472
- Mirrors the SDK's `getLogger()` and tracing APIs. Useful for cron jobs, shell scripts, and CI pipelines that need to push events into SecureNow **without booting the full OTel SDK**.
554
+ Mirrors the SDK's `getLogger()`, `track()` and tracing APIs. Useful for cron jobs, shell scripts, CI pipelines and non-JS services that need to push data into SecureNow **without booting the full OTel SDK**.
473
555
 
474
556
  ```bash
475
- # Send a structured log record to your OTLP collector
557
+ # Send a structured log record
476
558
  npx securenow log send "Deployment completed" --level info --attrs version=1.2.3,service=api
477
559
  npx securenow log send "Backup failed" --level error --attrs host=db-01
478
560
 
479
- # Emit a test span to verify the collector accepts OTLP traffic
561
+ # Send a custom security event (same as track())
562
+ npx securenow event send auth.login.failure --user u_1 --ip 203.0.113.10 --attrs reason=bad_password
563
+
564
+ # Emit a test span to verify the ingest accepts OTLP traffic
480
565
  npx securenow test-span
481
566
  npx securenow test-span "ci.smoke-test" # custom span name
482
567
  ```
483
568
 
484
- Both commands use the resolved credentials JSON endpoints and headers. Non-zero exit on HTTP errors so CI/cron can detect failures.
569
+ All three use the resolved runtime credentials (file or `SECURENOW_CREDENTIALS`) and exit non-zero on HTTP errors so CI/cron can detect failures.
485
570
 
486
571
  ### Utilities -- Redaction, CIDR, Diagnostics
487
572
 
488
573
  SDK helpers surfaced as CLI commands so agents (and humans) can validate behavior without writing Node.
489
574
 
490
575
  ```bash
491
- # Redact sensitive fields (password, token, card, ssn, etc.)
576
+ # Redact sensitive fields (password, token, email, card, OTP codes, etc.)
492
577
  npx securenow redact '{"user":"alice","password":"s3cret","card":"4242"}'
493
578
  npx securenow redact @request.json --fields internal_id,sessionHash
494
579
 
@@ -503,6 +588,10 @@ npx securenow env --json # pipe to jq
503
588
  # End-to-end diagnostic: probe OTLP + API endpoints
504
589
  npx securenow doctor # exits 0 if healthy, 1 otherwise
505
590
  npx securenow doctor --json
591
+
592
+ # Threat-modeling prompts for your coding agent
593
+ npx securenow threat-model --list
594
+ npx securenow threat-model authentication --out threat-prompt.md
506
595
  ```
507
596
 
508
597
  ### Configuration
@@ -514,7 +603,6 @@ npx securenow config get
514
603
  # Set values
515
604
  npx securenow config set apiUrl https://custom-api.example.com
516
605
  npx securenow config set defaultApp <app-key>
517
- npx securenow config set format json
518
606
 
519
607
  # Show config file paths
520
608
  npx securenow config path
@@ -524,15 +612,15 @@ Config files are stored in `~/.securenow/` (global) or `.securenow/` in the proj
524
612
 
525
613
  | File | Description |
526
614
  |------|-------------|
527
- | `~/.securenow/config.json` | API URL, default app, output format |
615
+ | `~/.securenow/config.json` | API URL, dashboard URL, default app |
528
616
  | `~/.securenow/admin.json` | Global admin/control-plane CLI and MCP auth |
529
617
  | `~/.securenow/runtime.json` | Global SDK runtime app config and runtime API key |
530
618
  | `.securenow/admin.json` | Project-local admin/control-plane CLI and MCP auth |
531
619
  | `.securenow/runtime.json` | Project-local SDK runtime app config and runtime API key |
532
- | `.securenow/credentials.json` | Legacy combined credentials; still read for backward compatibility |
620
+ | `.securenow/credentials.json` | Legacy combined credentials, or the production runtime file mounted on a server |
533
621
  | `.securenow/credentials.<environment>.json` | Tokenless runtime credentials generated by `credentials runtime --env <environment>`; read in a fixed order, not selected from env vars |
534
622
 
535
- **Resolution order:** runtime config resolves from project `.securenow/runtime.json` -> legacy project `.securenow/credentials.json` -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> global `.securenow/runtime.json` -> legacy global credentials -> global named runtime credentials. Admin auth resolves from `admin.json` first, then legacy `credentials.json`. Legacy CLI token overrides still work for existing automation.
623
+ **Resolution order:** the SDK reads `SECURENOW_CREDENTIALS` first. Otherwise, starting from the working directory and the entry script's folder (under PM2, the app folder PM2 recorded) and walking up through parent folders, the first file found wins, checked in this order in each folder: `.securenow/runtime.json`, `.securenow/credentials.json`, then named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order. Then the same names in `~/.securenow/`. Files are never merged. Admin auth resolves from `admin.json` first, then legacy `credentials.json`.
536
624
 
537
625
  ### Global Flags
538
626
 
@@ -542,16 +630,20 @@ Every command supports these flags:
542
630
  |------|-------|-------------|
543
631
  | `--json` | `-j` | Output as JSON for scripting and CI/CD |
544
632
  | `--help` | | Show help for the command |
545
- | `--app <key>` | | Override the default application key |
633
+ | `--yes` | `-y` | Confirm a write without the prompt |
634
+
635
+ Most commands also accept `--app <key>` (target another app) and `--env <environment>` (default `production` for security commands).
546
636
 
547
637
  ### CLI Configuration
548
638
 
549
- Normal SDK runtime setup uses `.securenow/runtime.json`; admin CLI/MCP auth uses `.securenow/admin.json`.
550
- SecureNow-specific environment overrides are no longer supported by the SDK or CLI.
639
+ Normal SDK runtime setup uses `.securenow/runtime.json`; admin CLI/MCP auth uses `.securenow/admin.json`. The SDK reads only `SECURENOW_CREDENTIALS` and `SECURENOW_DISABLED` from the environment; every other `SECURENOW_*` variable is ignored.
551
640
 
552
- | Override | Description |
641
+ | Variable | Description |
553
642
  |----------|-------------|
554
643
  | `NO_COLOR` | Disable colored CLI output |
644
+ | `SECURENOW_MCP_PROFILE` | MCP tool profile (`investigator`, `detection`, `responder`), same as `securenow mcp --profile` |
645
+ | `SECURENOW_CREDENTIALS` | Runtime credentials JSON (raw or base64) for the SDK and for `env`, `doctor`, `test-span`, `log send`, `event send` |
646
+ | `SECURENOW_DISABLED` | `1` turns the SDK off in that process (test runs) |
555
647
 
556
648
  ### Multi-Project Sessions
557
649
 
@@ -580,10 +672,18 @@ npx securenow credentials runtime --env production
580
672
 
581
673
  # Store .securenow/credentials.production.json as a deployment secret file,
582
674
  # then materialize it as .securenow/credentials.json in the running app.
583
- # Since v7.7.2, mounting it as .securenow/credentials.production.json
584
- # also works when the canonical credentials.json file is absent.
675
+ # Mounting it as .securenow/credentials.production.json also works when no
676
+ # other credentials file is present (a runtime.json or credentials.json
677
+ # next to it would win).
585
678
  # The SDK checks named files in a fixed order and does not use env vars
586
679
  # to pick the credentials filename.
680
+ # Hosts without file mounts (Vercel, Railway, Render, Heroku, Fly):
681
+ npx securenow credentials runtime --env production --format env
682
+ # -> SECURENOW_CREDENTIALS=<base64>; store it as a secret environment variable.
683
+ # It takes precedence over credential files.
684
+
685
+ # Test runs: keep them from reporting to the real app
686
+ SECURENOW_DISABLED=1 npm test
587
687
 
588
688
  # Use --json for machine-readable output
589
689
  npx securenow logs --json --level error | jq '.logs'
@@ -593,59 +693,77 @@ npx securenow logs --json --level error | jq '.logs'
593
693
 
594
694
  | Category | Command | Description |
595
695
  |----------|---------|-------------|
596
- | **Setup** | `init` | Auto-scaffold instrumentation for your framework |
696
+ | **Setup** | `init` | Auto-scaffold instrumentation for your framework (`--env`, `--key`, `--typescript`/`--javascript`) |
697
+ | | `run <script>` | Run a Node app with the preload (`--firewall-only`, Node flags pass through) |
597
698
  | **Auth** | `login` | Friendly onboarding: admin auth + app runtime connection |
598
- | | `admin login` | Authenticate admin/control-plane CLI and MCP tools |
699
+ | | `admin login` / `admin logout` | Authenticate (or clear) admin/control-plane CLI and MCP tools |
599
700
  | | `app connect` | Select/create app and write SDK runtime config |
600
701
  | | `logout` | Clear admin auth; runtime app config remains |
601
702
  | | `whoami` | Show admin auth and runtime app status separately |
703
+ | | `credentials runtime` | Tokenless production runtime file (`--env`, `--out`, `--stdout`, `--format env`) |
602
704
  | **Apps** | `apps` | List applications |
603
- | | `apps create <name>` | Create application |
705
+ | | `apps create <name>` | Create application (`--hosts`, `--instance`) |
604
706
  | | `apps info <id>` | Application details |
605
707
  | | `apps delete <id>` | Delete application |
606
708
  | | `apps default <key>` | Set default app |
709
+ | | `apps discover` / `apps scan` | Discover subdomains and add them as apps |
607
710
  | **API Key** | `api-key create [--name "CLI runtime"] [--app <key-or-id>] [--global]` | Mint and save a one-app `runtime_app` key with your logged-in session. Defaults to the current runtime app |
608
711
  | | `api-key set <snk_live_...> [--global]` | Save runtime API key to `.securenow/runtime.json` |
609
712
  | | `api-key show` | Show masked key + source |
610
713
  | | `api-key clear [--global]` | Remove stored key (leaves session/app) |
611
- | **Observe** | `traces` | List traces |
714
+ | **Observe** | `today` | The Overview: what happened, what SecureNow did, what needs you |
715
+ | | `attacker <ip>` | Why an address was flagged |
716
+ | | `traces` | List traces |
612
717
  | | `traces show <id>` | Trace details |
613
718
  | | `traces analyze <id>` | AI trace analysis |
614
719
  | | `logs` | List logs |
615
720
  | | `logs trace <id>` | Logs for a trace |
721
+ | | `logs errors` | Error lines grouped by message |
616
722
  | | `analytics` | Response analytics |
617
723
  | | `status` | Dashboard overview |
618
- | **Detect** | `notifications` | List notifications |
724
+ | **Detect** | `protect` | Starter protection: `status`, `enable`, `disable`, `block`, `monitor`, `learn` |
725
+ | | `notifications` | List notifications |
619
726
  | | `notifications unread` | Unread count |
620
727
  | | `notifications read <id>` | Mark read |
621
728
  | | `notifications read-all` | Mark all read |
729
+ | | `review` | Alert Review: `list`, `show`, `keep`, `close`, `dismiss`, `group` |
730
+ | | `human` | Requires Human: `list`, `show`, `block`, `fp`, `close`, `report`, `action`, `prompt`, `work`, `promotions`, `approve-promotion`, `deny-promotion` |
731
+ | | `agent` | The autonomous agent: `status`, `on`, `off`, `mode`, `activity`, `undo`, `runs`, `run`, `verify`, `prompt`, `prompts` |
622
732
  | | `alerts rules` | List rules (status, apps, schedule) |
733
+ | | `alerts rules from-text "<english>" --app <key>` | Draft, check and create a rule from English (test mode) |
734
+ | | `alerts rules create` | Create a rule from SQL (`--sql`) or instant conditions (`--conditions`) |
623
735
  | | `alerts rules show <id>` | Rule detail |
624
736
  | | `alerts rules update <id> --applications-all` / `--apps k1,k2` | Application scope |
737
+ | | `alerts rules test <id> [--mode live] --wait` | Dry run, or live with synthetic attacks |
625
738
  | | `alerts rules runs <id>` / `run <id>` | Run history (outcome of each run) / run a periodic rule now |
626
739
  | | `alerts rules why <id> --ip <ip>` | Why the rule did or didn't alert on an address |
740
+ | | `alerts rules change <id> "<english>"` | Rewrite a rule from English as a new version |
741
+ | | `alerts rules promote <id>` / `demote <id>` | Test mode to prod and back (a person) |
742
+ | | `alerts rules request-promotion <id>` | Ask a person to promote (agents) |
743
+ | | `alerts rules validate --sql @rule.sql --app <key>` | Dry-run candidate SQL |
627
744
  | | `alerts channels` | Alert channels |
628
745
  | | `alerts history` | Alert history |
629
746
  | **Investigate** | `ip <addr>` | IP intelligence |
630
747
  | | `ip traces <addr>` | Traces from IP |
631
748
  | | `forensics "<query>"` | NL forensic query |
749
+ | | `forensics chat` / `forensics conversations` | Forensics chat with memory |
632
750
  | | `forensics library` | Saved queries |
633
- | | `api-map` | API endpoints |
634
- | | `api-map stats` | API stats |
635
- | **Firewall** | `firewall status` | Firewall status and API key info |
751
+ | **Firewall** | `firewall status` | Firewall status and layers |
752
+ | | `firewall apps` | Apps with their firewall on/off state |
753
+ | | `firewall enable` / `firewall disable` | Per-app, per-environment toggle |
636
754
  | | `firewall test-ip <ip>` | Check if an IP would be blocked |
755
+ | | `firewall hits` | Blocks, 429s and challenges per rule and per IP |
637
756
  | | `run --firewall-only <script>` | Preload firewall without OTel tracing overhead |
638
757
  | **Remediate** | `blocklist` | Blocked IPs |
639
- | | `automation` | List blocklist automation rules |
640
- | | `automation dry-run <id>` | Preview automation matches without writing blocks |
641
- | | `automation execute <id> --yes` | Run an automation rule now |
642
- | | `blocklist add <ip>` | Block IP |
758
+ | | `blocklist add <ip>` | Block IP (`--route`, `--mode`, `--method`, `--duration`, `--reason`) |
643
759
  | | `blocklist unblock <id>` | Unblock IP and retain report/history |
644
760
  | | `blocklist remove <id>` | Compatibility alias for unblock |
761
+ | | `blocklist approve <id>` / `blocklist reject <id>` | Decide a pending block |
645
762
  | | `blocklist stats` | Block stats |
646
- | | `ratelimit parse "<request>"` | Fill a rate-limit draft from natural language |
647
- | | `ratelimit from-text "<request>" --yes` | Create a soft rate-limit rule from natural language |
648
- | | `ratelimit test <ip> --path /api/login` | Check whether a request would match rate-limit rules |
763
+ | | `automation` | List blocklist automation rules (`defaults`, `show`, `create`, `update`, `dry-run`, `execute`, `delete`) |
764
+ | | `ratelimit add` / `from-text` / `parse` / `test` / `list` / `enable` / `disable` / `remove` | Rate-limit rules (alias `rate-limit`) |
765
+ | | `challenge add` / `test` / `list` / `enable` / `disable` / `remove` | Proof-of-work challenge rules |
766
+ | | `revoke session <id>` / `revoke user <id>` / `revoke list` / `revoke restore <id>` | Session revocation |
649
767
  | | `allowlist` | Allowed IPs (restrict-mode) |
650
768
  | | `allowlist add <ip>` | Allow IP (`--label`, `--reason`) |
651
769
  | | `allowlist remove <id>` | Remove from allowlist |
@@ -662,17 +780,21 @@ npx securenow logs --json --level error | jq '.logs'
662
780
  | | `fp dry-run --conditions '[...]'` | Dry-run against last 3 days of traces |
663
781
  | | `fp ai-fill --description "..."` | AI-generate exclusion conditions |
664
782
  | | `fp mark <notification-id> <ip>` | Mark an IP as FP on a notification |
665
- | **Telemetry** | `log send "<msg>" --level info --attrs k=v` | Emit an OTLP log record |
783
+ | **Telemetry** | `telemetry schema` / `telemetry fields` | Alert-SQL contract and fields observed for an app |
784
+ | | `log send "<msg>" --level info --attrs k=v` | Emit an OTLP log record |
785
+ | | `event send <type> --user <id> --ip <ip>` | Emit a custom security event |
666
786
  | | `test-span [<name>]` | Emit a test span to the collector |
667
787
  | **Utilities** | `redact '<json>' [--fields f1,f2]` | Redact sensitive fields (accepts `@file.json`) |
668
788
  | | `cidr match <ip> <cidrs>` | IP vs. CIDR list (exit 0 hit / 2 miss) |
669
789
  | | `cidr parse <cidr>` | Parse CIDR (network, broadcast, mask, size) |
670
790
  | | `env [--json]` | Show resolved config (service name, endpoints, credentials) |
671
791
  | | `doctor [--json]` | Probe OTLP + API endpoints, check config |
792
+ | | `threat-model <category>` | Print a threat-modeling prompt (`--list`, `--out`) |
793
+ | | `mcp [--profile <name>]` | Start the MCP server over stdio |
672
794
  | **Settings** | `instances` | List instances |
673
795
  | | `instances test <id>` | Test connection |
674
796
  | | `config get` | Show config |
675
- | | `config set <k> <v>` | Set config value |
797
+ | | `config set <k> <v>` | Set config value (`apiUrl`, `appUrl`, `defaultApp`) |
676
798
  | | `config path` | Config file paths |
677
799
  | | `version` | Show version |
678
800
 
@@ -680,8 +802,10 @@ npx securenow logs --json --level error | jq '.logs'
680
802
 
681
803
  ## Framework-Specific Setup
682
804
 
683
- > **v5.6.0+:** When `config.logging.enabled` is `true`, all `console.log`/`warn`/`error`/`info`/`debug` calls
684
- > are **automatically** forwarded as OTLP log records. The separate `require('securenow/console-instrumentation')` is no longer needed (but still available for backward compat).
805
+ > When `config.logging.enabled` is `true` (the default), all `console.log`/`warn`/`error`/`info`/`debug` calls
806
+ > are **automatically** forwarded as OTLP log records. No extra require is needed.
807
+
808
+ Every server below runs on `node:http`, so one preload covers it: `node -r securenow/register <entry>`. In a CommonJS entry, `require('securenow/register')` as the very first line is equivalent. The examples contain no SecureNow code.
685
809
 
686
810
  ### Express.js
687
811
 
@@ -691,7 +815,6 @@ npm install securenow express
691
815
 
692
816
  ```javascript
693
817
  // app.js
694
- require('securenow/register');
695
818
  const express = require('express');
696
819
 
697
820
  const app = express();
@@ -708,9 +831,11 @@ app.listen(3000, () => console.log('Express running on port 3000'));
708
831
  ```
709
832
 
710
833
  ```bash
711
- node app.js
834
+ node -r securenow/register app.js
712
835
  ```
713
836
 
837
+ Express 4 and 5 record `http.route`. One span per middleware is off by default (`config.otel.middlewareSpans: true` turns it on).
838
+
714
839
  #### With PM2
715
840
 
716
841
  ```javascript
@@ -719,12 +844,17 @@ module.exports = {
719
844
  apps: [{
720
845
  name: 'my-app',
721
846
  script: './app.js',
722
- node_args: '-r securenow/register',
847
+ cwd: __dirname,
848
+ instances: 'max', // or leave out for fork mode
849
+ // An absolute path, resolved when PM2 loads this file: in cluster mode Node resolves a bare
850
+ // '-r securenow/register' from the PM2 daemon's folder, so workers started by `pm2 resurrect`
851
+ // at boot crash-loop with "Cannot find module 'securenow/register'".
852
+ node_args: ['-r', require.resolve('securenow/register')],
723
853
  }]
724
854
  };
725
855
  ```
726
856
 
727
- > **Important:** Put `.securenow/runtime.json` in the PM2 app root for local/dev deployments, or mount the production runtime export as `.securenow/credentials.json`. Keep `node_args: '-r securenow/register'`. PM2 restarts will then load the SDK, traces/logs, body capture, and firewall without any SecureNow env vars.
857
+ > **Important:** Put `.securenow/runtime.json` in the PM2 app root for local/dev deployments, or mount the production runtime export as `.securenow/credentials.json`. PM2 restarts will then load the SDK, traces/logs, body capture, and firewall without any SecureNow env vars. Fork and cluster mode both read the app folder PM2 started (`pm_cwd`), whichever folder the PM2 daemon itself was started from (before 8.24.0 a cluster worker searched the daemon's folder and could start without credentials). After changing `node_args`, run `pm2 delete my-app && pm2 start ecosystem.config.cjs && pm2 save`.
728
858
 
729
859
  ---
730
860
 
@@ -736,7 +866,6 @@ npm install securenow fastify
736
866
 
737
867
  ```javascript
738
868
  // app.js
739
- require('securenow/register');
740
869
  const Fastify = require('fastify');
741
870
  const fastify = Fastify({ logger: true });
742
871
 
@@ -755,7 +884,11 @@ fastify.listen({ port: 3000 }, (err) => {
755
884
  });
756
885
  ```
757
886
 
758
- > **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. If a specific Fastify version or plugin stack reports request-stream conflicts, set `config.capture.body=false` in credentials as a local override.
887
+ ```bash
888
+ node -r securenow/register app.js
889
+ ```
890
+
891
+ > **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. The route template (`http.route`, span name `GET /tasks`) and handler exceptions are recorded through Fastify's `fastify.initialization` diagnostics channel (8.24.0+). Tested on Fastify 5.12.
759
892
 
760
893
  ---
761
894
 
@@ -767,7 +900,6 @@ npm install securenow koa @koa/router koa-bodyparser
767
900
 
768
901
  ```javascript
769
902
  // app.js
770
- require('securenow/register');
771
903
  const Koa = require('koa');
772
904
  const Router = require('@koa/router');
773
905
  const bodyParser = require('koa-bodyparser');
@@ -790,6 +922,12 @@ app.use(router.allowedMethods());
790
922
  app.listen(3000, () => console.log('Koa running on port 3000'));
791
923
  ```
792
924
 
925
+ ```bash
926
+ node -r securenow/register app.js
927
+ ```
928
+
929
+ `http.route` comes from `@koa/router`; body capture records what the body parser reads.
930
+
793
931
  ---
794
932
 
795
933
  ### NestJS
@@ -831,6 +969,8 @@ bootstrap();
831
969
 
832
970
  ```bash
833
971
  node -r securenow/register -r ts-node/register app.ts
972
+ # compiled output (production):
973
+ node -r securenow/register dist/main.js
834
974
  ```
835
975
 
836
976
  PM2 config:
@@ -840,7 +980,7 @@ PM2 config:
840
980
  name: 'my-nestjs-app',
841
981
  script: 'app.ts',
842
982
  interpreter: 'node',
843
- node_args: '-r securenow/register -r ts-node/register',
983
+ node_args: ['-r', require.resolve('securenow/register'), '-r', require.resolve('ts-node/register')],
844
984
  }
845
985
  ```
846
986
 
@@ -854,7 +994,6 @@ npm install securenow @hapi/hapi
854
994
 
855
995
  ```javascript
856
996
  // app.js
857
- require('securenow/register');
858
997
  const Hapi = require('@hapi/hapi');
859
998
 
860
999
  const init = async () => {
@@ -878,11 +1017,15 @@ const init = async () => {
878
1017
  init().catch((err) => { console.error(err); process.exit(1); });
879
1018
  ```
880
1019
 
881
- > **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. If a specific Hapi version or payload plugin reports request-stream conflicts, set `config.capture.body=false` in credentials as a local override.
1020
+ ```bash
1021
+ node -r securenow/register app.js
1022
+ ```
1023
+
1024
+ > **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. Hapi records `http.route`. Tested on Hapi 21.4.
882
1025
 
883
1026
  ---
884
1027
 
885
- ### h3 (UnJS / Nitro)
1028
+ ### h3 (UnJS)
886
1029
 
887
1030
  ```bash
888
1031
  npm install securenow h3
@@ -890,7 +1033,6 @@ npm install securenow h3
890
1033
 
891
1034
  ```javascript
892
1035
  // app.js
893
- require('securenow/register');
894
1036
  const { createApp, createRouter, defineEventHandler, readBody, setResponseStatus, toNodeListener } = require('h3');
895
1037
  const http = require('http');
896
1038
 
@@ -913,6 +1055,12 @@ http.createServer(toNodeListener(app)).listen(3000, () => {
913
1055
  });
914
1056
  ```
915
1057
 
1058
+ ```bash
1059
+ node -r securenow/register app.js
1060
+ ```
1061
+
1062
+ `http.route` from the h3 router **(8.25.0+)**; before, the raw path. Nitro inside Nuxt: see [Nuxt 3 and 4](#nuxt-3-and-4).
1063
+
916
1064
  ---
917
1065
 
918
1066
  ### Polka
@@ -925,7 +1073,6 @@ Polka is minimalist -- no built-in body parser:
925
1073
 
926
1074
  ```javascript
927
1075
  // app.js
928
- require('securenow/register');
929
1076
  const polka = require('polka');
930
1077
 
931
1078
  function jsonBody(req, res, next) {
@@ -950,6 +1097,12 @@ polka()
950
1097
  .listen(3000, () => console.log('Polka running on port 3000'));
951
1098
  ```
952
1099
 
1100
+ ```bash
1101
+ node -r securenow/register app.js
1102
+ ```
1103
+
1104
+ Pass errors to `next(err)` (a thrown error crashes Polka). `http.route` **(8.25.0+)**.
1105
+
953
1106
  ---
954
1107
 
955
1108
  ### Micro / Raw HTTP
@@ -962,7 +1115,6 @@ npm install securenow
962
1115
 
963
1116
  ```javascript
964
1117
  // app.js
965
- require('securenow/register');
966
1118
  const http = require('http');
967
1119
 
968
1120
  function sendJson(res, status, body) {
@@ -990,6 +1142,12 @@ async function handler(req, res) {
990
1142
  http.createServer(handler).listen(3000, () => console.log('HTTP running on port 3000'));
991
1143
  ```
992
1144
 
1145
+ ```bash
1146
+ node -r securenow/register app.js
1147
+ ```
1148
+
1149
+ `https.createServer` and the HTTP/2 compatibility API (`'request'` event) are covered the same way; spans carry the raw path.
1150
+
993
1151
  ---
994
1152
 
995
1153
  ### Hono (ESM)
@@ -1022,7 +1180,7 @@ serve({ fetch: app.fetch, port: 3000 }, () => console.log('Hono running on port
1022
1180
  node -r securenow/register app.mjs
1023
1181
  ```
1024
1182
 
1025
- > **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. For Hono ESM apps, keep using the `node -r securenow/register app.mjs` preload instead of adding `require('securenow/register')` inside `.mjs` files.
1183
+ > **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. Keep using the `node -r securenow/register app.mjs` preload instead of importing `securenow/register` inside `.mjs` files. Only Hono on `@hono/node-server` is covered (not `Bun.serve`, Deno or Workers). `http.route` **(8.25.0+)**.
1026
1184
 
1027
1185
  ---
1028
1186
 
@@ -1036,7 +1194,6 @@ Feathers uses Express transport -- same setup as Express:
1036
1194
 
1037
1195
  ```javascript
1038
1196
  // app.js
1039
- require('securenow/register');
1040
1197
  const feathers = require('@feathersjs/feathers');
1041
1198
  const express = require('@feathersjs/express');
1042
1199
  const errors = require('@feathersjs/errors');
@@ -1063,6 +1220,12 @@ app.use(express.errorHandler());
1063
1220
  app.listen(3000, () => console.log('Feathers running on port 3000'));
1064
1221
  ```
1065
1222
 
1223
+ ```bash
1224
+ node -r securenow/register app.js
1225
+ ```
1226
+
1227
+ `http.route` **(8.25.0+)**. The Koa transport works the same way.
1228
+
1066
1229
  ---
1067
1230
 
1068
1231
  ### Next.js
@@ -1071,7 +1234,7 @@ Use `npx securenow init` for the current Next.js integration; it creates or prin
1071
1234
 
1072
1235
  #### Option A: `securenow init` (Recommended)
1073
1236
 
1074
- The init command handles the boring parts: credentials defaults, `instrumentation.ts`, body auto-capture, `serverExternalPackages`, and standalone output tracing includes for Next 15+. If existing files are custom, it prints a Codex/Claude-ready prompt instead of guessing.
1237
+ The init command handles the boring parts: credentials defaults, `instrumentation.ts`, body auto-capture, `serverExternalPackages`, and standalone output tracing includes for Next 15+ (Next 14 **(8.25.0+)**). If existing files are custom, it prints a Codex/Claude-ready prompt instead of guessing.
1075
1238
 
1076
1239
  ```bash
1077
1240
  npx securenow login
@@ -1082,12 +1245,16 @@ npx securenow init
1082
1245
 
1083
1246
  ```typescript
1084
1247
  export async function register() {
1085
- if (process.env.NEXT_RUNTIME !== 'nodejs') return;
1086
-
1087
- const securenowNext = await import(/* webpackIgnore: true */ 'securenow/nextjs');
1088
- const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
1089
- registerSecureNow({ captureBody: true });
1090
- await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
1248
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
1249
+ try {
1250
+ const securenowNext = await import('securenow/nextjs');
1251
+ const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
1252
+ registerSecureNow({ captureBody: true });
1253
+ await import('securenow/nextjs-auto-capture');
1254
+ } catch (error) {
1255
+ console.error('[securenow] SecureNow did not start; the app is running without it:', error);
1256
+ }
1257
+ }
1091
1258
  }
1092
1259
  ```
1093
1260
 
@@ -1104,11 +1271,17 @@ const nextConfig = {
1104
1271
  export default nextConfig;
1105
1272
  ```
1106
1273
 
1107
- No `.env.local` is needed locally or in production. Local setup reads `.securenow/runtime.json`; production should use `npx securenow credentials runtime --env production` and mount/copy the resulting JSON as `.securenow/credentials.json`.
1274
+ No `.env.local` is needed locally or in production. Local setup reads `.securenow/runtime.json`; production should use `npx securenow credentials runtime --env production` and mount/copy the resulting JSON as `.securenow/credentials.json` (in a standalone Docker image, next to `server.js`, i.e. `.next/standalone/.securenow/credentials.json` before copying). On Vercel or another host without file mounts, run `npx securenow credentials runtime --env production --format env` and store the printed `SECURENOW_CREDENTIALS` value as a secret environment variable. On Vercel the integration uses `@vercel/otel`; that setup is not tested end to end and the firewall is not verified on Vercel serverless functions.
1275
+
1276
+ Keep the imports inside the `nodejs` block and without `webpackIgnore`: Next.js then drops them from the Edge build and traces `securenow` and its dependencies into `output: 'standalone'`. An instrumentation file written by `init` before 8.24.0 used `webpackIgnore`, so standalone images shipped without OpenTelemetry and every request returned 500; run `npx securenow init` again to replace it.
1277
+
1278
+ **Where the file goes:** next to `app/` or `pages/`. With a `src/` layout (`src/app`, `src/pages`), Next.js 14 and 15 only load `src/instrumentation.ts` (16 also loads a root file). From 8.25.0 `init` writes it there and moves a root file it generated before; `--src`/`--root` force the folder, `--force` replaces an existing file (the old one is kept as `.bak`).
1279
+
1280
+ **What's covered:** HTTP server spans with client IP and redacted headers and bodies, console logs, the firewall, rate limits and challenges, for Node.js route handlers, server components and API routes. The Edge runtime is not instrumented (Edge middleware and `export const runtime = 'edge'` routes get nothing). `http.route` is 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`.
1108
1281
 
1109
1282
  #### Option B: Manual configuration
1110
1283
 
1111
- If you prefer not to run `init`, manually externalize SecureNow:
1284
+ If you prefer not to run `init`, create the instrumentation file above and externalize SecureNow:
1112
1285
 
1113
1286
  ```javascript
1114
1287
  // next.config.js (Next.js 15+)
@@ -1121,23 +1294,25 @@ module.exports = {
1121
1294
  ```
1122
1295
 
1123
1296
  ```javascript
1124
- // next.config.js (Next.js 14)
1297
+ // next.config.js (Next.js 14): all three keys live under experimental
1125
1298
  module.exports = {
1126
1299
  experimental: {
1127
1300
  instrumentationHook: true,
1128
1301
  serverComponentsExternalPackages: ['securenow'],
1129
- },
1130
- outputFileTracingIncludes: {
1131
- '/*': ['./node_modules/securenow/**/*'],
1302
+ outputFileTracingIncludes: {
1303
+ '/*': ['./node_modules/securenow/**/*'],
1304
+ },
1132
1305
  },
1133
1306
  };
1134
1307
  ```
1135
1308
 
1136
- **Why is this needed?** Next.js bundles server code with webpack, which can break OpenTelemetry's dynamic `require()` calls and monkey-patching. Externalizing `securenow` keeps the SDK as normal Node.js runtime code. `outputFileTracingIncludes` keeps SecureNow's runtime modules available in standalone/self-hosted output, including the firewall.
1309
+ **Why is this needed?** Next.js bundles server code with webpack or Turbopack, which can break OpenTelemetry's dynamic `require()` calls and monkey-patching. Externalizing `securenow` keeps the SDK as normal Node.js runtime code, and Next.js traces its dependencies into standalone output. `outputFileTracingIncludes` adds SecureNow's own files that are loaded at runtime, including the firewall. Next.js 15.5 and 16.4 are tested (Turbopack and webpack, `next start` and standalone); Next.js 14 is expected to work but is not tested.
1310
+
1311
+ You don't need `securenow/nextjs-middleware` or `securenow/nextjs-wrapper` for body capture: since 8.24.0 bodies are captured at the HTTP layer, and `securenow/nextjs-auto-capture` is only a fallback.
1137
1312
 
1138
1313
  ---
1139
1314
 
1140
- ### Nuxt 3
1315
+ ### Nuxt 3 and 4
1141
1316
 
1142
1317
  ```bash
1143
1318
  npm install securenow
@@ -1151,47 +1326,66 @@ export default defineNuxtConfig({
1151
1326
  });
1152
1327
  ```
1153
1328
 
1154
- Local app identity, runtime API key, and secure defaults come from `.securenow/runtime.json`; production can mount the generated tokenless runtime file as `.securenow/credentials.json` or `.securenow/credentials.<env>.json`.
1329
+ Then run `npx securenow init` to fill `.securenow/runtime.json`. Local app identity, runtime API key, and secure defaults come from that file; production can mount the generated tokenless runtime file as `.securenow/credentials.json` in the folder you start `node .output/server/index.mjs` from, or set `SECURENOW_CREDENTIALS` (see `credentials runtime --format env`).
1155
1330
 
1156
- That's it -- the Nuxt module handles OTel SDK initialization, Nitro externalization, firewall activation, and request tracing automatically. Optional config:
1331
+ That's it: the module's Nitro plugin loads the same runtime as `node -r securenow/register` (tracing with header and body capture, logs, crash capture, firewall, rate limits, challenges) and keeps `securenow` external, so Nitro copies it with its dependencies into `.output/server/node_modules`. It also records the matched route as `http.route` and handler errors as exceptions. Settings go in `.securenow/runtime.json` (`config.capture.body`, `config.logging.enabled`, ...); `securenow: { ... }` options in `nuxt.config` are no longer used, and the build prints a warning if you set them.
1157
1332
 
1158
- ```typescript
1159
- export default defineNuxtConfig({
1160
- modules: ['securenow/nuxt'],
1161
- securenow: {
1162
- captureBody: true,
1163
- logging: true,
1164
- noUuid: true,
1165
- },
1166
- });
1333
+ `nuxt dev` is traced too. Its requests arrive through Nuxt's dev proxy: from 8.25.0 the client IP is known there; before, it was unknown and IP blocking only applied to the built server.
1334
+
1335
+ Before 8.24.0 the module's plugin crashed the `nuxt build` server at startup. Starting `.output/server/index.mjs` with `node -r securenow/register` and no module also works. Tested on Nuxt 3.21 and 4.6.
1336
+
1337
+ ---
1338
+
1339
+ ### Vite
1340
+
1341
+ 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:
1342
+
1343
+ ```bash
1344
+ # vite preview
1345
+ node -r securenow/register-vite node_modules/vite/bin/vite.js preview
1346
+
1347
+ # a Vite SSR server (Express + Vite middleware, ...)
1348
+ node -r securenow/register server.js
1349
+ ```
1350
+
1351
+ `securenow/register-vite` is the same runtime as `securenow/register`. A static build served by nginx or a CDN has no Node process for SecureNow to run in.
1352
+
1353
+ ### Bun
1354
+
1355
+ ```bash
1356
+ bun --preload securenow/register app.js
1167
1357
  ```
1168
1358
 
1169
- The Nuxt server plugin (v5.13.0+) initializes the firewall independently from OpenTelemetry, so IP blocking works even if tracing encounters an error.
1359
+ Works for apps served through `node:http` (tested with Express 5 on Bun 1.3). `Bun.serve` is not traced or protected. Deno is not supported.
1170
1360
 
1171
1361
  ---
1172
1362
 
1173
1363
  ### Compatibility Matrix
1174
1364
 
1175
- | Framework | Traces | Logs | Body Capture | Firewall | Notes |
1176
- |-----------|--------|------|--------------|----------|-------|
1177
- | Express | Yes | Yes | Yes | Yes | Fully compatible |
1178
- | Fastify | Yes | Yes | Yes | Yes | Default on; use `config.capture.body=false` only for local stream conflicts |
1179
- | Koa | Yes | Yes | Yes | Yes | Needs `koa-bodyparser` |
1180
- | NestJS | Yes | Yes | Yes | Yes | Use `-r ts-node/register` |
1181
- | Hapi | Yes | Yes | Yes | Yes | Default on; use `config.capture.body=false` only for local stream conflicts |
1182
- | h3 | Yes | Yes | Yes | Yes | Uses `toNodeListener()` |
1183
- | Polka | Yes | Yes | Yes | Yes | Needs manual body parser |
1184
- | Micro/HTTP | Yes | Yes | Yes | Yes | Full control |
1185
- | Hono | Yes | Yes | Yes | Yes | Use ESM `-r` preload |
1186
- | Feathers | Yes | Yes | Yes | Yes | Uses Express transport |
1187
- | Next.js | Yes | Yes | Yes | Yes | Use `instrumentation.ts` + `serverExternalPackages` + `outputFileTracingIncludes` |
1188
- | Nuxt 3 | Yes | Yes | Yes | Yes | Use `securenow/nuxt` module |
1365
+ | Framework | Traces | Logs | Body Capture | Firewall | `http.route` | Notes |
1366
+ |-----------|--------|------|--------------|----------|--------------|-------|
1367
+ | Express 4 and 5 | Yes | Yes | Yes | Yes | Yes | CommonJS, ESM, TypeScript via tsx (tested 4.22, 5.3) |
1368
+ | Fastify 5 | Yes | Yes | Yes | Yes | Yes | Route and handler exceptions through Fastify's `fastify.initialization` channel (8.24.0+) |
1369
+ | Koa 3 | Yes | Yes | Yes | Yes | With `@koa/router` | Body needs a body parser |
1370
+ | NestJS 11 | Yes | Yes | Yes | Yes | Yes | `-r securenow/register` before `-r ts-node/register` for TypeScript |
1371
+ | Hapi 21 | Yes | Yes | Yes | Yes | Yes | |
1372
+ | h3 1 | Yes | Yes | Yes | Yes | 8.25.0+ | Uses `toNodeListener()`; handler exceptions on the span 8.25.0+ |
1373
+ | Polka 0.5 | Yes | Yes | Yes | Yes | 8.25.0+ | Needs a body parser; pass errors to `next(err)` (a thrown error crashes Polka); `next(err)` exceptions on the span 8.25.0+ |
1374
+ | Raw `http`/`https`/`http2` | Yes | Yes | Yes | Yes | No (raw path) | HTTP/2 compatibility API covered, raw `'stream'` servers not |
1375
+ | Hono 4 (`@hono/node-server`) | Yes | Yes | Yes | Yes | 8.25.0+ | Errors reach the logs through Hono's default handler; handler exceptions on the span 8.25.0+ |
1376
+ | Feathers 5 | Yes | Yes | Yes | Yes | 8.25.0+ | Koa or Express transport; 5xx service errors on the span 8.25.0+ |
1377
+ | Next.js 15 and 16 | Yes | Yes | Yes | Yes | On the HTTP span 8.25.0+ | `instrumentation.ts` + `serverExternalPackages` + `outputFileTracingIncludes`; `next start` and `output: 'standalone'`; Node.js runtime only |
1378
+ | Next.js 14 | Yes | Yes | Yes | Yes | | `init` support 8.25.0+; tested on 14.2.35 |
1379
+ | Nuxt 3 and 4 | Yes | Yes | Yes | Yes | Yes | `securenow/nuxt` module; `nuxt build` output and `nuxt dev` |
1380
+ | Bun 1.3 (`node:http` apps) | Yes | Yes | Yes | Yes | As the framework | `bun --preload securenow/register`; `Bun.serve` is not supported |
1381
+
1382
+ Not supported: Deno, edge runtimes (Cloudflare Workers, Vercel Edge), `Bun.serve`, browser telemetry. Not tested: Vercel, AWS Lambda and other serverless platforms.
1189
1383
 
1190
1384
  ---
1191
1385
 
1192
1386
  ## Firewall -- Automatic IP Blocking
1193
1387
 
1194
- SecureNow can automatically block IPs from your blocklist at the application layer. No code changes -- provide an API key via `securenow login`, the `api-key` CLI, or a runtime credentials file and the firewall activates.
1388
+ SecureNow can automatically block IPs from your blocklist at the application layer. No code changes -- provide a runtime API key via `securenow login`, the `api-key` CLI, or a runtime credentials file and the firewall activates.
1195
1389
 
1196
1390
  ### Firewall Is Enabled by Default
1197
1391
 
@@ -1209,28 +1403,32 @@ npx securenow api-key set snk_live_abc123...
1209
1403
  npx securenow credentials runtime --env production
1210
1404
  ```
1211
1405
 
1212
- The SDK resolves the runtime API key from project `./.securenow/runtime.json`, legacy project `./.securenow/credentials.json`, project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order, global `~/.securenow/runtime.json`, legacy global credentials, then global named runtime credentials in the same fixed order.
1406
+ The SDK resolves the runtime API key from the same source as everything else: `SECURENOW_CREDENTIALS`, then project `./.securenow/runtime.json`, legacy project `./.securenow/credentials.json`, project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order, then the same files in `~/.securenow/`.
1213
1407
 
1214
1408
  On startup, you'll see:
1215
1409
 
1216
1410
  ```
1217
1411
  [securenow] Firewall: ENABLED
1412
+ [securenow] Firewall: environment=production
1218
1413
  [securenow] Firewall: Layer 1 (HTTP 403) active
1219
- [securenow] Firewall: synced 142 blocked IPs (138 exact + 4 CIDR ranges)
1414
+ [securenow] Firewall: synced 142 global blocked IPs (138 exact + 4 CIDR ranges) and 0 scoped block rules
1220
1415
  ```
1221
1416
 
1417
+ Each app and environment also has a remote on/off switch: `npx securenow firewall enable` / `disable --app <key> --env production`, applied without a deploy. `config.firewall.enabled` in the file is ignored.
1418
+
1222
1419
  ### How It Works
1223
1420
 
1224
1421
  The firewall uses a version-based sync protocol for efficiency:
1225
1422
 
1226
- 1. **Version check** every 10 seconds (lightweight request with ETag; unchanged polls return 304 with no body)
1227
- 2. **Full blocklist sync** only when the version changes (or every hour as a safety net)
1228
- 3. **In-memory matching** with a pre-compiled set (exact IPs) and sorted CIDR list for sub-millisecond lookups
1229
- 4. **Exponential backoff** with jitter when the API is temporarily unreachable
1230
- 5. **Allowlist support** -- trusted IPs are never blocked, even if they appear on the blocklist
1231
- 6. **Localhost fallback** -- when the configured API URL is unreachable (ECONNREFUSED), the SDK automatically tries `http://localhost:4000` for co-located deployments
1423
+ 1. **Version check** every 10 seconds (`config.firewall.versionCheckInterval`; a lightweight request with ETag, unchanged polls return 304 with no body)
1424
+ 2. **Full blocklist sync** only when the version changes (or every hour as a safety net, `config.firewall.syncInterval`)
1425
+ 3. **In-memory matching** with a pre-compiled set (exact IPs) and IPv4 and IPv6 CIDR lists (IPv6 8.25.0+) for sub-millisecond lookups
1426
+ 4. **Exponential backoff** with jitter when the API is temporarily unreachable, a circuit breaker after 5 consecutive errors (2-minute pause), and 429 `Retry-After` support
1427
+ 5. **Allowlist** -- deny-by-default: when an app/environment has any active allowlist entry, only listed IPs get through, and listed IPs skip the blocklist, rate limits and challenges
1428
+ 6. **Order per request** -- allowlist, blocklist, scoped (route/method) blocks, challenge, rate limit
1429
+ 7. **Fail-open** by default (`config.firewall.failMode: "open"`); `"closed"` answers 403 to every request until the first sync succeeds
1232
1430
 
1233
- After you block an IP in the dashboard or CLI, it typically takes 10-15 seconds to propagate to all running instances.
1431
+ After you block an IP in the dashboard or CLI, it reaches running instances within about 10 seconds (set `versionCheckInterval` to 1–2 for near-instant propagation).
1234
1432
 
1235
1433
  ### Firewall-Only Mode (No Tracing)
1236
1434
 
@@ -1258,7 +1456,7 @@ This is useful when:
1258
1456
  - You're adding the firewall to a project that uses a different tracing solution
1259
1457
  - For non-Next Node apps, this avoids framework-specific tracing setup entirely
1260
1458
 
1261
- Firewall-only mode uses the same runtime API key written by `login`, `app connect`, `api-key create`, `api-key set`, or mounted from a runtime credentials file:
1459
+ Firewall-only mode includes the blocklist, allowlist, scoped blocks, rate limits, challenges and, **(8.25.0+)**, the zero-code session guard. It uses the same runtime API key written by `login`, `app connect`, `api-key create`, `api-key set`, or mounted from a runtime credentials file:
1262
1460
 
1263
1461
  ```bash
1264
1462
  npx securenow credentials runtime --env production
@@ -1271,29 +1469,42 @@ The firewall supports four layers -- Layer 1 is always on, the rest are opt-in:
1271
1469
 
1272
1470
  | Layer | Credentials key | Description |
1273
1471
  |-------|---------|-------------|
1274
- | **Layer 1: HTTP** | *(always on)* | Returns 403 Forbidden with a security alert page. Works with proxy headers. |
1275
- | **Layer 2: TCP** | `config.firewall.tcp=true` | `socket.destroy()` -- zero bytes sent back |
1276
- | **Layer 3: iptables** | `config.firewall.iptables=true` | Kernel-level DROP (Linux, requires root) |
1277
- | **Layer 4: Cloud WAF** | `config.firewall.cloud="cloudflare"` | Pushes to Cloudflare, AWS WAF, or GCP Cloud Armor |
1472
+ | **Layer 1: HTTP** | *(always on)* | Returns 403 (`config.firewall.statusCode`): JSON `{"error":"Forbidden","ip":"..."}` for API clients, a security alert page for browsers. Works with proxy headers. |
1473
+ | **Layer 2: TCP** | `config.firewall.tcp=true` | `socket.destroy()` before HTTP parsing -- zero bytes sent back. Sees only the connection's source IP; connections from trusted proxies are left to Layer 1. Checked on each server `connection` **(8.25.0+)**; before, it hooked `listen()` and missed the servers most apps start before the SDK has finished starting |
1474
+ | **Layer 3: iptables** | `config.firewall.iptables=true` | Kernel-level DROP in a dedicated `SECURENOW_BLOCK` chain (ip6tables for IPv6), or a `securenow` nft table where iptables is missing **(8.25.0+)**. Linux, root or `CAP_NET_ADMIN`; direct client connections only. 8.25.0 validates every entry, runs without a shell and applies each sync as one batch |
1475
+ | **Layer 4: Cloud WAF** | `config.firewall.cloud="cloudflare"` / `"aws"` / `"gcp"` | Pushes the global blocklist to Cloudflare (an account IP list named `securenow-blocklist` that your own WAF rule must reference), AWS WAFv2 (IP set; `npm i @aws-sdk/client-wafv2`) or GCP Cloud Armor (`deny(403)` rules at priority 2000; `npm i @google-cloud/compute`), at most every 30 seconds. `config.firewall.cloudDryRun=true` only logs. 8.25.0 fixes IPv6 ranges, Cloud Armor's 10-range limit and stale rules. Request format tested, not run against real accounts |
1476
+
1477
+ Layers 2 to 4 carry the global blocklist; scoped blocks, rate limits and challenges are Layer 1 only. Turn Layers 2 to 4 on with 8.25.0+. In a container, Layer 3 needs `--cap-add NET_ADMIN` and the `iptables` package.
1278
1478
 
1279
1479
  ### Blocked Page
1280
1480
 
1281
1481
  When an IP is blocked at Layer 1, the user sees a full-page security alert with:
1282
- - Their detected IP address
1482
+ - Their detected IP address (masked)
1283
1483
  - A warning that malicious activity was detected
1284
1484
  - Contact information (`contact@securenow.ai`) for false positives
1285
1485
 
1286
1486
  ### Get an API Key
1287
1487
 
1288
1488
  ```bash
1289
- npx securenow login
1290
- npx securenow firewall status
1489
+ npx securenow app connect # pick/create the app; mints and saves the runtime key
1490
+ npx securenow api-key create # with admin auth: mint and save a key for the current app
1491
+ npx securenow firewall status # then check the firewall state
1291
1492
  ```
1292
1493
 
1293
- Or create one from the dashboard with the `firewall:read` scope.
1494
+ Or create one in the dashboard with the runtime scopes and save it with `npx securenow api-key set snk_live_...`.
1294
1495
 
1295
1496
  Use `npx securenow firewall status`, `npx securenow firewall apps`, and `npx securenow help firewall` for the current firewall reference.
1296
1497
 
1498
+ ### Trusted Proxies and the Client IP
1499
+
1500
+ Forwarding headers (`X-Forwarded-For`, then `CF-Connecting-IP`, `True-Client-IP`, `X-Real-IP`, `X-Client-IP`) count only when the connection comes from a trusted proxy. Trusted by default: private ranges, loopback, the host's own addresses and, **(8.25.0+)**, Unix-socket and named-pipe connections (for example nginx `proxy_pass http://unix:/run/app.sock`). Add public load balancers and CDNs:
1501
+
1502
+ ```json
1503
+ { "config": { "networking": { "trustedProxies": ["cloudflare", "203.0.113.10", "198.51.100.0/24"] } } }
1504
+ ```
1505
+
1506
+ Entries are exact IPv4/IPv6 addresses, IPv4 or IPv6 CIDRs (IPv6 8.25.0+), or `"cloudflare"` for Cloudflare's published edge ranges (IPv4, and IPv6 from 8.25.0). A client can't choose its IP through `X-Forwarded-For`.
1507
+
1297
1508
  ---
1298
1509
 
1299
1510
  ## Credentials Configuration
@@ -1306,22 +1517,49 @@ Use `.securenow/runtime.json` as the local source of truth. Run `npx securenow e
1306
1517
 
1307
1518
  | Field | Description | Default |
1308
1519
  |----------|-------------|---------|
1309
- | `app.key` | App routing UUID. The SecureNow ingestion gateway routes telemetry by this key. | selected during login |
1520
+ | `app.key` | App routing UUID. It becomes `service.name`; the SecureNow ingestion gateway routes telemetry by this key. | selected during login |
1310
1521
  | `app.name` | Human-readable app label. | selected during login |
1311
1522
  | `apiKey` | One-app scoped runtime API key (`snk_live_...`) for telemetry ingestion and firewall sync. Runtime scopes are `traces:write`, `logs:write`, `firewall:read`, `blocklist:read`, and `allowlist:read`. | minted during login |
1312
- | `config.runtime.deploymentEnvironment` | `deployment.environment` trace/log scope. | `local` from init, `production` from runtime credentials |
1313
- | `config.logging.enabled` | Automatic console log export. | `true` |
1523
+ | `config.runtime.deploymentEnvironment` | `deployment.environment` trace/log/firewall scope (`dev`/`development` become `local`, `prod` becomes `production`). | `production`; `local` from init |
1524
+ | `config.runtime.noUuid` | One service name for every worker. Leave it automatic: `false` breaks dashboard routing. | `null` (automatic) |
1525
+ | `config.runtime.strict` | Exit PM2/cluster workers when no app identity resolves. | `false` |
1526
+ | `config.runtime.testSpan` | Emit a startup smoke span. | `false` |
1527
+ | `config.logging.enabled` | Automatic console log export (and crash capture). | `true` |
1528
+ | `config.logging.redact` | Redact credential fields and secret shapes in exported log lines (8.25.0+). | `true` |
1314
1529
  | `config.capture.body` | Request body capture with redaction. | `true` |
1315
1530
  | `config.capture.multipart` | Multipart metadata capture, never file content. | `true` |
1531
+ | `config.capture.maxBodySize` | Largest body captured, in bytes (minimum 1024); larger bodies are recorded as `[TOO LARGE: <n> bytes]`. | `10240` |
1532
+ | `config.capture.sensitiveFields` | Extra field names to redact (also applied to header names). | `[]` |
1533
+ | `config.otel.endpoint` | Advanced: your own OTLP collector instead of `https://ingest.securenow.ai`. | `null` |
1534
+ | `config.otel.tracesEndpoint` / `logsEndpoint` | Advanced: split collectors. | `null` |
1535
+ | `config.otel.headers` | Extra OTLP headers (app key, auth and environment are added automatically). | `{}` |
1536
+ | `config.otel.logLevel` | OpenTelemetry diagnostics: `error`, `warn`, `info`, `debug`, `none`. | `error` |
1537
+ | `config.otel.disableInstrumentations` | Instrumentation package names to skip, e.g. `@opentelemetry/instrumentation-dns`. | `[]` |
1538
+ | `config.otel.middlewareSpans` | One span per Express/Koa middleware per request. | `false` |
1539
+ | `config.networking.trustedProxies` | Extra trusted proxies: IPv4/IPv6 addresses and CIDRs, `"cloudflare"`. | `[]` |
1540
+ | `config.firewall.failMode` | `open` or `closed` (403 for everyone until the first sync). | `open` |
1541
+ | `config.firewall.statusCode` | HTTP status for blocked requests. | `403` |
1542
+ | `config.firewall.log` | Firewall console lines (startup and each decision). | `true` |
1543
+ | `config.firewall.tcp` / `iptables` / `cloud` / `cloudDryRun` | Layers 2, 3, 4 and the Layer 4 dry run. | `false` / `false` / `null` / `false` |
1544
+ | `config.firewall.cloudflare.*`, `aws.*`, `gcp.*` | Layer 4 provider settings (`apiToken`, `accountId`; `wafIpSetId`, `wafIpSetName`, `wafScope`; `projectId`, `securityPolicy`). | `aws.wafIpSetName: "securenow-blocklist"`, `aws.wafScope: "REGIONAL"` |
1545
+ | `config.firewall.rateLimitStore` | `redis://` or `rediss://` URL for rate-limit counters shared across processes and hosts. | `null` (per process) |
1546
+ | `config.firewall.versionCheckInterval` | Seconds between lightweight version checks. | `10` |
1547
+ | `config.firewall.syncInterval` | Seconds between safety-net full resyncs. | `3600` |
1316
1548
  | `config.firewall.enabled` | Deprecated local hint. Runtime enable/disable uses the dashboard/API environment toggle. | ignored |
1317
- | `config.otel.*` | Optional custom endpoints, headers, and log level. | empty |
1549
+ | `config.sessions.guard` **(8.25.0+)** | 401 for revoked sessions in every framework, no code. | `false` |
1550
+ | `config.sessions.capture` **(8.25.0+)** | Send `session.seen` for session-theft detection. | `false` |
1551
+ | `config.sessions.cookieNames` **(8.25.0+)** | Extra session cookie names. | `[]` |
1552
+
1553
+ `app.instance` from older files is ignored; use `config.otel.endpoint` for a self-hosted collector.
1318
1554
 
1319
1555
  The credentials file is versioned with `_securenow.schemaVersion`. The SDK reads
1320
1556
  all runtime settings from this JSON plus built-in defaults. Production should
1321
1557
  mount a tokenless runtime credentials file at `.securenow/credentials.json`.
1322
- Environment-variable fallback is not supported.
1558
+ The only environment variables the SDK reads are `SECURENOW_CREDENTIALS` (the same
1559
+ JSON, raw or base64, for hosts that can't mount a file; it wins over every file)
1560
+ and `SECURENOW_DISABLED`. Every other `SECURENOW_*` variable is ignored.
1323
1561
 
1324
- **Default sensitive fields (auto-redacted):** `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`
1562
+ **Default sensitive fields (auto-redacted, 68 names):** passwords and secrets (`password`, `passwd`, `pwd`, `secret`, `credentials`, `mysql_pwd`), tokens and auth (`token`, `api_key`, `apikey`, `access_token`, `auth`, `authorization`, `bearer`, `sessionid`, `session_id`, `cookie`, `set-cookie`, `otp`, `mfa`, `totp`), payment (`stripeToken`, `card`, `cardnumber`, `ccv`, `cvc`, `cvv`, `iban`, `account`, `accountnumber`, `routing`, `sortcode`, `taxid`), personal data (`ssn`, `pin`, `email`, `e_mail`, `phone`, `mobile`, `dob`, `birthdate`, `firstname`, `lastname`, `fullname`, `address`, `postcode`, `zip`, `passport`, `license`), and OTP names matched exactly (`code`, `otpCode`, `oneTimeCode`, `oneTimePassword`, `passcode`, `verificationCode`, `confirmationCode`, `emailCode`, `smsCode`, `loginCode`, `signInCode`, `authCode`, `authenticationCode`, `mfaCode`, `totpCode`, `2faCode`, `securityCode`, `recoveryCode`, `backupCode`).
1325
1563
 
1326
1564
  For instrumentation, firewall layers, debugging, trusted proxies, and deployment
1327
1565
  environment, edit the matching `config.*` keys in `.securenow/runtime.json`
@@ -1339,12 +1577,19 @@ SecureNow provides multiple entry points depending on your needs:
1339
1577
  |-------------|-------|-------------------|-------------------|-------|
1340
1578
  | `securenow/register` | `node -r securenow/register app.js` | Yes | Yes | Default -- full tracing + firewall |
1341
1579
  | `securenow/firewall-only` | `node -r securenow/firewall-only app.js` | No | Yes | Firewall only, no OTel overhead |
1342
- | `securenow/nextjs` | `await import(/* webpackIgnore: true */ 'securenow/nextjs')` | Yes | Yes | Next.js instrumentation hook |
1343
- | `securenow/nuxt` | `modules: ['securenow/nuxt']` | Yes | Yes | Nuxt 3 module |
1344
- | `securenow/nextjs-webpack-config` | `withSecureNow(config)` | - | - | Next.js config wrapper |
1345
- | `securenow/firewall` | `require('securenow/firewall').init({...})` | No | Yes | Programmatic firewall API |
1346
- | `securenow/rate-limits` | `require('securenow/rate-limits').parseRateLimitText(...)` | No | Yes | Rate-limit remediation API helper |
1347
- | `securenow/tracing` | `require('securenow/tracing')` | Yes | No | Programmatic tracing API |
1580
+ | `securenow/nextjs` | `await import('securenow/nextjs')` inside the `nodejs` block of `instrumentation.ts` | Yes | Yes | Next.js instrumentation hook |
1581
+ | `securenow/nextjs-auto-capture` | imported by the generated `instrumentation.ts` | - | - | Body-capture fallback |
1582
+ | `securenow/nuxt` | `modules: ['securenow/nuxt']` | Yes | Yes | Nuxt 3 and 4 module |
1583
+ | `securenow/register-vite` | `node -r securenow/register-vite node_modules/vite/bin/vite.js preview` | Yes | Yes | Same as `securenow/register`, for the Node process serving a Vite app |
1584
+ | `securenow/events` | `require('securenow/events').track(type, props)` | No | No | Custom security events |
1585
+ | `securenow/sessions` | `require('securenow/sessions').guard()` / `.check(req)` **(8.25.0+)** | No | No | Session revocation |
1586
+ | `securenow/firewall` | `require('securenow/firewall').getStats()` | No | Yes | Programmatic firewall API (`init`, `shutdown`, `getStats`, `getMatcher`, `getAllowlistMatcher`) |
1587
+ | `securenow/rate-limits` | `require('securenow/rate-limits').parseRateLimitText(...)` | No | No | Rate-limit remediation API helper (calls the SecureNow API) |
1588
+ | `securenow/resolve-ip` | `require('securenow/resolve-ip').resolveClientIp(req)` | No | No | Client IP with the trusted-proxy rules |
1589
+ | `securenow/cidr` | `require('securenow/cidr').createMatcher([...]).isBlocked(ip)` | No | No | Exact-IP and CIDR matching, IPv4 and IPv6 (IPv6 ranges 8.25.0+) |
1590
+ | `securenow/tracing` | `require('securenow/tracing').getLogger(name, version)` | Yes | No | Programmatic logging API |
1591
+ | `securenow/telemetry-schema` | `require('securenow/telemetry-schema')` | No | No | Alert-SQL contract |
1592
+ | `securenow/nextjs-webpack-config` | `withSecureNow(config)` | - | - | Legacy Next.js config wrapper (also adds a webpack hook); prefer the plain config above |
1348
1593
 
1349
1594
  ---
1350
1595
 
@@ -1354,11 +1599,10 @@ SecureNow provides multiple entry points depending on your needs:
1354
1599
 
1355
1600
  Console log forwarding is enabled by default through
1356
1601
  `config.logging.enabled: true`; all console calls are automatically forwarded as
1357
- OTLP log records:
1602
+ OTLP log records, linked to the active trace:
1358
1603
 
1359
1604
  ```javascript
1360
- // At the top of your main file
1361
- require('securenow/register');
1605
+ // Started with: node -r securenow/register app.js
1362
1606
 
1363
1607
  // With config.logging.enabled=true, all console logs are automatically sent
1364
1608
  console.log('Application started');
@@ -1375,21 +1619,22 @@ console.debug('Debug info');
1375
1619
  - `console.error()` -> ERROR
1376
1620
  - `console.debug()` -> DEBUG
1377
1621
 
1378
- Logging is controlled by `config.logging.enabled` in `.securenow/runtime.json` locally or the mounted production runtime credentials file.
1622
+ A first string argument with printf placeholders (`%s`, `%d`...) is formatted like `console.log` does; objects are sent as JSON; errors keep their stack. Exported lines are redacted (8.25.0+; `config.logging.redact: false` turns it off): 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 shows the line as written.
1623
+
1624
+ Logging is controlled by `config.logging.enabled` in `.securenow/runtime.json` locally or the mounted production runtime credentials file. Crash capture (uncaught exceptions and unhandled rejections as `FATAL` records, flushed before the process exits) needs it on.
1379
1625
 
1380
1626
  ### Direct Logger API
1381
1627
 
1382
1628
  For more control, use the OpenTelemetry logger API:
1383
1629
 
1384
1630
  ```javascript
1385
- require('securenow/register');
1386
1631
  const { getLogger } = require('securenow/tracing');
1387
1632
 
1388
- // Get a logger instance
1633
+ // Get a logger instance (null when logging is disabled)
1389
1634
  const logger = getLogger('my-module', '1.0.0');
1390
1635
 
1391
1636
  // Emit structured logs
1392
- logger.emit({
1637
+ logger?.emit({
1393
1638
  severityNumber: 9, // INFO
1394
1639
  severityText: 'INFO',
1395
1640
  body: 'User logged in',
@@ -1406,19 +1651,20 @@ logger.emit({
1406
1651
  - `9` - INFO
1407
1652
  - `13` - WARN
1408
1653
  - `17` - ERROR
1654
+ - `21` - FATAL
1409
1655
 
1410
1656
  ### Using with NODE_OPTIONS
1411
1657
 
1412
1658
  ```bash
1413
- # Enable tracing + logging (console auto-forwarding is built-in since v5.6.0)
1414
- node -r securenow/register app.js
1659
+ # Tracing + logging without editing the start script
1660
+ NODE_OPTIONS="-r securenow/register" npm start
1415
1661
  ```
1416
1662
 
1417
1663
  ---
1418
1664
 
1419
1665
  ## Request Body Capture
1420
1666
 
1421
- SecureNow captures HTTP request bodies in traces by default, with sensitive fields automatically redacted. Set `config.capture.body=false` in `.securenow/runtime.json` locally or the mounted production runtime credentials file only when you need an opt-out.
1667
+ SecureNow captures HTTP request bodies in traces by default, with sensitive fields automatically redacted. Detections read them (instant rules see attacks in POST bodies). Set `config.capture.body=false` in `.securenow/runtime.json` locally or the mounted production runtime credentials file only when you need an opt-out.
1422
1668
 
1423
1669
  ### Body Capture Defaults
1424
1670
 
@@ -1441,6 +1687,8 @@ SecureNow captures HTTP request bodies in traces by default, with sensitive fiel
1441
1687
  - `application/graphql`
1442
1688
  - `multipart/form-data` (metadata capture is on unless `config.capture.multipart=false`)
1443
1689
 
1690
+ Other content types are not captured. The body is observed while your app reads it and never consumed, so a route that never reads its body (no body parser) records none. A body larger than `maxBodySize` is recorded as `[TOO LARGE: <n> bytes]`. Request headers are captured as `http.request.headers` with credential headers (`cookie`, `authorization`, anything containing `token`, `secret`, `session`, `csrf`, `api-key`...) redacted.
1691
+
1444
1692
  ### Multipart Body Capture (v5.8.0+)
1445
1693
 
1446
1694
  Multipart/form-data metadata capture is enabled by default. Set `config.capture.multipart=false` to disable it. Uses a streaming parser that never buffers file content -- memory stays at ~few KB regardless of upload size.
@@ -1468,7 +1716,9 @@ SecureNow 8.11.2 redacts sign-in credentials named `code`, `otpCode`, `oneTimeCo
1468
1716
 
1469
1717
  All request bodies are automatically scanned and sensitive fields are redacted:
1470
1718
 
1471
- **Automatically redacted fields:** `password`, `secret`, `token`, `api_key`, `card`, `cvv`, `ssn`, and more.
1719
+ **Automatically redacted fields:** `password`, `secret`, `token`, `api_key`, `card`, `cvv`, `ssn`, `email`, `phone`, `address` and more (the full list is in [Credentials Configuration](#credentials-configuration)). Field names match case-insensitively as substrings, so `creditCard` and `userEmail` are caught too.
1720
+
1721
+ **Redacted by shape, under any field name:** JWTs, `Bearer`/`Basic` credentials, `sk_`/`pk_`/`rk_`/`snk_` live and test keys, email addresses, and card numbers that pass the Luhn check.
1472
1722
 
1473
1723
  **Add custom fields to redact:**
1474
1724
 
@@ -1487,19 +1737,119 @@ All request bodies are automatically scanned and sensitive fields are redacted:
1487
1737
  ```javascript
1488
1738
  // POST /api/users with body:
1489
1739
  {
1740
+ "username": "johnd",
1490
1741
  "email": "user@example.com",
1491
1742
  "password": "secret123",
1492
- "name": "John Doe"
1743
+ "plan": "pro"
1493
1744
  }
1494
1745
 
1495
1746
  // Captured in trace as:
1496
1747
  {
1497
- "email": "user@example.com",
1748
+ "username": "johnd",
1749
+ "email": "[REDACTED]",
1498
1750
  "password": "[REDACTED]",
1499
- "name": "John Doe"
1751
+ "plan": "pro"
1752
+ }
1753
+ ```
1754
+
1755
+ Preview any payload with `npx securenow redact '<json>'`.
1756
+
1757
+ ---
1758
+
1759
+ ## Custom Security Events
1760
+
1761
+ Report what only your app knows (sign-ins, OTPs, password resets, MFA). Events feed brute-force, credential-stuffing and account-takeover detection, and alert rules can query them.
1762
+
1763
+ ```javascript
1764
+ const { track } = require('securenow/events');
1765
+ const { resolveClientIp } = require('securenow/resolve-ip');
1766
+
1767
+ app.post('/api/login', async (req, res) => {
1768
+ const ip = resolveClientIp(req); // same trusted-proxy rules as the firewall
1769
+ const user = await findUserByEmail(req.body.email);
1770
+ if (!user || !(await verifyPassword(user, req.body.password))) {
1771
+ track('auth.login.failure', {
1772
+ userId: user ? user.id : undefined,
1773
+ ip,
1774
+ userAgent: req.get('user-agent'),
1775
+ severity: 'warn',
1776
+ attributes: { reason: user ? 'bad_password' : 'unknown_user' },
1777
+ });
1778
+ return res.status(401).json({ error: 'Invalid email or password' });
1779
+ }
1780
+ track('auth.login.success', { userId: user.id, ip, userAgent: req.get('user-agent'), attributes: { method: 'password' } });
1781
+ res.json({ ok: true });
1782
+ });
1783
+ ```
1784
+
1785
+ `track(type, { userId, sessionId, ip, userAgent, severity, ts, attributes })` is fire-and-forget: it buffers up to 1000 events, sends batches of up to 100 every 2 seconds, and never throws. `flush()` returns a promise for scripts that exit right away. It works in any Node process that can resolve the runtime credentials, with or without the preload.
1786
+
1787
+ Recommended auth events: `auth.login.failure`, `auth.login.success`, `auth.otp.send.requested`, `auth.otp.send.result`, `auth.otp.verify.failure`, `auth.otp.verify.success`, `auth.password_reset.requested`, `auth.password_reset.completed`, `auth.mfa.challenge.failure`, `auth.mfa.challenge.success`, `auth.session.revoked`.
1788
+
1789
+ Check what arrives with `npx securenow telemetry fields --since 1h --json` (it lists custom event types). From a shell or a non-JS service: `npx securenow event send <type> --user <id> --ip <ip> --attrs k=v`.
1790
+
1791
+ ---
1792
+
1793
+ ## Session Revocation
1794
+
1795
+ Revoke a stolen session (or every session of a user) with `npx securenow revoke session <id>` / `revoke user <id>`; the SDK pulls active revocations every 30 seconds and rejects matching requests locally. It fails open: an outage never blocks sign-ins.
1796
+
1797
+ The session id is the SHA-256 of the Bearer token or of the first session cookie found (`__Secure-next-auth.session-token`, `__Host-next-auth.session-token`, `next-auth.session-token`, `connect.sid`, `session`, `sid`, plus `config.sessions.cookieNames` **(8.25.0+)**); the user id is the JWT `sub`.
1798
+
1799
+ **Zero code, every framework (8.25.0+)** — in `.securenow/runtime.json`:
1800
+
1801
+ ```json
1802
+ { "config": { "sessions": { "guard": true, "capture": true } } }
1803
+ ```
1804
+
1805
+ `guard` answers a revoked session or user with 401 and clears the session cookies; `capture` sends `session.seen` (deduplicated per session and IP every 5 minutes) for session-theft detection. It runs with `-r securenow/register`, Next.js, Nuxt and `securenow/firewall-only`, and needs the runtime API key.
1806
+
1807
+ **Express-style middleware** (Express, Connect, Polka, NestJS with the Express adapter, Feathers-Express):
1808
+
1809
+ ```js
1810
+ const sessions = require('securenow/sessions');
1811
+ // Emits session.seen (feeds impossible-travel / concurrent-session detection)
1812
+ // and rejects revoked sessions. Auto-detects NextAuth / connect.sid / JWT.
1813
+ app.use(sessions.guard());
1814
+ // ...or with your own ids:
1815
+ app.use(sessions.guard({ getSessionId: (req) => req.session?.id, getUserId: (req) => req.user?.id }));
1816
+ // ...or emit-only (detection without enforcement):
1817
+ app.use(sessions.capture());
1818
+ ```
1819
+
1820
+ **Anything else (8.25.0+)** — `sessions.check(req)` takes a Node request or a Web `Request` and returns `{ revoked, sessionId, userId }`; you return the 401:
1821
+
1822
+ ```js
1823
+ // Fastify
1824
+ fastify.addHook('onRequest', async (request, reply) => {
1825
+ if (sessions.check(request.raw).revoked) return reply.code(401).send({ error: 'session_revoked' });
1826
+ });
1827
+
1828
+ // Next.js route handler (Node.js runtime)
1829
+ import { check } from 'securenow/sessions';
1830
+ export async function GET(request) {
1831
+ if (check(request).revoked) return Response.json({ error: 'session_revoked' }, { status: 401 });
1832
+ return Response.json({ ok: true });
1500
1833
  }
1501
1834
  ```
1502
1835
 
1836
+ Koa (`sessions.check(ctx.req)`), Hono (`sessions.check(c.req.raw)`) and h3 (`sessions.check(event.node.req)`) work the same way; examples in SKILL-API.md, section 5.10.
1837
+
1838
+ **Lower level:** `sessions.isRevoked({ sessionId, userId })`. Call `sessions.start()` once at boot when this is all you use.
1839
+
1840
+ ---
1841
+
1842
+ ## Deployment
1843
+
1844
+ The same credentials file works everywhere; only how it reaches the app changes. Recipes with full Dockerfiles, Kubernetes manifests and systemd units are in SKILL-API.md, section 6.
1845
+
1846
+ - **Docker:** `CMD ["node", "-r", "securenow/register", "src/index.js"]` with `WORKDIR` set to the app folder. Mount the production file at `/app/.securenow/credentials.json` or pass `SECURENOW_CREDENTIALS` (`docker run --env-file securenow.env` with the line printed by `--format env`). Keep `.securenow/runtime.json` and `admin.json` out of the image with a `.dockerignore`. Next.js standalone: the file goes next to `server.js`. Nuxt: in the folder you run `node .output/server/index.mjs` from.
1847
+ - **Kubernetes:** a Secret mounted as `/app/.securenow/credentials.json` (`subPath`), or `SECURENOW_CREDENTIALS` from a Secret.
1848
+ - **PM2:** `node_args: ['-r', require.resolve('securenow/register')]` in `ecosystem.config.cjs`, fork or cluster.
1849
+ - **systemd / VMs:** `WorkingDirectory` = the app folder; `ExecStart=/usr/bin/node -r securenow/register src/index.js`.
1850
+ - **Railway, Render, Heroku, Fly:** `SECURENOW_CREDENTIALS` as a secret variable.
1851
+ - **Vercel:** `SECURENOW_CREDENTIALS` in the project's environment; not tested end to end, firewall not verified on serverless functions.
1852
+
1503
1853
  ---
1504
1854
 
1505
1855
  ## Advanced Configuration
@@ -1510,16 +1860,14 @@ All request bodies are automatically scanned and sensitive fields are redacted:
1510
1860
  {
1511
1861
  "apiKey": "snk_live_...",
1512
1862
  "app": {
1513
- "key": "my-production-app",
1514
- "name": "my-production-app",
1515
- "instance": "http://collector.example.com:4318"
1863
+ "key": "<app-uuid>",
1864
+ "name": "my-production-app"
1516
1865
  },
1517
1866
  "config": {
1518
- "runtime": { "noUuid": true, "strict": true, "deploymentEnvironment": "production" },
1867
+ "runtime": { "strict": true, "deploymentEnvironment": "production" },
1519
1868
  "otel": {
1520
- "headers": { "x-securenow-app-key": "my-production-app" },
1521
- "logLevel": "info",
1522
- "disableInstrumentations": ["fs", "dns"]
1869
+ "logLevel": "warn",
1870
+ "disableInstrumentations": ["@opentelemetry/instrumentation-dns", "@opentelemetry/instrumentation-net"]
1523
1871
  },
1524
1872
  "logging": { "enabled": true },
1525
1873
  "capture": {
@@ -1527,15 +1875,20 @@ All request bodies are automatically scanned and sensitive fields are redacted:
1527
1875
  "maxBodySize": 20480,
1528
1876
  "sensitiveFields": ["internal_id", "session_key"]
1529
1877
  },
1878
+ "networking": { "trustedProxies": ["cloudflare"] },
1530
1879
  "firewall": {
1531
- "tcp": true,
1532
- "versionCheckInterval": 10,
1533
- "syncInterval": 300
1534
- }
1880
+ "tcp": false,
1881
+ "versionCheckInterval": 2,
1882
+ "syncInterval": 3600,
1883
+ "rateLimitStore": "redis://redis:6379"
1884
+ },
1885
+ "sessions": { "guard": true, "capture": true }
1535
1886
  }
1536
1887
  }
1537
1888
  ```
1538
1889
 
1890
+ `sessions` needs 8.25.0+. To send telemetry to your own collector instead of SecureNow (advanced; the SecureNow dashboard then sees nothing), set `config.otel.endpoint`.
1891
+
1539
1892
  ### Using Multiple Logger Instances
1540
1893
 
1541
1894
  ```javascript
@@ -1545,14 +1898,14 @@ const authLogger = getLogger('auth-service', '1.0.0');
1545
1898
  const dbLogger = getLogger('database', '1.0.0');
1546
1899
  const apiLogger = getLogger('api-handler', '1.0.0');
1547
1900
 
1548
- authLogger.emit({
1901
+ authLogger?.emit({
1549
1902
  severityNumber: 9,
1550
1903
  severityText: 'INFO',
1551
1904
  body: 'User authenticated',
1552
1905
  attributes: { userId: 123 },
1553
1906
  });
1554
1907
 
1555
- dbLogger.emit({
1908
+ dbLogger?.emit({
1556
1909
  severityNumber: 13,
1557
1910
  severityText: 'WARN',
1558
1911
  body: 'Slow query detected',
@@ -1574,23 +1927,26 @@ if (isLoggingEnabled()) {
1574
1927
 
1575
1928
  ### Programmatic Configuration
1576
1929
 
1577
- Use `.securenow/runtime.json` for local development and tests; CI/production should mount the generated runtime credentials file:
1930
+ Use `.securenow/runtime.json` for local development and tests; CI/production should mount the generated runtime credentials file. There is no configuration API: the preload reads the file (or `SECURENOW_CREDENTIALS`) at boot.
1578
1931
 
1579
1932
  ```javascript
1580
1933
  // Reads .securenow/runtime.json first, with legacy/generated runtime credentials as fallbacks.
1581
1934
  require('securenow/register');
1582
1935
  ```
1583
1936
 
1937
+ `require('securenow/app-config')` exposes the resolved values (`resolveAll()`, `resolveFirewallOptions()`, ...) for diagnostics.
1938
+
1584
1939
  ---
1585
1940
 
1586
1941
  ## TypeScript Support
1587
1942
 
1588
- SecureNow includes full TypeScript definitions.
1943
+ SecureNow ships type definitions for `securenow/tracing`, `securenow/events`, `securenow/sessions`, `securenow/nextjs`, `securenow/nuxt`, `securenow/rate-limits` and `securenow/telemetry-schema`.
1589
1944
 
1590
1945
  ### Type Definitions
1591
1946
 
1592
1947
  ```typescript
1593
1948
  import { getLogger, isLoggingEnabled, Logger, LogRecord } from 'securenow/tracing';
1949
+ import { track } from 'securenow/events';
1594
1950
 
1595
1951
  // Get a typed logger
1596
1952
  const logger: Logger | null = getLogger('my-service', '1.0.0');
@@ -1606,38 +1962,52 @@ if (logger) {
1606
1962
  action: 'login',
1607
1963
  },
1608
1964
  };
1609
-
1965
+
1610
1966
  logger.emit(logRecord);
1611
1967
  }
1612
1968
 
1613
1969
  // Check if enabled
1614
1970
  const enabled: boolean = isLoggingEnabled();
1971
+
1972
+ // Custom event
1973
+ track('auth.login.success', { userId: 'u_123', attributes: { method: 'password' } });
1615
1974
  ```
1616
1975
 
1976
+ Run TypeScript apps with `node -r securenow/register --import tsx src/index.ts`, `node -r securenow/register -r ts-node/register src/index.ts`, or the compiled output.
1977
+
1617
1978
  ### Next.js with TypeScript
1618
1979
 
1619
1980
  ```typescript
1620
- // instrumentation.ts
1981
+ // instrumentation.ts (src/instrumentation.ts with a src/ layout)
1621
1982
  export async function register() {
1622
- if (process.env.NEXT_RUNTIME !== 'nodejs') return;
1623
-
1624
- const securenowNext = await import(/* webpackIgnore: true */ 'securenow/nextjs');
1625
- const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
1626
- registerSecureNow({ captureBody: true });
1627
- await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
1983
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
1984
+ try {
1985
+ const securenowNext = await import('securenow/nextjs');
1986
+ const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
1987
+ registerSecureNow({ captureBody: true });
1988
+ await import('securenow/nextjs-auto-capture');
1989
+ } catch (error) {
1990
+ console.error('[securenow] SecureNow did not start; the app is running without it:', error);
1991
+ }
1992
+ }
1628
1993
  }
1629
1994
  ```
1630
1995
 
1631
- ```javascript
1632
- // next.config.js
1633
- const { withSecureNow } = require('securenow/nextjs-webpack-config');
1996
+ ```typescript
1997
+ // next.config.ts (Next.js 15+)
1998
+ import type { NextConfig } from 'next';
1634
1999
 
1635
- module.exports = withSecureNow({
1636
- reactStrictMode: true,
1637
- });
2000
+ const nextConfig: NextConfig = {
2001
+ serverExternalPackages: ['securenow'],
2002
+ outputFileTracingIncludes: {
2003
+ '/*': ['./node_modules/securenow/**/*'],
2004
+ },
2005
+ };
2006
+
2007
+ export default nextConfig;
1638
2008
  ```
1639
2009
 
1640
- `withSecureNow()` adds the server external package config and `outputFileTracingIncludes` for standalone/self-hosted builds.
2010
+ The older `withSecureNow()` wrapper from `securenow/nextjs-webpack-config` still exists, but it also adds a `webpack` hook; the plain config above is what `init` writes and what is tested.
1641
2011
 
1642
2012
  ### NestJS with TypeScript
1643
2013
 
@@ -1669,25 +2039,22 @@ npx securenow env
1669
2039
  npx securenow status --env local
1670
2040
  ```
1671
2041
 
1672
- The output should show an app key, runtime API key source, collector endpoint, and deployment environment from `.securenow/runtime.json` or the mounted runtime credentials file.
2042
+ The output should show an app key, runtime API key source, collector endpoint, and deployment environment from `.securenow/runtime.json` or the mounted runtime credentials file. `Service name` must be the app key UUID.
1673
2043
 
1674
- **Check 2: Verify OTLP collector is running**
2044
+ **Check 2: Look at the right environment**
1675
2045
 
1676
- ```bash
1677
- curl http://localhost:4318/v1/traces
1678
- # Should return 200 or 405 (method not allowed)
1679
- ```
2046
+ `npx securenow init` writes `deploymentEnvironment: "local"`, while the CLI and dashboard default to `production`. Use `--env local` (or `--env all`), or set the environment you expect.
1680
2047
 
1681
2048
  **Check 3: Run doctor and enable debug diagnostics**
1682
2049
 
1683
2050
  ```bash
1684
2051
  npx securenow doctor --json
1685
- OTEL_LOG_LEVEL=debug node -r securenow/register app.js
2052
+ npx securenow test-span
1686
2053
  ```
1687
2054
 
1688
- Doctor checks OTLP reachability and flags duplicate `@opentelemetry/api` versions that can silently leave tracing on the OpenTelemetry noop provider.
2055
+ Doctor probes traces, logs, the API and firewall sync, and flags duplicate `@opentelemetry/api` versions that can silently leave tracing on the OpenTelemetry noop provider. `test-span` should print `Span accepted (HTTP 200)`.
1689
2056
 
1690
- For a persistent setting, update credentials:
2057
+ For a persistent diagnostic setting, update credentials (or run once with `OTEL_LOG_LEVEL=debug`):
1691
2058
 
1692
2059
  ```json
1693
2060
  {
@@ -1699,12 +2066,14 @@ For a persistent setting, update credentials:
1699
2066
 
1700
2067
  Look for lines like:
1701
2068
  ```
1702
- [securenow] OTel SDK started -> http://localhost:4318/v1/traces
2069
+ [securenow] OTel SDK started → https://ingest.securenow.ai/v1/traces
1703
2070
  ```
1704
2071
 
2072
+ No `[securenow]` line at all means the process that serves HTTP never loaded the SDK: put the preload on the command that actually starts it (start script, Docker `CMD`, PM2 `node_args`, systemd `ExecStart`).
2073
+
1705
2074
  **Check 4: Verify initialization order**
1706
2075
 
1707
- SecureNow must be required BEFORE any other modules:
2076
+ SecureNow must be loaded BEFORE any other modules:
1708
2077
 
1709
2078
  ```javascript
1710
2079
  // Correct
@@ -1716,19 +2085,28 @@ const express = require('express');
1716
2085
  require('securenow/register');
1717
2086
  ```
1718
2087
 
2088
+ In ESM apps, use `node -r securenow/register` instead of an `import` in your code, and look for `[securenow] ESM loader hook auto-registered (module.register)` at startup (Node >= 20.6).
2089
+
2090
+ **Check 5: Wrong credentials?**
2091
+
2092
+ The startup line shows `credentials=<file>` or `credentials=env:SECURENOW_CREDENTIALS`. `SECURENOW_CREDENTIALS` wins over every file; a `runtime.json` beats a `credentials.production.json` in the same folder; a parent folder or `~/.securenow/` is used when the project has none. Remove the stray source.
2093
+
1719
2094
  ### Firewall Not Blocking IPs
1720
2095
 
1721
2096
  **Check 1: Is a runtime API key in `.securenow/runtime.json` or the mounted runtime credentials file?**
1722
2097
 
1723
2098
  ```bash
1724
2099
  npx securenow api-key show
2100
+ npx securenow env # "Firewall enabled", not "missing firewall API key"
1725
2101
  ```
1726
2102
 
1727
- **Check 2: Is the IP in the blocklist?**
2103
+ **Check 2: Is the IP in the blocklist, for this app and environment?**
1728
2104
 
1729
2105
  ```bash
1730
- npx securenow blocklist
1731
- npx securenow firewall test-ip 1.2.3.4
2106
+ npx securenow blocklist --env production
2107
+ npx securenow firewall test-ip 1.2.3.4 --path /admin --env production
2108
+ npx securenow firewall apps # firewall on for that environment?
2109
+ npx securenow firewall hits --ip 1.2.3.4
1732
2110
  ```
1733
2111
 
1734
2112
  **Check 3: Check startup logs for sync status**
@@ -1736,14 +2114,15 @@ npx securenow firewall test-ip 1.2.3.4
1736
2114
  You should see:
1737
2115
  ```
1738
2116
  [securenow] Firewall: ENABLED
1739
- [securenow] Firewall: synced 142 blocked IPs
2117
+ [securenow] Firewall: environment=production
2118
+ [securenow] Firewall: synced 142 global blocked IPs (138 exact + 4 CIDR ranges) and 0 scoped block rules
1740
2119
  ```
1741
2120
 
1742
- If you see `initial sync failed`, check the API URL and key.
2121
+ If you see `initial sync failed`, check the network and the key. `Firewall: environment=` must match the environment of the block.
1743
2122
 
1744
2123
  **Check 4: Wait for propagation**
1745
2124
 
1746
- After blocking an IP, it takes 10-15 seconds to propagate (one version-check interval).
2125
+ After blocking an IP, it takes up to one version-check interval (10 seconds by default) to propagate.
1747
2126
 
1748
2127
  **Check 5: Are you behind a proxy?**
1749
2128
 
@@ -1755,11 +2134,15 @@ proxy_set_header X-Real-IP $remote_addr;
1755
2134
  proxy_set_header X-Forwarded-Proto $scheme;
1756
2135
  ```
1757
2136
 
1758
- SecureNow trusts private, loopback, and same-host proxy peers automatically. For external load balancers or public proxy IPs, add them to `config.networking.trustedProxies` in `.securenow/runtime.json` or the mounted runtime credentials file: exact IPs, IPv4 ranges such as `"203.0.113.0/24"`, or `"cloudflare"` for Cloudflare's published edge ranges (8.21.0+). If no forwarded header reaches the app, SecureNow can detect the attack shape but cannot recover the real visitor IP from traces. Starter protection refuses block mode for an app whose traffic arrives through a proxy it doesn't trust, because a block would hit the proxy and everyone behind it.
2137
+ SecureNow trusts private, loopback, and same-host proxy peers automatically, and Unix-socket connections from 8.25.0. For external load balancers or public proxy IPs, add them to `config.networking.trustedProxies` in `.securenow/runtime.json` or the mounted runtime credentials file: exact IPs, IPv4 ranges such as `"203.0.113.0/24"`, or `"cloudflare"` for Cloudflare's published edge ranges (8.21.0+). If no forwarded header reaches the app, SecureNow can detect the attack shape but cannot recover the real visitor IP from traces. Starter protection refuses block mode for an app whose traffic arrives through a proxy it doesn't trust, because a block would hit the proxy and everyone behind it.
2138
+
2139
+ **Check 6: Allowlist or layers**
1759
2140
 
1760
- **Check 6: Using PM2?**
2141
+ An IP on the app's allowlist skips the blocklist. Layers 2 and 3 only see direct connections; behind a proxy, Layer 1 does the blocking. At Layer 1, IPv6 addresses and ranges match from 8.25.0 (before, IPv6 ranges never matched).
1761
2142
 
1762
- Make sure `node_args: '-r securenow/register'` is in your `ecosystem.config.cjs`. Without it, PM2 restarts skip the SDK entirely.
2143
+ **Check 7: Using PM2?**
2144
+
2145
+ Make sure `node_args: ['-r', require.resolve('securenow/register')]` is in your `ecosystem.config.cjs`. Without it, PM2 restarts skip the SDK entirely; with a bare `'-r securenow/register'`, cluster workers started from another folder (`pm2 resurrect` at boot) crash with `Cannot find module 'securenow/register'`.
1763
2146
 
1764
2147
  ### Logs Not Appearing
1765
2148
 
@@ -1773,24 +2156,24 @@ Make sure `node_args: '-r securenow/register'` is in your `ecosystem.config.cjs`
1773
2156
  }
1774
2157
  ```
1775
2158
 
1776
- **Check 2: Verify console instrumentation is loaded**
2159
+ **Check 2: Verify the SDK is loaded**
1777
2160
 
1778
- ```javascript
1779
- require('securenow/register');
2161
+ ```bash
2162
+ node -r securenow/register app.js
1780
2163
  ```
1781
2164
 
1782
2165
  **Check 3: Check console output**
1783
2166
 
1784
2167
  You should see:
1785
2168
  ```
1786
- [securenow] Logging: ENABLED -> http://localhost:4318/v1/logs
2169
+ [securenow] Logging: ENABLED → https://ingest.securenow.ai/v1/logs
1787
2170
  ```
1788
2171
 
1789
- **Check 4: Verify OTLP logs endpoint**
2172
+ **Check 4: Query them**
1790
2173
 
1791
2174
  ```bash
1792
- curl http://localhost:4318/v1/logs
1793
- # Should return 200 or 405
2175
+ npx securenow logs --since 15m --env local
2176
+ npx securenow log send "manual test" --level info
1794
2177
  ```
1795
2178
 
1796
2179
  ### Request Body Not Captured
@@ -1811,10 +2194,15 @@ Body capture only works for:
1811
2194
  - `application/json`
1812
2195
  - `application/x-www-form-urlencoded`
1813
2196
  - `application/graphql`
2197
+ - `multipart/form-data` (metadata)
2198
+
2199
+ **Check 3: Does the route read its body?**
2200
+
2201
+ SecureNow records the body as the app reads it. A route without a body parser (Koa without `koa-bodyparser`, Polka without a parser) records none.
1814
2202
 
1815
- **Check 3: Check body size**
2203
+ **Check 4: Check body size**
1816
2204
 
1817
- Bodies larger than `config.capture.maxBodySize` are truncated:
2205
+ Bodies larger than `config.capture.maxBodySize` are recorded as `[TOO LARGE: <n> bytes]` instead of their content:
1818
2206
 
1819
2207
  ```json
1820
2208
  {
@@ -1824,6 +2212,10 @@ Bodies larger than `config.capture.maxBodySize` are truncated:
1824
2212
  }
1825
2213
  ```
1826
2214
 
2215
+ **Check 5: Next.js Edge runtime**
2216
+
2217
+ Routes with `export const runtime = 'edge'` and Edge middleware are not instrumented.
2218
+
1827
2219
  ### High Memory Usage
1828
2220
 
1829
2221
  **Option 1: Disable body capture**
@@ -1846,16 +2238,18 @@ Bodies larger than `config.capture.maxBodySize` are truncated:
1846
2238
  }
1847
2239
  ```
1848
2240
 
1849
- **Option 3: Disable specific instrumentations**
2241
+ **Option 3: Disable specific instrumentations** (full package names)
1850
2242
 
1851
2243
  ```json
1852
2244
  {
1853
2245
  "config": {
1854
- "otel": { "disableInstrumentations": ["fs", "dns", "net"] }
2246
+ "otel": { "disableInstrumentations": ["@opentelemetry/instrumentation-dns", "@opentelemetry/instrumentation-net"] }
1855
2247
  }
1856
2248
  }
1857
2249
  ```
1858
2250
 
2251
+ Keep `config.otel.middlewareSpans` off (the default): one span per middleware per request is most of the telemetry on a busy Express app.
2252
+
1859
2253
  ### Next.js Instrumentation Not Working
1860
2254
 
1861
2255
  **Check 1: Is `securenow` externalized?**
@@ -1869,15 +2263,15 @@ module.exports = {
1869
2263
  };
1870
2264
  ```
1871
2265
 
1872
- For Next.js < 15, add `securenow` to `experimental.serverComponentsExternalPackages` and enable `experimental.instrumentationHook`.
2266
+ For Next.js 14, the keys go under `experimental` together with `instrumentationHook: true` (see Option B above).
1873
2267
 
1874
2268
  **Check 2: Verify instrumentation file location**
1875
2269
 
1876
- `instrumentation.ts` or `instrumentation.js` must be in the project root (same level as `app/` or `pages/`).
2270
+ `instrumentation.ts` or `instrumentation.js` must sit next to `app/` or `pages/`: the project root, or `src/` when the project uses a `src/` layout.
1877
2271
 
1878
2272
  **Check 3: Check for OTel MODULE_NOT_FOUND errors**
1879
2273
 
1880
- If you see `MODULE_NOT_FOUND` for `@opentelemetry/*`, `./firewall`, or other SecureNow runtime modules, your `next.config.js` is missing the externalization or standalone output include. Run `npx securenow init`; if your config is custom, use the prompt it prints to merge the edit safely.
2274
+ If you see `MODULE_NOT_FOUND` for `@opentelemetry/*`, `./firewall`, or other SecureNow runtime modules, or every standalone request returns 500, your instrumentation file still uses `webpackIgnore` (written by `init` before 8.24.0) or `next.config.js` is missing the externalization or standalone output include. Run `npx securenow init`; if your config is custom, use the prompt it prints to merge the edit safely.
1881
2275
 
1882
2276
  **Check 4: Restart dev server**
1883
2277
 
@@ -1890,7 +2284,9 @@ npm run dev
1890
2284
 
1891
2285
  **Problem: Different service names for each worker**
1892
2286
 
1893
- **Solution: Set `config.runtime.noUuid`**
2287
+ **Solution: let `config.runtime.noUuid` stay automatic**
2288
+
2289
+ With `app.key` in the credentials, every worker reports under the app key. A per-worker suffix appears only when `noUuid` is set to `false` or no app key was found (check the `credentials=` startup line):
1894
2290
 
1895
2291
  ```json
1896
2292
  {
@@ -1900,8 +2296,6 @@ npm run dev
1900
2296
  }
1901
2297
  ```
1902
2298
 
1903
- This uses the same service name for all workers.
1904
-
1905
2299
  **Problem: Workers not instrumented / firewall not active**
1906
2300
 
1907
2301
  **Solution: Use node_args in PM2 config**
@@ -1913,12 +2307,16 @@ module.exports = {
1913
2307
  name: 'my-app',
1914
2308
  script: './app.js',
1915
2309
  instances: 4,
1916
- node_args: '-r securenow/register',
2310
+ node_args: ['-r', require.resolve('securenow/register')],
1917
2311
  }]
1918
2312
  };
1919
2313
  ```
1920
2314
 
1921
- Without `node_args`, PM2 starts your script directly without the securenow preload, so neither tracing nor the firewall will be active.
2315
+ Without `node_args`, PM2 starts your script directly without the securenow preload, so neither tracing nor the firewall will be active. Use the absolute path: in cluster mode Node resolves a bare `-r securenow/register` from the PM2 daemon's folder, and the workers crash-loop when the daemon was started elsewhere (as `pm2 resurrect` does at boot). Rate limits count per worker unless `config.firewall.rateLimitStore` points at Redis.
2316
+
2317
+ ### Tests Sending Data
2318
+
2319
+ Test runs load the project's `.securenow` credentials and report to the real app. Run them with `SECURENOW_DISABLED=1 npm test`.
1922
2320
 
1923
2321
  ---
1924
2322
 
@@ -1933,7 +2331,7 @@ Do not hardcode configuration in code or deployment dashboards. Use `.securenow/
1933
2331
  const appKey = 'hardcoded-value';
1934
2332
 
1935
2333
  // Good: use .securenow/runtime.json
1936
- // { "app": { "key": "<uuid>", "instance": "https://ingest.securenow.ai" }, "apiKey": "snk_live_..." }
2334
+ // { "app": { "key": "<uuid>", "name": "my-api" }, "apiKey": "snk_live_..." }
1937
2335
  ```
1938
2336
 
1939
2337
  ### 2. Use Structured Logging
@@ -1947,7 +2345,7 @@ console.log('User 123 logged in');
1947
2345
  // Better -- structured attributes
1948
2346
  console.log('User logged in', {
1949
2347
  userId: 123,
1950
- email: 'user@example.com',
2348
+ plan: 'pro',
1951
2349
  timestamp: new Date().toISOString(),
1952
2350
  });
1953
2351
  ```
@@ -1971,18 +2369,21 @@ console.debug('Cache hit', { key: 'user:123' });
1971
2369
 
1972
2370
  ### 4. Don't Log Sensitive Data
1973
2371
 
1974
- Even though SecureNow automatically redacts common sensitive fields, avoid logging:
2372
+ Request bodies, headers and (from 8.25.0) log lines are redacted, but log redaction recognizes field names and value shapes, not meaning:
1975
2373
 
1976
2374
  ```javascript
1977
- // Bad
2375
+ // Bad: "hunter2" has no secret shape and no field name, so it is exported
2376
+ console.log(`Password for ${user.id} reset to hunter2`);
2377
+
2378
+ // Redacted (8.25.0+), but still better not logged
1978
2379
  console.log('Login attempt', {
1979
- email: 'user@example.com',
1980
- password: 'secret123', // Will be redacted, but don't log it!
2380
+ email: 'user@example.com', // -> [REDACTED]
2381
+ password: 'secret123', // -> [REDACTED]
1981
2382
  });
1982
2383
 
1983
2384
  // Good
1984
2385
  console.log('Login attempt', {
1985
- email: 'user@example.com',
2386
+ userId: user.id,
1986
2387
  timestamp: Date.now(),
1987
2388
  });
1988
2389
  ```
@@ -2000,51 +2401,47 @@ const dbLogger = getLogger('database', '1.0.0');
2000
2401
  const apiLogger = getLogger('api', '1.0.0');
2001
2402
  ```
2002
2403
 
2003
- ### 6. Disable Body Capture Outside Development
2404
+ ### 6. Keep Body Capture On, Tune Redaction
2405
+
2406
+ Detections read request bodies (SQL injection, XSS and command payloads often arrive in POST bodies). Rather than turning capture off, add your own field names:
2004
2407
 
2005
2408
  ```json
2006
2409
  {
2007
2410
  "config": {
2008
- "capture": { "body": false }
2411
+ "capture": { "sensitiveFields": ["national_id", "internal_token"] }
2009
2412
  }
2010
2413
  }
2011
2414
  ```
2012
2415
 
2416
+ ### 7. Emit Auth Events
2417
+
2418
+ `track()` the sign-in, OTP, reset and MFA outcomes (see [Custom Security Events](#custom-security-events)): they are what brute-force and account-takeover detection needs.
2419
+
2013
2420
  ---
2014
2421
 
2015
2422
  ## Supported Runtimes
2016
2423
 
2017
- - **Node.js:** 18+
2018
- - **Frameworks:** Express, Next.js, Nuxt 3, Fastify, NestJS, Koa, Hapi, and more
2019
- - **Databases:** PostgreSQL, MySQL, MongoDB, Redis
2020
- - **HTTP Clients:** axios, fetch, node-fetch, got, request
2021
- - **GraphQL:** Apollo Server, GraphQL.js
2022
- - **Message Queues:** Redis, BullMQ
2023
- - **And many more via OpenTelemetry auto-instrumentation**
2424
+ - **Node.js:** tested on Node 24.22 (CI: Node 24); use a patched 22, 24 or 26 release (see the security note). ESM apps need Node >= 20.6 for the automatic loader hook.
2425
+ - **Bun:** 1.3, for apps served through `node:http` (`Bun.serve` is not supported)
2426
+ - **Frameworks (tested):** 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 15.5 and 16.4, Nuxt 3.21 and 4.6; PM2 5.4 (fork and cluster). Older majors are expected to work but are not tested.
2427
+ - **Databases:** PostgreSQL, MySQL, MongoDB, Redis (OpenTelemetry auto-instrumentation)
2428
+ - **HTTP Clients:** Node `http`/`https` and the clients built on them, `fetch` (undici)
2429
+ - **Not supported:** Deno, edge runtimes (Cloudflare Workers, Vercel Edge), browser telemetry. Not tested: Vercel, AWS Lambda and other serverless platforms.
2024
2430
 
2025
2431
  ---
2026
2432
 
2027
2433
  ## Automatic Instrumentations
2028
2434
 
2029
- SecureNow automatically instruments:
2435
+ SecureNow loads the OpenTelemetry Node auto-instrumentations (`@opentelemetry/auto-instrumentations-node`), plus its own HTTP and MongoDB setup and route capture for Fastify (and, 8.25.0+, Hono, h3, Feathers, Polka and Next.js). Among them:
2030
2436
 
2031
2437
  - HTTP/HTTPS (incoming and outgoing)
2032
- - Express.js
2033
- - Fastify
2034
- - Koa
2035
- - Hapi
2036
- - Next.js
2037
- - PostgreSQL
2038
- - MySQL/MySQL2
2039
- - MongoDB
2040
- - Redis
2041
- - GraphQL
2042
- - gRPC
2043
- - DNS
2044
- - File System
2045
- - And 50+ more libraries
2046
-
2047
- No code changes needed!
2438
+ - Express.js, Koa, Hapi, NestJS
2439
+ - PostgreSQL, MySQL/MySQL2, MongoDB, Redis/ioredis
2440
+ - GraphQL, gRPC
2441
+ - DNS, net
2442
+ - And the rest of the OpenTelemetry auto-instrumentation set
2443
+
2444
+ Skip any of them with `config.otel.disableInstrumentations` (full package names). No code changes needed!
2048
2445
 
2049
2446
  ---
2050
2447
 
@@ -2058,53 +2455,54 @@ No code changes needed!
2058
2455
  +---------------------------+
2059
2456
  | SecureNow Library |
2060
2457
  | +---------------------+ |
2061
- | | Firewall Layer | | <-- Blocks IPs before they reach your app
2458
+ | | Firewall Layer | | <-- Blocks, rate-limits and challenges before your app
2062
2459
  | +---------------------+ |
2063
2460
  | | Auto-instrumentation | |
2064
2461
  | | Console wrapper | |
2065
2462
  | | Body capture | |
2463
+ | | Crash capture | |
2066
2464
  | +---------------------+ |
2067
2465
  +---------------------------+
2068
2466
  |
2069
2467
  +---------+---------+
2070
2468
  | |
2071
2469
  v v
2072
- +-----------+ +-----------+
2073
- | OTel SDK | | SecureNow |
2074
- | OTLP | | API |
2075
- | Exporters | | (blocklist|
2076
- +-----------+ | sync) |
2077
- | +-----------+
2078
- v
2079
- +-----------+
2080
- | Your OTLP |
2081
- | Backend |
2082
- +-----------+
2470
+ +-----------+ +-------------+
2471
+ | OTel SDK | | Firewall |
2472
+ | OTLP | | sync |
2473
+ | Exporters | | (blocklist, |
2474
+ +-----------+ | rules) |
2475
+ | +-------------+
2476
+ v |
2477
+ +--------------------------------+
2478
+ | SecureNow ingest gateway |
2479
+ | https://ingest.securenow.ai |
2480
+ +--------------------------------+
2083
2481
  ```
2084
2482
 
2085
2483
  ---
2086
2484
 
2087
2485
  ## Performance
2088
2486
 
2089
- - **Minimal overhead:** < 1% CPU and memory impact
2090
- - **Batch export:** Traces and logs are batched before sending
2487
+ - **Low overhead:** traces and logs are batched before export
2091
2488
  - **Async processing:** No blocking of application threads
2092
- - **Configurable:** Disable instrumentations you don't need
2093
- - **Firewall:** Sub-millisecond IP lookups using pre-compiled hash sets and sorted CIDR lists
2489
+ - **Configurable:** Disable instrumentations you don't need; middleware spans are off by default
2490
+ - **Firewall:** Sub-millisecond IP lookups using pre-compiled hash sets and CIDR lists, all in memory
2094
2491
  - **Smart sync:** Version-based polling with ETag/304 eliminates unnecessary data transfer
2492
+ - **Rate limits:** the firewall never waits on Redis when a shared store is configured
2095
2493
 
2096
2494
  ---
2097
2495
 
2098
2496
  ## Security
2099
2497
 
2100
- - **Automatic redaction** of sensitive fields (passwords, tokens, keys)
2498
+ - **Automatic redaction** of sensitive fields (passwords, tokens, keys, emails, card numbers, OTP codes) in captured headers and bodies
2101
2499
  - **Configurable** sensitive field patterns
2102
- - **No data stored** locally -- everything sent to your OTLP backend
2500
+ - **No data stored** locally -- telemetry is sent to the SecureNow ingest gateway
2103
2501
  - **Multi-layer firewall** -- blocks IPs at HTTP, TCP, OS, and cloud-edge levels
2104
- - **API keys** with granular scopes, IP allowlisting, and SHA-256 hashing
2105
- - **Allowlist support** -- trusted IPs are never blocked
2502
+ - **Runtime API keys** scoped to one app (`traces:write`, `logs:write`, `firewall:read`, `blocklist:read`, `allowlist:read`)
2503
+ - **Allowlist support** -- deny-by-default lock to known IPs
2106
2504
  - **Fail-open by default** -- network issues never accidentally block legitimate traffic
2107
- - **Open source** -- audit the code yourself
2505
+ - **Readable source** -- the package ships unminified; audit it in `node_modules/securenow`
2108
2506
 
2109
2507
  ---
2110
2508
 
@@ -2116,21 +2514,20 @@ ISC
2116
2514
 
2117
2515
  ## Support
2118
2516
 
2119
- - **Documentation:** [GitHub Docs](https://github.com/your-repo/securenow-npm/tree/main/docs)
2120
- - **Issues:** [GitHub Issues](https://github.com/your-repo/securenow-npm/issues)
2121
- - **Examples:** [GitHub Examples](https://github.com/your-repo/securenow-npm/tree/main/examples)
2517
+ - **Documentation:** [`SKILL-API.md`](./SKILL-API.md), [`SKILL-CLI.md`](./SKILL-CLI.md) and `TELEMETRY-SQL.md` in this package, `npx securenow help`, and [securenow.ai](https://securenow.ai)
2518
+ - **Contact:** [contact@securenow.ai](mailto:contact@securenow.ai)
2122
2519
 
2123
2520
  ---
2124
2521
 
2125
2522
  ## Changelog
2126
2523
 
2127
- See [CHANGELOG.md](CHANGELOG.md) for version history.
2524
+ `npx securenow version` prints the installed version. Release notes are kept in the CHANGELOG of the SecureNow source repository.
2128
2525
 
2129
2526
  ---
2130
2527
 
2131
2528
  ## Contributing
2132
2529
 
2133
- Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
2530
+ Questions, bug reports and suggestions: [contact@securenow.ai](mailto:contact@securenow.ai).
2134
2531
 
2135
2532
  ---
2136
2533