securenow 7.7.14 → 7.7.16

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,12 @@ 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
+ firewallApiUrl: firewall.apiUrl,
40
+ firewallSyncEndpoint: `${firewall.apiUrl.replace(/\/$/, '')}/api/v1/firewall/sync`,
41
+ loggingEnabled: appConfig.boolConfig('logging.enabled', true),
42
+ captureBody: appConfig.boolConfig('capture.body', true),
43
+ captureMultipart: appConfig.boolConfig('capture.multipart', true),
44
+ otelLogLevel: appConfig.configValue('otel.logLevel', 'error') || 'error',
43
45
  firewallEnabled,
44
46
  firewallLayers: {
45
47
  http: firewallEnabled,
@@ -330,11 +332,12 @@ async function logSend(args, flags) {
330
332
  }
331
333
  }
332
334
 
333
- function okHttpStatus(status) {
335
+ function okHttpStatus(status, okStatuses) {
336
+ if (Array.isArray(okStatuses) && okStatuses.length) return okStatuses.includes(status);
334
337
  return status >= 200 && status < 300;
335
338
  }
336
339
 
337
- async function probe({ endpoint, method = 'POST', headers = {}, body = null, timeoutMs = 3000 }) {
340
+ async function probe({ endpoint, method = 'POST', headers = {}, body = null, timeoutMs = 3000, okStatuses = null }) {
338
341
  try {
339
342
  const res = await httpRequest({
340
343
  method,
@@ -344,9 +347,9 @@ async function probe({ endpoint, method = 'POST', headers = {}, body = null, tim
344
347
  timeoutMs,
345
348
  });
346
349
  return {
347
- ok: okHttpStatus(res.status),
350
+ ok: okHttpStatus(res.status, okStatuses),
348
351
  status: res.status,
349
- ...(okHttpStatus(res.status) ? {} : { error: `HTTP ${res.status}`, body: res.body ? res.body.slice(0, 500) : '' }),
352
+ ...(okHttpStatus(res.status, okStatuses) ? {} : { error: `HTTP ${res.status}`, body: res.body ? res.body.slice(0, 500) : '' }),
350
353
  };
351
354
  } catch (err) {
352
355
  return { ok: false, error: err.message };
@@ -372,6 +375,8 @@ function env(_args, flags) {
372
375
  otlpHeaders: Object.keys(cfg.headers || {}).length ? '***' : null,
373
376
  firewallApiKey: cfg.apiKey ? `${cfg.apiKey.slice(0, 12)}...` : null,
374
377
  apiUrl: cfg.apiUrl,
378
+ firewallApiUrl: cfg.firewallApiUrl,
379
+ firewallSyncEndpoint: cfg.firewallSyncEndpoint,
375
380
  loggingEnabled: cfg.loggingEnabled,
376
381
  otelLogLevel: cfg.otelLogLevel,
377
382
  captureBody: cfg.captureBody,
@@ -397,6 +402,7 @@ function env(_args, flags) {
397
402
  ['Body capture', cfg.captureBody ? ui.c.green('enabled') : ui.c.dim('disabled')],
398
403
  ['Multipart capture', cfg.captureMultipart ? ui.c.green('enabled') : ui.c.dim('disabled')],
399
404
  ['Firewall', firewallStatusLabel(cfg)],
405
+ ['Firewall sync', cfg.firewallEnabled ? cfg.firewallSyncEndpoint : ui.c.dim('(not active)')],
400
406
  ]);
401
407
 
402
408
  ui.heading('Resolved credentials');
@@ -454,6 +460,26 @@ async function doctor(_args, flags) {
454
460
  checks.push({ name: 'api', ...api });
455
461
  }
456
462
 
463
+ if (cfg.apiKey && cfg.firewallLocalEnabled) {
464
+ const query = new URLSearchParams();
465
+ if (cfg.appKey) query.set('app', cfg.appKey);
466
+ if (cfg.deploymentEnvironment) query.set('env', cfg.deploymentEnvironment);
467
+ const endpoint = `${cfg.firewallSyncEndpoint}${query.toString() ? `?${query.toString()}` : ''}`;
468
+ const spin4 = ui.spinner(`Probing firewall sync ${endpoint}`);
469
+ const sync = await probe({
470
+ method: 'GET',
471
+ endpoint,
472
+ headers: {
473
+ Authorization: `Bearer ${cfg.apiKey}`,
474
+ 'X-SecureNow-Environment': cfg.deploymentEnvironment,
475
+ },
476
+ okStatuses: [200, 304],
477
+ });
478
+ if (sync.ok) spin4.stop(`Firewall sync reachable (HTTP ${sync.status})`);
479
+ else spin4.fail(`Firewall sync unreachable: ${sync.error}`);
480
+ checks.push({ name: 'firewall-sync', ...sync });
481
+ }
482
+
457
483
  const warnings = [];
458
484
  const singletonOkMessage = otelApiCheck.ok && otelApiCheck.packages.length
459
485
  ? `OpenTelemetry API singleton OK (${otelApiCheck.versions[0] || 'unknown'})`
@@ -467,8 +493,12 @@ async function doctor(_args, flags) {
467
493
  if (!cfg.appKey) {
468
494
  warnings.push('No app key resolved. Run `npx securenow login` or set app.key in .securenow/credentials.json.');
469
495
  }
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.');
496
+ if (cfg.instance === 'https://ingest.securenow.ai') {
497
+ warnings.push('Using the SecureNow ingest gateway. Dedicated instances are routed server-side after policy checks.');
498
+ } else if (cfg.instance === 'https://api.securenow.ai/api/otlp') {
499
+ warnings.push('Using the legacy SecureNow telemetry gateway path. Regenerate credentials so telemetry uses https://ingest.securenow.ai.');
500
+ } else if (cfg.instance === 'https://freetrial.securenow.ai:4318') {
501
+ warnings.push('Using the legacy free-trial collector directly. Regenerate credentials so telemetry flows through https://ingest.securenow.ai.');
472
502
  }
473
503
  if (!cfg.apiKey && token) {
474
504
  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.',
package/firewall-only.js CHANGED
@@ -26,6 +26,7 @@ if (firewallOptions.apiKey && firewallOptions.enabled) {
26
26
  appKey: firewallOptions.appKey,
27
27
  environment: firewallOptions.environment,
28
28
  apiUrl: firewallOptions.apiUrl,
29
+ apiUrlFallbacks: firewallOptions.apiUrlFallbacks,
29
30
  versionCheckInterval: firewallOptions.versionCheckInterval,
30
31
  syncInterval: firewallOptions.syncInterval,
31
32
  failMode: firewallOptions.failMode,
package/firewall.js CHANGED
@@ -21,6 +21,7 @@ let _stats = { syncs: 0, blocked: 0, rateLimited: 0, allowed: 0, versionChecks:
21
21
  let _localhostFallbackTried = false;
22
22
  let _eventQueue = [];
23
23
  let _eventTimer = null;
24
+ let _remainingApiUrlFallbacks = [];
24
25
 
25
26
  // Remote toggle - set by /firewall/sync when an appKey is in scope. Default
26
27
  // true so a missing/unreachable backend fails open (matches pre-7.3 behavior).
@@ -60,7 +61,7 @@ const _httpsAgent = new https.Agent({ keepAlive: false });
60
61
  const EVENT_FLUSH_INTERVAL_MS = 2_000;
61
62
  const EVENT_BATCH_SIZE = 25;
62
63
  const EVENT_QUEUE_MAX = 1_000;
63
- const TRANSIENT_NETWORK_CODES = new Set(['ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EAI_AGAIN']);
64
+ const TRANSIENT_NETWORK_CODES = new Set(['ECONNRESET', 'ECONNREFUSED', 'EPIPE', 'ETIMEDOUT', 'EAI_AGAIN', 'ENOTFOUND']);
64
65
 
65
66
  // Unified sync uses /firewall/sync (v2). Falls back to legacy on 404.
66
67
  let _useUnifiedSync = true;
@@ -156,7 +157,13 @@ function agentFor(url) {
156
157
  function isTransientNetworkError(err) {
157
158
  if (!err) return false;
158
159
  if (err.code && TRANSIENT_NETWORK_CODES.has(err.code)) return true;
159
- return /socket hang up|connection reset|ECONNRESET/i.test(String(err.message || ''));
160
+ return /socket hang up|connection reset|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|timed out/i.test(String(err.message || ''));
161
+ }
162
+
163
+ function isApiReachabilityError(err) {
164
+ if (isTransientNetworkError(err)) return true;
165
+ const text = `${err && err.code || ''} ${err && err.message || ''}`;
166
+ return /TLS|SSL|certificate|CERT_|UNABLE_TO_VERIFY|self signed/i.test(text);
160
167
  }
161
168
 
162
169
  function formatRequestError(err) {
@@ -167,6 +174,33 @@ function formatRequestError(err) {
167
174
  return parts.join(' ');
168
175
  }
169
176
 
177
+ function resetApiUrlFallbacks() {
178
+ const seen = new Set([_options && _options.apiUrl].filter(Boolean));
179
+ _remainingApiUrlFallbacks = [];
180
+ for (const candidate of Array.isArray(_options && _options.apiUrlFallbacks) ? _options.apiUrlFallbacks : []) {
181
+ const url = String(candidate || '').trim().replace(/\/$/, '');
182
+ if (!url || seen.has(url)) continue;
183
+ seen.add(url);
184
+ _remainingApiUrlFallbacks.push(url);
185
+ }
186
+ }
187
+
188
+ function switchToNextApiUrl(reason) {
189
+ if (!_options || _remainingApiUrlFallbacks.length === 0) return false;
190
+ const previous = _options.apiUrl;
191
+ _options.apiUrl = _remainingApiUrlFallbacks.shift();
192
+ _lastUnifiedEtag = null;
193
+ _lastSyncEtag = null;
194
+ _lastAllowlistSyncEtag = null;
195
+ if (_options.log) {
196
+ fwWarn('[securenow] Firewall: %s unreachable (%s), retrying sync via %s',
197
+ previous,
198
+ reason || 'network error',
199
+ _options.apiUrl);
200
+ }
201
+ return true;
202
+ }
203
+
170
204
  function requestOnce(method, url, body, extraHeaders, timeout, callback) {
171
205
  const mod = url.startsWith('https') ? https : http;
172
206
  const parsed = new URL(url);
@@ -562,9 +596,15 @@ function pollOnce(callback) {
562
596
 
563
597
  const done = (err, result) => {
564
598
  _pollInflight = false;
565
- if (err) {
566
- _consecutiveErrors++;
567
- _stats.errors++;
599
+ if (err) {
600
+ if (isApiReachabilityError(err) && switchToNextApiUrl(formatRequestError(err))) {
601
+ _pollInflight = false;
602
+ const retryTimer = setTimeout(() => pollOnce(callback), 1000);
603
+ if (retryTimer.unref) retryTimer.unref();
604
+ return;
605
+ }
606
+ _consecutiveErrors++;
607
+ _stats.errors++;
568
608
  maybeOpenCircuit();
569
609
  if (_options.log) fwWarn('[securenow] Firewall: poll failed:', formatRequestError(err));
570
610
  return callback(err);
@@ -623,9 +663,14 @@ function startSyncLoop() {
623
663
  const syncFn = _useUnifiedSync ? doUnifiedSync : (cb) => doLegacyPoll(cb);
624
664
 
625
665
  syncFn((err, result) => {
626
- if (err) {
627
- const isConnErr = /ECONNREFUSED|ENOTFOUND|timed out/i.test(err.message);
628
- if (isConnErr && !_localhostFallbackTried && _options.apiUrl !== 'http://localhost:4000') {
666
+ if (err) {
667
+ const isConnErr = /ECONNREFUSED|ENOTFOUND|timed out/i.test(err.message);
668
+ if (isApiReachabilityError(err) && switchToNextApiUrl(formatRequestError(err))) {
669
+ const retryTimer = setTimeout(initialSync, 1000);
670
+ if (retryTimer.unref) retryTimer.unref();
671
+ return;
672
+ }
673
+ if (isConnErr && !_localhostFallbackTried && _options.apiUrl !== 'http://localhost:4000') {
629
674
  _localhostFallbackTried = true;
630
675
  const origUrl = _options.apiUrl;
631
676
  _options.apiUrl = 'http://localhost:4000';
@@ -981,10 +1026,15 @@ function patchHttpLayer() {
981
1026
 
982
1027
  // Init
983
1028
 
984
- function init(options) {
985
- _options = options;
1029
+ function init(options) {
1030
+ _options = options || {};
1031
+ _options.apiUrl = String(_options.apiUrl || '').trim().replace(/\/$/, '');
1032
+ _localhostFallbackTried = false;
1033
+ _useUnifiedSync = true;
1034
+ resetApiUrlFallbacks();
986
1035
 
987
1036
  if (_options.log) fwLog('[securenow] Firewall: ENABLED');
1037
+ if (_options.log && _options.apiUrl) fwLog('[securenow] Firewall: sync endpoint=%s/api/v1/firewall/sync', _options.apiUrl);
988
1038
  if (_options.log && _options.environment) fwLog('[securenow] Firewall: environment=%s', _options.environment);
989
1039
 
990
1040
  patchHttpLayer();
@@ -1043,8 +1093,10 @@ function shutdown() {
1043
1093
  _consecutiveErrors = 0;
1044
1094
  _pollInflight = false;
1045
1095
  _retryAfterUntil = 0;
1096
+ _localhostFallbackTried = false;
1046
1097
  _rateLimitRules = [];
1047
1098
  _rateLimitBuckets = new Map();
1099
+ _remainingApiUrlFallbacks = [];
1048
1100
 
1049
1101
  _httpAgent.destroy();
1050
1102
  _httpsAgent.destroy();
@@ -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;