securenow 7.5.1 → 7.6.1

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 (50) 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 +205 -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 +68 -104
  12. package/cli/firewall.js +29 -14
  13. package/cli/init.js +211 -212
  14. package/cli/monitor.js +107 -43
  15. package/cli/security.js +24 -12
  16. package/cli/utils.js +2 -1
  17. package/cli.js +72 -40
  18. package/console-instrumentation.js +1 -1
  19. package/docs/ENVIRONMENT-VARIABLES.md +137 -863
  20. package/docs/ENVIRONMENTS.md +60 -0
  21. package/docs/EXPRESS-SETUP-GUIDE.md +3 -0
  22. package/docs/FIREWALL-GUIDE.md +3 -0
  23. package/docs/INDEX.md +6 -8
  24. package/docs/LOGGING-GUIDE.md +3 -0
  25. package/docs/MCP-GUIDE.md +8 -0
  26. package/docs/NEXTJS-GUIDE.md +3 -0
  27. package/docs/NEXTJS-QUICKSTART.md +22 -16
  28. package/docs/NUXT-GUIDE.md +3 -0
  29. package/docs/QUICKSTART-BODY-CAPTURE.md +3 -0
  30. package/docs/REQUEST-BODY-CAPTURE.md +3 -0
  31. package/firewall-cloud.js +10 -10
  32. package/firewall-only.js +25 -23
  33. package/firewall.js +47 -29
  34. package/free-trial-banner.js +1 -1
  35. package/mcp/catalog.js +104 -17
  36. package/nextjs-auto-capture.d.ts +7 -4
  37. package/nextjs-auto-capture.js +7 -7
  38. package/nextjs-middleware.js +4 -3
  39. package/nextjs-wrapper.js +6 -6
  40. package/nextjs.d.ts +36 -25
  41. package/nextjs.js +48 -55
  42. package/nuxt-server-plugin.mjs +35 -51
  43. package/nuxt.d.ts +29 -23
  44. package/package.json +1 -1
  45. package/postinstall.js +27 -61
  46. package/register.d.ts +19 -33
  47. package/register.js +8 -8
  48. package/resolve-ip.js +4 -5
  49. package/tracing.d.ts +21 -19
  50. package/tracing.js +34 -42
package/register.js CHANGED
@@ -6,13 +6,13 @@
6
6
  // On older Node versions it falls back to a warning.
7
7
  'use strict';
8
8
 
9
- // 1. load .env before anything else
10
- try {
11
- require('dotenv').config();
12
- console.log('[securenow] dotenv loaded from', process.env.DOTENV_CONFIG_PATH || '.env');
13
- } catch (e) {
14
- console.warn('[securenow] dotenv not found or failed to load');
15
- }
9
+ // 1. Load dotenv quietly only for legacy installs. Normal local and production
10
+ // configuration comes from .securenow/credentials.json via app-config.js.
11
+ try {
12
+ require('dotenv').config();
13
+ } catch (e) {
14
+ // dotenv is optional.
15
+ }
16
16
 
17
17
  // 2. Auto-register the ESM loader hook so customers never need --import
18
18
  (() => {
@@ -22,7 +22,7 @@ try {
22
22
  const pkgPath = path.resolve(process.cwd(), 'package.json');
23
23
  if (!fs.existsSync(pkgPath)) return;
24
24
 
25
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
25
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8').replace(/^\uFEFF/, ''));
26
26
  if (pkg.type !== 'module') return;
27
27
 
28
28
  // Already registered via --import?
package/resolve-ip.js CHANGED
@@ -1,14 +1,13 @@
1
1
  'use strict';
2
2
 
3
- const os = require('os');
3
+ const os = require('os');
4
+ const appConfig = require('./app-config');
4
5
 
5
6
  const LOOPBACK_RE = /^(127\.|::1$|::ffff:127\.)/;
6
7
  const PRIVATE_IP_RE = /^(127\.|::1$|::ffff:127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|f[cd][0-9a-f]{2}:)/;
7
8
 
8
- const trustedProxyCsv = (process.env.SECURENOW_TRUSTED_PROXIES || '').trim();
9
- const trustedProxySet = trustedProxyCsv
10
- ? new Set(trustedProxyCsv.split(',').map(s => s.trim()).filter(Boolean))
11
- : null;
9
+ const trustedProxies = appConfig.listEnv('SECURENOW_TRUSTED_PROXIES');
10
+ const trustedProxySet = trustedProxies.length ? new Set(trustedProxies) : null;
12
11
 
13
12
  let _hostIp = null;
14
13
  function getHostIp() {
package/tracing.d.ts CHANGED
@@ -142,7 +142,7 @@ export function getLogger(name?: string, version?: string): Logger | null;
142
142
  /**
143
143
  * Check if logging is enabled
144
144
  *
145
- * @returns true if SECURENOW_LOGGING_ENABLED=1, false otherwise
145
+ * @returns true when logging resolves enabled from .securenow/credentials.json defaults
146
146
  *
147
147
  * @example
148
148
  * ```typescript
@@ -162,22 +162,24 @@ export function isLoggingEnabled(): boolean;
162
162
  export const loggerProvider: LoggerProvider | null;
163
163
 
164
164
  /**
165
- * Environment Variables (same as register.js):
166
- *
167
- * Required:
168
- * - SECURENOW_APPID=your-app-name
169
- * - SECURENOW_INSTANCE=http://host:4318
170
- *
171
- * Optional:
172
- * - SECURENOW_LOGGING_ENABLED=1 # Enable logging (default: 1)
173
- * - SECURENOW_NO_UUID=1
174
- * - SECURENOW_STRICT=1
175
- * - SECURENOW_CAPTURE_BODY=1
176
- * - SECURENOW_MAX_BODY_SIZE=10240
177
- * - SECURENOW_SENSITIVE_FIELDS=field1,field2
178
- * - SECURENOW_DISABLE_INSTRUMENTATIONS=pkg1,pkg2
179
- * - OTEL_LOG_LEVEL=info|debug
180
- * - SECURENOW_TEST_SPAN=1
181
- * - SECURENOW_TRUSTED_PROXIES=ip1,ip2 # Additional trusted proxy IPs for X-Forwarded-For
182
- * - OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=... # Override logs endpoint
165
+ * Configuration:
166
+ *
167
+ * Local development reads .securenow/credentials.json first. Run
168
+ * `npx securenow login` to write app identity/firewall key and
169
+ * `npx securenow init` to ensure secure defaults and explanations.
170
+ *
171
+ * Production uses the same file shape. Run
172
+ * `npx securenow credentials runtime --env production`, then mount/copy the
173
+ * generated JSON to .securenow/credentials.json in the running app.
174
+ *
175
+ * Main config fields:
176
+ * - app.key / app.name / app.instance
177
+ * - apiKey
178
+ * - config.runtime.deploymentEnvironment
179
+ * - config.logging.enabled
180
+ * - config.capture.body / multipart / maxBodySize / sensitiveFields
181
+ * - config.otel.endpoint / tracesEndpoint / logsEndpoint / headers
182
+ * - config.firewall.enabled and layer/provider options
183
+ *
184
+ * Legacy env vars are fallback-only for existing installs.
183
185
  */
package/tracing.js CHANGED
@@ -38,17 +38,9 @@ const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventi
38
38
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
39
39
  const { MongoDBInstrumentation } = require('@opentelemetry/instrumentation-mongodb');
40
40
  const { v4: uuidv4 } = require('uuid');
41
+ const appConfig = require('./app-config');
41
42
 
42
- const env = k => process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
43
- const parseHeaders = str => {
44
- const out = {}; if (!str) return out;
45
- for (const raw of String(str).split(',')) {
46
- const s = raw.trim(); if (!s) continue;
47
- const i = s.indexOf('='); if (i === -1) continue;
48
- out[s.slice(0, i).trim().toLowerCase()] = s.slice(i + 1).trim();
49
- }
50
- return out;
51
- };
43
+ const env = appConfig.env;
52
44
 
53
45
  // Default sensitive fields to redact from request bodies
54
46
  const DEFAULT_SENSITIVE_FIELDS = [
@@ -273,35 +265,31 @@ const diagLevel = (env('OTEL_LOG_LEVEL') || '').toLowerCase();
273
265
  })();
274
266
 
275
267
  // -------- endpoints & app resolution --------
276
- // Resolution order for endpoint/appId/apiKey: env → .securenow/credentials.json package.json#name defaults.
277
- const appConfig = require('./app-config');
268
+ // Resolution order for endpoint/appId/apiKey: .securenow/credentials.json -> legacy env fallback -> package.json#name -> defaults.
278
269
  const resolvedApp = appConfig.resolveAll();
270
+ const resolvedEndpoints = appConfig.resolveEndpoints();
279
271
 
280
- const endpointBase = resolvedApp.instance.replace(/\/$/, '');
281
- const tracesUrl = env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
282
- const logsUrl = env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') || `${endpointBase}/v1/logs`;
272
+ const endpointBase = resolvedEndpoints.endpointBase;
273
+ const tracesUrl = resolvedEndpoints.tracesUrl;
274
+ const logsUrl = resolvedEndpoints.logsUrl;
283
275
 
284
- // If the credentials file provided an app key and no OTLP headers are set,
285
- // surface it as x-api-key so the collector can route telemetry to the right app bucket.
286
- if (resolvedApp.appKey && !env('OTEL_EXPORTER_OTLP_HEADERS') && !env('SECURENOW_API_KEY')) {
287
- process.env.SECURENOW_API_KEY = resolvedApp.appKey;
288
- process.env.OTEL_EXPORTER_OTLP_HEADERS = `x-api-key=${resolvedApp.appKey}`;
289
- }
290
- const headers = parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS'));
276
+ // resolveEndpoints() also adds x-api-key from the credentials app key when
277
+ // explicit OTLP headers did not provide one.
278
+ const headers = resolvedEndpoints.headers;
291
279
 
292
280
  // -------- naming rules --------
293
281
  const rawBase = (resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
294
282
  const baseName = rawBase || null;
295
283
  // Auto-disables the per-worker suffix when we resolved a routing UUID from
296
284
  // credentials — the dashboard does exact-match IN on service.name, so any
297
- // suffix breaks routing. Env SECURENOW_NO_UUID=0|1 still overrides.
285
+ // suffix breaks routing. config.runtime.noUuid can override.
298
286
  const noUuid = appConfig.resolveNoUuid();
299
287
  const strict = String(env('SECURENOW_STRICT')) === '1' || String(env('SECURENOW_STRICT')).toLowerCase() === 'true';
300
288
  const inPm2Cluster = !!(process.env.NODE_APP_INSTANCE || process.env.pm_id);
301
289
 
302
290
  // Fail fast in cluster if base is missing (no more "free" names)
303
291
  if (!baseName && inPm2Cluster && strict) {
304
- console.error('[securenow] FATAL: SECURENOW_APPID/OTEL_SERVICE_NAME missing in cluster (pid=%d). Exiting due to SECURENOW_STRICT=1.', process.pid);
292
+ console.error('[securenow] FATAL: app identity missing in cluster (pid=%d). Exiting due to config.runtime.strict=true.', process.pid);
305
293
  // small delay so the log flushes
306
294
  setTimeout(() => process.exit(1), 10);
307
295
  }
@@ -336,7 +324,7 @@ for (const n of (env('SECURENOW_DISABLE_INSTRUMENTATIONS') || '').split(',').map
336
324
  }
337
325
 
338
326
  // -------- Body Capture Configuration --------
339
- // Opt-out defaults: set =0 or =false to disable.
327
+ // Opt-out defaults: set config.capture.body=false to disable.
340
328
  const captureBody = !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
341
329
  const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
342
330
  const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
@@ -481,7 +469,7 @@ const loggingEnabled = !/^(0|false)$/i.test(String(env('SECURENOW_LOGGING_ENABLE
481
469
  const sharedResource = new Resource({
482
470
  [SemanticResourceAttributes.SERVICE_NAME]: serviceName,
483
471
  [SemanticResourceAttributes.SERVICE_INSTANCE_ID]: serviceInstanceId,
484
- [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: env('NODE_ENV') || 'production',
472
+ [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: appConfig.resolveDeploymentEnvironment(),
485
473
  [SemanticResourceAttributes.SERVICE_VERSION]: process.env.npm_package_version || undefined,
486
474
  });
487
475
 
@@ -591,7 +579,7 @@ const sdk = new NodeSDK({
591
579
  if (loggingEnabled) {
592
580
  console.log('[securenow] 📋 Logging: ENABLED → %s', logsUrl);
593
581
  } else {
594
- console.log('[securenow] 📋 Logging: DISABLED (SECURENOW_LOGGING_ENABLED=0)');
582
+ console.log('[securenow] Logging: DISABLED (config.logging.enabled=false)');
595
583
  }
596
584
  if (captureBody) {
597
585
  console.log('[securenow] 📝 Request body capture: ENABLED (max: %d bytes, redacting %d sensitive fields)', maxBodySize, allSensitiveFields.length);
@@ -614,21 +602,25 @@ const sdk = new NodeSDK({
614
602
  // Firewall — auto-activates only when a real snk_live_ key is resolvable.
615
603
  // resolveApiKey() enforces the prefix, so we skip cleanly when the app has
616
604
  // only an app-routing UUID (or nothing at all) — no 401 polling loops.
617
- const firewallApiKey = appConfig.resolveApiKey();
618
- const firewallAppKey = appConfig.resolveAppKey();
619
- if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
605
+ const firewallOptions = appConfig.resolveFirewallOptions();
606
+ if (firewallOptions.apiKey && firewallOptions.enabled) {
620
607
  require('./firewall').init({
621
- apiKey: firewallApiKey,
622
- appKey: firewallAppKey || null,
623
- apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
624
- versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
625
- syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
626
- failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
627
- statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
628
- log: env('SECURENOW_FIREWALL_LOG') !== '0',
629
- tcp: env('SECURENOW_FIREWALL_TCP') === '1',
630
- iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
631
- cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
608
+ apiKey: firewallOptions.apiKey,
609
+ appKey: firewallOptions.appKey,
610
+ environment: firewallOptions.environment,
611
+ apiUrl: firewallOptions.apiUrl,
612
+ versionCheckInterval: firewallOptions.versionCheckInterval,
613
+ syncInterval: firewallOptions.syncInterval,
614
+ failMode: firewallOptions.failMode,
615
+ statusCode: firewallOptions.statusCode,
616
+ log: firewallOptions.log,
617
+ tcp: firewallOptions.tcp,
618
+ iptables: firewallOptions.iptables,
619
+ cloud: firewallOptions.cloud,
620
+ cloudDryRun: firewallOptions.cloudDryRun,
621
+ cloudflare: firewallOptions.cloudflare,
622
+ aws: firewallOptions.aws,
623
+ gcp: firewallOptions.gcp,
632
624
  });
633
625
  }
634
626
  } catch (e) {
@@ -659,7 +651,7 @@ module.exports = {
659
651
  loggerProvider,
660
652
  getLogger: (name = 'default', version = '1.0.0') => {
661
653
  if (!loggerProvider) {
662
- console.warn('[securenow] Logging is disabled (SECURENOW_LOGGING_ENABLED=0). Remove the override to enable.');
654
+ console.warn('[securenow] Logging is disabled (config.logging.enabled=false). Enable it in .securenow/credentials.json to use getLogger().');
663
655
  return null;
664
656
  }
665
657
  return loggerProvider.getLogger(name, version);