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/cli/auth.js CHANGED
@@ -75,7 +75,6 @@ async function loginWithBrowser() {
75
75
  const returnedState = url.searchParams.get('state');
76
76
  const appKey = url.searchParams.get('app_key');
77
77
  const appName = url.searchParams.get('app_name');
78
- const appInstance = url.searchParams.get('app_instance');
79
78
  const apiKey = url.searchParams.get('api_key');
80
79
 
81
80
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
@@ -96,11 +95,10 @@ async function loginWithBrowser() {
96
95
 
97
96
  if (token) {
98
97
  pendingToken = token;
99
- if (appKey || appName || appInstance) {
98
+ if (appKey || appName) {
100
99
  pendingApp = {
101
100
  key: appKey || null,
102
101
  name: appName || null,
103
- instance: appInstance || null,
104
102
  };
105
103
  }
106
104
  if (apiKey && apiKey.startsWith('snk_live_')) {
package/cli/config.js CHANGED
@@ -153,11 +153,10 @@ function getToken() {
153
153
  function setAuth(token, email, expiresAt, { local = false, app = null, enableFirewall = false } = {}) {
154
154
  const targetFile = credentialsFileForLocal(local);
155
155
  const payload = { ...loadJSON(targetFile), token, email, expiresAt };
156
- if (app && (app.key || app.name || app.instance)) {
156
+ if (app && (app.key || app.name)) {
157
157
  payload.app = {
158
158
  key: app.key || null,
159
159
  name: app.name || null,
160
- instance: app.instance || null,
161
160
  };
162
161
  }
163
162
  saveJSON(
@@ -201,23 +200,52 @@ function setApp(app, { local } = {}) {
201
200
  app: {
202
201
  key: app.key || null,
203
202
  name: app.name || null,
204
- instance: app.instance || null,
205
203
  },
206
204
  }) || {});
207
205
  }
208
206
 
209
207
  function ensureLocalGitignore() {
210
208
  const gitignorePath = path.join(process.cwd(), '.gitignore');
211
- const entry = '.securenow/';
209
+ const legacyEntry = '.securenow/';
210
+ const entries = [
211
+ '.securenow/credentials.json',
212
+ '.securenow/credentials.*.json',
213
+ '!.securenow/credentials.example.json',
214
+ '!.securenow/credentials.*.example.json',
215
+ ];
212
216
  try {
217
+ let content = '';
213
218
  if (fs.existsSync(gitignorePath)) {
214
- const content = fs.readFileSync(gitignorePath, 'utf8');
215
- if (!content.split('\n').some(line => line.trim() === entry)) {
216
- fs.appendFileSync(gitignorePath, `\n# SecureNow local credentials\n${entry}\n`);
219
+ content = fs.readFileSync(gitignorePath, 'utf8');
220
+ }
221
+
222
+ const lines = content ? content.split(/\r?\n/) : [];
223
+ let replacedLegacyEntry = false;
224
+ const nextLines = [];
225
+
226
+ for (const line of lines) {
227
+ if (line.trim() === legacyEntry) {
228
+ if (!replacedLegacyEntry) {
229
+ nextLines.push(...entries);
230
+ replacedLegacyEntry = true;
231
+ }
232
+ continue;
217
233
  }
218
- } else {
219
- fs.writeFileSync(gitignorePath, `# SecureNow local credentials\n${entry}\n`);
234
+ nextLines.push(line);
235
+ }
236
+
237
+ const hasLine = (entry) => nextLines.some(line => line.trim() === entry);
238
+ const missing = entries.filter(entry => !hasLine(entry));
239
+ if (!replacedLegacyEntry && missing.length === 0) return;
240
+
241
+ let nextContent = nextLines.join('\n').replace(/\s*$/, '');
242
+
243
+ if (missing.length > 0) {
244
+ const block = ['# SecureNow local credential files', '# Keep .securenow/ itself trackable for repo-owned docs/templates.', ...missing].join('\n');
245
+ nextContent = nextContent ? `${nextContent}\n\n${block}` : block;
220
246
  }
247
+
248
+ fs.writeFileSync(gitignorePath, `${nextContent}\n`);
221
249
  } catch {}
222
250
  }
223
251
 
@@ -25,7 +25,6 @@ function buildRuntimeCredentials(options = {}) {
25
25
  app: {
26
26
  key: creds.app?.key || null,
27
27
  name: creds.app?.name || null,
28
- instance: creds.app?.instance || appConfig.FREE_TRIAL_INSTANCE,
29
28
  },
30
29
  config: {
31
30
  ...(creds.config || {}),
@@ -41,6 +40,7 @@ function buildRuntimeCredentials(options = {}) {
41
40
  _securenow: {
42
41
  ...(creds._securenow || {}),
43
42
  note: 'Runtime SecureNow credentials and SDK defaults. Mount or copy this JSON as .securenow/credentials.json or .securenow/credentials.<environment>.json in production. Do not commit it.',
43
+ routing: 'Telemetry is sent to the default SecureNow ingestion gateway. The gateway routes by app.key, so runtime credentials do not expose per-instance collector URLs.',
44
44
  runtimeOnly: 'This file intentionally omits CLI OAuth fields: token, email, and expiresAt.',
45
45
  production: 'Production can use this same file shape instead of environment variables.',
46
46
  },
@@ -36,10 +36,10 @@ function resolvedConfig(options = {}) {
36
36
  apiKey,
37
37
  firewallLocalEnabled,
38
38
  apiUrl: config.getApiUrl(),
39
- loggingEnabled: appConfig.boolEnv('SECURENOW_LOGGING_ENABLED', true),
40
- captureBody: appConfig.boolEnv('SECURENOW_CAPTURE_BODY', true),
41
- captureMultipart: appConfig.boolEnv('SECURENOW_CAPTURE_MULTIPART', true),
42
- otelLogLevel: (process.env.OTEL_LOG_LEVEL != null ? process.env.OTEL_LOG_LEVEL : appConfig.env('OTEL_LOG_LEVEL')) || 'error',
39
+ loggingEnabled: appConfig.boolConfig('logging.enabled', true),
40
+ captureBody: appConfig.boolConfig('capture.body', true),
41
+ captureMultipart: appConfig.boolConfig('capture.multipart', true),
42
+ otelLogLevel: appConfig.configValue('otel.logLevel', 'error') || 'error',
43
43
  firewallEnabled,
44
44
  firewallLayers: {
45
45
  http: firewallEnabled,
@@ -467,8 +467,12 @@ async function doctor(_args, flags) {
467
467
  if (!cfg.appKey) {
468
468
  warnings.push('No app key resolved. Run `npx securenow login` or set app.key in .securenow/credentials.json.');
469
469
  }
470
- if (cfg.instance === 'https://freetrial.securenow.ai:4318') {
471
- warnings.push('Using the free-trial collector. For production, set app.instance in .securenow/credentials.json.');
470
+ if (cfg.instance === 'https://ingest.securenow.ai') {
471
+ warnings.push('Using the SecureNow ingest gateway. Dedicated instances are routed server-side after policy checks.');
472
+ } else if (cfg.instance === 'https://api.securenow.ai/api/otlp') {
473
+ warnings.push('Using the legacy SecureNow telemetry gateway path. Regenerate credentials so telemetry uses https://ingest.securenow.ai.');
474
+ } else if (cfg.instance === 'https://freetrial.securenow.ai:4318') {
475
+ warnings.push('Using the legacy free-trial collector directly. Regenerate credentials so telemetry flows through https://ingest.securenow.ai.');
472
476
  }
473
477
  if (!cfg.apiKey && token) {
474
478
  warnings.push('CLI/MCP is authenticated with your SecureNow session token. Runtime firewall enforcement key is missing. Run `npx securenow login` or `npx securenow api-key set snk_live_...` to refresh .securenow/credentials.json.');
package/cli/init.js CHANGED
@@ -234,6 +234,7 @@ function printAgentPrompt(kind, filename, major, project) {
234
234
  console.log([
235
235
  'Set up SecureNow in this existing Next.js project without using .env files.',
236
236
  'Use .securenow/credentials.json for local and production configuration; do not add .env files.',
237
+ 'Ignore only .securenow/credentials.json and .securenow/credentials.*.json in git; keep the .securenow/ directory itself trackable for repo-owned files.',
237
238
  commands
238
239
  ? `Use the project scripts for verification when appropriate: ${commands}. Ask before starting long-running dev/start servers, and ask which command to use if these scripts are not the right customer workflow.`
239
240
  : 'Ask the customer which command starts, builds, and tests this app because package.json does not expose an obvious script.',
@@ -7,10 +7,10 @@
7
7
  * Opt-out: set config.runtime.hideBanner=true in .securenow/credentials.json
8
8
  */
9
9
 
10
- const FREETRIAL_HOST = 'freetrial.securenow.ai';
10
+ const FREE_TRIAL_HOSTS = ['ingest.securenow.ai', 'freetrial.securenow.ai'];
11
11
 
12
12
  function isFreeTrial(endpointBase) {
13
- return !!endpointBase && endpointBase.includes(FREETRIAL_HOST);
13
+ return !!endpointBase && FREE_TRIAL_HOSTS.some((host) => endpointBase.includes(host));
14
14
  }
15
15
 
16
16
  /* istanbul ignore next โ€” runs in browser, not Node */
package/mcp/catalog.js CHANGED
@@ -17,7 +17,7 @@ Primary goals:
17
17
 
18
18
  Safety rules:
19
19
  - Do not print full API keys, JWTs, tokens, or .securenow/credentials.json. Mask secrets.
20
- - Do not commit secrets. Ensure .securenow/ is in .gitignore.
20
+ - Do not commit secrets. Ignore only local SecureNow credential files (.securenow/credentials.json and .securenow/credentials.*.json); keep the .securenow/ directory itself trackable for repo-owned docs/templates.
21
21
  - Do not manually browse to a SecureNow auth URL. Always start auth with npx securenow login so the CLI generates the required callback and state.
22
22
  - If the browser says "Missing callback parameter", you opened the wrong URL: rerun npx securenow login from the project root.
23
23
  - Do not skip login, app selection, firewall connection, or verification unless I explicitly say to.
@@ -42,7 +42,7 @@ Runbook:
42
42
  - Confirm .securenow/credentials.json exists.
43
43
  - Confirm it has SecureNow's default config/explanations block.
44
44
  - Confirm it has an app key/name/instance and a firewall API key after login/app selection.
45
- - Confirm .securenow/ is ignored by git.
45
+ - Confirm .securenow/credentials.json and any .securenow/credentials.*.json runtime files are ignored by git, without ignoring the entire .securenow/ directory.
46
46
  6. Run npx securenow init. If it fails with ui.header is not a function or another CLI bug, upgrade to securenow@latest, verify >=7.5.1, and retry. Do not silently ignore init failures.
47
47
  7. Configure the least invasive framework-specific integration:
48
48
  - Next.js: preserve instrumentation.js/ts. Register securenow/nextjs only when NEXT_RUNTIME is nodejs. In ESM files, use createRequire before require("securenow/nextjs"). Include require("securenow/nextjs-auto-capture") for body capture. For Next 15+, add securenow to serverExternalPackages. For older Next.js, use experimental.serverComponentsExternalPackages. Preserve proxy.js/middleware.js.
package/nextjs.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- /**
2
- * SecureNow Next.js Integration TypeScript Declarations
3
- */
4
-
5
- export interface RegisterOptions {
1
+ /**
2
+ * SecureNow Next.js Integration TypeScript Declarations
3
+ */
4
+
5
+ export interface RegisterOptions {
6
6
  /**
7
7
  * Service name for OpenTelemetry traces
8
8
  * @default .securenow/credentials.json app.key/app.name
@@ -11,7 +11,11 @@ export interface RegisterOptions {
11
11
 
12
12
  /**
13
13
  * OTLP endpoint for traces
14
- * @default .securenow/credentials.json app.instance, or https://freetrial.securenow.ai:4318
14
+ * @default https://ingest.securenow.ai
15
+ *
16
+ * Advanced OTLP endpoint override. Normal SecureNow apps should leave this
17
+ * unset so the ingest gateway can route by app.key to the dashboard-selected
18
+ * instance.
15
19
  */
16
20
  endpoint?: string;
17
21
 
@@ -20,26 +24,26 @@ export interface RegisterOptions {
20
24
  * @default .securenow/credentials.json config.runtime.deploymentEnvironment
21
25
  */
22
26
  environment?: string;
23
-
24
- /**
25
- * Don't append UUID to service name
26
- * @default false
27
- */
28
- noUuid?: boolean;
29
-
27
+
28
+ /**
29
+ * Don't append UUID to service name
30
+ * @default false
31
+ */
32
+ noUuid?: boolean;
33
+
30
34
  /**
31
35
  * Enable request body capture
32
36
  * @default true from .securenow/credentials.json secure defaults
33
37
  */
34
38
  captureBody?: boolean;
35
39
  }
36
-
37
- /**
38
- * Register SecureNow OpenTelemetry instrumentation for Next.js
39
- *
40
- * @param options - Optional configuration options
41
- *
42
- * @example
40
+
41
+ /**
42
+ * Register SecureNow OpenTelemetry instrumentation for Next.js
43
+ *
44
+ * @param options - Optional configuration options
45
+ *
46
+ * @example
43
47
  * ```typescript
44
48
  * // instrumentation.ts
45
49
  * import { createRequire } from 'node:module';
@@ -53,46 +57,46 @@ export interface RegisterOptions {
53
57
  * require('securenow/nextjs-auto-capture');
54
58
  * }
55
59
  * ```
56
- *
57
- * @example
58
- * ```typescript
59
- * // With custom options
60
- * import { registerSecureNow } from 'securenow/nextjs';
61
- *
62
- * export function register() {
63
- * registerSecureNow({
64
- * serviceName: 'my-nextjs-app',
65
- * endpoint: 'http://your-otlp-backend.example.com:4318',
66
- * noUuid: true,
67
- * });
68
- * }
69
- * ```
70
- */
71
- export function registerSecureNow(options?: RegisterOptions): void;
72
-
73
- /**
74
- * Default sensitive fields that are automatically redacted from traces
75
- */
76
- export const DEFAULT_SENSITIVE_FIELDS: readonly string[];
77
-
78
- /**
79
- * Redact sensitive fields from an object
80
- * @param obj - Object to redact
81
- * @param sensitiveFields - Array of field names to redact (case-insensitive substring match)
82
- * @returns Redacted copy of the object
83
- */
84
- export function redactSensitiveData<T = any>(
85
- obj: T,
86
- sensitiveFields?: string[]
87
- ): T;
88
-
89
- /**
90
- * Redact sensitive data from GraphQL query strings
91
- * @param query - GraphQL query string
92
- * @param sensitiveFields - Array of field names to redact
93
- * @returns Redacted query string
94
- */
95
- export function redactGraphQLQuery(
96
- query: string,
97
- sensitiveFields?: string[]
98
- ): string;
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // With custom options
64
+ * import { registerSecureNow } from 'securenow/nextjs';
65
+ *
66
+ * export function register() {
67
+ * registerSecureNow({
68
+ * serviceName: 'my-nextjs-app',
69
+ * endpoint: 'http://your-otlp-backend.example.com:4318',
70
+ * noUuid: true,
71
+ * });
72
+ * }
73
+ * ```
74
+ */
75
+ export function registerSecureNow(options?: RegisterOptions): void;
76
+
77
+ /**
78
+ * Default sensitive fields that are automatically redacted from traces
79
+ */
80
+ export const DEFAULT_SENSITIVE_FIELDS: readonly string[];
81
+
82
+ /**
83
+ * Redact sensitive fields from an object
84
+ * @param obj - Object to redact
85
+ * @param sensitiveFields - Array of field names to redact (case-insensitive substring match)
86
+ * @returns Redacted copy of the object
87
+ */
88
+ export function redactSensitiveData<T = any>(
89
+ obj: T,
90
+ sensitiveFields?: string[]
91
+ ): T;
92
+
93
+ /**
94
+ * Redact sensitive data from GraphQL query strings
95
+ * @param query - GraphQL query string
96
+ * @param sensitiveFields - Array of field names to redact
97
+ * @returns Redacted query string
98
+ */
99
+ export function redactGraphQLQuery(
100
+ query: string,
101
+ sensitiveFields?: string[]
102
+ ): string;
package/nextjs.js CHANGED
@@ -35,8 +35,6 @@ const appConfig = require('./app-config');
35
35
  const { resolveClientIpWithDetails } = require('./resolve-ip');
36
36
  const otelResources = require('@opentelemetry/resources');
37
37
 
38
- const env = appConfig.env;
39
-
40
38
  let isRegistered = false;
41
39
 
42
40
  function requireRuntimeModule(name) {
@@ -126,7 +124,7 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
126
124
  * Register SecureNow OpenTelemetry for Next.js using @vercel/otel
127
125
  * @param {Object} options - Optional configuration
128
126
  * @param {string} options.serviceName - Service name (defaults to .securenow/credentials.json app.key/app.name)
129
- * @param {string} options.endpoint - Traces endpoint (defaults to .securenow/credentials.json app.instance)
127
+ * @param {string} options.endpoint - Advanced OTLP endpoint override (defaults to the SecureNow ingest gateway)
130
128
  * @param {string} options.environment - deployment.environment override (defaults to config.runtime.deploymentEnvironment)
131
129
  * @param {boolean} options.noUuid - Don't append UUID to service name
132
130
  */
@@ -144,24 +142,26 @@ function registerSecureNow(options = {}) {
144
142
  }
145
143
 
146
144
  // Detect environment outside try block for error handling
147
- const isVercel = !!(env('VERCEL') || env('VERCEL_ENV') || env('VERCEL_URL'));
145
+ const isVercel = !!(process.env.VERCEL || process.env.VERCEL_ENV || process.env.VERCEL_URL);
148
146
  let deploymentEnvironment = appConfig.resolveDeploymentEnvironment();
149
147
 
150
148
  try {
151
149
  console.log('[securenow] Next.js integration loading (pid=%d)', process.pid);
152
150
 
153
151
  // -------- Configuration --------
154
- // Resolution order: explicit options -> .securenow/credentials.json -> legacy env fallback -> package.json#name
152
+ // Resolution order: explicit options -> .securenow/credentials.json -> package.json#name.
153
+ // Telemetry goes to the stable ingest gateway by default; the API gateway
154
+ // routes by app.key to the dashboard-selected instance.
155
155
  const resolvedApp = appConfig.resolveAll();
156
156
 
157
157
  const rawBase = (options.serviceName || resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
158
158
  const baseName = rawBase || null;
159
159
  // Default: auto-disable suffix when logged in (appId is the routing UUID
160
- // and the dashboard does exact match). opts.noUuid or SECURENOW_NO_UUID
161
- // override.
160
+ // and the dashboard does exact match). opts.noUuid or config.runtime.noUuid
161
+ // can still override.
162
162
  const noUuid = appConfig.resolveNoUuid({ noUuid: options.noUuid });
163
163
  deploymentEnvironment = appConfig.normalizeDeploymentEnvironment(
164
- options.environment || resolvedApp.deploymentEnvironment || env('VERCEL_ENV')
164
+ options.environment || resolvedApp.deploymentEnvironment
165
165
  );
166
166
 
167
167
  // service.name
@@ -170,7 +170,7 @@ function registerSecureNow(options = {}) {
170
170
  serviceName = noUuid ? baseName : `${baseName}-${randomUUID()}`;
171
171
  } else {
172
172
  serviceName = `nextjs-app-${randomUUID()}`;
173
- console.warn('[securenow] โš ๏ธ No app identity resolved. Using fallback: %s', serviceName);
173
+ console.warn('[securenow] No app identity resolved. Using fallback: %s', serviceName);
174
174
  console.warn('[securenow] Run `npx securenow login` and `npx securenow init` to write .securenow/credentials.json');
175
175
  }
176
176
 
@@ -180,24 +180,30 @@ function registerSecureNow(options = {}) {
180
180
  const tracesUrl = resolvedEndpoints.tracesUrl;
181
181
  const logsUrl = resolvedEndpoints.logsUrl;
182
182
  const headers = resolvedEndpoints.headers;
183
+ const otelLogLevel = String(appConfig.configValue('otel.logLevel', '') || '').toLowerCase();
184
+ const isDevelopmentRuntime = process.env.NODE_ENV === 'development';
183
185
 
184
- if (!process.env.OTEL_SERVICE_NAME) process.env.OTEL_SERVICE_NAME = serviceName;
185
- if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_ENDPOINT = endpointBase;
186
- if (!process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = tracesUrl;
186
+ // @vercel/otel still reads OTel process variables internally. These are
187
+ // derived from credentials JSON; customers do not set them.
188
+ if (isVercel) {
189
+ if (!process.env.OTEL_SERVICE_NAME) process.env.OTEL_SERVICE_NAME = serviceName;
190
+ if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_ENDPOINT = endpointBase;
191
+ if (!process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = tracesUrl;
192
+ }
187
193
 
188
194
  console.log('[securenow] Next.js App -> service.name=%s', serviceName);
189
195
  console.log('[securenow] Environment: %s', deploymentEnvironment);
190
196
 
191
197
  // -------- Body Capture Configuration --------
192
198
  // Opt-out default: set config.capture.body=false (or options.captureBody=false) to disable.
193
- const captureBody = options.captureBody ?? !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
194
- const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
195
- const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
199
+ const captureBody = options.captureBody ?? appConfig.boolConfig('capture.body', true);
200
+ const maxBodySize = appConfig.numberConfig('capture.maxBodySize', 10240, 1024);
201
+ const customSensitiveFields = appConfig.listConfig('capture.sensitiveFields');
196
202
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
197
203
 
198
204
  // -------- Log environment detection --------
199
205
  if (!isVercel) {
200
- console.log('[securenow] ๐Ÿ–ฅ๏ธ Self-hosted environment detected (EC2/PM2) - using vanilla SDK');
206
+ console.log('[securenow] Self-hosted environment detected (EC2/PM2) - using vanilla SDK');
201
207
  }
202
208
 
203
209
  // -------- Use different initialization based on environment --------
@@ -331,8 +337,8 @@ function registerSecureNow(options = {}) {
331
337
  }
332
338
 
333
339
  // Debug log in development
334
- if (env('NODE_ENV') === 'development' || env('OTEL_LOG_LEVEL') === 'debug') {
335
- console.log('[securenow] ๐Ÿ“ก Captured IP: %s (from: %s)',
340
+ if (isDevelopmentRuntime || otelLogLevel === 'debug') {
341
+ console.log('[securenow] Captured IP: %s (from: %s)',
336
342
  primaryIp,
337
343
  ipDetails.source || 'unknown'
338
344
  );
@@ -345,8 +351,8 @@ function registerSecureNow(options = {}) {
345
351
 
346
352
  } catch (error) {
347
353
  // Silently fail to not break the request
348
- if (env('OTEL_LOG_LEVEL') === 'debug') {
349
- console.error('[securenow] โš ๏ธ Error in requestHook:', error.message);
354
+ if (otelLogLevel === 'debug') {
355
+ console.error('[securenow] Error in requestHook:', error.message);
350
356
  }
351
357
  }
352
358
  },
@@ -383,7 +389,7 @@ function registerSecureNow(options = {}) {
383
389
  return /OTLPTraceExporter|OTLPLogExporter|otlp|exporter.*http|BatchSpanProcessor|BatchLogRecordProcessor/i.test(s)
384
390
  || /node:_http_client|ClientRequest|TLSSocket/i.test(s);
385
391
  }
386
- const _diagDebug = (env('OTEL_LOG_LEVEL') || '').toLowerCase() === 'debug';
392
+ const _diagDebug = otelLogLevel === 'debug';
387
393
  process.on('uncaughtException', (err, origin) => {
388
394
  if (_isOtlpTransientError(err) && _looksLikeOtlpStack(err)) {
389
395
  if (_diagDebug) {
@@ -455,11 +461,11 @@ function registerSecureNow(options = {}) {
455
461
  });
456
462
 
457
463
  sdk.start();
458
- console.log('[securenow] ๐ŸŽฏ Vanilla SDK initialized for self-hosted environment');
464
+ console.log('[securenow] Vanilla SDK initialized for self-hosted environment');
459
465
 
460
466
  // -------- Logging (self-hosted only) --------
461
467
  // Opt-out default: set config.logging.enabled=false to disable.
462
- const loggingEnabled = !/^(0|false)$/i.test(String(env('SECURENOW_LOGGING_ENABLED') ?? ''));
468
+ const loggingEnabled = appConfig.boolConfig('logging.enabled', true);
463
469
  if (loggingEnabled) {
464
470
  try {
465
471
  const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
@@ -511,7 +517,7 @@ function registerSecureNow(options = {}) {
511
517
  _emitLog(SeverityNumber.ERROR, 'ERROR', args);
512
518
  };
513
519
 
514
- console.log('[securenow] ๐Ÿ“‹ Logging: ENABLED โ†’ %s', logsUrl);
520
+ console.log('[securenow] Logging: ENABLED -> %s', logsUrl);
515
521
 
516
522
  // Auto-log every incoming HTTP request/response
517
523
  try {
@@ -559,51 +565,51 @@ function registerSecureNow(options = {}) {
559
565
  }
560
566
  return originalEmit.apply(this, arguments);
561
567
  };
562
- console.log('[securenow] ๐Ÿ“‹ HTTP request logging: ENABLED');
568
+ console.log('[securenow] HTTP request logging: ENABLED');
563
569
  } catch (_) {}
564
570
 
565
571
  // Graceful shutdown for logs
566
572
  process.on('SIGTERM', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { requireRuntimeModule('./firewall').shutdown(); } catch (_) {} });
567
573
  process.on('SIGINT', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { requireRuntimeModule('./firewall').shutdown(); } catch (_) {} });
568
574
  } catch (e) {
569
- console.warn('[securenow] โš ๏ธ Logging setup failed (missing @opentelemetry/exporter-logs-otlp-http or @opentelemetry/sdk-logs):', e.message);
575
+ console.warn('[securenow] Logging setup failed (missing @opentelemetry/exporter-logs-otlp-http or @opentelemetry/sdk-logs):', e.message);
570
576
  }
571
577
  } else {
572
- console.log('[securenow] ๐Ÿ“‹ Logging: DISABLED (SECURENOW_LOGGING_ENABLED=0)');
578
+ console.log('[securenow] Logging: DISABLED (config.logging.enabled=false)');
573
579
  }
574
580
  }
575
581
 
576
582
  isRegistered = true;
577
583
 
578
- // Free trial banner (optional โ€” may not be bundled in standalone builds)
584
+ // Free trial banner (optional - may not be bundled in standalone builds)
579
585
  try {
580
586
  const { isFreeTrial, patchHttpForBanner } = requireRuntimeModule('./free-trial-banner');
581
- if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
587
+ if (isFreeTrial(endpointBase) && !appConfig.boolConfig('runtime.hideBanner', false)) {
582
588
  patchHttpForBanner();
583
589
  }
584
590
  } catch (_) {}
585
591
 
586
- console.log('[securenow] โœ… OpenTelemetry started for Next.js โ†’ %s', tracesUrl);
587
- console.log('[securenow] ๐Ÿ“Š Auto-capturing comprehensive request metadata:');
588
- console.log('[securenow] โ€ข IP addresses (x-forwarded-for, x-real-ip, socket)');
589
- console.log('[securenow] โ€ข User-Agent, Referer, Origin, Accept headers');
590
- console.log('[securenow] โ€ข Protocol, Host, Port (proxy-aware)');
591
- console.log('[securenow] โ€ข Geographic data (Vercel/Cloudflare)');
592
- console.log('[securenow] โ€ข Request IDs, CSRF tokens, Auth presence');
593
- console.log('[securenow] โ€ข Response status, content-type, content-length');
594
- console.log('[securenow] โš ๏ธ Body capture DISABLED at HTTP instrumentation level (prevents Next.js conflicts)');
592
+ console.log('[securenow] OpenTelemetry started for Next.js -> %s', tracesUrl);
593
+ console.log('[securenow] Auto-capturing comprehensive request metadata:');
594
+ console.log('[securenow] - IP addresses (x-forwarded-for, x-real-ip, socket)');
595
+ console.log('[securenow] - User-Agent, Referer, Origin, Accept headers');
596
+ console.log('[securenow] - Protocol, Host, Port (proxy-aware)');
597
+ console.log('[securenow] - Geographic data (Vercel/Cloudflare)');
598
+ console.log('[securenow] - Request IDs, CSRF tokens, Auth presence');
599
+ console.log('[securenow] - Response status, content-type, content-length');
600
+ console.log('[securenow] Body capture DISABLED at HTTP instrumentation level (prevents Next.js conflicts)');
595
601
  if (captureBody) {
596
- console.log('[securenow] ๐Ÿ’ก For body capture in Next.js, use: import "securenow/nextjs-auto-capture"');
602
+ console.log('[securenow] For body capture in Next.js, use: import "securenow/nextjs-auto-capture"');
597
603
  }
598
604
 
599
605
  // Optional test span
600
- if (String(env('SECURENOW_TEST_SPAN')) === '1') {
606
+ if (appConfig.boolConfig('runtime.testSpan', false)) {
601
607
  const api = require('@opentelemetry/api');
602
608
  const tracer = api.trace.getTracer('securenow-nextjs');
603
609
  const span = tracer.startSpan('securenow.nextjs.startup');
604
610
  span.setAttribute('next.runtime', process.env.NEXT_RUNTIME || 'nodejs');
605
611
  span.end();
606
- console.log('[securenow] ๐Ÿงช Test span created');
612
+ console.log('[securenow] Test span created');
607
613
  }
608
614
 
609
615
  } catch (error) {
@@ -615,7 +621,7 @@ function registerSecureNow(options = {}) {
615
621
  }
616
622
  }
617
623
 
618
- // Firewall โ€” runs independently from OTel so it works even if tracing fails.
624
+ // Firewall - runs independently from OTel so it works even if tracing fails.
619
625
  // Key and environment come from .securenow/credentials.json (written by
620
626
  // login/init or credentials runtime), so no .env entry is needed.
621
627
  const firewallOptions = appConfig.resolveFirewallOptions();
@@ -26,8 +26,6 @@ const { resolveClientIpWithDetails } = nodeRequire('./resolve-ip');
26
26
 
27
27
  // โ”€โ”€ Helpers โ”€โ”€
28
28
 
29
- const env = appConfig.env;
30
-
31
29
  function createResource(attributes) {
32
30
  if (typeof otelResources.resourceFromAttributes === 'function') {
33
31
  return otelResources.resourceFromAttributes(attributes);
@@ -74,7 +72,7 @@ function getRuntimeOptions() {
74
72
  export default defineNitroPlugin(async (nitroApp) => {
75
73
  const opts = getRuntimeOptions();
76
74
 
77
- // Resolution order: opts -> .securenow/credentials.json -> legacy env fallback -> package.json#name
75
+ // Resolution order: opts -> .securenow/credentials.json -> package.json#name
78
76
  const resolvedApp = appConfig.resolveAll();
79
77
 
80
78
  // โ”€โ”€ Naming โ”€โ”€
@@ -125,12 +123,9 @@ export default defineNitroPlugin(async (nitroApp) => {
125
123
  // Opt-out default: set config.capture.body=false (or opts.captureBody=false) to disable.
126
124
  const captureBody =
127
125
  opts.captureBody ??
128
- !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
129
- const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
130
- const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '')
131
- .split(',')
132
- .map((s) => s.trim())
133
- .filter(Boolean);
126
+ appConfig.boolConfig('capture.body', true);
127
+ const maxBodySize = appConfig.numberConfig('capture.maxBodySize', 10240, 1024);
128
+ const customSensitiveFields = appConfig.listConfig('capture.sensitiveFields');
134
129
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
135
130
 
136
131
  // โ”€โ”€ HTTP instrumentation โ”€โ”€
@@ -238,7 +233,7 @@ export default defineNitroPlugin(async (nitroApp) => {
238
233
  // Opt-out default: set config.logging.enabled=false (or opts.logging=false) to disable.
239
234
  const loggingEnabled =
240
235
  opts.logging ??
241
- !/^(0|false)$/i.test(String(env('SECURENOW_LOGGING_ENABLED') ?? ''));
236
+ appConfig.boolConfig('logging.enabled', true);
242
237
 
243
238
  let loggerProvider = null;
244
239
 
@@ -314,7 +309,7 @@ export default defineNitroPlugin(async (nitroApp) => {
314
309
  // โ”€โ”€ Free trial banner โ”€โ”€
315
310
  try {
316
311
  const { isFreeTrial, patchHttpForBanner } = await import('./free-trial-banner.js');
317
- if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
312
+ if (isFreeTrial(endpointBase) && !appConfig.boolConfig('runtime.hideBanner', false)) {
318
313
  patchHttpForBanner();
319
314
  }
320
315
  } catch {