securenow 7.6.6 → 7.6.8

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.
Files changed (68) hide show
  1. package/NPM_README.md +13 -13
  2. package/README.md +21 -37
  3. package/app-config.js +5 -3
  4. package/cli/config.js +4 -3
  5. package/cli/diagnostics.js +54 -15
  6. package/cli/run.js +40 -11
  7. package/firewall-only.js +1 -1
  8. package/firewall.js +88 -57
  9. package/mcp/catalog.js +1 -1
  10. package/nextjs-webpack-config.js +3 -15
  11. package/nextjs.js +21 -23
  12. package/nuxt-server-plugin.mjs +20 -10
  13. package/package.json +33 -34
  14. package/register.js +1 -1
  15. package/tracing.js +17 -7
  16. package/web-vite.mjs +23 -13
  17. package/CONSUMING-APPS-GUIDE.md +0 -463
  18. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +0 -1388
  19. package/docs/API-KEYS-GUIDE.md +0 -278
  20. package/docs/ARCHITECTURE.md +0 -408
  21. package/docs/AUTO-BODY-CAPTURE.md +0 -412
  22. package/docs/AUTO-SETUP-SUMMARY.md +0 -331
  23. package/docs/AUTO-SETUP.md +0 -419
  24. package/docs/AUTOMATIC-IP-CAPTURE.md +0 -359
  25. package/docs/BODY-CAPTURE-FIX.md +0 -261
  26. package/docs/BODY-CAPTURE-QUICKSTART.md +0 -147
  27. package/docs/CHANGELOG-NEXTJS.md +0 -235
  28. package/docs/COMPLETION-REPORT.md +0 -408
  29. package/docs/CUSTOMER-GUIDE.md +0 -364
  30. package/docs/EASIEST-SETUP.md +0 -342
  31. package/docs/ENVIRONMENT-VARIABLES.md +0 -166
  32. package/docs/ENVIRONMENTS.md +0 -60
  33. package/docs/EXPRESS-BODY-CAPTURE.md +0 -1028
  34. package/docs/EXPRESS-SETUP-GUIDE.md +0 -722
  35. package/docs/FINAL-SOLUTION.md +0 -335
  36. package/docs/FIREWALL-GUIDE.md +0 -440
  37. package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
  38. package/docs/INDEX.md +0 -222
  39. package/docs/LOGGING-GUIDE.md +0 -704
  40. package/docs/LOGGING-QUICKSTART.md +0 -221
  41. package/docs/MCP-GUIDE.md +0 -58
  42. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
  43. package/docs/NEXTJS-BODY-CAPTURE.md +0 -368
  44. package/docs/NEXTJS-GUIDE.md +0 -392
  45. package/docs/NEXTJS-QUICKSTART.md +0 -83
  46. package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
  47. package/docs/NEXTJS-WEBPACK-WARNINGS.md +0 -267
  48. package/docs/NEXTJS-WRAPPER-APPROACH.md +0 -414
  49. package/docs/NUXT-GUIDE.md +0 -173
  50. package/docs/QUICKSTART-BODY-CAPTURE.md +0 -293
  51. package/docs/REDACTION-EXAMPLES.md +0 -484
  52. package/docs/REQUEST-BODY-CAPTURE.md +0 -587
  53. package/docs/SOLUTION-SUMMARY.md +0 -312
  54. package/docs/VERCEL-OTEL-MIGRATION.md +0 -255
  55. package/examples/README.md +0 -265
  56. package/examples/express-with-logging.js +0 -137
  57. package/examples/instrumentation-with-auto-capture.ts +0 -41
  58. package/examples/next.config.js +0 -37
  59. package/examples/nextjs-api-route-with-body-capture.ts +0 -54
  60. package/examples/nextjs-env-example.txt +0 -32
  61. package/examples/nextjs-instrumentation.js +0 -36
  62. package/examples/nextjs-instrumentation.ts +0 -36
  63. package/examples/nextjs-middleware.js +0 -37
  64. package/examples/nextjs-middleware.ts +0 -37
  65. package/examples/nextjs-with-logging-example.md +0 -301
  66. package/examples/nextjs-with-options.ts +0 -36
  67. package/examples/test-nextjs-setup.js +0 -70
  68. package/postinstall.js +0 -296
package/NPM_README.md CHANGED
@@ -59,7 +59,7 @@ yarn add securenow
59
59
 
60
60
  ### 1. Automatic Setup (Recommended)
61
61
 
62
- Run login it's a browser flow that picks or creates an app and connects the firewall automatically:
62
+ Run login - it is a browser flow that picks or creates an app and connects the firewall automatically:
63
63
 
64
64
  ```bash
65
65
  npx securenow login
@@ -138,7 +138,7 @@ You'll see confirmation in the console:
138
138
 
139
139
  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.
140
140
 
141
- **Full CLI/SDK parity (v6.1.0+):** every SDK export has a matching CLI command. `redactSensitiveData` `securenow redact`, `createMatcher` `securenow cidr match`, `getLogger().emit()` `securenow log send`, `SECURENOW_TEST_SPAN` `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.
141
+ **Full CLI/SDK parity (v6.1.0+):** every SDK export has a matching CLI command. `redactSensitiveData` -> `securenow redact`, `createMatcher` -> `securenow cidr match`, `getLogger().emit()` -> `securenow log send`, `SECURENOW_TEST_SPAN` -> `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.
142
142
 
143
143
  ### Getting Started
144
144
 
@@ -326,7 +326,7 @@ npx securenow instances test <id>
326
326
 
327
327
  ### False-Positive Management
328
328
 
329
- Full FP triage from the terminal no dashboard required.
329
+ Full FP triage from the terminal - no dashboard required.
330
330
 
331
331
  ```bash
332
332
  # Browse & inspect rules
@@ -380,7 +380,7 @@ SDK helpers surfaced as CLI commands so agents (and humans) can validate behavio
380
380
  npx securenow redact '{"user":"alice","password":"s3cret","card":"4242"}'
381
381
  npx securenow redact @request.json --fields internal_id,sessionHash
382
382
 
383
- # CIDR match an IP against a list, or parse a range
383
+ # CIDR - match an IP against a list, or parse a range
384
384
  npx securenow cidr match 10.0.0.5 10.0.0.0/8,192.168.1.0/24 # exit 0 = hit, 2 = miss
385
385
  npx securenow cidr parse 10.0.0.0/24 # network, broadcast, mask, size
386
386
 
@@ -416,7 +416,7 @@ Config files are stored in `~/.securenow/` (global) or `.securenow/` in the proj
416
416
  | `~/.securenow/credentials.json` | Auth token, app, API key, config - global (use `login --global`) |
417
417
  | `.securenow/credentials.json` | Auth token, app, API key, config, explanations - project-local default |
418
418
 
419
- **Resolution order:** project `.securenow/credentials.json` global `~/.securenow/credentials.json`. Legacy CLI token overrides still work for existing automation.
419
+ **Resolution order:** project `.securenow/credentials.json` -> global `~/.securenow/credentials.json`. Legacy CLI token overrides still work for existing automation.
420
420
 
421
421
  ### Global Flags
422
422
 
@@ -432,7 +432,7 @@ Every command supports these flags:
432
432
 
433
433
  | Variable | Description |
434
434
  |----------|-------------|
435
- | `SECURENOW_TOKEN` | JWT token overrides all file-based credentials |
435
+ | `SECURENOW_TOKEN` | JWT token - overrides all file-based credentials |
436
436
  | `SECURENOW_API_URL` | Override the API base URL |
437
437
  | `SECURENOW_DEBUG` | Show stack traces on errors |
438
438
  | `NO_COLOR` | Disable colored output |
@@ -442,11 +442,11 @@ Every command supports these flags:
442
442
  Project-local credentials are the default, so separate projects can use separate SecureNow apps on the same machine:
443
443
 
444
444
  ```bash
445
- # In project A log in as user-a@company.com
445
+ # In project A - log in as user-a@company.com
446
446
  cd ~/projects/project-a
447
447
  npx securenow login
448
448
 
449
- # In project B log in as user-b@company.com
449
+ # In project B - log in as user-b@company.com
450
450
  cd ~/projects/project-b
451
451
  npx securenow login
452
452
 
@@ -935,7 +935,7 @@ app.listen(3000, () => console.log('Feathers running on port 3000'));
935
935
 
936
936
  ### Next.js
937
937
 
938
- See [Next.js Complete Guide](./docs/NEXTJS-SETUP-COMPLETE.md) for the full reference.
938
+ Use `npx securenow init` for the current Next.js integration; it creates or prints the exact merge instructions for `instrumentation.ts` and `next.config.*`.
939
939
 
940
940
  #### Option A: `securenow init` (Recommended)
941
941
 
@@ -1101,7 +1101,7 @@ If you only need IP blocking without OpenTelemetry tracing overhead, use the sta
1101
1101
  # Manual preload flag
1102
1102
  node -r securenow/firewall-only app.js
1103
1103
 
1104
- # Or via the CLI (v6.1.0+) same effect, clearer intent
1104
+ # Or via the CLI (v6.1.0+) - same effect, clearer intent
1105
1105
  securenow run --firewall-only app.js
1106
1106
  ```
1107
1107
 
@@ -1153,7 +1153,7 @@ npx securenow firewall status
1153
1153
 
1154
1154
  Or create one from the dashboard with the `firewall:read` scope.
1155
1155
 
1156
- See the [Firewall Guide](./docs/FIREWALL-GUIDE.md) for the full reference.
1156
+ Use `npx securenow firewall status`, `npx securenow firewall apps`, and `npx securenow help firewall` for the current firewall reference.
1157
1157
 
1158
1158
  ---
1159
1159
 
@@ -1161,7 +1161,7 @@ See the [Firewall Guide](./docs/FIREWALL-GUIDE.md) for the full reference.
1161
1161
 
1162
1162
  Local development and production use `.securenow/credentials.json`. Run `npx securenow login` and `npx securenow init`; for production, run `npx securenow credentials runtime --env production` and mount/copy the generated JSON as `.securenow/credentials.json`.
1163
1163
 
1164
- See [docs/ENVIRONMENT-VARIABLES.md](./docs/ENVIRONMENT-VARIABLES.md) and [docs/ENVIRONMENTS.md](./docs/ENVIRONMENTS.md) for the full credentials and environment reference.
1164
+ Use `.securenow/credentials.json` as the source of truth. Run `npx securenow env --json` to inspect resolved settings without exposing secrets.
1165
1165
 
1166
1166
  ### Credentials Fields
1167
1167
 
@@ -1247,7 +1247,7 @@ Legacy env fallback aliases are listed below for existing installs only.
1247
1247
  | `SECURENOW_FIREWALL_CLOUD_DRY_RUN` | Log cloud pushes without applying changes. | `0` |
1248
1248
  | `SECURENOW_TRUSTED_PROXIES` | Comma-separated trusted proxy IPs. | - |
1249
1249
 
1250
- See [Firewall Guide](./docs/FIREWALL-GUIDE.md) for complete details on all layers.
1250
+ Use `npx securenow help firewall` for complete details on all layers.
1251
1251
 
1252
1252
  #### Debugging
1253
1253
 
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # SecureNow
2
2
 
3
- Zero-config OpenTelemetry for Node.js, Next.js, and Nuxt traces, logs, body capture, and IP firewall in one install. **No env vars. No copy-pasting keys.**
3
+ Zero-config OpenTelemetry for Node.js, Next.js, and Nuxt: traces, logs, body capture, and IP firewall in one install. **No env vars. No copy-pasting keys.**
4
4
 
5
5
  **Official npm package:** [securenow](http://securenow.ai/)
6
6
 
7
7
  ---
8
8
 
9
- ## 🚀 30-second setup
9
+ ## 30-second setup
10
10
 
11
11
  ```bash
12
12
  # 1. Install
@@ -17,7 +17,7 @@ npm install securenow
17
17
  # a scoped firewall API key in the same file - no env vars.
18
18
  npx securenow login
19
19
 
20
- # 3. Start your app one flag is all it takes
20
+ # 3. Start your app - one flag is all it takes
21
21
  node -r securenow/register src/index.js
22
22
  ```
23
23
 
@@ -108,7 +108,7 @@ const nextConfig = {
108
108
  export default nextConfig;
109
109
  ```
110
110
 
111
- If a custom config or existing instrumentation file needs human judgment, `init` prints a Codex/Claude-ready prompt with the exact edits to merge. See [Next.js Complete Guide](./docs/NEXTJS-GUIDE.md).
111
+ If a custom config or existing instrumentation file needs human judgment, `init` prints a Codex/Claude-ready prompt with the exact edits to merge.
112
112
 
113
113
  ### Nuxt 3
114
114
 
@@ -119,18 +119,18 @@ export default defineNuxtConfig({
119
119
  });
120
120
  ```
121
121
 
122
- See [Nuxt 3 Guide](./docs/NUXT-GUIDE.md).
122
+ Run `npx securenow init` after installing so the credentials file and secure defaults are created before the app starts.
123
123
 
124
124
  ---
125
125
 
126
126
  ## What's captured automatically
127
127
 
128
- - HTTP spans (Express, Fastify, NestJS, Koa, Hapi, Next.js, Nuxt, raw `http`)
129
- - Database spans (Postgres, MySQL, MongoDB, Redis)
130
- - `console.log/info/warn/error/debug` forwarded as OTLP logs with trace correlation
131
- - Request body capture (JSON, GraphQL, form-encoded) with auto-redaction of `password`, `token`, `api_key`, `authorization`, `cookie`, etc.
132
- - Multipart upload metadata (field names, file names, sizes, content-types never file content)
133
- - Firewall (500k+ known-bad IPs, refreshed hourly) activates as soon as you've logged in
128
+ - HTTP spans (Express, Fastify, NestJS, Koa, Hapi, Next.js, Nuxt, raw `http`)
129
+ - Database spans (Postgres, MySQL, MongoDB, Redis)
130
+ - `console.log/info/warn/error/debug` forwarded as OTLP logs with trace correlation
131
+ - Request body capture (JSON, GraphQL, form-encoded) with auto-redaction of `password`, `token`, `api_key`, `authorization`, `cookie`, etc.
132
+ - Multipart upload metadata (field names, file names, sizes, content-types; never file content)
133
+ - Firewall protection from the selected app's SecureNow blocklist and IPDB sync - activates as soon as you've logged in
134
134
 
135
135
  All of these are **on by default**. Tune them in `.securenow/credentials.json` under the `config` block.
136
136
 
@@ -235,7 +235,7 @@ Legacy env fallback aliases:
235
235
 
236
236
  | Variable | Default | Purpose |
237
237
  |---|---|---|
238
- | `SECURENOW_APPID` | from credentials file | App routing key (UUID) sent as OTel `service.name` |
238
+ | `SECURENOW_APPID` | from credentials file | App routing key (UUID), sent as OTel `service.name` |
239
239
  | `SECURENOW_INSTANCE` | `https://freetrial.securenow.ai:4318` | OTLP collector endpoint |
240
240
  | `SECURENOW_API_KEY` | from credentials file | Scoped firewall API key (`snk_live_...`) |
241
241
  | `SECURENOW_LOGGING_ENABLED` | `1` (on) | Forward `console.*` as OTLP logs. Set to `0` to disable. |
@@ -243,26 +243,26 @@ Legacy env fallback aliases:
243
243
  | `SECURENOW_CAPTURE_MULTIPART` | `1` (on) | Capture multipart metadata (not content). |
244
244
  | `SECURENOW_MAX_BODY_SIZE` | `10240` | Max bytes captured per body. |
245
245
  | `SECURENOW_SENSITIVE_FIELDS` | `password,token,authorization,...` | Extra fields to redact (comma-separated). |
246
- | `SECURENOW_DISABLE_INSTRUMENTATIONS` | | Comma-separated OTel instrumentations to disable. |
246
+ | `SECURENOW_DISABLE_INSTRUMENTATIONS` | - | Comma-separated OTel instrumentations to disable. |
247
247
  | `SECURENOW_NO_UUID` | `0` | Don't append a UUID to `service.instance.id`. |
248
248
  | `SECURENOW_STRICT` | `0` | Exit with code 1 if `SECURENOW_APPID` is missing in a PM2 cluster. |
249
- | `OTEL_EXPORTER_OTLP_HEADERS` | | Raw OTLP headers (e.g. `x-api-key=...`). |
250
- | `OTEL_LOG_LEVEL` | | `debug`/`info`/`warn`/`error`. |
249
+ | `OTEL_EXPORTER_OTLP_HEADERS` | - | Raw OTLP headers (e.g. `x-api-key=...`). |
250
+ | `OTEL_LOG_LEVEL` | - | `debug`/`info`/`warn`/`error`. |
251
251
 
252
- Full list: [docs/ENVIRONMENT-VARIABLES.md](./docs/ENVIRONMENT-VARIABLES.md).
252
+ New installs should use `.securenow/credentials.json`; environment variables remain legacy fallbacks for existing deployments.
253
253
 
254
254
  ---
255
255
 
256
256
  ## Supported frameworks
257
257
 
258
258
  ### Web
259
- Next.js (App & Pages Router) · Nuxt 3 · Express · Fastify · NestJS · Koa · Hapi
259
+ Next.js (App & Pages Router), Nuxt 3, Express, Fastify, NestJS, Koa, Hapi
260
260
 
261
261
  ### Databases
262
- PostgreSQL · MySQL / MySQL2 · MongoDB · Redis
262
+ PostgreSQL, MySQL / MySQL2, MongoDB, Redis
263
263
 
264
264
  ### Other
265
- HTTP/HTTPS · GraphQL · gRPC · and many more via [@opentelemetry/auto-instrumentations-node](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node).
265
+ HTTP/HTTPS, GraphQL, gRPC, and many more via [@opentelemetry/auto-instrumentations-node](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node).
266
266
 
267
267
  > MongoDB instrumentation is opt-in (`SECURENOW_ENABLE_MONGODB_INSTRUMENTATION=1`) because older versions corrupted cursors on `mongodb@6.6+`. Safe again since SDK v6.0.2.
268
268
 
@@ -270,23 +270,7 @@ HTTP/HTTPS · GraphQL · gRPC · and many more via [@opentelemetry/auto-instrume
270
270
 
271
271
  ## Documentation
272
272
 
273
- ### Quick Starts
274
- - [Next.js Quick Start](./docs/NEXTJS-QUICKSTART.md)
275
- - [Nuxt 3 Guide](./docs/NUXT-GUIDE.md)
276
- - [All Frameworks](./docs/ALL-FRAMEWORKS-QUICKSTART.md)
277
- - [Logging Quick Start](./docs/LOGGING-QUICKSTART.md)
278
-
279
- ### Complete Guides
280
- - [Firewall](./docs/FIREWALL-GUIDE.md)
281
- - [API Keys](./docs/API-KEYS-GUIDE.md)
282
- - [MCP](./docs/MCP-GUIDE.md)
283
- - [Next.js Complete](./docs/NEXTJS-GUIDE.md)
284
- - [Nuxt 3 Complete](./docs/NUXT-GUIDE.md)
285
- - [Logging Complete](./docs/LOGGING-GUIDE.md)
286
- - [📚 All Docs](./docs/INDEX.md)
287
-
288
- ### Examples
289
- - [Code Examples](./examples/)
273
+ The npm package ships the current setup references: this README, `NPM_README.md`, `SKILL-API.md`, `SKILL-CLI.md`, and the MCP resources exposed by `npx securenow mcp`. Older long-form guides are kept in the repository only so customer installs do not receive stale `.env`-first instructions.
290
274
 
291
275
  ---
292
276
 
@@ -424,7 +408,7 @@ Override the dashboard API with `securenow config set apiUrl <url>`.
424
408
  ## Support
425
409
 
426
410
  - **Website:** [securenow.ai](http://securenow.ai/)
427
- - **Docs:** see `docs/` folder
411
+ - **Docs:** this README, `NPM_README.md`, `SKILL-API.md`, `SKILL-CLI.md`, and `npx securenow help`
428
412
  - **Issues:** report bugs and requests on GitHub
429
413
 
430
414
  ---
package/app-config.js CHANGED
@@ -256,9 +256,11 @@ function loadGlobalCredentials() {
256
256
 
257
257
  function loadCredentials() {
258
258
  try {
259
- const local = readJsonSafe(resolveLocalCredentialsFile());
260
- const global = readJsonSafe(path.join(os.homedir(), '.securenow', 'credentials.json'));
261
- return withCredentialDefaults(mergeCredentials(global, local));
259
+ const localFile = resolveLocalCredentialsFile();
260
+ if (localFile) {
261
+ return withCredentialDefaults(readJsonSafe(localFile));
262
+ }
263
+ return loadGlobalCredentials();
262
264
  } catch {
263
265
  return loadLocalCredentials() || loadGlobalCredentials() || null;
264
266
  }
package/cli/config.js CHANGED
@@ -91,9 +91,10 @@ function setConfigValue(key, value) {
91
91
  }
92
92
 
93
93
  function loadCredentials() {
94
- const global = loadJSON(CREDENTIALS_FILE);
95
- const local = fs.existsSync(LOCAL_CREDENTIALS_FILE) ? loadJSON(LOCAL_CREDENTIALS_FILE) : null;
96
- return appConfig.withCredentialDefaults(appConfig.mergeCredentials(global, local)) || {};
94
+ const credentials = fs.existsSync(LOCAL_CREDENTIALS_FILE)
95
+ ? loadJSON(LOCAL_CREDENTIALS_FILE)
96
+ : loadJSON(CREDENTIALS_FILE);
97
+ return appConfig.withCredentialDefaults(credentials) || {};
97
98
  }
98
99
 
99
100
  function saveCredentials(creds, { local = false } = {}) {
@@ -21,6 +21,7 @@ function resolvedConfig(options = {}) {
21
21
  : '(auto-generated)';
22
22
  const apiKey = firewall.apiKey || '';
23
23
  const firewallEnabled = !!apiKey && firewall.enabled;
24
+ const firewallLocalEnabled = firewall.enabled;
24
25
 
25
26
  return {
26
27
  serviceName,
@@ -31,6 +32,7 @@ function resolvedConfig(options = {}) {
31
32
  logsEndpoint: endpoints.logsUrl,
32
33
  headers: endpoints.headers,
33
34
  apiKey,
35
+ firewallLocalEnabled,
34
36
  apiUrl: config.getApiUrl(),
35
37
  loggingEnabled: appConfig.boolEnv('SECURENOW_LOGGING_ENABLED', true),
36
38
  captureBody: appConfig.boolEnv('SECURENOW_CAPTURE_BODY', true),
@@ -263,21 +265,36 @@ async function logSend(args, flags) {
263
265
  }
264
266
  }
265
267
 
266
- async function probe(endpoint, timeoutMs = 3000) {
268
+ function okHttpStatus(status) {
269
+ return status >= 200 && status < 300;
270
+ }
271
+
272
+ async function probe({ endpoint, method = 'POST', headers = {}, body = null, timeoutMs = 3000 }) {
267
273
  try {
268
274
  const res = await httpRequest({
269
- method: 'POST',
275
+ method,
270
276
  endpoint,
271
- headers: { 'Content-Type': 'application/json' },
272
- body: '{}',
277
+ headers,
278
+ body,
273
279
  timeoutMs,
274
280
  });
275
- return { ok: true, status: res.status };
281
+ return {
282
+ ok: okHttpStatus(res.status),
283
+ status: res.status,
284
+ ...(okHttpStatus(res.status) ? {} : { error: `HTTP ${res.status}`, body: res.body ? res.body.slice(0, 500) : '' }),
285
+ };
276
286
  } catch (err) {
277
287
  return { ok: false, error: err.message };
278
288
  }
279
289
  }
280
290
 
291
+ function firewallStatusLabel(cfg) {
292
+ if (cfg.firewallEnabled) return ui.c.green('enabled');
293
+ if (!cfg.apiKey) return ui.c.dim('disabled (missing firewall API key)');
294
+ if (!cfg.firewallLocalEnabled) return ui.c.yellow('disabled (config.firewall.enabled=false)');
295
+ return ui.c.dim('disabled');
296
+ }
297
+
281
298
  function env(_args, flags) {
282
299
  const cfg = resolvedConfig();
283
300
  const resolved = {
@@ -294,6 +311,7 @@ function env(_args, flags) {
294
311
  captureBody: cfg.captureBody,
295
312
  captureMultipart: cfg.captureMultipart,
296
313
  noUuid: appConfig.resolveNoUuid(),
314
+ firewallLocalEnabled: cfg.firewallLocalEnabled,
297
315
  firewallLayers: cfg.firewallLayers,
298
316
  };
299
317
 
@@ -311,7 +329,7 @@ function env(_args, flags) {
311
329
  ['Logging', cfg.loggingEnabled ? ui.c.green('enabled') : ui.c.dim('disabled')],
312
330
  ['Body capture', cfg.captureBody ? ui.c.green('enabled') : ui.c.dim('disabled')],
313
331
  ['Multipart capture', cfg.captureMultipart ? ui.c.green('enabled') : ui.c.dim('disabled')],
314
- ['Firewall', cfg.firewallEnabled ? ui.c.green('enabled') : ui.c.dim('disabled (no API key)')],
332
+ ['Firewall', firewallStatusLabel(cfg)],
315
333
  ]);
316
334
 
317
335
  ui.heading('Resolved credentials');
@@ -325,22 +343,43 @@ async function doctor(_args, flags) {
325
343
  const cfg = resolvedConfig();
326
344
  const checks = [];
327
345
 
328
- const spin1 = ui.spinner(`Probing traces endpoint ${cfg.tracesEndpoint}`);
329
- const traces = await probe(cfg.tracesEndpoint);
330
- if (traces.ok) spin1.stop(`Traces endpoint reachable (HTTP ${traces.status})`);
331
- else spin1.fail(`Traces endpoint unreachable: ${traces.error}`);
346
+ const jsonHeaders = { 'Content-Type': 'application/json', ...cfg.headers };
347
+
348
+ const spin1 = ui.spinner(`Sending doctor span to ${cfg.tracesEndpoint}`);
349
+ const traces = await probe({
350
+ endpoint: cfg.tracesEndpoint,
351
+ headers: jsonHeaders,
352
+ body: JSON.stringify(buildTracePayload(cfg, {
353
+ name: 'securenow.doctor.probe',
354
+ attributes: { 'test.source': 'securenow-cli-doctor' },
355
+ })),
356
+ });
357
+ if (traces.ok) spin1.stop(`Traces endpoint accepted probe (HTTP ${traces.status})`);
358
+ else spin1.fail(`Traces endpoint rejected probe: ${traces.error}`);
332
359
  checks.push({ name: 'traces', ...traces });
333
360
 
334
- const spin2 = ui.spinner(`Probing logs endpoint ${cfg.logsEndpoint}`);
335
- const logs = await probe(cfg.logsEndpoint);
336
- if (logs.ok) spin2.stop(`Logs endpoint reachable (HTTP ${logs.status})`);
337
- else spin2.fail(`Logs endpoint unreachable: ${logs.error}`);
361
+ const spin2 = ui.spinner(`Sending doctor log to ${cfg.logsEndpoint}`);
362
+ const logs = await probe({
363
+ endpoint: cfg.logsEndpoint,
364
+ headers: jsonHeaders,
365
+ body: JSON.stringify(buildLogPayload(cfg, {
366
+ message: 'SecureNow doctor probe',
367
+ level: 'info',
368
+ attributes: { 'test.source': 'securenow-cli-doctor' },
369
+ })),
370
+ });
371
+ if (logs.ok) spin2.stop(`Logs endpoint accepted probe (HTTP ${logs.status})`);
372
+ else spin2.fail(`Logs endpoint rejected probe: ${logs.error}`);
338
373
  checks.push({ name: 'logs', ...logs });
339
374
 
340
375
  const token = config.getToken();
341
376
  if (cfg.apiKey || token) {
342
377
  const spin3 = ui.spinner(`Probing SecureNow API ${cfg.apiUrl}`);
343
- const api = await probe(`${cfg.apiUrl.replace(/\/$/, '')}/health`);
378
+ const api = await probe({
379
+ method: 'GET',
380
+ endpoint: `${cfg.apiUrl.replace(/\/$/, '')}/health`,
381
+ headers: token ? { Authorization: `Bearer ${token}` } : cfg.apiKey ? { Authorization: `Bearer ${cfg.apiKey}` } : {},
382
+ });
344
383
  if (api.ok) spin3.stop(`API reachable (HTTP ${api.status})`);
345
384
  else spin3.fail(`API unreachable: ${api.error}`);
346
385
  checks.push({ name: 'api', ...api });
package/cli/run.js CHANGED
@@ -3,8 +3,24 @@
3
3
  const { spawn } = require('child_process');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
- const ui = require('./ui');
7
-
6
+ const ui = require('./ui');
7
+
8
+ const NODE_FLAGS_WITH_VALUE = new Set([
9
+ '-r',
10
+ '--require',
11
+ '--import',
12
+ '--loader',
13
+ '--experimental-loader',
14
+ '--env-file',
15
+ '--conditions',
16
+ '-C',
17
+ '--icu-data-dir',
18
+ '--inspect-port',
19
+ '--max-old-space-size',
20
+ '--stack-trace-limit',
21
+ '--title',
22
+ ]);
23
+
8
24
  function isESM(scriptPath) {
9
25
  if (scriptPath.endsWith('.mjs')) return true;
10
26
  if (scriptPath.endsWith('.cjs')) return false;
@@ -93,18 +109,31 @@ function run(rawArgs) {
93
109
  firewallOnly = true;
94
110
  continue;
95
111
  }
96
- if (args[i].startsWith('-')) {
97
- nodeFlags.push(args[i]);
98
- } else {
99
- scriptIdx = i;
100
- break;
112
+ if (args[i] === '--') {
113
+ scriptIdx = i + 1;
114
+ break;
115
+ }
116
+ if (args[i].startsWith('-')) {
117
+ const flag = args[i];
118
+ nodeFlags.push(flag);
119
+ const flagName = flag.split('=')[0];
120
+ if (NODE_FLAGS_WITH_VALUE.has(flagName) && !flag.includes('=')) {
121
+ if (i + 1 >= args.length) {
122
+ ui.error(`Node flag ${flag} requires a value.`);
123
+ process.exit(1);
124
+ }
125
+ nodeFlags.push(args[++i]);
126
+ }
127
+ } else {
128
+ scriptIdx = i;
129
+ break;
101
130
  }
102
131
  }
103
132
 
104
- if (scriptIdx === -1) {
105
- ui.error('No script path found. Provide a .js/.mjs/.ts file to run.');
106
- process.exit(1);
107
- }
133
+ if (scriptIdx === -1 || scriptIdx >= args.length) {
134
+ ui.error('No script path found. Provide a .js/.mjs/.ts file to run.');
135
+ process.exit(1);
136
+ }
108
137
 
109
138
  const scriptPath = args[scriptIdx];
110
139
  const appArgs = args.slice(scriptIdx + 1);
package/firewall-only.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * is resolvable.
13
13
  */
14
14
 
15
- try { require('dotenv').config(); } catch (_) {}
15
+ try { require('dotenv').config({ quiet: true }); } catch (_) {}
16
16
 
17
17
  const appConfig = require('./app-config');
18
18
  const firewallOptions = appConfig.resolveFirewallOptions();