securenow 7.7.14 → 7.7.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL-API.md CHANGED
@@ -1,74 +1,74 @@
1
- # SecureNow SDK Agent Skill
2
-
3
- Instrument any Node.js application with OpenTelemetry tracing, structured logging, request body capture, and a multi-layer IP firewall. Supports Express, Fastify, NestJS, Koa, Hapi, Next.js, Nuxt 3, Vite (browser), and raw `http.createServer` with zero code changes for most setups.
4
-
5
- **CLI parity:** every capability exposed below (redaction, CIDR matching, log/span emission, firewall preload, config inspection) has an equivalent `securenow` CLI command. See [SKILL-CLI.md](./SKILL-CLI.md) for the terminal surface.
6
-
1
+ # SecureNow SDK — Agent Skill
2
+
3
+ Instrument any Node.js application with OpenTelemetry tracing, structured logging, request body capture, and a multi-layer IP firewall. Supports Express, Fastify, NestJS, Koa, Hapi, Next.js, Nuxt 3, Vite (browser), and raw `http.createServer` — with zero code changes for most setups.
4
+
5
+ **CLI parity:** every capability exposed below (redaction, CIDR matching, log/span emission, firewall preload, config inspection) has an equivalent `securenow` CLI command. See [SKILL-CLI.md](./SKILL-CLI.md) for the terminal surface.
6
+
7
7
  **MCP parity (v7.5+):** `npx securenow mcp` starts a local stdio MCP server for Codex, Claude, and other MCP clients. It reuses the same `.securenow/credentials.json` file as the CLI/SDK and exposes SecureNow tools, bundled docs resources, and setup prompts to agents. Alert-rule operators can inspect notifications, read exact `metadata.matchedSubdetectors`, dry-run candidate SQL with `securenow_alert_rule_candidate_test`, and apply global system-rule query fixes with `securenow_alert_rule_query_update` when evidence is clear.
8
8
 
9
9
  **Noisy alert-rule reviews:** prefer fixing a generic system-rule detector over creating customer-specific false positives. Dry-run candidate SQL first, preserve tenant scoping with `__USER_APP_KEYS__`, keep exploit-specific indicators, then save the shared query mapping only with an audit reason and explicit confirmation.
10
-
11
- ## Installation
12
-
13
- ```bash
14
- npm install securenow@latest
15
- ```
16
-
17
- ### Install This Skill in Cursor
18
-
19
- Save this file as `.cursor/skills/securenow-api/SKILL.md` in your project. Your AI agent will auto-discover it whenever you ask about integrating securenow, configuring tracing, setting up the firewall, or instrumenting any framework.
20
-
21
- ## Quick Start Any Node.js Framework
22
-
23
- ### 1. Install, Login, And Init
24
-
25
- ```bash
26
- npm install securenow@latest
27
- node -p "require('./node_modules/securenow/package.json').version"
28
- npx securenow version
29
- npx securenow login
30
- npx securenow init
31
- ```
32
-
33
- Use `securenow@7.5.1` or newer. Start authentication with `npx securenow login`; do not manually open auth URLs, because the CLI generates the callback/state values. The login flow lets the user pick or create an app, enables the app firewall by default, writes `.securenow/credentials.json`, and `init` scaffolds the framework integration.
34
-
35
- ### 2. Run With Instrumentation
36
-
37
- **Option A CLI (recommended):**
38
-
39
- ```bash
40
- npx securenow run src/index.js
41
- ```
42
-
43
- **Option B Node preload flag:**
44
-
45
- ```bash
46
- node -r securenow/register src/index.js
47
- ```
48
-
49
- **Option C Auto-setup for Next.js:**
50
-
51
- ```bash
52
- npx securenow init
53
- ```
54
-
55
- That's it. Traces, logs, request body capture, multipart metadata capture, and firewall enforcement are on by default. No code changes for Express, Fastify, NestJS, Koa, Hapi, and raw Node.
56
-
57
- ### 3. Firewall Is Enabled by Default
58
-
59
- Since v7.5.1, the browser login flow connects the firewall automatically after
60
- the user picks or creates an app. The firewall key lives in your credentials
61
- file no env var required:
62
-
63
- ```bash
64
- npx securenow login # pick/create app; firewall key is minted automatically
65
- # or, if you already have one:
66
- npx securenow api-key set snk_live_abc123...
67
- ```
68
-
69
- Both paths write the key to `.securenow/credentials.json` (auto-gitignored) and the firewall activates on next start. For production, run `npx securenow credentials runtime --env production` and mount/copy the tokenless file as `.securenow/credentials.json`.
70
-
71
- The firewall syncs your blocklist and enforces it on every request zero code changes.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install securenow@latest
15
+ ```
16
+
17
+ ### Install This Skill in Cursor
18
+
19
+ Save this file as `.cursor/skills/securenow-api/SKILL.md` in your project. Your AI agent will auto-discover it whenever you ask about integrating securenow, configuring tracing, setting up the firewall, or instrumenting any framework.
20
+
21
+ ## Quick Start — Any Node.js Framework
22
+
23
+ ### 1. Install, Login, And Init
24
+
25
+ ```bash
26
+ npm install securenow@latest
27
+ node -p "require('./node_modules/securenow/package.json').version"
28
+ npx securenow version
29
+ npx securenow login
30
+ npx securenow init
31
+ ```
32
+
33
+ Use `securenow@7.5.1` or newer. Start authentication with `npx securenow login`; do not manually open auth URLs, because the CLI generates the callback/state values. The login flow lets the user pick or create an app, enables the app firewall by default, writes `.securenow/credentials.json`, and `init` scaffolds the framework integration.
34
+
35
+ ### 2. Run With Instrumentation
36
+
37
+ **Option A — CLI (recommended):**
38
+
39
+ ```bash
40
+ npx securenow run src/index.js
41
+ ```
42
+
43
+ **Option B — Node preload flag:**
44
+
45
+ ```bash
46
+ node -r securenow/register src/index.js
47
+ ```
48
+
49
+ **Option C — Auto-setup for Next.js:**
50
+
51
+ ```bash
52
+ npx securenow init
53
+ ```
54
+
55
+ That's it. Traces, logs, request body capture, multipart metadata capture, and firewall enforcement are on by default. No code changes for Express, Fastify, NestJS, Koa, Hapi, and raw Node.
56
+
57
+ ### 3. Firewall Is Enabled by Default
58
+
59
+ Since v7.5.1, the browser login flow connects the firewall automatically after
60
+ the user picks or creates an app. The firewall key lives in your credentials
61
+ file — no env var required:
62
+
63
+ ```bash
64
+ npx securenow login # pick/create app; firewall key is minted automatically
65
+ # or, if you already have one:
66
+ npx securenow api-key set snk_live_abc123...
67
+ ```
68
+
69
+ Both paths write the key to `.securenow/credentials.json` (gitignored via credential-file patterns, not a whole-directory `.securenow/` ignore) and the firewall activates on next start. For production, run `npx securenow credentials runtime --env production` and mount/copy the tokenless file as `.securenow/credentials.json`.
70
+
71
+ The firewall syncs your blocklist and enforces it on every request — zero code changes.
72
72
 
73
73
  Blocklist unblocks are audit-preserving: dashboard/API/CLI/MCP unblock actions
74
74
  mark the active block as `removed`, invalidate firewall sync, clear expiry to
@@ -76,9 +76,10 @@ avoid TTL deletion, and retain block reports/history for future review or
76
76
  reblock context.
77
77
 
78
78
  For near-realtime propagation after a block/unblock, set
79
- `SECURENOW_FIREWALL_VERSION_INTERVAL=1` or `2` in the protected app. The SDK
80
- polls `/firewall/sync` with ETag/304, so unchanged checks are lightweight; keep
81
- `SECURENOW_FIREWALL_SYNC_INTERVAL` high as a safety-net full refresh.
79
+ `config.firewall.versionCheckInterval` to `1` or `2` in the protected app's
80
+ `.securenow/credentials.json`. The SDK polls `/firewall/sync` with ETag/304, so
81
+ unchanged checks are lightweight; keep `config.firewall.syncInterval` high as a
82
+ safety-net full refresh.
82
83
 
83
84
  Default automation is active for new and existing customers. The API
84
85
  idempotently provisions risk-score rules for all apps/environments:
@@ -87,52 +88,52 @@ Run `securenow automation defaults --yes` or the API backfill script when an
87
88
  operator needs to ensure those defaults immediately.
88
89
 
89
90
  ---
90
-
91
- ## Import Map
92
-
93
- | Import | Purpose | Type |
94
- |--------|---------|------|
95
- | `securenow` / `securenow/register` | Auto-register tracing + firewall (side-effect) | Preload (`-r`) |
96
- | `securenow/tracing` | Core OTel SDK; exports `getLogger()`, `isLoggingEnabled()` | CJS |
97
- | `securenow/nextjs` | Next.js instrumentation; exports `registerSecureNow(options?)` | CJS |
98
- | `securenow/nextjs-webpack-config` | Next.js config wrapper; exports `withSecureNow()`, `getSecureNowWebpackConfig()`, `EXTERNAL_PACKAGES` | CJS |
99
- | `securenow/nextjs-middleware` | Edge middleware body capture; exports `middleware()`, `redactSensitiveData()`, `DEFAULT_SENSITIVE_FIELDS` | CJS |
100
- | `securenow/nextjs-wrapper` | Route handler wrappers; exports `withSecureNow()`, `withSecureNowAsync()`, `captureRequestBody()`, `redactSensitiveData()` | CJS |
101
- | `securenow/nextjs-auto-capture` | Auto-patch Next request for body capture; exports `patchNextRequest()`, `safeBodyCapture()`, `redactSensitiveData()`, `isBodyCaptureEnabled()` | CJS |
91
+
92
+ ## Import Map
93
+
94
+ | Import | Purpose | Type |
95
+ |--------|---------|------|
96
+ | `securenow` / `securenow/register` | Auto-register tracing + firewall (side-effect) | Preload (`-r`) |
97
+ | `securenow/tracing` | Core OTel SDK; exports `getLogger()`, `isLoggingEnabled()` | CJS |
98
+ | `securenow/nextjs` | Next.js instrumentation; exports `registerSecureNow(options?)` | CJS |
99
+ | `securenow/nextjs-webpack-config` | Next.js config wrapper; exports `withSecureNow()`, `getSecureNowWebpackConfig()`, `EXTERNAL_PACKAGES` | CJS |
100
+ | `securenow/nextjs-middleware` | Edge middleware body capture; exports `middleware()`, `redactSensitiveData()`, `DEFAULT_SENSITIVE_FIELDS` | CJS |
101
+ | `securenow/nextjs-wrapper` | Route handler wrappers; exports `withSecureNow()`, `withSecureNowAsync()`, `captureRequestBody()`, `redactSensitiveData()` | CJS |
102
+ | `securenow/nextjs-auto-capture` | Auto-patch Next request for body capture; exports `patchNextRequest()`, `safeBodyCapture()`, `redactSensitiveData()`, `isBodyCaptureEnabled()` | CJS |
102
103
  | `securenow/nuxt` | Nuxt 3 module (add to `modules` array) | ESM |
103
104
  | `securenow/firewall` | Standalone firewall; exports `init()`, `shutdown()`, `getStats()`, `getMatcher()`, `getAllowlistMatcher()` | CJS |
104
105
  | `securenow/rate-limits` | Rate-limit remediation API helper; exports `parseRateLimitText()`, `createRateLimitFromText()`, `createRateLimit()`, `listRateLimits()` | CJS |
105
106
  | `securenow/firewall-only` | Preload: dotenv + firewall only, no tracing | Preload (`-r`) |
106
- | `securenow/cidr` | CIDR utilities; exports `createMatcher()`, `ipToInt()`, `parseCidr()`, `matchesCidr()` | CJS |
107
- | `securenow/resolve-ip` | IP resolution; exports `resolveClientIp()`, `resolveSocketIp()`, `isFromTrustedProxy()` | CJS |
108
- | `securenow/console-instrumentation` | ConsoleOTLP bridge; exports `originalConsole`, `restoreConsole()` | CJS |
109
- | `securenow/web-vite` | Browser OTel (document load, fetch, XHR, user interaction); default export `startSecurenowWeb()` | ESM |
110
- | `securenow/register-vite` | CJS bridge for Vite preload | CJS |
111
-
112
- ---
113
-
114
- ## Framework Integration Guides
115
-
116
- ### Express / Fastify / NestJS / Koa / Hapi / Raw Node
117
-
118
- No code changes. Use the preload:
119
-
120
- ```bash
121
- node -r securenow/register app.js
122
- ```
123
-
124
- Or with the CLI:
125
-
126
- ```bash
127
- npx securenow run app.js
128
- ```
129
-
130
- **PM2:**
131
-
132
- ```javascript
133
- // ecosystem.config.cjs
134
- module.exports = {
135
- apps: [{
107
+ | `securenow/cidr` | CIDR utilities; exports `createMatcher()`, `ipToInt()`, `parseCidr()`, `matchesCidr()` | CJS |
108
+ | `securenow/resolve-ip` | IP resolution; exports `resolveClientIp()`, `resolveSocketIp()`, `isFromTrustedProxy()` | CJS |
109
+ | `securenow/console-instrumentation` | Console→OTLP bridge; exports `originalConsole`, `restoreConsole()` | CJS |
110
+ | `securenow/web-vite` | Browser OTel (document load, fetch, XHR, user interaction); default export `startSecurenowWeb()` | ESM |
111
+ | `securenow/register-vite` | CJS bridge for Vite preload | CJS |
112
+
113
+ ---
114
+
115
+ ## Framework Integration Guides
116
+
117
+ ### Express / Fastify / NestJS / Koa / Hapi / Raw Node
118
+
119
+ No code changes. Use the preload:
120
+
121
+ ```bash
122
+ node -r securenow/register app.js
123
+ ```
124
+
125
+ Or with the CLI:
126
+
127
+ ```bash
128
+ npx securenow run app.js
129
+ ```
130
+
131
+ **PM2:**
132
+
133
+ ```javascript
134
+ // ecosystem.config.cjs
135
+ module.exports = {
136
+ apps: [{
136
137
  name: 'my-app',
137
138
  script: './app.js',
138
139
  instances: 4,
@@ -141,16 +142,16 @@ module.exports = {
141
142
  }],
142
143
  };
143
144
  ```
144
-
145
- **Docker:**
146
-
145
+
146
+ **Docker:**
147
+
147
148
  ```dockerfile
148
149
  COPY .securenow/credentials.json ./.securenow/credentials.json
149
150
  CMD ["node", "-r", "securenow/register", "app.js"]
150
151
  ```
151
-
152
- ---
153
-
152
+
153
+ ---
154
+
154
155
  ### Next.js
155
156
 
156
157
  Run `npx securenow init` first. It creates the straightforward integration below when files are missing, and prints a Codex/Claude-ready merge prompt when existing files need careful edits.
@@ -182,13 +183,13 @@ export async function register() {
182
183
  await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
183
184
  }
184
185
  ```
185
-
186
- `registerSecureNow(options?)` accepts:
187
-
186
+
187
+ `registerSecureNow(options?)` accepts:
188
+
188
189
  ```typescript
189
190
  interface RegisterOptions {
190
191
  serviceName?: string; // override credentials app key/name
191
- endpoint?: string; // override credentials app instance
192
+ endpoint?: string; // advanced OTLP endpoint override
192
193
  noUuid?: boolean; // override credentials config.runtime.noUuid
193
194
  captureBody?: boolean; // override credentials config.capture.body
194
195
  }
@@ -203,7 +204,7 @@ On Vercel it uses `@vercel/otel`; self-hosted uses vanilla `@opentelemetry/sdk-n
203
204
  "app": {
204
205
  "key": "<uuid>",
205
206
  "name": "my-nextjs-app",
206
- "instance": "https://freetrial.securenow.ai:4318"
207
+ "instance": "https://ingest.securenow.ai"
207
208
  },
208
209
  "config": {
209
210
  "logging": { "enabled": true },
@@ -214,13 +215,13 @@ On Vercel it uses `@vercel/otel`; self-hosted uses vanilla `@opentelemetry/sdk-n
214
215
  ```
215
216
 
216
217
  Local development and production do not need `.env.local`; `npx securenow login` and `npx securenow init` keep `.securenow/credentials.json` filled and gitignored. For production, run `npx securenow credentials runtime --env production` and mount/copy the generated JSON as `.securenow/credentials.json`.
217
-
218
- #### Next.js Body Capture
219
-
220
- **Option A Auto-capture (recommended):**
221
-
222
- Add to your `instrumentation.ts`:
223
-
218
+
219
+ #### Next.js Body Capture
220
+
221
+ **Option A — Auto-capture (recommended):**
222
+
223
+ Add to your `instrumentation.ts`:
224
+
224
225
  ```typescript
225
226
  export async function register() {
226
227
  if (process.env.NEXT_RUNTIME !== 'nodejs') return;
@@ -231,42 +232,42 @@ export async function register() {
231
232
  await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
232
233
  }
233
234
  ```
234
-
235
- **Option B Middleware:**
236
-
237
- ```typescript
238
- // middleware.ts
239
- export { middleware } from 'securenow/nextjs-middleware';
240
- export const config = { matcher: ['/api/:path*'] };
241
- ```
242
-
243
- **Option C Per-route wrapper:**
244
-
245
- ```typescript
246
- import { withSecureNow } from 'securenow/nextjs-wrapper';
247
-
248
- export const POST = withSecureNow(async (req) => {
249
- // handler
250
- });
251
- ```
252
-
253
- #### Next.js with `securenow init`
254
-
255
- ```bash
256
- npx securenow login
257
- npx securenow init
258
- ```
259
-
235
+
236
+ **Option B — Middleware:**
237
+
238
+ ```typescript
239
+ // middleware.ts
240
+ export { middleware } from 'securenow/nextjs-middleware';
241
+ export const config = { matcher: ['/api/:path*'] };
242
+ ```
243
+
244
+ **Option C — Per-route wrapper:**
245
+
246
+ ```typescript
247
+ import { withSecureNow } from 'securenow/nextjs-wrapper';
248
+
249
+ export const POST = withSecureNow(async (req) => {
250
+ // handler
251
+ });
252
+ ```
253
+
254
+ #### Next.js with `securenow init`
255
+
256
+ ```bash
257
+ npx securenow login
258
+ npx securenow init
259
+ ```
260
+
260
261
  Auto-detects Next.js, creates `instrumentation.ts`, adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, and reuses the app, instance, firewall key, and secure defaults in `.securenow/credentials.json`. If files already exist, it prints an agent-ready prompt with the exact edits to propose.
261
-
262
- ---
263
-
264
- ### Nuxt 3
265
-
266
- **`nuxt.config.ts`:**
267
-
268
- ```typescript
269
- export default defineNuxtConfig({
262
+
263
+ ---
264
+
265
+ ### Nuxt 3
266
+
267
+ **`nuxt.config.ts`:**
268
+
269
+ ```typescript
270
+ export default defineNuxtConfig({
270
271
  modules: ['securenow/nuxt'],
271
272
  securenow: {
272
273
  // optional overrides (defaults come from .securenow/credentials.json)
@@ -275,60 +276,60 @@ export default defineNuxtConfig({
275
276
  ```
276
277
 
277
278
  The Nuxt module auto-configures Nitro externals, runtime config, and a server plugin that sets up OTel tracing + logging + firewall. Local and production app identity, firewall key, and secure defaults come from `.securenow/credentials.json`.
278
-
279
- ---
280
-
281
- ### Vite / Browser
282
-
283
- ```javascript
284
- import startSecurenowWeb from 'securenow/web-vite';
285
-
286
- startSecurenowWeb({
287
- serviceName: 'my-frontend',
288
- endpoint: 'https://your-collector:4318',
289
- });
290
- ```
291
-
292
- Instruments document load, fetch, XMLHttpRequest, and user interactions with browser-side OpenTelemetry.
293
-
294
- ---
295
-
296
- ## Firewall Multi-Layer IP Blocking
297
-
298
- The firewall auto-activates once an API key is resolvable and the app firewall toggle is on. Since **v7.5.1**, `npx securenow login` enables the selected app firewall by default and writes the scoped key to `.securenow/credentials.json`; `securenow api-key set` can still write/rotate the key later. Production should use the tokenless file generated by `securenow credentials runtime --env production`. Resolution order: project `./.securenow/credentials.json` -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> global `~/.securenow/credentials.json` -> global named runtime credentials in the same fixed order; legacy env vars are fallback-only for existing deployments and do not choose the credentials filename.
299
-
300
- ```
301
- Layer 4: Cloud/Edge WAF blocked at CDN (Cloudflare, AWS WAF, GCP Cloud Armor)
302
- Layer 3: OS Firewall kernel-level DROP (iptables/nftables)
303
- Layer 2: TCP Socket socket.destroy() before HTTP parsing
304
- Layer 1: HTTP Handler 403 JSON response (always active)
305
- ```
306
-
307
- ### Activate
308
-
309
- ```bash
310
- # Zero-config (recommended) writes the key to .securenow/credentials.json
311
- npx securenow login # pick/create app; firewall connects automatically
312
- # or, if you already have a key:
313
- npx securenow api-key set snk_live_abc123...
314
-
279
+
280
+ ---
281
+
282
+ ### Vite / Browser
283
+
284
+ ```javascript
285
+ import startSecurenowWeb from 'securenow/web-vite';
286
+
287
+ startSecurenowWeb({
288
+ serviceName: 'my-frontend',
289
+ endpoint: 'https://ingest.securenow.ai',
290
+ });
291
+ ```
292
+
293
+ Instruments document load, fetch, XMLHttpRequest, and user interactions with browser-side OpenTelemetry.
294
+
295
+ ---
296
+
297
+ ## Firewall — Multi-Layer IP Blocking
298
+
299
+ The firewall auto-activates once an API key is resolvable and the app firewall toggle is on. Since **v7.5.1**, `npx securenow login` enables the selected app firewall by default and writes the scoped key to `.securenow/credentials.json`; `securenow api-key set` can still write/rotate the key later. Production should use the tokenless file generated by `securenow credentials runtime --env production`. Resolution order: project `./.securenow/credentials.json` -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> global `~/.securenow/credentials.json` -> global named runtime credentials in the same fixed order. Runtime config is credentials-json based; legacy env fallback is disabled unless `SECURENOW_ENABLE_LEGACY_ENV=1` is explicitly set for an old deployment.
300
+
301
+ ```
302
+ Layer 4: Cloud/Edge WAF → blocked at CDN (Cloudflare, AWS WAF, GCP Cloud Armor)
303
+ Layer 3: OS Firewall → kernel-level DROP (iptables/nftables)
304
+ Layer 2: TCP Socket → socket.destroy() before HTTP parsing
305
+ Layer 1: HTTP Handler → 403 JSON response (always active)
306
+ ```
307
+
308
+ ### Activate
309
+
310
+ ```bash
311
+ # Zero-config (recommended) — writes the key to .securenow/credentials.json
312
+ npx securenow login # pick/create app; firewall connects automatically
313
+ # or, if you already have a key:
314
+ npx securenow api-key set snk_live_abc123...
315
+
315
316
  # Production runtime file:
316
317
  npx securenow credentials runtime --env production
317
- ```
318
-
319
- ### Firewall-Only Mode (No Tracing Overhead)
320
-
321
- ```bash
322
- node -r securenow/firewall-only app.js
323
-
324
- # Or via the CLI (same effect)
325
- securenow run --firewall-only app.js
326
- ```
327
-
328
- Loads only dotenv + firewall. No OpenTelemetry, no tracing, no external packages needed.
329
-
330
- ### Programmatic Firewall API
331
-
318
+ ```
319
+
320
+ ### Firewall-Only Mode (No Tracing Overhead)
321
+
322
+ ```bash
323
+ node -r securenow/firewall-only app.js
324
+
325
+ # Or via the CLI (same effect)
326
+ securenow run --firewall-only app.js
327
+ ```
328
+
329
+ Loads only dotenv + firewall. No OpenTelemetry, no tracing, no external packages needed.
330
+
331
+ ### Programmatic Firewall API
332
+
332
333
  ```javascript
333
334
  const firewall = require('securenow/firewall');
334
335
  const appConfig = require('securenow/app-config');
@@ -338,260 +339,260 @@ await firewall.init({
338
339
  apiUrl: 'https://api.securenow.ai',
339
340
  syncInterval: 3600, // safety-net full sync every hour
340
341
  versionCheckInterval: 10, // lightweight ETag check every 10s
341
- failMode: 'open', // 'open' or 'closed'
342
- statusCode: 403,
343
- log: true,
344
- tcp: false,
345
- iptables: false,
346
- cloud: null, // 'cloudflare' | 'aws' | 'gcp'
347
- });
348
-
349
- const stats = firewall.getStats();
350
- const matcher = firewall.getMatcher(); // (ip) => boolean
351
- const allowMatcher = firewall.getAllowlistMatcher();
352
-
353
- await firewall.shutdown();
354
- ```
355
-
356
- ### Cloud WAF Providers
357
-
358
- **Cloudflare:**
359
- ```bash
360
- SECURENOW_FIREWALL_CLOUD=cloudflare
361
- CLOUDFLARE_API_TOKEN=your-token
362
- CLOUDFLARE_ACCOUNT_ID=your-account-id
363
- ```
364
-
365
- **AWS WAF:**
366
- ```bash
367
- SECURENOW_FIREWALL_CLOUD=aws
368
- AWS_WAF_IP_SET_ID=your-ip-set-id
369
- # + standard AWS credentials (env, profile, or IAM role)
370
- ```
371
- Requires peer dep: `npm install @aws-sdk/client-wafv2`
372
-
373
- **GCP Cloud Armor:**
374
- ```bash
375
- SECURENOW_FIREWALL_CLOUD=gcp
376
- GCP_PROJECT_ID=your-project
377
- GCP_SECURITY_POLICY=your-policy-name
378
- ```
379
- Requires peer dep: `npm install @google-cloud/compute`
380
-
381
- **Dry-run (log only, no actual WAF changes):**
382
- ```bash
383
- SECURENOW_FIREWALL_CLOUD_DRY_RUN=1
384
- ```
385
-
386
- ---
387
-
388
- ## Logging
389
-
390
- Logging is enabled by default (`SECURENOW_LOGGING_ENABLED=1`). Logs are exported to your OTLP collector alongside traces.
391
-
392
- ### Get a Logger
393
-
394
- ```javascript
395
- const { getLogger } = require('securenow/tracing');
396
-
397
- const logger = getLogger('my-module', '1.0.0');
398
- if (logger) {
399
- logger.emit({ body: 'User login succeeded', severityText: 'INFO', attributes: { userId: '123' } });
400
- }
401
- ```
402
-
403
- **CLI equivalent** (for shell scripts, cron, CI):
404
-
405
- ```bash
406
- securenow log send "User login succeeded" --level info --attrs userId=123
407
- securenow test-span "ci.smoke-test" # emit a span without booting the SDK
408
- ```
409
-
410
- ### Console Instrumentation
411
-
412
- When tracing is active, `console.log/warn/error` are automatically patched to emit OTLP log records correlated with the active trace span. To access the original console:
413
-
414
- ```javascript
415
- const { originalConsole, restoreConsole } = require('securenow/console-instrumentation');
416
- originalConsole.log('This bypasses OTLP');
417
- restoreConsole(); // undo the patch
418
- ```
419
-
420
- ---
421
-
422
- ## IP Resolution Utilities
423
-
424
- ```javascript
425
- const { resolveClientIp, resolveSocketIp, isFromTrustedProxy } = require('securenow/resolve-ip');
426
-
427
- // From an HTTP request (respects X-Forwarded-For from trusted proxies)
428
- const clientIp = resolveClientIp(req);
429
-
430
- // Direct socket IP
431
- const socketIp = resolveSocketIp(req);
432
-
433
- // Check if request comes from a trusted proxy
434
- const trusted = isFromTrustedProxy(req);
435
- ```
436
-
437
- ### CIDR Matching
438
-
439
- ```javascript
440
- const { createMatcher, ipToInt, parseCidr, matchesCidr } = require('securenow/cidr');
441
-
442
- const isBlocked = createMatcher(['10.0.0.0/8', '192.168.1.0/24']);
443
- isBlocked('10.0.0.5'); // true
444
- isBlocked('8.8.8.8'); // false
445
- ```
446
-
447
- **CLI equivalent:**
448
-
449
- ```bash
450
- securenow cidr match 10.0.0.5 10.0.0.0/8,192.168.1.0/24 # exit 0 = match, 2 = miss
451
- securenow cidr parse 10.0.0.0/8 # network/broadcast/mask/size
452
- ```
453
-
454
- ---
455
-
456
- ## Sensitive Data Redaction
457
-
458
- Available from multiple entry points (`securenow/nextjs-middleware`, `securenow/nextjs-wrapper`, `securenow/nextjs-auto-capture`):
459
-
460
- ```javascript
461
- const { redactSensitiveData, DEFAULT_SENSITIVE_FIELDS } = require('securenow/nextjs-middleware');
462
-
463
- const safe = redactSensitiveData({ username: 'alice', password: 's3cret', token: 'abc' });
464
- // { username: 'alice', password: '[REDACTED]', token: '[REDACTED]' }
465
- ```
466
-
467
- **Auto-redacted fields:** `password`, `passwd`, `pwd`, `secret`, `token`, `api_key`, `apikey`, `access_token`, `auth`, `credentials`, `mysql_pwd`, `stripeToken`, `card`, `cardnumber`, `ccv`, `cvc`, `cvv`, `ssn`, `pin`.
468
-
469
- Add custom fields via `config.capture.sensitiveFields` in `.securenow/credentials.json`; `SECURENOW_SENSITIVE_FIELDS=field1,field2` is a legacy fallback for existing installs.
470
-
471
- **CLI equivalent** (for piping, scripts, debugging what a payload looks like post-redaction):
472
-
473
- ```bash
474
- securenow redact '{"user":"alice","password":"s3cret"}'
475
- securenow redact @request.json --fields internal_id,sessionHash
476
- ```
477
-
478
- ---
479
-
342
+ failMode: 'open', // 'open' or 'closed'
343
+ statusCode: 403,
344
+ log: true,
345
+ tcp: false,
346
+ iptables: false,
347
+ cloud: null, // 'cloudflare' | 'aws' | 'gcp'
348
+ });
349
+
350
+ const stats = firewall.getStats();
351
+ const matcher = firewall.getMatcher(); // (ip) => boolean
352
+ const allowMatcher = firewall.getAllowlistMatcher();
353
+
354
+ await firewall.shutdown();
355
+ ```
356
+
357
+ ### Cloud WAF Providers
358
+
359
+ **Cloudflare:**
360
+ ```json
361
+ {
362
+ "config": {
363
+ "firewall": {
364
+ "cloud": "cloudflare",
365
+ "cloudflare": {
366
+ "apiToken": "your-token",
367
+ "accountId": "your-account-id"
368
+ }
369
+ }
370
+ }
371
+ }
372
+ ```
373
+
374
+ **AWS WAF:**
375
+ ```json
376
+ {
377
+ "config": {
378
+ "firewall": {
379
+ "cloud": "aws",
380
+ "aws": {
381
+ "wafIpSetId": "your-ip-set-id",
382
+ "wafIpSetName": "your-ip-set-name",
383
+ "wafScope": "REGIONAL"
384
+ }
385
+ }
386
+ }
387
+ }
388
+ ```
389
+ AWS authentication should use the standard AWS credential chain outside the
390
+ SecureNow SDK config.
391
+ Requires peer dep: `npm install @aws-sdk/client-wafv2`
392
+
393
+ **GCP Cloud Armor:**
394
+ ```json
395
+ {
396
+ "config": {
397
+ "firewall": {
398
+ "cloud": "gcp",
399
+ "gcp": {
400
+ "projectId": "your-project",
401
+ "securityPolicy": "your-policy-name"
402
+ }
403
+ }
404
+ }
405
+ }
406
+ ```
407
+ Requires peer dep: `npm install @google-cloud/compute`
408
+
409
+ **Dry-run (log only, no actual WAF changes):**
410
+ ```json
411
+ {
412
+ "config": {
413
+ "firewall": {
414
+ "cloudDryRun": true
415
+ }
416
+ }
417
+ }
418
+ ```
419
+
420
+ ---
421
+
422
+ ## Logging
423
+
424
+ Logging is enabled by default (`config.logging.enabled: true`). Logs are exported to your OTLP collector alongside traces.
425
+
426
+ ### Get a Logger
427
+
428
+ ```javascript
429
+ const { getLogger } = require('securenow/tracing');
430
+
431
+ const logger = getLogger('my-module', '1.0.0');
432
+ if (logger) {
433
+ logger.emit({ body: 'User login succeeded', severityText: 'INFO', attributes: { userId: '123' } });
434
+ }
435
+ ```
436
+
437
+ **CLI equivalent** (for shell scripts, cron, CI):
438
+
439
+ ```bash
440
+ securenow log send "User login succeeded" --level info --attrs userId=123
441
+ securenow test-span "ci.smoke-test" # emit a span without booting the SDK
442
+ ```
443
+
444
+ ### Console Instrumentation
445
+
446
+ When tracing is active, `console.log/warn/error` are automatically patched to emit OTLP log records correlated with the active trace span. To access the original console:
447
+
448
+ ```javascript
449
+ const { originalConsole, restoreConsole } = require('securenow/console-instrumentation');
450
+ originalConsole.log('This bypasses OTLP');
451
+ restoreConsole(); // undo the patch
452
+ ```
453
+
454
+ ---
455
+
456
+ ## IP Resolution Utilities
457
+
458
+ ```javascript
459
+ const { resolveClientIp, resolveSocketIp, isFromTrustedProxy } = require('securenow/resolve-ip');
460
+
461
+ // From an HTTP request (respects X-Forwarded-For from trusted proxies)
462
+ const clientIp = resolveClientIp(req);
463
+
464
+ // Direct socket IP
465
+ const socketIp = resolveSocketIp(req);
466
+
467
+ // Check if request comes from a trusted proxy
468
+ const trusted = isFromTrustedProxy(req);
469
+ ```
470
+
471
+ ### CIDR Matching
472
+
473
+ ```javascript
474
+ const { createMatcher, ipToInt, parseCidr, matchesCidr } = require('securenow/cidr');
475
+
476
+ const isBlocked = createMatcher(['10.0.0.0/8', '192.168.1.0/24']);
477
+ isBlocked('10.0.0.5'); // true
478
+ isBlocked('8.8.8.8'); // false
479
+ ```
480
+
481
+ **CLI equivalent:**
482
+
483
+ ```bash
484
+ securenow cidr match 10.0.0.5 10.0.0.0/8,192.168.1.0/24 # exit 0 = match, 2 = miss
485
+ securenow cidr parse 10.0.0.0/8 # network/broadcast/mask/size
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Sensitive Data Redaction
491
+
492
+ Available from multiple entry points (`securenow/nextjs-middleware`, `securenow/nextjs-wrapper`, `securenow/nextjs-auto-capture`):
493
+
494
+ ```javascript
495
+ const { redactSensitiveData, DEFAULT_SENSITIVE_FIELDS } = require('securenow/nextjs-middleware');
496
+
497
+ const safe = redactSensitiveData({ username: 'alice', password: 's3cret', token: 'abc' });
498
+ // { username: 'alice', password: '[REDACTED]', token: '[REDACTED]' }
499
+ ```
500
+
501
+ **Auto-redacted fields:** `password`, `passwd`, `pwd`, `secret`, `token`, `api_key`, `apikey`, `access_token`, `auth`, `credentials`, `mysql_pwd`, `stripeToken`, `card`, `cardnumber`, `ccv`, `cvc`, `cvv`, `ssn`, `pin`.
502
+
503
+ Add custom fields via `config.capture.sensitiveFields` in `.securenow/credentials.json`.
504
+
505
+ **CLI equivalent** (for piping, scripts, debugging what a payload looks like post-redaction):
506
+
507
+ ```bash
508
+ securenow redact '{"user":"alice","password":"s3cret"}'
509
+ securenow redact @request.json --fields internal_id,sessionHash
510
+ ```
511
+
512
+ ---
513
+
480
514
  ## Credentials Configuration
481
515
 
482
- Local development and production use `.securenow/credentials.json`. Every setting below lives under `app` or `config`; `npx securenow credentials runtime --env production` creates a tokenless production file with the same structure. Since v7.7.2, the SDK also accepts named runtime files such as `.securenow/credentials.production.json` when the canonical `credentials.json` file is absent. Filename lookup is deterministic and does not read environment variables. Environment variables are legacy fallbacks only.
483
-
484
- ### App Identity
485
-
486
- | Variable | Description | Default |
487
- |----------|-------------|---------|
488
- | `SECURENOW_APPID` | Legacy fallback for `app.key` / service name | from credentials file, then package name |
489
- | `SECURENOW_INSTANCE` | Legacy fallback for `app.instance` / OTLP collector base URL | from credentials file, then `https://freetrial.securenow.ai:4318` |
490
-
491
- ### Service Naming
492
-
493
- | Variable | Description | Default |
494
- |----------|-------------|---------|
495
- | `OTEL_SERVICE_NAME` | Legacy fallback for `app.name` | — |
496
- | `SECURENOW_NO_UUID` | `1` to use exact app ID without UUID suffix | `0` |
497
- | `SECURENOW_STRICT` | `1` to exit if APPID missing in PM2 cluster | `0` |
498
-
499
- ### OTLP Connection
500
-
501
- | Variable | Description | Default |
502
- |----------|-------------|---------|
503
- | `OTEL_EXPORTER_OTLP_ENDPOINT` | Legacy fallback for `config.otel.endpoint` | |
504
- | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Legacy fallback for `config.otel.tracesEndpoint` | `{instance}/v1/traces` |
505
- | `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Legacy fallback for `config.otel.logsEndpoint` | `{instance}/v1/logs` |
506
- | `OTEL_EXPORTER_OTLP_HEADERS` | Comma-separated `key=value` headers for OTLP requests | — |
507
-
508
- ### Behavior
509
-
510
- | Variable | Description | Default |
511
- |----------|-------------|---------|
512
- | `SECURENOW_LOGGING_ENABLED` | Enable OTLP log export | `1` |
513
- | `SECURENOW_CAPTURE_BODY` | Capture HTTP request bodies | `1` |
514
- | `SECURENOW_MAX_BODY_SIZE` | Max body size in bytes | `10240` |
515
- | `SECURENOW_CAPTURE_MULTIPART` | Capture multipart/form-data (streaming, metadata only) | `1` |
516
- | `SECURENOW_SENSITIVE_FIELDS` | Comma-separated extra fields to redact | — |
517
- | `SECURENOW_DISABLE_INSTRUMENTATIONS` | Comma-separated packages to skip (e.g. `fs,dns`) | — |
518
- | `SECURENOW_TEST_SPAN` | `1` to emit a test span on startup | `0` |
519
- | `SECURENOW_HIDE_BANNER` | `1` to suppress free-trial upgrade banner | `0` |
520
- | `OTEL_LOG_LEVEL` | SDK diagnostic override: `error`, `warn`, `info`, `debug`, or `none` | `error` |
521
- | `SECURENOW_ENVIRONMENT` / `SECURENOW_DEPLOYMENT_ENVIRONMENT` / `NODE_ENV` | Legacy fallback for `config.runtime.deploymentEnvironment` | `production` |
522
-
523
- ### Firewall
524
-
525
- | Variable | Description | Default |
526
- |----------|-------------|---------|
527
- | `SECURENOW_API_KEY` | Legacy env override for the `apiKey` field (`snk_live_...`). Since v7.5.1, login writes the scoped firewall key to `.securenow/credentials.json`. | - |
528
- | `SECURENOW_API_URL` | SecureNow API base URL | `https://api.securenow.ai` |
529
- | `SECURENOW_FIREWALL_VERSION_INTERVAL` | Seconds between lightweight ETag checks | `10` |
530
- | `SECURENOW_FIREWALL_SYNC_INTERVAL` | Safety-net full blocklist refresh interval in seconds | `3600` |
531
- | `SECURENOW_FIREWALL_FAIL_MODE` | `open` (allow all when unavailable) or `closed` | `open` |
532
- | `SECURENOW_FIREWALL_STATUS_CODE` | HTTP status for blocked requests | `403` |
533
- | `SECURENOW_FIREWALL_LOG` | Log blocked requests | `1` |
534
- | `SECURENOW_FIREWALL_TCP` | Enable Layer 2 TCP blocking | `0` |
535
- | `SECURENOW_FIREWALL_IPTABLES` | Enable Layer 3 iptables/nftables | `0` |
536
- | `SECURENOW_FIREWALL_CLOUD` | Cloud WAF: `cloudflare`, `aws`, or `gcp` | — |
537
- | `SECURENOW_FIREWALL_CLOUD_DRY_RUN` | `1` to log cloud pushes without applying | `0` |
538
- | `SECURENOW_TRUSTED_PROXIES` | Comma-separated trusted proxy IPs | — |
539
-
516
+ Local development and production use `.securenow/credentials.json`. Every setting below lives under `app` or `config`; `npx securenow credentials runtime --env production` creates a tokenless production file with the same structure. Since v7.7.2, the SDK also accepts named runtime files such as `.securenow/credentials.production.json` when the canonical `credentials.json` file is absent. Filename lookup is deterministic and does not read environment variables. Legacy env fallback is disabled unless `SECURENOW_ENABLE_LEGACY_ENV=1` is explicitly set.
517
+
518
+ | Credentials path | Purpose |
519
+ |---|---|
520
+ | `app.key` | App routing UUID. The SecureNow ingestion gateway routes telemetry by this key |
521
+ | `app.name` | Human-readable app label |
522
+ | `apiKey` | Scoped firewall key (`snk_live_...`) |
523
+ | `config.otel.endpoint` | Optional OTLP base endpoint override |
524
+ | `config.otel.tracesEndpoint` | Optional full traces endpoint |
525
+ | `config.otel.logsEndpoint` | Optional full logs endpoint |
526
+ | `config.otel.headers` | Extra OTLP headers as JSON object |
527
+ | `config.otel.logLevel` | SDK diagnostics: `error`, `warn`, `info`, `debug`, or `none` |
528
+ | `config.otel.disableInstrumentations` | OTel instrumentation package names to skip |
529
+ | `config.logging.enabled` | Enable/disable OTLP log export |
530
+ | `config.capture.body` | Capture HTTP request bodies |
531
+ | `config.capture.maxBodySize` | Max body size in bytes |
532
+ | `config.capture.multipart` | Capture multipart metadata, never file content |
533
+ | `config.capture.sensitiveFields` | Extra redaction field fragments |
534
+ | `config.runtime.deploymentEnvironment` | Sent as `deployment.environment` |
535
+ | `config.runtime.noUuid` | Use exact app key as service name without UUID suffix |
536
+ | `config.runtime.strict` | Exit clustered workers when no app identity resolves |
537
+ | `config.runtime.testSpan` | Emit a startup test span |
538
+ | `config.runtime.hideBanner` | Suppress the free-trial upgrade banner |
539
+ | `config.firewall.*` | Local firewall behavior, API URL, intervals, cloud WAF, and trusted proxy settings |
540
+
540
541
  **Resilience:** The firewall SDK includes a circuit breaker (opens after 5 consecutive errors, 2-min cooldown), in-flight request guards (prevents overlapping requests), 429 Retry-After support, and exponential backoff on both lightweight ETag checks and initial sync retries.
541
-
542
- ### Cloud WAF Provider Variables
543
-
544
- | Provider | Variables |
545
- |----------|-----------|
546
- | Cloudflare | `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` |
547
- | AWS WAF | `AWS_WAF_IP_SET_ID`, `AWS_WAF_IP_SET_NAME`, `AWS_WAF_SCOPE` |
548
- | GCP | `GCP_PROJECT_ID`, `GCP_SECURITY_POLICY` |
549
-
550
- ### Priority Order
551
-
552
- **Service name:** credentials `app.key` > credentials `app.name` > legacy env fallback > package name
553
-
554
- **Endpoint:** credentials `config.otel.tracesEndpoint` / `config.otel.endpoint` / `app.instance` > legacy env fallback > `https://freetrial.securenow.ai:4318`
555
-
556
- ---
557
-
558
- ## Recipes for Agentic AI
559
-
560
- ### Add Observability to an Existing Express App
561
-
562
- ```bash
542
+
543
+ ### Cloud WAF Provider Variables
544
+
545
+ | Provider | Variables |
546
+ |----------|-----------|
547
+ | Cloudflare | `config.firewall.cloudflare.apiToken`, `config.firewall.cloudflare.accountId` |
548
+ | AWS WAF | `config.firewall.aws.wafIpSetId`, `config.firewall.aws.wafIpSetName`, `config.firewall.aws.wafScope` |
549
+ | GCP | `config.firewall.gcp.projectId`, `config.firewall.gcp.securityPolicy` |
550
+
551
+ ### Priority Order
552
+
553
+ **Service name:** credentials `app.key` > credentials `app.name` > package name
554
+
555
+ **Endpoint:** credentials `config.otel.tracesEndpoint` / `config.otel.endpoint` > default SecureNow ingestion gateway `https://ingest.securenow.ai`
556
+
557
+ ---
558
+
559
+ ## Recipes for Agentic AI
560
+
561
+ ### Add Observability to an Existing Express App
562
+
563
+ ```bash
563
564
  npm install securenow@latest
564
565
  npx securenow login
565
- ```
566
-
567
- No `.env` is needed. `npx securenow login` writes app identity, collector URL, firewall key, and secure defaults to `.securenow/credentials.json`; `npx securenow init` makes sure the file has explanations and is gitignored.
568
-
569
- Update `package.json`:
570
- ```json
571
- { "scripts": { "start": "node -r securenow/register src/index.js" } }
572
- ```
573
-
574
- No code changes to the application needed.
575
-
576
- ### Add Observability + Firewall to a Next.js App
577
-
578
- ```bash
566
+ ```
567
+
568
+ No `.env` is needed. `npx securenow login` writes app identity, firewall key, and secure defaults to `.securenow/credentials.json`; the SDK uses the default SecureNow ingestion gateway and the gateway routes by `app.key`. `npx securenow init` makes sure the file has explanations and is gitignored without ignoring the whole `.securenow/` directory.
569
+
570
+ Update `package.json`:
571
+ ```json
572
+ { "scripts": { "start": "node -r securenow/register src/index.js" } }
573
+ ```
574
+
575
+ No code changes to the application needed.
576
+
577
+ ### Add Observability + Firewall to a Next.js App
578
+
579
+ ```bash
579
580
  npm install securenow@latest
580
581
  npx securenow login # pick/create app; firewall key is minted automatically
581
- ```
582
-
583
- `securenow login` enables the selected app's firewall toggle and writes session, app, and firewall key to `.securenow/credentials.json` (auto-gitignored). Traces, logs, request body capture, multipart metadata capture, and firewall enforcement are enabled by default. Then run `npx securenow init`; it creates `instrumentation.ts`, patches `next.config.*` when safe, or prints exact Codex/Claude merge instructions for existing files.
584
-
585
- ### Enable Firewall With Zero Tracing Overhead
586
-
587
- For apps that only need IP blocking:
588
-
589
- ```bash
590
- node -r securenow/firewall-only app.js
591
- ```
592
-
582
+ ```
583
+
584
+ `securenow login` enables the selected app's firewall toggle and writes session, app, and firewall key to `.securenow/credentials.json` (gitignored via credential-file patterns, not a whole-directory `.securenow/` ignore). Traces, logs, request body capture, multipart metadata capture, and firewall enforcement are enabled by default. Then run `npx securenow init`; it creates `instrumentation.ts`, patches `next.config.*` when safe, or prints exact Codex/Claude merge instructions for existing files.
585
+
586
+ ### Enable Firewall With Zero Tracing Overhead
587
+
588
+ For apps that only need IP blocking:
589
+
590
+ ```bash
591
+ node -r securenow/firewall-only app.js
592
+ ```
593
+
593
594
  Make sure an API key is resolvable by running `npx securenow login` first. If you already have a key, `securenow api-key set snk_live_...` writes the creds file. For production, run `npx securenow credentials runtime --env production` and mount/copy it as `.securenow/credentials.json`.
594
-
595
+
595
596
  ### Production Hardened Configuration
596
597
 
597
598
  ```json
@@ -611,22 +612,22 @@ Make sure an API key is resolvable by running `npx securenow login` first. If yo
611
612
  }
612
613
  }
613
614
  ```
614
-
615
- ### Instrument a Docker Container
616
-
617
- ```dockerfile
618
- FROM node:20-slim
619
- WORKDIR /app
620
- COPY package*.json ./
621
- RUN npm ci --omit=dev
615
+
616
+ ### Instrument a Docker Container
617
+
618
+ ```dockerfile
619
+ FROM node:20-slim
620
+ WORKDIR /app
621
+ COPY package*.json ./
622
+ RUN npm ci --omit=dev
622
623
  COPY . .
623
624
  # Mount/copy secret file at runtime as /app/.securenow/credentials.json.
624
625
 
625
626
  CMD ["node", "-r", "securenow/register", "src/index.js"]
626
627
  ```
627
-
628
- ### Kubernetes with Separate Trace/Log Collectors
629
-
628
+
629
+ ### Kubernetes with Separate Trace/Log Collectors
630
+
630
631
  ```json
631
632
  {
632
633
  "app": { "key": "k8s-service", "name": "k8s-service" },
@@ -640,26 +641,26 @@ CMD ["node", "-r", "securenow/register", "src/index.js"]
640
641
  }
641
642
  }
642
643
  ```
643
-
644
- ---
645
-
646
- ## Verification
647
-
648
- On startup, securenow logs its configuration:
649
-
650
- ```
651
- [securenow] app.key="my-app" service.name=my-app
652
- [securenow] OTel SDK started https://collector:4318/v1/traces
653
- [securenow] Logging: ENABLED https://collector:4318/v1/logs
644
+
645
+ ---
646
+
647
+ ## Verification
648
+
649
+ On startup, securenow logs its configuration:
650
+
651
+ ```
652
+ [securenow] app.key="my-app" → service.name=my-app
653
+ [securenow] OTel SDK started → https://collector:4318/v1/traces
654
+ [securenow] Logging: ENABLED → https://collector:4318/v1/logs
654
655
  [securenow] Request body capture: ENABLED (max: 10240 bytes)
655
656
  [securenow] Firewall: ENABLED
656
657
  [securenow] Firewall: synced 142 blocked IPs
657
658
  ```
658
659
 
659
660
  Set `config.otel.logLevel` to `debug`, or temporarily run with `OTEL_LOG_LEVEL=debug`, and run `securenow doctor --json` to troubleshoot connectivity, duplicate OpenTelemetry API packages, and provider registration issues.
660
-
661
- **CLI equivalent** (works without booting the SDK useful when the app won't start):
662
-
661
+
662
+ **CLI equivalent** (works without booting the SDK — useful when the app won't start):
663
+
663
664
  ```bash
664
665
  securenow env # show resolved app key, endpoints, and credentials fields
665
666
  securenow doctor # probe OTLP + API endpoints, exits 1 on failure