securenow 7.5.0 → 7.6.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.
Files changed (51) hide show
  1. package/CONSUMING-APPS-GUIDE.md +2 -0
  2. package/NPM_README.md +201 -237
  3. package/README.md +73 -26
  4. package/SKILL-API.md +209 -205
  5. package/SKILL-CLI.md +71 -64
  6. package/app-config.js +479 -83
  7. package/cli/apiKey.js +1 -1
  8. package/cli/apps.js +1 -1
  9. package/cli/config.js +31 -12
  10. package/cli/credentials.js +88 -0
  11. package/cli/diagnostics.js +81 -98
  12. package/cli/firewall.js +29 -14
  13. package/cli/init.js +246 -201
  14. package/cli/monitor.js +107 -43
  15. package/cli/security.js +24 -12
  16. package/cli/ui.js +22 -12
  17. package/cli/utils.js +2 -1
  18. package/cli.js +71 -39
  19. package/console-instrumentation.js +1 -1
  20. package/docs/ENVIRONMENT-VARIABLES.md +137 -863
  21. package/docs/ENVIRONMENTS.md +60 -0
  22. package/docs/EXPRESS-SETUP-GUIDE.md +3 -0
  23. package/docs/FIREWALL-GUIDE.md +3 -0
  24. package/docs/INDEX.md +6 -8
  25. package/docs/LOGGING-GUIDE.md +3 -0
  26. package/docs/MCP-GUIDE.md +8 -0
  27. package/docs/NEXTJS-GUIDE.md +3 -0
  28. package/docs/NEXTJS-QUICKSTART.md +24 -16
  29. package/docs/NUXT-GUIDE.md +3 -0
  30. package/docs/QUICKSTART-BODY-CAPTURE.md +3 -0
  31. package/docs/REQUEST-BODY-CAPTURE.md +3 -0
  32. package/firewall-cloud.js +10 -10
  33. package/firewall-only.js +25 -23
  34. package/firewall.js +47 -29
  35. package/free-trial-banner.js +1 -1
  36. package/mcp/catalog.js +104 -17
  37. package/nextjs-auto-capture.d.ts +7 -4
  38. package/nextjs-auto-capture.js +7 -7
  39. package/nextjs-middleware.js +4 -3
  40. package/nextjs-wrapper.js +6 -6
  41. package/nextjs.d.ts +36 -25
  42. package/nextjs.js +47 -55
  43. package/nuxt-server-plugin.mjs +35 -51
  44. package/nuxt.d.ts +29 -23
  45. package/package.json +1 -1
  46. package/postinstall.js +27 -61
  47. package/register.d.ts +19 -33
  48. package/register.js +8 -8
  49. package/resolve-ip.js +4 -5
  50. package/tracing.d.ts +21 -19
  51. package/tracing.js +34 -42
@@ -0,0 +1,60 @@
1
+ # SecureNow Environments
2
+
3
+ SecureNow uses one app id for one application, then separates data by deployment environment.
4
+
5
+ ## Recommended Model
6
+
7
+ - Use the same `app.key` for local, preview, staging, and production.
8
+ - Set `config.runtime.deploymentEnvironment` in `.securenow/credentials.json`.
9
+ - Default local setup writes `local`.
10
+ - Production runtime credentials should write `production`.
11
+ - The SDK sends this value as the OpenTelemetry `deployment.environment` resource attribute.
12
+ - The firewall sync sends the same environment to SecureNow so app firewall settings can differ per environment.
13
+
14
+ Example local file:
15
+
16
+ ```json
17
+ {
18
+ "app": {
19
+ "key": "00000000-0000-0000-0000-000000000000",
20
+ "name": "my-app",
21
+ "instance": "https://freetrial.securenow.ai:4318"
22
+ },
23
+ "config": {
24
+ "runtime": {
25
+ "deploymentEnvironment": "local"
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ Example production flow:
32
+
33
+ ```bash
34
+ npx securenow credentials runtime --env production
35
+ ```
36
+
37
+ This writes `.securenow/credentials.production.json`. Deploy the generated JSON as a secret file and mount or copy it to:
38
+
39
+ ```text
40
+ <app-root>/.securenow/credentials.json
41
+ ```
42
+
43
+ ## Investigation Defaults
44
+
45
+ Forensics, firewall status, and security investigation tools default to `production`. Use `--env local`, `--env staging`, or `--env all` when you explicitly want another scope.
46
+
47
+ ```bash
48
+ npx securenow traces --app <app-key> --env production
49
+ npx securenow logs --app <app-key> --env local
50
+ npx securenow forensics "show suspicious IPs in the last hour" --app <app-key> --env production
51
+ npx securenow firewall disable --app <app-key> --env local
52
+ ```
53
+
54
+ ## Firewall Defaults
55
+
56
+ - Production defaults to firewall on.
57
+ - Local, preview, staging, and test default to firewall off until explicitly enabled.
58
+ - Blocklists and allowlists are still app-scoped, but the app firewall toggle and threshold are environment-scoped.
59
+
60
+ This keeps local development friction low while preserving production as the primary security boundary.
@@ -717,3 +717,6 @@ NODE_ENV=production
717
717
  ---
718
718
 
719
719
  **Your Express app is now fully observable!** 🎉
720
+ # Current setup note
721
+
722
+ Use `.securenow/credentials.json` for local and production. Run `npx securenow login`, `npx securenow init`, and for production generate `npx securenow credentials runtime --env production`; mount/copy that file as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
@@ -434,3 +434,6 @@ npx securenow firewall test-ip 1.2.3.4
434
434
  - [Environment Variables Reference](./ENVIRONMENT-VARIABLES.md) — All configuration options
435
435
  - [All Frameworks Quick Start](./ALL-FRAMEWORKS-QUICKSTART.md) — Framework setup guides
436
436
  - [Automatic IP Capture](./AUTOMATIC-IP-CAPTURE.md) — How client IPs are resolved
437
+ # Current setup note
438
+
439
+ Use `.securenow/credentials.json` for local and production. `npx securenow login` writes the firewall key locally; `npx securenow credentials runtime --env production` creates the tokenless production file to mount/copy as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
package/docs/INDEX.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # SecureNow Documentation
2
2
 
3
- Complete documentation for SecureNow - OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt with tracing and logging.
3
+ Complete documentation for SecureNow - OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt with tracing, logging, body capture, and firewall protection.
4
+
5
+ **Current setup model:** local and production configuration live in `.securenow/credentials.json`. Run `npx securenow login`, then `npx securenow init`. For production, generate a tokenless runtime file with `npx securenow credentials runtime --env production` and mount/copy it to `.securenow/credentials.json`. Older guides may mention env vars as legacy fallbacks; new integrations should use the credentials file.
4
6
 
5
7
  ## 📚 Table of Contents
6
8
 
@@ -65,12 +67,8 @@ Complete documentation for SecureNow - OpenTelemetry instrumentation for Node.js
65
67
 
66
68
  ### ⚙️ Configuration
67
69
 
68
- - **[Environment Variables Complete Reference](ENVIRONMENT-VARIABLES.md)** - All variables explained
69
- - Required variables
70
- - Optional variables
71
- - Configuration examples
72
- - Best practices
73
- - Priority and overrides
70
+ - **[Credentials Reference](ENVIRONMENT-VARIABLES.md)** - `.securenow/credentials.json` fields, secure defaults, and legacy fallbacks
71
+ - **[Environment Separation](ENVIRONMENTS.md)** - One app id across local, preview, staging, and production
74
72
 
75
73
  ### 🔐 Next.js Body Capture
76
74
 
@@ -141,7 +139,7 @@ Complete documentation for SecureNow - OpenTelemetry instrumentation for Node.js
141
139
  ### "I want to block malicious IPs automatically"
142
140
  1. Start with [Firewall Guide](FIREWALL-GUIDE.md)
143
141
  2. Create an API key: [API Keys Guide](API-KEYS-GUIDE.md)
144
- 3. Configure environment variables: [Environment Variables Reference](ENVIRONMENT-VARIABLES.md)
142
+ 3. Review credentials fields: [Credentials Reference](ENVIRONMENT-VARIABLES.md)
145
143
 
146
144
  ### "I need to capture IP addresses and headers"
147
145
  1. Read [Automatic IP Capture](AUTOMATIC-IP-CAPTURE.md)
@@ -699,3 +699,6 @@ logger.emit({
699
699
  - **Issues**: [GitHub Issues](https://github.com/your-repo/securenow-npm)
700
700
  - **Documentation**: [Full Docs](./INDEX.md)
701
701
  - **Website**: [securenow.ai](http://securenow.ai/)
702
+ # Current setup note
703
+
704
+ Use `.securenow/credentials.json` for local and production. Run `npx securenow login`, `npx securenow init`, and for production generate `npx securenow credentials runtime --env production`; mount/copy that file as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
package/docs/MCP-GUIDE.md CHANGED
@@ -20,6 +20,14 @@ npx -p securenow securenow-mcp
20
20
  The local MCP server reads the same project-local `.securenow/credentials.json`
21
21
  as the CLI and SDK. No production deployment is required.
22
22
 
23
+ ## Environment Scope
24
+
25
+ MCP tools use the same environment model as the CLI and UI: one app key across
26
+ local, preview, staging, and production, separated by
27
+ `config.runtime.deploymentEnvironment`. Investigation tools default to
28
+ `production`. Pass `environment: "local"`, `"staging"`, `"preview"`, or
29
+ `"all"` only when that scope is intentional.
30
+
23
31
  ## Hosted MCP
24
32
 
25
33
  For hosted clients, expose the secured API endpoint:
@@ -386,4 +386,7 @@ ISC
386
386
  ---
387
387
 
388
388
  **Made with ❤️ for the Next.js and SecureNow community**
389
+ # Current setup note
390
+
391
+ Use `.securenow/credentials.json` for local and production. Run `npx securenow login`, `npx securenow init`, and for production generate `npx securenow credentials runtime --env production`; mount/copy that file as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
389
392
 
@@ -9,7 +9,7 @@ npm install securenow
9
9
  # 2. Pick (or create) your app in the browser — writes .securenow/ locally
10
10
  npx securenow login
11
11
 
12
- # 3. Scaffold instrumentation.ts and wrap next.config.js
12
+ # 3. Scaffold instrumentation.ts and update next.config.js
13
13
  npx securenow init
14
14
 
15
15
  # 4. Run
@@ -25,23 +25,29 @@ No `.env.local` edits. No API key copy-paste. The app you picked in step 2 is wh
25
25
  **`instrumentation.ts`** (or `.js`, auto-detected):
26
26
 
27
27
  ```typescript
28
- import { registerSecureNow } from 'securenow/nextjs';
29
- export function register() {
30
- registerSecureNow();
28
+ import { createRequire } from 'node:module';
29
+
30
+ const require = createRequire(import.meta.url);
31
+
32
+ export async function register() {
33
+ if (process.env.NEXT_RUNTIME !== 'nodejs') return;
34
+ const { registerSecureNow } = require('securenow/nextjs');
35
+ registerSecureNow({ captureBody: true });
36
+ require('securenow/nextjs-auto-capture');
31
37
  }
32
38
  ```
33
39
 
34
- It also tells you to wrap `next.config.js`:
40
+ For Next.js 15+, `init` adds `securenow` to `serverExternalPackages` when it can safely patch the file:
35
41
 
36
42
  ```javascript
37
- const { withSecureNow } = require('securenow/nextjs-webpack-config');
43
+ const nextConfig = {
44
+ serverExternalPackages: ['securenow'],
45
+ };
38
46
 
39
- module.exports = withSecureNow({
40
- // your existing config
41
- });
47
+ export default nextConfig;
42
48
  ```
43
49
 
44
- `withSecureNow()` auto-detects Next.js 14 vs 15 vs 16 and sets the right externalization config.
50
+ For older Next.js, use `experimental.serverComponentsExternalPackages`. If you already have custom `instrumentation.*` or a complex `next.config.*`, `init` prints a Codex/Claude-ready prompt with the exact edits to merge instead of guessing.
45
51
 
46
52
  ---
47
53
 
@@ -57,21 +63,23 @@ Start your app. In the console you should see:
57
63
  Then:
58
64
 
59
65
  ```bash
60
- npx securenow test-span # emit a test span
61
- npx securenow traces # see it appear
66
+ npx securenow test-span # emits with config.runtime.deploymentEnvironment
67
+ npx securenow traces --env local
68
+ npx securenow status --env local
62
69
  ```
63
70
 
64
71
  If `traces` shows your span under the app name you picked, you're done.
65
72
 
66
73
  ---
67
74
 
68
- ## Overriding for CI / Docker / Vercel
75
+ ## Production / Docker / Vercel
69
76
 
70
- `.securenow/credentials.json` is for local dev. For anywhere you can't run `npx securenow login`, set env vars — they always win:
77
+ Production uses the same credentials shape. Generate a tokenless runtime file:
71
78
 
72
79
  ```bash
73
- SECURENOW_APPID=<app-key-uuid> # from: npx securenow apps
74
- SECURENOW_INSTANCE=https://freetrial.securenow.ai:4318
80
+ npx securenow credentials runtime --env production
75
81
  ```
76
82
 
83
+ Deploy `.securenow/credentials.production.json` as a secret file and mount or copy it to `<app-root>/.securenow/credentials.json`. It contains `app`, `apiKey`, `config`, and explanations, but no CLI OAuth token.
84
+
77
85
  See [NEXTJS-GUIDE.md](./NEXTJS-GUIDE.md) for Vercel, standalone builds, and edge runtime details.
@@ -168,3 +168,6 @@ export default defineNuxtConfig({
168
168
  },
169
169
  });
170
170
  ```
171
+ # Current setup note
172
+
173
+ Use `.securenow/credentials.json` for local and production. Run `npx securenow login`, `npx securenow init`, and for production generate `npx securenow credentials runtime --env production`; mount/copy that file as `.securenow/credentials.json`. Env-var examples in this older guide are legacy fallback snippets.
@@ -288,3 +288,6 @@ export const POST = withSecureNow(handler);
288
288
 
289
289
 
290
290
 
291
+ # Current setup note
292
+
293
+ Use `.securenow/credentials.json` for local and production. Body capture defaults live under `config.capture.*`; run `npx securenow init` to create secure defaults. Env-var examples in this older guide are legacy fallback snippets.
@@ -581,4 +581,7 @@ SECURENOW_SENSITIVE_FIELDS="" # Don't do this!
581
581
  ---
582
582
 
583
583
  **Made with security in mind** 🔒
584
+ # Current setup note
585
+
586
+ Use `.securenow/credentials.json` for local and production. Body capture defaults live under `config.capture.*`; run `npx securenow init` to create secure defaults. Env-var examples in this older guide are legacy fallback snippets.
584
587
 
package/firewall-cloud.js CHANGED
@@ -21,10 +21,10 @@ const MIN_PUSH_INTERVAL_MS = 30000;
21
21
 
22
22
  // ────── Cloudflare ──────
23
23
 
24
- async function cloudflareSync(ips) {
25
- const token = process.env.CLOUDFLARE_API_TOKEN;
26
- const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
27
- if (!token || !accountId) throw new Error('Missing CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID');
24
+ async function cloudflareSync(ips) {
25
+ const token = _options?.cloudflare?.apiToken;
26
+ const accountId = _options?.cloudflare?.accountId;
27
+ if (!token || !accountId) throw new Error('Missing CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID');
28
28
 
29
29
  const listName = 'securenow-blocklist';
30
30
 
@@ -82,9 +82,9 @@ async function awsSync(ips) {
82
82
  throw new Error('AWS WAF requires @aws-sdk/client-wafv2 — install it: npm i @aws-sdk/client-wafv2');
83
83
  }
84
84
 
85
- const ipSetId = process.env.AWS_WAF_IP_SET_ID;
86
- const ipSetName = process.env.AWS_WAF_IP_SET_NAME || 'securenow-blocklist';
87
- const scope = process.env.AWS_WAF_SCOPE || 'REGIONAL';
85
+ const ipSetId = _options?.aws?.wafIpSetId;
86
+ const ipSetName = _options?.aws?.wafIpSetName || 'securenow-blocklist';
87
+ const scope = _options?.aws?.wafScope || 'REGIONAL';
88
88
  if (!ipSetId) throw new Error('Missing AWS_WAF_IP_SET_ID');
89
89
 
90
90
  const client = new WAFV2.WAFV2Client({});
@@ -117,8 +117,8 @@ async function gcpSync(ips) {
117
117
  throw new Error('GCP Cloud Armor requires @google-cloud/compute — install it: npm i @google-cloud/compute');
118
118
  }
119
119
 
120
- const project = process.env.GCP_PROJECT_ID;
121
- const policyName = process.env.GCP_SECURITY_POLICY;
120
+ const project = _options?.gcp?.projectId;
121
+ const policyName = _options?.gcp?.securityPolicy;
122
122
  if (!project || !policyName) throw new Error('Missing GCP_PROJECT_ID or GCP_SECURITY_POLICY');
123
123
 
124
124
  const client = new compute.SecurityPoliciesClient();
@@ -191,7 +191,7 @@ async function sync(ips) {
191
191
  if (now - _lastPushTime < MIN_PUSH_INTERVAL_MS) return;
192
192
  _lastPushTime = now;
193
193
 
194
- const dryRun = process.env.SECURENOW_FIREWALL_CLOUD_DRY_RUN === '1';
194
+ const dryRun = _options?.cloudDryRun === true;
195
195
  if (dryRun) {
196
196
  if (_options.log) console.log('[securenow] Firewall cloud: dry-run — would push %d IPs to %s', ips.length, _provider);
197
197
  return;
package/firewall-only.js CHANGED
@@ -7,36 +7,38 @@
7
7
  * node -r securenow/firewall-only app.js
8
8
  * NODE_OPTIONS='-r securenow/firewall-only' next start
9
9
  *
10
- * Reads .env via dotenv (if installed), then initialises the HTTP-level
11
- * firewall when an API key is resolvable (env var or .securenow/credentials.json).
10
+ * Reads .securenow/credentials.json first, with legacy env vars supported only
11
+ * as fallbacks. Initialises the HTTP-level firewall when a snk_live_ API key
12
+ * is resolvable.
12
13
  */
13
14
 
14
15
  try { require('dotenv').config(); } catch (_) {}
15
16
 
16
- const env = (k) =>
17
- process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
17
+ const appConfig = require('./app-config');
18
+ const firewallOptions = appConfig.resolveFirewallOptions();
18
19
 
19
- const { resolveApiKey, resolveAppKey } = require('./app-config');
20
- const firewallApiKey = resolveApiKey();
21
- const appKey = resolveAppKey();
22
-
23
- // SECURENOW_FIREWALL_ENABLED=0 is a hard local override (ops escape hatch).
20
+ // config.firewall.enabled=false disables the local SDK firewall.
24
21
  // In all other cases the toggle lives in the dashboard; default ON when an
25
22
  // API key is present.
26
- if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
27
- require('./firewall').init({
28
- apiKey: firewallApiKey,
29
- appKey: appKey || null,
30
- apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
31
- versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
32
- syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
33
- failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
34
- statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
35
- log: env('SECURENOW_FIREWALL_LOG') !== '0',
36
- tcp: env('SECURENOW_FIREWALL_TCP') === '1',
37
- iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
38
- cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
39
- });
23
+ if (firewallOptions.apiKey && firewallOptions.enabled) {
24
+ require('./firewall').init({
25
+ apiKey: firewallOptions.apiKey,
26
+ appKey: firewallOptions.appKey,
27
+ environment: firewallOptions.environment,
28
+ apiUrl: firewallOptions.apiUrl,
29
+ versionCheckInterval: firewallOptions.versionCheckInterval,
30
+ syncInterval: firewallOptions.syncInterval,
31
+ failMode: firewallOptions.failMode,
32
+ statusCode: firewallOptions.statusCode,
33
+ log: firewallOptions.log,
34
+ tcp: firewallOptions.tcp,
35
+ iptables: firewallOptions.iptables,
36
+ cloud: firewallOptions.cloud,
37
+ cloudDryRun: firewallOptions.cloudDryRun,
38
+ cloudflare: firewallOptions.cloudflare,
39
+ aws: firewallOptions.aws,
40
+ gcp: firewallOptions.gcp,
41
+ });
40
42
 
41
43
  const shutdown = () => { try { require('./firewall').shutdown(); } catch (_) {} };
42
44
  process.on('SIGINT', shutdown);
package/firewall.js CHANGED
@@ -98,9 +98,17 @@ function handleRetryAfter(res) {
98
98
 
99
99
  // ────── HTTP helpers ──────
100
100
 
101
- function buildUrl(apiUrl, path) {
102
- return apiUrl.replace(/\/+$/, '') + '/api/v1' + path;
103
- }
101
+ function buildUrl(apiUrl, path) {
102
+ return apiUrl.replace(/\/+$/, '') + '/api/v1' + path;
103
+ }
104
+
105
+ function buildFirewallUrl(path) {
106
+ const query = new URLSearchParams();
107
+ if (_options && _options.appKey) query.set('app', _options.appKey);
108
+ if (_options && _options.environment) query.set('env', _options.environment);
109
+ const suffix = query.toString() ? `?${query.toString()}` : '';
110
+ return buildUrl(_options.apiUrl, path) + suffix;
111
+ }
104
112
 
105
113
  function jitter(baseMs) {
106
114
  return baseMs * (0.8 + Math.random() * 0.4);
@@ -139,10 +147,14 @@ function httpGet(url, extraHeaders, timeout, callback) {
139
147
 
140
148
  // ────── Unified Sync (v2 — single request for everything) ──────
141
149
 
142
- function doUnifiedSync(callback) {
143
- const appQuery = _options.appKey ? `?app=${encodeURIComponent(_options.appKey)}` : '';
144
- const url = buildUrl(_options.apiUrl, '/firewall/sync') + appQuery;
145
- const headers = {};
150
+ function doUnifiedSync(callback) {
151
+ const query = new URLSearchParams();
152
+ if (_options.appKey) query.set('app', _options.appKey);
153
+ if (_options.environment) query.set('env', _options.environment);
154
+ const suffix = query.toString() ? `?${query.toString()}` : '';
155
+ const url = buildUrl(_options.apiUrl, '/firewall/sync') + suffix;
156
+ const headers = {};
157
+ if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
146
158
  if (_lastVersion) headers['X-Blocklist-Version'] = _lastVersion;
147
159
  if (_lastAllowlistVersion) headers['X-Allowlist-Version'] = _lastAllowlistVersion;
148
160
 
@@ -229,9 +241,10 @@ function doUnifiedSync(callback) {
229
241
 
230
242
  // ────── Legacy Sync (v1 — separate endpoints, kept for backward compat) ──────
231
243
 
232
- function legacyBlocklistSync(callback) {
233
- const url = buildUrl(_options.apiUrl, '/firewall/blocklist');
234
- const headers = {};
244
+ function legacyBlocklistSync(callback) {
245
+ const url = buildFirewallUrl('/firewall/blocklist');
246
+ const headers = {};
247
+ if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
235
248
  if (_lastSyncEtag) headers['If-None-Match'] = _lastSyncEtag;
236
249
  else if (_lastModified) headers['If-Modified-Since'] = _lastModified;
237
250
 
@@ -257,9 +270,10 @@ function legacyBlocklistSync(callback) {
257
270
  });
258
271
  }
259
272
 
260
- function legacyAllowlistSync(callback) {
261
- const url = buildUrl(_options.apiUrl, '/firewall/allowlist');
262
- const headers = {};
273
+ function legacyAllowlistSync(callback) {
274
+ const url = buildFirewallUrl('/firewall/allowlist');
275
+ const headers = {};
276
+ if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
263
277
  if (_lastAllowlistSyncEtag) headers['If-None-Match'] = _lastAllowlistSyncEtag;
264
278
  else if (_lastAllowlistModified) headers['If-Modified-Since'] = _lastAllowlistModified;
265
279
 
@@ -283,9 +297,10 @@ function legacyAllowlistSync(callback) {
283
297
  });
284
298
  }
285
299
 
286
- function legacyVersionCheck(callback) {
287
- const url = buildUrl(_options.apiUrl, '/firewall/blocklist/version');
288
- const headers = {};
300
+ function legacyVersionCheck(callback) {
301
+ const url = buildFirewallUrl('/firewall/blocklist/version');
302
+ const headers = {};
303
+ if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
289
304
  if (_lastVersion) headers['If-None-Match'] = _lastVersion;
290
305
 
291
306
  httpGet(url, headers, 5000, (err, res, data) => {
@@ -305,9 +320,10 @@ function legacyVersionCheck(callback) {
305
320
  });
306
321
  }
307
322
 
308
- function legacyAllowlistVersionCheck(callback) {
309
- const url = buildUrl(_options.apiUrl, '/firewall/allowlist/version');
310
- const headers = {};
323
+ function legacyAllowlistVersionCheck(callback) {
324
+ const url = buildFirewallUrl('/firewall/allowlist/version');
325
+ const headers = {};
326
+ if (_options.environment) headers['X-SecureNow-Environment'] = _options.environment;
311
327
  if (_lastAllowlistVersion) headers['If-None-Match'] = _lastAllowlistVersion;
312
328
 
313
329
  httpGet(url, headers, 5000, (err, res, data) => {
@@ -655,9 +671,10 @@ function patchHttpLayer() {
655
671
  // ────── Init ──────
656
672
 
657
673
  function init(options) {
658
- _options = options;
659
-
660
- if (_options.log) console.log('[securenow] Firewall: ENABLED');
674
+ _options = options;
675
+
676
+ if (_options.log) console.log('[securenow] Firewall: ENABLED');
677
+ if (_options.log && _options.environment) console.log('[securenow] Firewall: environment=%s', _options.environment);
661
678
 
662
679
  patchHttpLayer();
663
680
  if (_options.log) console.log('[securenow] Firewall: Layer 1 (HTTP 403) active');
@@ -672,7 +689,7 @@ function init(options) {
672
689
  if (_options.log) console.warn('[securenow] Firewall: Layer 2 (TCP drop) failed:', e.message);
673
690
  }
674
691
  } else {
675
- if (_options.log) console.log('[securenow] Firewall: Layer 2 (TCP drop) disabled (set SECURENOW_FIREWALL_TCP=1)');
692
+ if (_options.log) console.log('[securenow] Firewall: Layer 2 (TCP drop) disabled (set config.firewall.tcp=true)');
676
693
  }
677
694
 
678
695
  if (_options.iptables) {
@@ -685,7 +702,7 @@ function init(options) {
685
702
  if (_options.log) console.warn('[securenow] Firewall: Layer 3 (iptables) failed:', e.message);
686
703
  }
687
704
  } else {
688
- if (_options.log) console.log('[securenow] Firewall: Layer 3 (iptables) disabled (set SECURENOW_FIREWALL_IPTABLES=1)');
705
+ if (_options.log) console.log('[securenow] Firewall: Layer 3 (iptables) disabled (set config.firewall.iptables=true)');
689
706
  }
690
707
 
691
708
  if (_options.cloud) {
@@ -698,7 +715,7 @@ function init(options) {
698
715
  if (_options.log) console.warn('[securenow] Firewall: Layer 4 (Cloud WAF) failed:', e.message);
699
716
  }
700
717
  } else {
701
- if (_options.log) console.log('[securenow] Firewall: Layer 4 (Cloud WAF) disabled (set SECURENOW_FIREWALL_CLOUD=cloudflare|aws|gcp)');
718
+ if (_options.log) console.log('[securenow] Firewall: Layer 4 (Cloud WAF) disabled (set config.firewall.cloud=cloudflare|aws|gcp)');
702
719
  }
703
720
 
704
721
  startSyncLoop();
@@ -742,10 +759,11 @@ function getStats() {
742
759
  circuitState: _circuitState,
743
760
  consecutiveErrors: _consecutiveErrors,
744
761
  unifiedSync: _useUnifiedSync,
745
- remoteEnabled: _remoteEnabled,
746
- appKey: _options ? _options.appKey || null : null,
747
- };
748
- }
762
+ remoteEnabled: _remoteEnabled,
763
+ appKey: _options ? _options.appKey || null : null,
764
+ environment: _options ? _options.environment || null : null,
765
+ };
766
+ }
749
767
 
750
768
  // Layers (TCP / iptables / cloud) read the matcher to populate kernel-level
751
769
  // rules. When the remote toggle is off, return null so they treat the policy
@@ -4,7 +4,7 @@
4
4
  * Free Trial Banner — auto-injects a visible "Testing Environment" banner
5
5
  * into HTML pages served by apps using the SecureNow free trial instance.
6
6
  *
7
- * Opt-out: set SECURENOW_HIDE_BANNER=1
7
+ * Opt-out: set config.runtime.hideBanner=true in .securenow/credentials.json
8
8
  */
9
9
 
10
10
  const FREETRIAL_HOST = 'freetrial.securenow.ai';