securenow 8.24.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 +693 -305
- package/README.md +76 -28
- package/SKILL-API.md +648 -525
- package/SKILL-CLI.md +293 -663
- package/app-config.js +19 -1
- package/cidr.js +139 -20
- package/cli/init.js +77 -15
- package/cli/utils.js +26 -14
- package/cli.js +7 -5
- package/console-instrumentation.js +6 -0
- package/firewall-cloud.js +81 -32
- package/firewall-iptables.js +123 -62
- package/firewall-only.js +2 -0
- package/firewall-tcp.js +19 -5
- package/firewall.js +21 -1
- package/framework-routes.js +222 -0
- package/log-format.js +2 -0
- package/log-redaction.js +135 -0
- package/mcp/catalog.js +14 -9
- package/next-route.js +61 -0
- package/nextjs-middleware.js +8 -4
- package/nextjs-wrapper.js +8 -4
- package/nextjs.d.ts +35 -67
- package/nextjs.js +29 -6
- package/package.json +5 -2
- package/register.d.ts +27 -34
- package/request-capture.js +11 -6
- package/resolve-ip.js +23 -5
- package/sessions.d.ts +27 -0
- package/sessions.js +71 -10
- package/tracing.d.ts +40 -121
- package/tracing.js +12 -4
package/NPM_README.md
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
10
|
-
- Automatic logging with console instrumentation
|
|
11
|
-
- Built-in sensitive data redaction
|
|
12
|
-
- Request body capture for debugging
|
|
13
|
-
- Multi-layer firewall --
|
|
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
|
-
-
|
|
18
|
-
-
|
|
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
|
|
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
|
|
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
|
-
- [
|
|
41
|
-
- [
|
|
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
|
|
89
|
-
- **Nuxt 3**:
|
|
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 (
|
|
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]
|
|
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:
|
|
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
|
-
**
|
|
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 <
|
|
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 (
|
|
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
|
|
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
|
-
|
|
232
|
+
Cursor uses `.cursor/mcp.json`: `{ "mcpServers": { "securenow": { "command": "npx", "args": ["securenow", "mcp"] } } }`.
|
|
201
233
|
|
|
202
|
-
|
|
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> --
|
|
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
|
|
|
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.
|
|
312
|
+
|
|
262
313
|
### Alerting
|
|
263
314
|
|
|
264
|
-
|
|
315
|
+
#### Revoke / kill sessions
|
|
265
316
|
|
|
266
|
-
|
|
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
|
-
```
|
|
276
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
346
392
|
|
|
347
|
-
#
|
|
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
|
|
396
|
+
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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:**
|
|
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
|
-
| `--
|
|
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
|
-
|
|
|
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,8 +672,9 @@ 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
|
-
#
|
|
584
|
-
#
|
|
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.
|
|
587
680
|
# Hosts without file mounts (Vercel, Railway, Render, Heroku, Fly):
|
|
@@ -589,6 +682,9 @@ npx securenow credentials runtime --env production --format env
|
|
|
589
682
|
# -> SECURENOW_CREDENTIALS=<base64>; store it as a secret environment variable.
|
|
590
683
|
# It takes precedence over credential files.
|
|
591
684
|
|
|
685
|
+
# Test runs: keep them from reporting to the real app
|
|
686
|
+
SECURENOW_DISABLED=1 npm test
|
|
687
|
+
|
|
592
688
|
# Use --json for machine-readable output
|
|
593
689
|
npx securenow logs --json --level error | jq '.logs'
|
|
594
690
|
```
|
|
@@ -597,59 +693,77 @@ npx securenow logs --json --level error | jq '.logs'
|
|
|
597
693
|
|
|
598
694
|
| Category | Command | Description |
|
|
599
695
|
|----------|---------|-------------|
|
|
600
|
-
| **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) |
|
|
601
698
|
| **Auth** | `login` | Friendly onboarding: admin auth + app runtime connection |
|
|
602
|
-
| | `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 |
|
|
603
700
|
| | `app connect` | Select/create app and write SDK runtime config |
|
|
604
701
|
| | `logout` | Clear admin auth; runtime app config remains |
|
|
605
702
|
| | `whoami` | Show admin auth and runtime app status separately |
|
|
703
|
+
| | `credentials runtime` | Tokenless production runtime file (`--env`, `--out`, `--stdout`, `--format env`) |
|
|
606
704
|
| **Apps** | `apps` | List applications |
|
|
607
|
-
| | `apps create <name>` | Create application |
|
|
705
|
+
| | `apps create <name>` | Create application (`--hosts`, `--instance`) |
|
|
608
706
|
| | `apps info <id>` | Application details |
|
|
609
707
|
| | `apps delete <id>` | Delete application |
|
|
610
708
|
| | `apps default <key>` | Set default app |
|
|
709
|
+
| | `apps discover` / `apps scan` | Discover subdomains and add them as apps |
|
|
611
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 |
|
|
612
711
|
| | `api-key set <snk_live_...> [--global]` | Save runtime API key to `.securenow/runtime.json` |
|
|
613
712
|
| | `api-key show` | Show masked key + source |
|
|
614
713
|
| | `api-key clear [--global]` | Remove stored key (leaves session/app) |
|
|
615
|
-
| **Observe** | `
|
|
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 |
|
|
616
717
|
| | `traces show <id>` | Trace details |
|
|
617
718
|
| | `traces analyze <id>` | AI trace analysis |
|
|
618
719
|
| | `logs` | List logs |
|
|
619
720
|
| | `logs trace <id>` | Logs for a trace |
|
|
721
|
+
| | `logs errors` | Error lines grouped by message |
|
|
620
722
|
| | `analytics` | Response analytics |
|
|
621
723
|
| | `status` | Dashboard overview |
|
|
622
|
-
| **Detect** | `
|
|
724
|
+
| **Detect** | `protect` | Starter protection: `status`, `enable`, `disable`, `block`, `monitor`, `learn` |
|
|
725
|
+
| | `notifications` | List notifications |
|
|
623
726
|
| | `notifications unread` | Unread count |
|
|
624
727
|
| | `notifications read <id>` | Mark read |
|
|
625
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` |
|
|
626
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`) |
|
|
627
735
|
| | `alerts rules show <id>` | Rule detail |
|
|
628
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 |
|
|
629
738
|
| | `alerts rules runs <id>` / `run <id>` | Run history (outcome of each run) / run a periodic rule now |
|
|
630
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 |
|
|
631
744
|
| | `alerts channels` | Alert channels |
|
|
632
745
|
| | `alerts history` | Alert history |
|
|
633
746
|
| **Investigate** | `ip <addr>` | IP intelligence |
|
|
634
747
|
| | `ip traces <addr>` | Traces from IP |
|
|
635
748
|
| | `forensics "<query>"` | NL forensic query |
|
|
749
|
+
| | `forensics chat` / `forensics conversations` | Forensics chat with memory |
|
|
636
750
|
| | `forensics library` | Saved queries |
|
|
637
|
-
| | `
|
|
638
|
-
| | `
|
|
639
|
-
|
|
|
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 |
|
|
640
754
|
| | `firewall test-ip <ip>` | Check if an IP would be blocked |
|
|
755
|
+
| | `firewall hits` | Blocks, 429s and challenges per rule and per IP |
|
|
641
756
|
| | `run --firewall-only <script>` | Preload firewall without OTel tracing overhead |
|
|
642
757
|
| **Remediate** | `blocklist` | Blocked IPs |
|
|
643
|
-
| | `
|
|
644
|
-
| | `automation dry-run <id>` | Preview automation matches without writing blocks |
|
|
645
|
-
| | `automation execute <id> --yes` | Run an automation rule now |
|
|
646
|
-
| | `blocklist add <ip>` | Block IP |
|
|
758
|
+
| | `blocklist add <ip>` | Block IP (`--route`, `--mode`, `--method`, `--duration`, `--reason`) |
|
|
647
759
|
| | `blocklist unblock <id>` | Unblock IP and retain report/history |
|
|
648
760
|
| | `blocklist remove <id>` | Compatibility alias for unblock |
|
|
761
|
+
| | `blocklist approve <id>` / `blocklist reject <id>` | Decide a pending block |
|
|
649
762
|
| | `blocklist stats` | Block stats |
|
|
650
|
-
| | `
|
|
651
|
-
| | `ratelimit from-text
|
|
652
|
-
| | `
|
|
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 |
|
|
653
767
|
| | `allowlist` | Allowed IPs (restrict-mode) |
|
|
654
768
|
| | `allowlist add <ip>` | Allow IP (`--label`, `--reason`) |
|
|
655
769
|
| | `allowlist remove <id>` | Remove from allowlist |
|
|
@@ -666,17 +780,21 @@ npx securenow logs --json --level error | jq '.logs'
|
|
|
666
780
|
| | `fp dry-run --conditions '[...]'` | Dry-run against last 3 days of traces |
|
|
667
781
|
| | `fp ai-fill --description "..."` | AI-generate exclusion conditions |
|
|
668
782
|
| | `fp mark <notification-id> <ip>` | Mark an IP as FP on a notification |
|
|
669
|
-
| **Telemetry** | `
|
|
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 |
|
|
670
786
|
| | `test-span [<name>]` | Emit a test span to the collector |
|
|
671
787
|
| **Utilities** | `redact '<json>' [--fields f1,f2]` | Redact sensitive fields (accepts `@file.json`) |
|
|
672
788
|
| | `cidr match <ip> <cidrs>` | IP vs. CIDR list (exit 0 hit / 2 miss) |
|
|
673
789
|
| | `cidr parse <cidr>` | Parse CIDR (network, broadcast, mask, size) |
|
|
674
790
|
| | `env [--json]` | Show resolved config (service name, endpoints, credentials) |
|
|
675
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 |
|
|
676
794
|
| **Settings** | `instances` | List instances |
|
|
677
795
|
| | `instances test <id>` | Test connection |
|
|
678
796
|
| | `config get` | Show config |
|
|
679
|
-
| | `config set <k> <v>` | Set config value |
|
|
797
|
+
| | `config set <k> <v>` | Set config value (`apiUrl`, `appUrl`, `defaultApp`) |
|
|
680
798
|
| | `config path` | Config file paths |
|
|
681
799
|
| | `version` | Show version |
|
|
682
800
|
|
|
@@ -684,8 +802,10 @@ npx securenow logs --json --level error | jq '.logs'
|
|
|
684
802
|
|
|
685
803
|
## Framework-Specific Setup
|
|
686
804
|
|
|
687
|
-
>
|
|
688
|
-
> are **automatically** forwarded as OTLP log records.
|
|
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.
|
|
689
809
|
|
|
690
810
|
### Express.js
|
|
691
811
|
|
|
@@ -695,7 +815,6 @@ npm install securenow express
|
|
|
695
815
|
|
|
696
816
|
```javascript
|
|
697
817
|
// app.js
|
|
698
|
-
require('securenow/register');
|
|
699
818
|
const express = require('express');
|
|
700
819
|
|
|
701
820
|
const app = express();
|
|
@@ -712,9 +831,11 @@ app.listen(3000, () => console.log('Express running on port 3000'));
|
|
|
712
831
|
```
|
|
713
832
|
|
|
714
833
|
```bash
|
|
715
|
-
node app.js
|
|
834
|
+
node -r securenow/register app.js
|
|
716
835
|
```
|
|
717
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
|
+
|
|
718
839
|
#### With PM2
|
|
719
840
|
|
|
720
841
|
```javascript
|
|
@@ -723,6 +844,7 @@ module.exports = {
|
|
|
723
844
|
apps: [{
|
|
724
845
|
name: 'my-app',
|
|
725
846
|
script: './app.js',
|
|
847
|
+
cwd: __dirname,
|
|
726
848
|
instances: 'max', // or leave out for fork mode
|
|
727
849
|
// An absolute path, resolved when PM2 loads this file: in cluster mode Node resolves a bare
|
|
728
850
|
// '-r securenow/register' from the PM2 daemon's folder, so workers started by `pm2 resurrect`
|
|
@@ -732,7 +854,7 @@ module.exports = {
|
|
|
732
854
|
};
|
|
733
855
|
```
|
|
734
856
|
|
|
735
|
-
> **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).
|
|
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`.
|
|
736
858
|
|
|
737
859
|
---
|
|
738
860
|
|
|
@@ -744,7 +866,6 @@ npm install securenow fastify
|
|
|
744
866
|
|
|
745
867
|
```javascript
|
|
746
868
|
// app.js
|
|
747
|
-
require('securenow/register');
|
|
748
869
|
const Fastify = require('fastify');
|
|
749
870
|
const fastify = Fastify({ logger: true });
|
|
750
871
|
|
|
@@ -763,7 +884,11 @@ fastify.listen({ port: 3000 }, (err) => {
|
|
|
763
884
|
});
|
|
764
885
|
```
|
|
765
886
|
|
|
766
|
-
|
|
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.
|
|
767
892
|
|
|
768
893
|
---
|
|
769
894
|
|
|
@@ -775,7 +900,6 @@ npm install securenow koa @koa/router koa-bodyparser
|
|
|
775
900
|
|
|
776
901
|
```javascript
|
|
777
902
|
// app.js
|
|
778
|
-
require('securenow/register');
|
|
779
903
|
const Koa = require('koa');
|
|
780
904
|
const Router = require('@koa/router');
|
|
781
905
|
const bodyParser = require('koa-bodyparser');
|
|
@@ -798,6 +922,12 @@ app.use(router.allowedMethods());
|
|
|
798
922
|
app.listen(3000, () => console.log('Koa running on port 3000'));
|
|
799
923
|
```
|
|
800
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
|
+
|
|
801
931
|
---
|
|
802
932
|
|
|
803
933
|
### NestJS
|
|
@@ -839,6 +969,8 @@ bootstrap();
|
|
|
839
969
|
|
|
840
970
|
```bash
|
|
841
971
|
node -r securenow/register -r ts-node/register app.ts
|
|
972
|
+
# compiled output (production):
|
|
973
|
+
node -r securenow/register dist/main.js
|
|
842
974
|
```
|
|
843
975
|
|
|
844
976
|
PM2 config:
|
|
@@ -862,7 +994,6 @@ npm install securenow @hapi/hapi
|
|
|
862
994
|
|
|
863
995
|
```javascript
|
|
864
996
|
// app.js
|
|
865
|
-
require('securenow/register');
|
|
866
997
|
const Hapi = require('@hapi/hapi');
|
|
867
998
|
|
|
868
999
|
const init = async () => {
|
|
@@ -886,11 +1017,15 @@ const init = async () => {
|
|
|
886
1017
|
init().catch((err) => { console.error(err); process.exit(1); });
|
|
887
1018
|
```
|
|
888
1019
|
|
|
889
|
-
|
|
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.
|
|
890
1025
|
|
|
891
1026
|
---
|
|
892
1027
|
|
|
893
|
-
### h3 (UnJS
|
|
1028
|
+
### h3 (UnJS)
|
|
894
1029
|
|
|
895
1030
|
```bash
|
|
896
1031
|
npm install securenow h3
|
|
@@ -898,7 +1033,6 @@ npm install securenow h3
|
|
|
898
1033
|
|
|
899
1034
|
```javascript
|
|
900
1035
|
// app.js
|
|
901
|
-
require('securenow/register');
|
|
902
1036
|
const { createApp, createRouter, defineEventHandler, readBody, setResponseStatus, toNodeListener } = require('h3');
|
|
903
1037
|
const http = require('http');
|
|
904
1038
|
|
|
@@ -921,6 +1055,12 @@ http.createServer(toNodeListener(app)).listen(3000, () => {
|
|
|
921
1055
|
});
|
|
922
1056
|
```
|
|
923
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
|
+
|
|
924
1064
|
---
|
|
925
1065
|
|
|
926
1066
|
### Polka
|
|
@@ -933,7 +1073,6 @@ Polka is minimalist -- no built-in body parser:
|
|
|
933
1073
|
|
|
934
1074
|
```javascript
|
|
935
1075
|
// app.js
|
|
936
|
-
require('securenow/register');
|
|
937
1076
|
const polka = require('polka');
|
|
938
1077
|
|
|
939
1078
|
function jsonBody(req, res, next) {
|
|
@@ -958,6 +1097,12 @@ polka()
|
|
|
958
1097
|
.listen(3000, () => console.log('Polka running on port 3000'));
|
|
959
1098
|
```
|
|
960
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
|
+
|
|
961
1106
|
---
|
|
962
1107
|
|
|
963
1108
|
### Micro / Raw HTTP
|
|
@@ -970,7 +1115,6 @@ npm install securenow
|
|
|
970
1115
|
|
|
971
1116
|
```javascript
|
|
972
1117
|
// app.js
|
|
973
|
-
require('securenow/register');
|
|
974
1118
|
const http = require('http');
|
|
975
1119
|
|
|
976
1120
|
function sendJson(res, status, body) {
|
|
@@ -998,6 +1142,12 @@ async function handler(req, res) {
|
|
|
998
1142
|
http.createServer(handler).listen(3000, () => console.log('HTTP running on port 3000'));
|
|
999
1143
|
```
|
|
1000
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
|
+
|
|
1001
1151
|
---
|
|
1002
1152
|
|
|
1003
1153
|
### Hono (ESM)
|
|
@@ -1030,7 +1180,7 @@ serve({ fetch: app.fetch, port: 3000 }, () => console.log('Hono running on port
|
|
|
1030
1180
|
node -r securenow/register app.mjs
|
|
1031
1181
|
```
|
|
1032
1182
|
|
|
1033
|
-
> **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on.
|
|
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+)**.
|
|
1034
1184
|
|
|
1035
1185
|
---
|
|
1036
1186
|
|
|
@@ -1044,7 +1194,6 @@ Feathers uses Express transport -- same setup as Express:
|
|
|
1044
1194
|
|
|
1045
1195
|
```javascript
|
|
1046
1196
|
// app.js
|
|
1047
|
-
require('securenow/register');
|
|
1048
1197
|
const feathers = require('@feathersjs/feathers');
|
|
1049
1198
|
const express = require('@feathersjs/express');
|
|
1050
1199
|
const errors = require('@feathersjs/errors');
|
|
@@ -1071,6 +1220,12 @@ app.use(express.errorHandler());
|
|
|
1071
1220
|
app.listen(3000, () => console.log('Feathers running on port 3000'));
|
|
1072
1221
|
```
|
|
1073
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
|
+
|
|
1074
1229
|
---
|
|
1075
1230
|
|
|
1076
1231
|
### Next.js
|
|
@@ -1079,7 +1234,7 @@ Use `npx securenow init` for the current Next.js integration; it creates or prin
|
|
|
1079
1234
|
|
|
1080
1235
|
#### Option A: `securenow init` (Recommended)
|
|
1081
1236
|
|
|
1082
|
-
The init command handles the boring parts: credentials defaults, `instrumentation.ts`, body auto-capture, `serverExternalPackages`, and standalone output tracing includes for Next 15
|
|
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.
|
|
1083
1238
|
|
|
1084
1239
|
```bash
|
|
1085
1240
|
npx securenow login
|
|
@@ -1116,13 +1271,17 @@ const nextConfig = {
|
|
|
1116
1271
|
export default nextConfig;
|
|
1117
1272
|
```
|
|
1118
1273
|
|
|
1119
|
-
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`). 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.
|
|
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.
|
|
1120
1275
|
|
|
1121
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.
|
|
1122
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`.
|
|
1281
|
+
|
|
1123
1282
|
#### Option B: Manual configuration
|
|
1124
1283
|
|
|
1125
|
-
If you prefer not to run `init`,
|
|
1284
|
+
If you prefer not to run `init`, create the instrumentation file above and externalize SecureNow:
|
|
1126
1285
|
|
|
1127
1286
|
```javascript
|
|
1128
1287
|
// next.config.js (Next.js 15+)
|
|
@@ -1135,19 +1294,21 @@ module.exports = {
|
|
|
1135
1294
|
```
|
|
1136
1295
|
|
|
1137
1296
|
```javascript
|
|
1138
|
-
// next.config.js (Next.js 14)
|
|
1297
|
+
// next.config.js (Next.js 14): all three keys live under experimental
|
|
1139
1298
|
module.exports = {
|
|
1140
1299
|
experimental: {
|
|
1141
1300
|
instrumentationHook: true,
|
|
1142
1301
|
serverComponentsExternalPackages: ['securenow'],
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1302
|
+
outputFileTracingIncludes: {
|
|
1303
|
+
'/*': ['./node_modules/securenow/**/*'],
|
|
1304
|
+
},
|
|
1146
1305
|
},
|
|
1147
1306
|
};
|
|
1148
1307
|
```
|
|
1149
1308
|
|
|
1150
|
-
**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.
|
|
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.
|
|
1151
1312
|
|
|
1152
1313
|
---
|
|
1153
1314
|
|
|
@@ -1165,37 +1326,66 @@ export default defineNuxtConfig({
|
|
|
1165
1326
|
});
|
|
1166
1327
|
```
|
|
1167
1328
|
|
|
1168
|
-
Local app identity, runtime API key, and secure defaults come from
|
|
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`).
|
|
1169
1330
|
|
|
1170
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.
|
|
1171
1332
|
|
|
1172
|
-
|
|
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
|
|
1357
|
+
```
|
|
1358
|
+
|
|
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.
|
|
1173
1360
|
|
|
1174
1361
|
---
|
|
1175
1362
|
|
|
1176
1363
|
### Compatibility Matrix
|
|
1177
1364
|
|
|
1178
|
-
| Framework | Traces | Logs | Body Capture | Firewall | Notes |
|
|
1179
|
-
|
|
1180
|
-
| Express 4 and 5 | Yes | Yes | Yes | Yes |
|
|
1181
|
-
| Fastify 5 | Yes | Yes | Yes | Yes |
|
|
1182
|
-
| Koa 3 | Yes | Yes | Yes | Yes | Body needs a body parser
|
|
1183
|
-
| NestJS 11 | Yes | Yes | Yes | Yes | `-r securenow/register` before `-r ts-node/register` for TypeScript |
|
|
1184
|
-
| Hapi 21 | Yes | Yes | Yes | Yes |
|
|
1185
|
-
| h3 1 | Yes | Yes | Yes | Yes | Uses `toNodeListener()`;
|
|
1186
|
-
| Polka | Yes | Yes | Yes | Yes | Needs a body parser;
|
|
1187
|
-
|
|
|
1188
|
-
| Hono 4 (`@hono/node-server`) | Yes | Yes | Yes | Yes |
|
|
1189
|
-
| Feathers 5 | Yes | Yes | Yes | Yes | Koa or Express transport;
|
|
1190
|
-
| Next.js 15 and 16 | Yes | Yes | Yes | Yes | `instrumentation.ts` + `serverExternalPackages` + `outputFileTracingIncludes`; `next start` and `output: 'standalone'
|
|
1191
|
-
|
|
|
1192
|
-
|
|
|
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.
|
|
1193
1383
|
|
|
1194
1384
|
---
|
|
1195
1385
|
|
|
1196
1386
|
## Firewall -- Automatic IP Blocking
|
|
1197
1387
|
|
|
1198
|
-
SecureNow can automatically block IPs from your blocklist at the application layer. No code changes -- provide
|
|
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.
|
|
1199
1389
|
|
|
1200
1390
|
### Firewall Is Enabled by Default
|
|
1201
1391
|
|
|
@@ -1213,28 +1403,32 @@ npx securenow api-key set snk_live_abc123...
|
|
|
1213
1403
|
npx securenow credentials runtime --env production
|
|
1214
1404
|
```
|
|
1215
1405
|
|
|
1216
|
-
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,
|
|
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/`.
|
|
1217
1407
|
|
|
1218
1408
|
On startup, you'll see:
|
|
1219
1409
|
|
|
1220
1410
|
```
|
|
1221
1411
|
[securenow] Firewall: ENABLED
|
|
1412
|
+
[securenow] Firewall: environment=production
|
|
1222
1413
|
[securenow] Firewall: Layer 1 (HTTP 403) active
|
|
1223
|
-
[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
|
|
1224
1415
|
```
|
|
1225
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
|
+
|
|
1226
1419
|
### How It Works
|
|
1227
1420
|
|
|
1228
1421
|
The firewall uses a version-based sync protocol for efficiency:
|
|
1229
1422
|
|
|
1230
|
-
1. **Version check** every 10 seconds (lightweight request with ETag
|
|
1231
|
-
2. **Full blocklist sync** only when the version changes (or every hour as a safety net)
|
|
1232
|
-
3. **In-memory matching** with a pre-compiled set (exact IPs) and
|
|
1233
|
-
4. **Exponential backoff** with jitter when the API is temporarily unreachable
|
|
1234
|
-
5. **Allowlist
|
|
1235
|
-
6. **
|
|
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
|
|
1236
1430
|
|
|
1237
|
-
After you block an IP in the dashboard or CLI, it
|
|
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).
|
|
1238
1432
|
|
|
1239
1433
|
### Firewall-Only Mode (No Tracing)
|
|
1240
1434
|
|
|
@@ -1262,7 +1456,7 @@ This is useful when:
|
|
|
1262
1456
|
- You're adding the firewall to a project that uses a different tracing solution
|
|
1263
1457
|
- For non-Next Node apps, this avoids framework-specific tracing setup entirely
|
|
1264
1458
|
|
|
1265
|
-
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:
|
|
1266
1460
|
|
|
1267
1461
|
```bash
|
|
1268
1462
|
npx securenow credentials runtime --env production
|
|
@@ -1275,29 +1469,42 @@ The firewall supports four layers -- Layer 1 is always on, the rest are opt-in:
|
|
|
1275
1469
|
|
|
1276
1470
|
| Layer | Credentials key | Description |
|
|
1277
1471
|
|-------|---------|-------------|
|
|
1278
|
-
| **Layer 1: HTTP** | *(always on)* | Returns 403 Forbidden
|
|
1279
|
-
| **Layer 2: TCP** | `config.firewall.tcp=true` | `socket.destroy()` -- zero bytes sent back |
|
|
1280
|
-
| **Layer 3: iptables** | `config.firewall.iptables=true` | Kernel-level DROP (
|
|
1281
|
-
| **Layer 4: Cloud WAF** | `config.firewall.cloud="cloudflare"` | Pushes to Cloudflare
|
|
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.
|
|
1282
1478
|
|
|
1283
1479
|
### Blocked Page
|
|
1284
1480
|
|
|
1285
1481
|
When an IP is blocked at Layer 1, the user sees a full-page security alert with:
|
|
1286
|
-
- Their detected IP address
|
|
1482
|
+
- Their detected IP address (masked)
|
|
1287
1483
|
- A warning that malicious activity was detected
|
|
1288
1484
|
- Contact information (`contact@securenow.ai`) for false positives
|
|
1289
1485
|
|
|
1290
1486
|
### Get an API Key
|
|
1291
1487
|
|
|
1292
1488
|
```bash
|
|
1293
|
-
npx securenow
|
|
1294
|
-
npx securenow
|
|
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
|
|
1295
1492
|
```
|
|
1296
1493
|
|
|
1297
|
-
Or create one
|
|
1494
|
+
Or create one in the dashboard with the runtime scopes and save it with `npx securenow api-key set snk_live_...`.
|
|
1298
1495
|
|
|
1299
1496
|
Use `npx securenow firewall status`, `npx securenow firewall apps`, and `npx securenow help firewall` for the current firewall reference.
|
|
1300
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
|
+
|
|
1301
1508
|
---
|
|
1302
1509
|
|
|
1303
1510
|
## Credentials Configuration
|
|
@@ -1310,22 +1517,49 @@ Use `.securenow/runtime.json` as the local source of truth. Run `npx securenow e
|
|
|
1310
1517
|
|
|
1311
1518
|
| Field | Description | Default |
|
|
1312
1519
|
|----------|-------------|---------|
|
|
1313
|
-
| `app.key` | App routing UUID.
|
|
1520
|
+
| `app.key` | App routing UUID. It becomes `service.name`; the SecureNow ingestion gateway routes telemetry by this key. | selected during login |
|
|
1314
1521
|
| `app.name` | Human-readable app label. | selected during login |
|
|
1315
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 |
|
|
1316
|
-
| `config.runtime.deploymentEnvironment` | `deployment.environment` trace/log scope
|
|
1317
|
-
| `config.
|
|
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` |
|
|
1318
1529
|
| `config.capture.body` | Request body capture with redaction. | `true` |
|
|
1319
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` |
|
|
1320
1548
|
| `config.firewall.enabled` | Deprecated local hint. Runtime enable/disable uses the dashboard/API environment toggle. | ignored |
|
|
1321
|
-
| `config.
|
|
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.
|
|
1322
1554
|
|
|
1323
1555
|
The credentials file is versioned with `_securenow.schemaVersion`. The SDK reads
|
|
1324
1556
|
all runtime settings from this JSON plus built-in defaults. Production should
|
|
1325
1557
|
mount a tokenless runtime credentials file at `.securenow/credentials.json`.
|
|
1326
|
-
|
|
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.
|
|
1327
1561
|
|
|
1328
|
-
**Default sensitive fields (auto-redacted):** `password`, `passwd`, `pwd`, `secret`, `token`, `api_key`, `apikey`, `access_token`, `auth`, `
|
|
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`).
|
|
1329
1563
|
|
|
1330
1564
|
For instrumentation, firewall layers, debugging, trusted proxies, and deployment
|
|
1331
1565
|
environment, edit the matching `config.*` keys in `.securenow/runtime.json`
|
|
@@ -1344,12 +1578,18 @@ SecureNow provides multiple entry points depending on your needs:
|
|
|
1344
1578
|
| `securenow/register` | `node -r securenow/register app.js` | Yes | Yes | Default -- full tracing + firewall |
|
|
1345
1579
|
| `securenow/firewall-only` | `node -r securenow/firewall-only app.js` | No | Yes | Firewall only, no OTel overhead |
|
|
1346
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 |
|
|
1347
1582
|
| `securenow/nuxt` | `modules: ['securenow/nuxt']` | Yes | Yes | Nuxt 3 and 4 module |
|
|
1348
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 |
|
|
1349
|
-
| `securenow/
|
|
1350
|
-
| `securenow/
|
|
1351
|
-
| `securenow/
|
|
1352
|
-
| `securenow/
|
|
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 |
|
|
1353
1593
|
|
|
1354
1594
|
---
|
|
1355
1595
|
|
|
@@ -1359,11 +1599,10 @@ SecureNow provides multiple entry points depending on your needs:
|
|
|
1359
1599
|
|
|
1360
1600
|
Console log forwarding is enabled by default through
|
|
1361
1601
|
`config.logging.enabled: true`; all console calls are automatically forwarded as
|
|
1362
|
-
OTLP log records:
|
|
1602
|
+
OTLP log records, linked to the active trace:
|
|
1363
1603
|
|
|
1364
1604
|
```javascript
|
|
1365
|
-
//
|
|
1366
|
-
require('securenow/register');
|
|
1605
|
+
// Started with: node -r securenow/register app.js
|
|
1367
1606
|
|
|
1368
1607
|
// With config.logging.enabled=true, all console logs are automatically sent
|
|
1369
1608
|
console.log('Application started');
|
|
@@ -1380,21 +1619,22 @@ console.debug('Debug info');
|
|
|
1380
1619
|
- `console.error()` -> ERROR
|
|
1381
1620
|
- `console.debug()` -> DEBUG
|
|
1382
1621
|
|
|
1383
|
-
|
|
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.
|
|
1384
1625
|
|
|
1385
1626
|
### Direct Logger API
|
|
1386
1627
|
|
|
1387
1628
|
For more control, use the OpenTelemetry logger API:
|
|
1388
1629
|
|
|
1389
1630
|
```javascript
|
|
1390
|
-
require('securenow/register');
|
|
1391
1631
|
const { getLogger } = require('securenow/tracing');
|
|
1392
1632
|
|
|
1393
|
-
// Get a logger instance
|
|
1633
|
+
// Get a logger instance (null when logging is disabled)
|
|
1394
1634
|
const logger = getLogger('my-module', '1.0.0');
|
|
1395
1635
|
|
|
1396
1636
|
// Emit structured logs
|
|
1397
|
-
logger
|
|
1637
|
+
logger?.emit({
|
|
1398
1638
|
severityNumber: 9, // INFO
|
|
1399
1639
|
severityText: 'INFO',
|
|
1400
1640
|
body: 'User logged in',
|
|
@@ -1411,19 +1651,20 @@ logger.emit({
|
|
|
1411
1651
|
- `9` - INFO
|
|
1412
1652
|
- `13` - WARN
|
|
1413
1653
|
- `17` - ERROR
|
|
1654
|
+
- `21` - FATAL
|
|
1414
1655
|
|
|
1415
1656
|
### Using with NODE_OPTIONS
|
|
1416
1657
|
|
|
1417
1658
|
```bash
|
|
1418
|
-
#
|
|
1419
|
-
|
|
1659
|
+
# Tracing + logging without editing the start script
|
|
1660
|
+
NODE_OPTIONS="-r securenow/register" npm start
|
|
1420
1661
|
```
|
|
1421
1662
|
|
|
1422
1663
|
---
|
|
1423
1664
|
|
|
1424
1665
|
## Request Body Capture
|
|
1425
1666
|
|
|
1426
|
-
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.
|
|
1427
1668
|
|
|
1428
1669
|
### Body Capture Defaults
|
|
1429
1670
|
|
|
@@ -1446,6 +1687,8 @@ SecureNow captures HTTP request bodies in traces by default, with sensitive fiel
|
|
|
1446
1687
|
- `application/graphql`
|
|
1447
1688
|
- `multipart/form-data` (metadata capture is on unless `config.capture.multipart=false`)
|
|
1448
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
|
+
|
|
1449
1692
|
### Multipart Body Capture (v5.8.0+)
|
|
1450
1693
|
|
|
1451
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.
|
|
@@ -1473,7 +1716,9 @@ SecureNow 8.11.2 redacts sign-in credentials named `code`, `otpCode`, `oneTimeCo
|
|
|
1473
1716
|
|
|
1474
1717
|
All request bodies are automatically scanned and sensitive fields are redacted:
|
|
1475
1718
|
|
|
1476
|
-
**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.
|
|
1477
1722
|
|
|
1478
1723
|
**Add custom fields to redact:**
|
|
1479
1724
|
|
|
@@ -1492,19 +1737,119 @@ All request bodies are automatically scanned and sensitive fields are redacted:
|
|
|
1492
1737
|
```javascript
|
|
1493
1738
|
// POST /api/users with body:
|
|
1494
1739
|
{
|
|
1740
|
+
"username": "johnd",
|
|
1495
1741
|
"email": "user@example.com",
|
|
1496
1742
|
"password": "secret123",
|
|
1497
|
-
"
|
|
1743
|
+
"plan": "pro"
|
|
1498
1744
|
}
|
|
1499
1745
|
|
|
1500
1746
|
// Captured in trace as:
|
|
1501
1747
|
{
|
|
1502
|
-
"
|
|
1748
|
+
"username": "johnd",
|
|
1749
|
+
"email": "[REDACTED]",
|
|
1503
1750
|
"password": "[REDACTED]",
|
|
1504
|
-
"
|
|
1751
|
+
"plan": "pro"
|
|
1505
1752
|
}
|
|
1506
1753
|
```
|
|
1507
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 });
|
|
1833
|
+
}
|
|
1834
|
+
```
|
|
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
|
+
|
|
1508
1853
|
---
|
|
1509
1854
|
|
|
1510
1855
|
## Advanced Configuration
|
|
@@ -1515,16 +1860,14 @@ All request bodies are automatically scanned and sensitive fields are redacted:
|
|
|
1515
1860
|
{
|
|
1516
1861
|
"apiKey": "snk_live_...",
|
|
1517
1862
|
"app": {
|
|
1518
|
-
"key": "
|
|
1519
|
-
"name": "my-production-app"
|
|
1520
|
-
"instance": "http://collector.example.com:4318"
|
|
1863
|
+
"key": "<app-uuid>",
|
|
1864
|
+
"name": "my-production-app"
|
|
1521
1865
|
},
|
|
1522
1866
|
"config": {
|
|
1523
|
-
"runtime": { "
|
|
1867
|
+
"runtime": { "strict": true, "deploymentEnvironment": "production" },
|
|
1524
1868
|
"otel": {
|
|
1525
|
-
"
|
|
1526
|
-
"
|
|
1527
|
-
"disableInstrumentations": ["fs", "dns"]
|
|
1869
|
+
"logLevel": "warn",
|
|
1870
|
+
"disableInstrumentations": ["@opentelemetry/instrumentation-dns", "@opentelemetry/instrumentation-net"]
|
|
1528
1871
|
},
|
|
1529
1872
|
"logging": { "enabled": true },
|
|
1530
1873
|
"capture": {
|
|
@@ -1532,15 +1875,20 @@ All request bodies are automatically scanned and sensitive fields are redacted:
|
|
|
1532
1875
|
"maxBodySize": 20480,
|
|
1533
1876
|
"sensitiveFields": ["internal_id", "session_key"]
|
|
1534
1877
|
},
|
|
1878
|
+
"networking": { "trustedProxies": ["cloudflare"] },
|
|
1535
1879
|
"firewall": {
|
|
1536
|
-
"tcp":
|
|
1537
|
-
"versionCheckInterval":
|
|
1538
|
-
"syncInterval":
|
|
1539
|
-
|
|
1880
|
+
"tcp": false,
|
|
1881
|
+
"versionCheckInterval": 2,
|
|
1882
|
+
"syncInterval": 3600,
|
|
1883
|
+
"rateLimitStore": "redis://redis:6379"
|
|
1884
|
+
},
|
|
1885
|
+
"sessions": { "guard": true, "capture": true }
|
|
1540
1886
|
}
|
|
1541
1887
|
}
|
|
1542
1888
|
```
|
|
1543
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
|
+
|
|
1544
1892
|
### Using Multiple Logger Instances
|
|
1545
1893
|
|
|
1546
1894
|
```javascript
|
|
@@ -1550,14 +1898,14 @@ const authLogger = getLogger('auth-service', '1.0.0');
|
|
|
1550
1898
|
const dbLogger = getLogger('database', '1.0.0');
|
|
1551
1899
|
const apiLogger = getLogger('api-handler', '1.0.0');
|
|
1552
1900
|
|
|
1553
|
-
authLogger
|
|
1901
|
+
authLogger?.emit({
|
|
1554
1902
|
severityNumber: 9,
|
|
1555
1903
|
severityText: 'INFO',
|
|
1556
1904
|
body: 'User authenticated',
|
|
1557
1905
|
attributes: { userId: 123 },
|
|
1558
1906
|
});
|
|
1559
1907
|
|
|
1560
|
-
dbLogger
|
|
1908
|
+
dbLogger?.emit({
|
|
1561
1909
|
severityNumber: 13,
|
|
1562
1910
|
severityText: 'WARN',
|
|
1563
1911
|
body: 'Slow query detected',
|
|
@@ -1579,23 +1927,26 @@ if (isLoggingEnabled()) {
|
|
|
1579
1927
|
|
|
1580
1928
|
### Programmatic Configuration
|
|
1581
1929
|
|
|
1582
|
-
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.
|
|
1583
1931
|
|
|
1584
1932
|
```javascript
|
|
1585
1933
|
// Reads .securenow/runtime.json first, with legacy/generated runtime credentials as fallbacks.
|
|
1586
1934
|
require('securenow/register');
|
|
1587
1935
|
```
|
|
1588
1936
|
|
|
1937
|
+
`require('securenow/app-config')` exposes the resolved values (`resolveAll()`, `resolveFirewallOptions()`, ...) for diagnostics.
|
|
1938
|
+
|
|
1589
1939
|
---
|
|
1590
1940
|
|
|
1591
1941
|
## TypeScript Support
|
|
1592
1942
|
|
|
1593
|
-
SecureNow
|
|
1943
|
+
SecureNow ships type definitions for `securenow/tracing`, `securenow/events`, `securenow/sessions`, `securenow/nextjs`, `securenow/nuxt`, `securenow/rate-limits` and `securenow/telemetry-schema`.
|
|
1594
1944
|
|
|
1595
1945
|
### Type Definitions
|
|
1596
1946
|
|
|
1597
1947
|
```typescript
|
|
1598
1948
|
import { getLogger, isLoggingEnabled, Logger, LogRecord } from 'securenow/tracing';
|
|
1949
|
+
import { track } from 'securenow/events';
|
|
1599
1950
|
|
|
1600
1951
|
// Get a typed logger
|
|
1601
1952
|
const logger: Logger | null = getLogger('my-service', '1.0.0');
|
|
@@ -1611,18 +1962,23 @@ if (logger) {
|
|
|
1611
1962
|
action: 'login',
|
|
1612
1963
|
},
|
|
1613
1964
|
};
|
|
1614
|
-
|
|
1965
|
+
|
|
1615
1966
|
logger.emit(logRecord);
|
|
1616
1967
|
}
|
|
1617
1968
|
|
|
1618
1969
|
// Check if enabled
|
|
1619
1970
|
const enabled: boolean = isLoggingEnabled();
|
|
1971
|
+
|
|
1972
|
+
// Custom event
|
|
1973
|
+
track('auth.login.success', { userId: 'u_123', attributes: { method: 'password' } });
|
|
1620
1974
|
```
|
|
1621
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
|
+
|
|
1622
1978
|
### Next.js with TypeScript
|
|
1623
1979
|
|
|
1624
1980
|
```typescript
|
|
1625
|
-
// instrumentation.ts
|
|
1981
|
+
// instrumentation.ts (src/instrumentation.ts with a src/ layout)
|
|
1626
1982
|
export async function register() {
|
|
1627
1983
|
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
1628
1984
|
try {
|
|
@@ -1637,16 +1993,21 @@ export async function register() {
|
|
|
1637
1993
|
}
|
|
1638
1994
|
```
|
|
1639
1995
|
|
|
1640
|
-
```
|
|
1641
|
-
// next.config.js
|
|
1642
|
-
|
|
1996
|
+
```typescript
|
|
1997
|
+
// next.config.ts (Next.js 15+)
|
|
1998
|
+
import type { NextConfig } from 'next';
|
|
1643
1999
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
2000
|
+
const nextConfig: NextConfig = {
|
|
2001
|
+
serverExternalPackages: ['securenow'],
|
|
2002
|
+
outputFileTracingIncludes: {
|
|
2003
|
+
'/*': ['./node_modules/securenow/**/*'],
|
|
2004
|
+
},
|
|
2005
|
+
};
|
|
2006
|
+
|
|
2007
|
+
export default nextConfig;
|
|
1647
2008
|
```
|
|
1648
2009
|
|
|
1649
|
-
`withSecureNow()` adds
|
|
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.
|
|
1650
2011
|
|
|
1651
2012
|
### NestJS with TypeScript
|
|
1652
2013
|
|
|
@@ -1678,25 +2039,22 @@ npx securenow env
|
|
|
1678
2039
|
npx securenow status --env local
|
|
1679
2040
|
```
|
|
1680
2041
|
|
|
1681
|
-
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.
|
|
1682
2043
|
|
|
1683
|
-
**Check 2:
|
|
2044
|
+
**Check 2: Look at the right environment**
|
|
1684
2045
|
|
|
1685
|
-
|
|
1686
|
-
curl http://localhost:4318/v1/traces
|
|
1687
|
-
# Should return 200 or 405 (method not allowed)
|
|
1688
|
-
```
|
|
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.
|
|
1689
2047
|
|
|
1690
2048
|
**Check 3: Run doctor and enable debug diagnostics**
|
|
1691
2049
|
|
|
1692
2050
|
```bash
|
|
1693
2051
|
npx securenow doctor --json
|
|
1694
|
-
|
|
2052
|
+
npx securenow test-span
|
|
1695
2053
|
```
|
|
1696
2054
|
|
|
1697
|
-
Doctor
|
|
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)`.
|
|
1698
2056
|
|
|
1699
|
-
For a persistent setting, update credentials:
|
|
2057
|
+
For a persistent diagnostic setting, update credentials (or run once with `OTEL_LOG_LEVEL=debug`):
|
|
1700
2058
|
|
|
1701
2059
|
```json
|
|
1702
2060
|
{
|
|
@@ -1708,12 +2066,14 @@ For a persistent setting, update credentials:
|
|
|
1708
2066
|
|
|
1709
2067
|
Look for lines like:
|
|
1710
2068
|
```
|
|
1711
|
-
[securenow] OTel SDK started
|
|
2069
|
+
[securenow] OTel SDK started → https://ingest.securenow.ai/v1/traces
|
|
1712
2070
|
```
|
|
1713
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
|
+
|
|
1714
2074
|
**Check 4: Verify initialization order**
|
|
1715
2075
|
|
|
1716
|
-
SecureNow must be
|
|
2076
|
+
SecureNow must be loaded BEFORE any other modules:
|
|
1717
2077
|
|
|
1718
2078
|
```javascript
|
|
1719
2079
|
// Correct
|
|
@@ -1725,19 +2085,28 @@ const express = require('express');
|
|
|
1725
2085
|
require('securenow/register');
|
|
1726
2086
|
```
|
|
1727
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
|
+
|
|
1728
2094
|
### Firewall Not Blocking IPs
|
|
1729
2095
|
|
|
1730
2096
|
**Check 1: Is a runtime API key in `.securenow/runtime.json` or the mounted runtime credentials file?**
|
|
1731
2097
|
|
|
1732
2098
|
```bash
|
|
1733
2099
|
npx securenow api-key show
|
|
2100
|
+
npx securenow env # "Firewall enabled", not "missing firewall API key"
|
|
1734
2101
|
```
|
|
1735
2102
|
|
|
1736
|
-
**Check 2: Is the IP in the blocklist?**
|
|
2103
|
+
**Check 2: Is the IP in the blocklist, for this app and environment?**
|
|
1737
2104
|
|
|
1738
2105
|
```bash
|
|
1739
|
-
npx securenow blocklist
|
|
1740
|
-
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
|
|
1741
2110
|
```
|
|
1742
2111
|
|
|
1743
2112
|
**Check 3: Check startup logs for sync status**
|
|
@@ -1745,14 +2114,15 @@ npx securenow firewall test-ip 1.2.3.4
|
|
|
1745
2114
|
You should see:
|
|
1746
2115
|
```
|
|
1747
2116
|
[securenow] Firewall: ENABLED
|
|
1748
|
-
[securenow] Firewall:
|
|
2117
|
+
[securenow] Firewall: environment=production
|
|
2118
|
+
[securenow] Firewall: synced 142 global blocked IPs (138 exact + 4 CIDR ranges) and 0 scoped block rules
|
|
1749
2119
|
```
|
|
1750
2120
|
|
|
1751
|
-
If you see `initial sync failed`, check the
|
|
2121
|
+
If you see `initial sync failed`, check the network and the key. `Firewall: environment=` must match the environment of the block.
|
|
1752
2122
|
|
|
1753
2123
|
**Check 4: Wait for propagation**
|
|
1754
2124
|
|
|
1755
|
-
After blocking an IP, it takes
|
|
2125
|
+
After blocking an IP, it takes up to one version-check interval (10 seconds by default) to propagate.
|
|
1756
2126
|
|
|
1757
2127
|
**Check 5: Are you behind a proxy?**
|
|
1758
2128
|
|
|
@@ -1764,9 +2134,13 @@ proxy_set_header X-Real-IP $remote_addr;
|
|
|
1764
2134
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
1765
2135
|
```
|
|
1766
2136
|
|
|
1767
|
-
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**
|
|
1768
2140
|
|
|
1769
|
-
|
|
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).
|
|
2142
|
+
|
|
2143
|
+
**Check 7: Using PM2?**
|
|
1770
2144
|
|
|
1771
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'`.
|
|
1772
2146
|
|
|
@@ -1782,24 +2156,24 @@ Make sure `node_args: ['-r', require.resolve('securenow/register')]` is in your
|
|
|
1782
2156
|
}
|
|
1783
2157
|
```
|
|
1784
2158
|
|
|
1785
|
-
**Check 2: Verify
|
|
2159
|
+
**Check 2: Verify the SDK is loaded**
|
|
1786
2160
|
|
|
1787
|
-
```
|
|
1788
|
-
|
|
2161
|
+
```bash
|
|
2162
|
+
node -r securenow/register app.js
|
|
1789
2163
|
```
|
|
1790
2164
|
|
|
1791
2165
|
**Check 3: Check console output**
|
|
1792
2166
|
|
|
1793
2167
|
You should see:
|
|
1794
2168
|
```
|
|
1795
|
-
[securenow] Logging: ENABLED
|
|
2169
|
+
[securenow] Logging: ENABLED → https://ingest.securenow.ai/v1/logs
|
|
1796
2170
|
```
|
|
1797
2171
|
|
|
1798
|
-
**Check 4:
|
|
2172
|
+
**Check 4: Query them**
|
|
1799
2173
|
|
|
1800
2174
|
```bash
|
|
1801
|
-
|
|
1802
|
-
|
|
2175
|
+
npx securenow logs --since 15m --env local
|
|
2176
|
+
npx securenow log send "manual test" --level info
|
|
1803
2177
|
```
|
|
1804
2178
|
|
|
1805
2179
|
### Request Body Not Captured
|
|
@@ -1820,10 +2194,15 @@ Body capture only works for:
|
|
|
1820
2194
|
- `application/json`
|
|
1821
2195
|
- `application/x-www-form-urlencoded`
|
|
1822
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.
|
|
1823
2202
|
|
|
1824
|
-
**Check
|
|
2203
|
+
**Check 4: Check body size**
|
|
1825
2204
|
|
|
1826
|
-
Bodies larger than `config.capture.maxBodySize` are
|
|
2205
|
+
Bodies larger than `config.capture.maxBodySize` are recorded as `[TOO LARGE: <n> bytes]` instead of their content:
|
|
1827
2206
|
|
|
1828
2207
|
```json
|
|
1829
2208
|
{
|
|
@@ -1833,6 +2212,10 @@ Bodies larger than `config.capture.maxBodySize` are truncated:
|
|
|
1833
2212
|
}
|
|
1834
2213
|
```
|
|
1835
2214
|
|
|
2215
|
+
**Check 5: Next.js Edge runtime**
|
|
2216
|
+
|
|
2217
|
+
Routes with `export const runtime = 'edge'` and Edge middleware are not instrumented.
|
|
2218
|
+
|
|
1836
2219
|
### High Memory Usage
|
|
1837
2220
|
|
|
1838
2221
|
**Option 1: Disable body capture**
|
|
@@ -1855,16 +2238,18 @@ Bodies larger than `config.capture.maxBodySize` are truncated:
|
|
|
1855
2238
|
}
|
|
1856
2239
|
```
|
|
1857
2240
|
|
|
1858
|
-
**Option 3: Disable specific instrumentations**
|
|
2241
|
+
**Option 3: Disable specific instrumentations** (full package names)
|
|
1859
2242
|
|
|
1860
2243
|
```json
|
|
1861
2244
|
{
|
|
1862
2245
|
"config": {
|
|
1863
|
-
"otel": { "disableInstrumentations": ["
|
|
2246
|
+
"otel": { "disableInstrumentations": ["@opentelemetry/instrumentation-dns", "@opentelemetry/instrumentation-net"] }
|
|
1864
2247
|
}
|
|
1865
2248
|
}
|
|
1866
2249
|
```
|
|
1867
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
|
+
|
|
1868
2253
|
### Next.js Instrumentation Not Working
|
|
1869
2254
|
|
|
1870
2255
|
**Check 1: Is `securenow` externalized?**
|
|
@@ -1878,15 +2263,15 @@ module.exports = {
|
|
|
1878
2263
|
};
|
|
1879
2264
|
```
|
|
1880
2265
|
|
|
1881
|
-
For Next.js
|
|
2266
|
+
For Next.js 14, the keys go under `experimental` together with `instrumentationHook: true` (see Option B above).
|
|
1882
2267
|
|
|
1883
2268
|
**Check 2: Verify instrumentation file location**
|
|
1884
2269
|
|
|
1885
|
-
`instrumentation.ts` or `instrumentation.js` must
|
|
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.
|
|
1886
2271
|
|
|
1887
2272
|
**Check 3: Check for OTel MODULE_NOT_FOUND errors**
|
|
1888
2273
|
|
|
1889
|
-
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.
|
|
1890
2275
|
|
|
1891
2276
|
**Check 4: Restart dev server**
|
|
1892
2277
|
|
|
@@ -1899,7 +2284,9 @@ npm run dev
|
|
|
1899
2284
|
|
|
1900
2285
|
**Problem: Different service names for each worker**
|
|
1901
2286
|
|
|
1902
|
-
**Solution:
|
|
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):
|
|
1903
2290
|
|
|
1904
2291
|
```json
|
|
1905
2292
|
{
|
|
@@ -1909,8 +2296,6 @@ npm run dev
|
|
|
1909
2296
|
}
|
|
1910
2297
|
```
|
|
1911
2298
|
|
|
1912
|
-
This uses the same service name for all workers.
|
|
1913
|
-
|
|
1914
2299
|
**Problem: Workers not instrumented / firewall not active**
|
|
1915
2300
|
|
|
1916
2301
|
**Solution: Use node_args in PM2 config**
|
|
@@ -1927,7 +2312,11 @@ module.exports = {
|
|
|
1927
2312
|
};
|
|
1928
2313
|
```
|
|
1929
2314
|
|
|
1930
|
-
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).
|
|
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`.
|
|
1931
2320
|
|
|
1932
2321
|
---
|
|
1933
2322
|
|
|
@@ -1942,7 +2331,7 @@ Do not hardcode configuration in code or deployment dashboards. Use `.securenow/
|
|
|
1942
2331
|
const appKey = 'hardcoded-value';
|
|
1943
2332
|
|
|
1944
2333
|
// Good: use .securenow/runtime.json
|
|
1945
|
-
// { "app": { "key": "<uuid>", "
|
|
2334
|
+
// { "app": { "key": "<uuid>", "name": "my-api" }, "apiKey": "snk_live_..." }
|
|
1946
2335
|
```
|
|
1947
2336
|
|
|
1948
2337
|
### 2. Use Structured Logging
|
|
@@ -1956,7 +2345,7 @@ console.log('User 123 logged in');
|
|
|
1956
2345
|
// Better -- structured attributes
|
|
1957
2346
|
console.log('User logged in', {
|
|
1958
2347
|
userId: 123,
|
|
1959
|
-
|
|
2348
|
+
plan: 'pro',
|
|
1960
2349
|
timestamp: new Date().toISOString(),
|
|
1961
2350
|
});
|
|
1962
2351
|
```
|
|
@@ -1980,18 +2369,21 @@ console.debug('Cache hit', { key: 'user:123' });
|
|
|
1980
2369
|
|
|
1981
2370
|
### 4. Don't Log Sensitive Data
|
|
1982
2371
|
|
|
1983
|
-
|
|
2372
|
+
Request bodies, headers and (from 8.25.0) log lines are redacted, but log redaction recognizes field names and value shapes, not meaning:
|
|
1984
2373
|
|
|
1985
2374
|
```javascript
|
|
1986
|
-
// 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
|
|
1987
2379
|
console.log('Login attempt', {
|
|
1988
|
-
email: 'user@example.com',
|
|
1989
|
-
password: 'secret123',
|
|
2380
|
+
email: 'user@example.com', // -> [REDACTED]
|
|
2381
|
+
password: 'secret123', // -> [REDACTED]
|
|
1990
2382
|
});
|
|
1991
2383
|
|
|
1992
2384
|
// Good
|
|
1993
2385
|
console.log('Login attempt', {
|
|
1994
|
-
|
|
2386
|
+
userId: user.id,
|
|
1995
2387
|
timestamp: Date.now(),
|
|
1996
2388
|
});
|
|
1997
2389
|
```
|
|
@@ -2009,51 +2401,47 @@ const dbLogger = getLogger('database', '1.0.0');
|
|
|
2009
2401
|
const apiLogger = getLogger('api', '1.0.0');
|
|
2010
2402
|
```
|
|
2011
2403
|
|
|
2012
|
-
### 6.
|
|
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:
|
|
2013
2407
|
|
|
2014
2408
|
```json
|
|
2015
2409
|
{
|
|
2016
2410
|
"config": {
|
|
2017
|
-
"capture": { "
|
|
2411
|
+
"capture": { "sensitiveFields": ["national_id", "internal_token"] }
|
|
2018
2412
|
}
|
|
2019
2413
|
}
|
|
2020
2414
|
```
|
|
2021
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
|
+
|
|
2022
2420
|
---
|
|
2023
2421
|
|
|
2024
2422
|
## Supported Runtimes
|
|
2025
2423
|
|
|
2026
|
-
- **Node.js:**
|
|
2027
|
-
- **
|
|
2028
|
-
- **
|
|
2029
|
-
- **
|
|
2030
|
-
- **
|
|
2031
|
-
- **
|
|
2032
|
-
- **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.
|
|
2033
2430
|
|
|
2034
2431
|
---
|
|
2035
2432
|
|
|
2036
2433
|
## Automatic Instrumentations
|
|
2037
2434
|
|
|
2038
|
-
SecureNow
|
|
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:
|
|
2039
2436
|
|
|
2040
2437
|
- HTTP/HTTPS (incoming and outgoing)
|
|
2041
|
-
- Express.js
|
|
2042
|
-
-
|
|
2043
|
-
-
|
|
2044
|
-
-
|
|
2045
|
-
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
- MongoDB
|
|
2049
|
-
- Redis
|
|
2050
|
-
- GraphQL
|
|
2051
|
-
- gRPC
|
|
2052
|
-
- DNS
|
|
2053
|
-
- File System
|
|
2054
|
-
- And 50+ more libraries
|
|
2055
|
-
|
|
2056
|
-
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!
|
|
2057
2445
|
|
|
2058
2446
|
---
|
|
2059
2447
|
|
|
@@ -2067,53 +2455,54 @@ No code changes needed!
|
|
|
2067
2455
|
+---------------------------+
|
|
2068
2456
|
| SecureNow Library |
|
|
2069
2457
|
| +---------------------+ |
|
|
2070
|
-
| | Firewall Layer | | <-- Blocks
|
|
2458
|
+
| | Firewall Layer | | <-- Blocks, rate-limits and challenges before your app
|
|
2071
2459
|
| +---------------------+ |
|
|
2072
2460
|
| | Auto-instrumentation | |
|
|
2073
2461
|
| | Console wrapper | |
|
|
2074
2462
|
| | Body capture | |
|
|
2463
|
+
| | Crash capture | |
|
|
2075
2464
|
| +---------------------+ |
|
|
2076
2465
|
+---------------------------+
|
|
2077
2466
|
|
|
|
2078
2467
|
+---------+---------+
|
|
2079
2468
|
| |
|
|
2080
2469
|
v v
|
|
2081
|
-
+-----------+
|
|
2082
|
-
| OTel SDK | |
|
|
2083
|
-
| OTLP | |
|
|
2084
|
-
| Exporters | | (blocklist|
|
|
2085
|
-
+-----------+ |
|
|
2086
|
-
|
|
|
2087
|
-
v
|
|
2088
|
-
|
|
2089
|
-
|
|
|
2090
|
-
|
|
|
2091
|
-
|
|
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
|
+
+--------------------------------+
|
|
2092
2481
|
```
|
|
2093
2482
|
|
|
2094
2483
|
---
|
|
2095
2484
|
|
|
2096
2485
|
## Performance
|
|
2097
2486
|
|
|
2098
|
-
- **
|
|
2099
|
-
- **Batch export:** Traces and logs are batched before sending
|
|
2487
|
+
- **Low overhead:** traces and logs are batched before export
|
|
2100
2488
|
- **Async processing:** No blocking of application threads
|
|
2101
|
-
- **Configurable:** Disable instrumentations you don't need
|
|
2102
|
-
- **Firewall:** Sub-millisecond IP lookups using pre-compiled hash sets and
|
|
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
|
|
2103
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
|
|
2104
2493
|
|
|
2105
2494
|
---
|
|
2106
2495
|
|
|
2107
2496
|
## Security
|
|
2108
2497
|
|
|
2109
|
-
- **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
|
|
2110
2499
|
- **Configurable** sensitive field patterns
|
|
2111
|
-
- **No data stored** locally --
|
|
2500
|
+
- **No data stored** locally -- telemetry is sent to the SecureNow ingest gateway
|
|
2112
2501
|
- **Multi-layer firewall** -- blocks IPs at HTTP, TCP, OS, and cloud-edge levels
|
|
2113
|
-
- **API keys**
|
|
2114
|
-
- **Allowlist support** --
|
|
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
|
|
2115
2504
|
- **Fail-open by default** -- network issues never accidentally block legitimate traffic
|
|
2116
|
-
- **
|
|
2505
|
+
- **Readable source** -- the package ships unminified; audit it in `node_modules/securenow`
|
|
2117
2506
|
|
|
2118
2507
|
---
|
|
2119
2508
|
|
|
@@ -2125,21 +2514,20 @@ ISC
|
|
|
2125
2514
|
|
|
2126
2515
|
## Support
|
|
2127
2516
|
|
|
2128
|
-
- **Documentation:** [
|
|
2129
|
-
- **
|
|
2130
|
-
- **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)
|
|
2131
2519
|
|
|
2132
2520
|
---
|
|
2133
2521
|
|
|
2134
2522
|
## Changelog
|
|
2135
2523
|
|
|
2136
|
-
|
|
2524
|
+
`npx securenow version` prints the installed version. Release notes are kept in the CHANGELOG of the SecureNow source repository.
|
|
2137
2525
|
|
|
2138
2526
|
---
|
|
2139
2527
|
|
|
2140
2528
|
## Contributing
|
|
2141
2529
|
|
|
2142
|
-
|
|
2530
|
+
Questions, bug reports and suggestions: [contact@securenow.ai](mailto:contact@securenow.ai).
|
|
2143
2531
|
|
|
2144
2532
|
---
|
|
2145
2533
|
|