securenow 7.8.1 → 8.0.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.
package/mcp/server.js CHANGED
@@ -95,7 +95,7 @@ function localAuthStatus() {
95
95
  const apiKey = config.getApiKey();
96
96
  const hasBearer = !!(token || apiKey);
97
97
  const runtimeFirewallWarning = app && !apiKey
98
- ? 'Runtime app is connected, but the firewall key is missing. Run `npx securenow app connect` or `npx securenow api-key set snk_live_...` to refresh .securenow/runtime.json.'
98
+ ? 'Runtime app is connected, but the runtime API key is missing. Run `npx securenow app connect` or `npx securenow api-key set snk_live_...` to refresh .securenow/runtime.json.'
99
99
  : null;
100
100
  return {
101
101
  authenticated: hasBearer,
@@ -18,7 +18,6 @@
18
18
 
19
19
  const { trace } = require('@opentelemetry/api');
20
20
  const appConfig = require('./app-config');
21
- const env = appConfig.env;
22
21
 
23
22
  // Default sensitive fields to redact
24
23
  const DEFAULT_SENSITIVE_FIELDS = [
@@ -56,8 +55,8 @@ async function safeBodyCapture(request, span) {
56
55
 
57
56
  try {
58
57
  const contentType = request.headers.get('content-type') || '';
59
- const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
60
- const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
58
+ const maxBodySize = appConfig.numberConfig('capture.maxBodySize', 10240, 1024);
59
+ const customSensitiveFields = appConfig.listConfig('capture.sensitiveFields');
61
60
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
62
61
 
63
62
  // Only for supported types
@@ -127,8 +126,7 @@ async function safeBodyCapture(request, span) {
127
126
  * Check if body capture is enabled
128
127
  */
129
128
  function isBodyCaptureEnabled() {
130
- // Opt-out default: set config.capture.body=false to disable.
131
- return !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
129
+ return appConfig.boolConfig('capture.body', true);
132
130
  }
133
131
 
134
132
  /**
@@ -195,4 +193,3 @@ module.exports = {
195
193
  isBodyCaptureEnabled,
196
194
  };
197
195
 
198
-
@@ -17,7 +17,6 @@
17
17
 
18
18
  const { trace, context, SpanStatusCode } = require('@opentelemetry/api');
19
19
  const appConfig = require('./app-config');
20
- const env = appConfig.env;
21
20
 
22
21
  // Default sensitive fields to redact
23
22
  const DEFAULT_SENSITIVE_FIELDS = [
@@ -101,8 +100,8 @@ async function middleware(request) {
101
100
 
102
101
  try {
103
102
  const contentType = request.headers.get('content-type') || '';
104
- const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
105
- const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
103
+ const maxBodySize = appConfig.numberConfig('capture.maxBodySize', 10240, 1024);
104
+ const customSensitiveFields = appConfig.listConfig('capture.sensitiveFields');
106
105
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
107
106
 
108
107
  // Only capture supported types
@@ -184,4 +183,3 @@ module.exports = {
184
183
  };
185
184
 
186
185
 
187
-
package/nextjs-wrapper.js CHANGED
@@ -17,7 +17,6 @@
17
17
 
18
18
  const { trace } = require('@opentelemetry/api');
19
19
  const appConfig = require('./app-config');
20
- const env = appConfig.env;
21
20
 
22
21
  // Default sensitive fields to redact
23
22
  const DEFAULT_SENSITIVE_FIELDS = [
@@ -51,8 +50,7 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
51
50
  * Capture body from Request object (clone to avoid consuming)
52
51
  */
53
52
  async function captureRequestBody(request) {
54
- // Opt-out default: set config.capture.body=false to disable.
55
- const captureBody = !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
53
+ const captureBody = appConfig.boolConfig('capture.body', true);
56
54
 
57
55
  if (!captureBody) return;
58
56
  if (!['POST', 'PUT', 'PATCH'].includes(request.method)) return;
@@ -62,8 +60,8 @@ async function captureRequestBody(request) {
62
60
 
63
61
  try {
64
62
  const contentType = request.headers.get('content-type') || '';
65
- const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
66
- const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
63
+ const maxBodySize = appConfig.numberConfig('capture.maxBodySize', 10240, 1024);
64
+ const customSensitiveFields = appConfig.listConfig('capture.sensitiveFields');
67
65
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
68
66
 
69
67
  // Only for supported types
@@ -155,4 +153,3 @@ module.exports = {
155
153
  DEFAULT_SENSITIVE_FIELDS,
156
154
  };
157
155
 
158
-
package/nextjs.js CHANGED
@@ -186,16 +186,6 @@ function registerSecureNow(options = {}) {
186
186
  const otelLogLevel = String(appConfig.configValue('otel.logLevel', '') || '').toLowerCase();
187
187
  const isDevelopmentRuntime = process.env.NODE_ENV === 'development';
188
188
 
189
- // @vercel/otel still reads OTel process variables internally. These are
190
- // derived from credentials JSON; customers do not set them.
191
- if (isVercel) {
192
- if (!process.env.OTEL_SERVICE_NAME) process.env.OTEL_SERVICE_NAME = serviceName;
193
- if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_ENDPOINT = endpointBase;
194
- if (!process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = tracesUrl;
195
- const headerString = appConfig.headersToString(headers);
196
- if (headerString && !process.env.OTEL_EXPORTER_OTLP_HEADERS) process.env.OTEL_EXPORTER_OTLP_HEADERS = headerString;
197
- }
198
-
199
189
  console.log('[securenow] Next.js App -> service.name=%s', serviceName);
200
190
  console.log('[securenow] Environment: %s', deploymentEnvironment);
201
191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "7.8.1",
3
+ "version": "8.0.0",
4
4
  "description": "OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt - Send traces and logs to any OTLP-compatible backend",
5
5
  "type": "commonjs",
6
6
  "main": "register.js",
@@ -167,8 +167,7 @@
167
167
  "@opentelemetry/sdk-node": "0.218.0",
168
168
  "@opentelemetry/sdk-trace-base": "2.7.1",
169
169
  "@opentelemetry/sdk-trace-web": "2.7.1",
170
- "@opentelemetry/semantic-conventions": "1.41.1",
171
- "dotenv": "17.2.1"
170
+ "@opentelemetry/semantic-conventions": "1.41.1"
172
171
  },
173
172
  "optionalDependencies": {
174
173
  "@vercel/otel": "2.1.2"
package/rate-limits.js CHANGED
@@ -12,8 +12,6 @@ function authToken(options = {}) {
12
12
  options.token ||
13
13
  options.apiKey ||
14
14
  options.authToken ||
15
- process.env.SECURENOW_TOKEN ||
16
- process.env.SECURENOW_API_KEY ||
17
15
  config.getToken() ||
18
16
  config.getApiKey() ||
19
17
  undefined
package/register-vite.js CHANGED
@@ -1,12 +1,5 @@
1
- // node_modules/securenow/register-vite.js (CommonJS, for -r)
2
- 'use strict';
3
-
4
- try {
5
- require('dotenv').config();
6
- console.log('[securenow] dotenv loaded for Vite preload');
7
- } catch {
8
- console.log('[securenow] dotenv not available, continuing without .env');
9
- }
10
-
11
- // Trace the Node process (Vite dev/preview server) using your existing Node tracer
12
- module.exports = require('./web-vite.mjs');
1
+ // node_modules/securenow/register-vite.js (CommonJS, for -r)
2
+ 'use strict';
3
+
4
+ // Trace the Node process (Vite dev/preview server) using your existing Node tracer
5
+ module.exports = require('./web-vite.mjs');
package/register.js CHANGED
@@ -4,17 +4,9 @@
4
4
  // For ESM apps ("type": "module"), this file auto-registers the
5
5
  // OpenTelemetry ESM loader hook via module.register() (Node >=20.6).
6
6
  // On older Node versions it falls back to a warning.
7
- 'use strict';
8
-
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({ quiet: true });
13
- } catch (e) {
14
- // dotenv is optional.
15
- }
16
-
17
- // 2. Auto-register the ESM loader hook so customers never need --import
7
+ 'use strict';
8
+
9
+ // 1. Auto-register the ESM loader hook so customers never need --import
18
10
  (() => {
19
11
  try {
20
12
  const fs = require('fs');
@@ -45,5 +37,5 @@ try {
45
37
  }
46
38
  })();
47
39
 
48
- // 3. Run the OTel SDK setup
49
- require('./tracing');
40
+ // 2. Run the OTel SDK setup
41
+ require('./tracing');
package/resolve-ip.js CHANGED
@@ -6,7 +6,7 @@ const appConfig = require('./app-config');
6
6
  const LOOPBACK_RE = /^(127\.|::1$|::ffff:127\.)/;
7
7
  const PRIVATE_IP_RE = /^(127\.|::1$|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.|f[cd][0-9a-f]{2}:)/i;
8
8
 
9
- const trustedProxies = appConfig.listEnv('SECURENOW_TRUSTED_PROXIES');
9
+ const trustedProxies = appConfig.listConfig('networking.trustedProxies');
10
10
  const trustedProxySet = trustedProxies.length ? new Set(trustedProxies.map(normalizeIp).filter(Boolean)) : null;
11
11
 
12
12
  let _hostIps = null;
package/tracing.d.ts CHANGED
@@ -165,7 +165,7 @@ export const loggerProvider: LoggerProvider | null;
165
165
  * Configuration:
166
166
  *
167
167
  * Local development reads .securenow/credentials.json first. Run
168
- * `npx securenow login` to write app identity/firewall key and
168
+ * `npx securenow login` to write app identity/runtime API key and
169
169
  * `npx securenow init` to ensure secure defaults and explanations.
170
170
  *
171
171
  * Production uses the same file shape. Run
package/tracing.js CHANGED
@@ -280,8 +280,8 @@ const endpointBase = resolvedEndpoints.endpointBase;
280
280
  const tracesUrl = resolvedEndpoints.tracesUrl;
281
281
  const logsUrl = resolvedEndpoints.logsUrl;
282
282
 
283
- // resolveEndpoints() also adds x-api-key from the credentials app key when
284
- // explicit OTLP headers did not provide one.
283
+ // resolveEndpoints() also adds app routing and runtime auth headers when
284
+ // explicit OTLP headers did not provide them.
285
285
  const headers = resolvedEndpoints.headers;
286
286
 
287
287
  // -------- naming rules --------
package/web-vite.mjs CHANGED
@@ -12,21 +12,21 @@ import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
12
12
  import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';
13
13
 
14
14
  // ---- helpers / browser config ----
15
- const viteEnv = import.meta.env || {};
16
-
17
- const ENV_TO_BROWSER_CONFIG_PATH = Object.freeze({
18
- SECURENOW_APPID: 'app.key',
19
- SECURENOW_INSTANCE: 'config.otel.endpoint',
20
- OTEL_SERVICE_NAME: 'app.name',
21
- OTEL_EXPORTER_OTLP_ENDPOINT: 'config.otel.endpoint',
22
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: 'config.otel.tracesEndpoint',
23
- OTEL_EXPORTER_OTLP_HEADERS: 'config.otel.headers',
24
- SECURENOW_NO_UUID: 'config.runtime.noUuid',
25
- SECURENOW_STRICT: 'config.runtime.strict',
26
- SECURENOW_TEST_SPAN: 'config.runtime.testSpan',
27
- SECURENOW_HIDE_BANNER: 'config.runtime.hideBanner',
28
- SECURENOW_ENVIRONMENT: 'config.runtime.deploymentEnvironment',
29
- SECURENOW_DEPLOYMENT_ENVIRONMENT: 'config.runtime.deploymentEnvironment',
15
+ const DEFAULT_BROWSER_CONFIG = Object.freeze({
16
+ config: {
17
+ otel: {
18
+ endpoint: 'https://ingest.securenow.ai',
19
+ tracesEndpoint: null,
20
+ headers: {},
21
+ },
22
+ runtime: {
23
+ deploymentEnvironment: 'production',
24
+ noUuid: null,
25
+ strict: false,
26
+ testSpan: false,
27
+ hideBanner: false,
28
+ },
29
+ },
30
30
  });
31
31
 
32
32
  function createResource(attributes) {
@@ -39,15 +39,6 @@ function createResource(attributes) {
39
39
  throw new Error('Unsupported @opentelemetry/resources version');
40
40
  }
41
41
 
42
- function legacyViteEnvFallbackEnabled() {
43
- const raw =
44
- viteEnv.SECURENOW_ENABLE_LEGACY_ENV ??
45
- viteEnv.VITE_SECURENOW_ENABLE_LEGACY_ENV ??
46
- viteEnv.SECURENOW_ALLOW_ENV_CONFIG ??
47
- viteEnv.VITE_SECURENOW_ALLOW_ENV_CONFIG;
48
- return /^(1|true|yes)$/i.test(String(raw || '').trim());
49
- }
50
-
51
42
  function getPath(obj, path) {
52
43
  if (!obj || !path) return undefined;
53
44
  let cur = obj;
@@ -74,28 +65,27 @@ function toConfigString(value) {
74
65
  return String(value);
75
66
  }
76
67
 
77
- function env(k) {
78
- // Browser config should be injected as window.__SECURENOW__ by the app.
79
- const w = globalThis.window;
80
- const injected = w && w.__SECURENOW__;
81
- const mappedPath = ENV_TO_BROWSER_CONFIG_PATH[String(k).toUpperCase()];
82
- if (injected) {
83
- if (mappedPath) {
84
- const mappedValue = getPath(injected, mappedPath);
85
- const resolved = toConfigString(mappedValue);
86
- if (resolved !== undefined) return resolved;
87
- }
88
- if (!mappedPath && k in injected) return toConfigString(injected[k]);
89
- }
68
+ function browserConfig() {
69
+ const injected = globalThis.window && globalThis.window.__SECURENOW__;
70
+ return injected && typeof injected === 'object' ? injected : {};
71
+ }
90
72
 
91
- // Vite env fallbacks are legacy and disabled by default.
92
- if (!legacyViteEnvFallbackEnabled()) return undefined;
93
- const direct =
94
- viteEnv[k] ??
95
- viteEnv[k.toUpperCase()] ??
96
- viteEnv[k.toLowerCase()];
97
- if (direct != null) return String(direct);
98
- return undefined;
73
+ function configValue(path, fallback) {
74
+ const injected = getPath(browserConfig(), path);
75
+ if (injected !== undefined && injected !== null && injected !== '') return injected;
76
+ const defaultValue = getPath(DEFAULT_BROWSER_CONFIG, path);
77
+ if (defaultValue !== undefined && defaultValue !== null && defaultValue !== '') return defaultValue;
78
+ return fallback;
79
+ }
80
+
81
+ function boolConfig(path, fallback = false) {
82
+ const value = configValue(path, fallback);
83
+ if (typeof value === 'boolean') return value;
84
+ if (typeof value === 'number') return value !== 0;
85
+ const text = String(value || '').trim().toLowerCase();
86
+ if (['1', 'true', 'yes', 'on', 'enabled'].includes(text)) return true;
87
+ if (['0', 'false', 'no', 'off', 'disabled'].includes(text)) return false;
88
+ return fallback;
99
89
  }
100
90
 
101
91
  function parseHeaders(str) {
@@ -139,29 +129,30 @@ function normalizeSignalEndpoint(value, signalType) {
139
129
 
140
130
  // ---- endpoints (same defaults as tracing.js) ----
141
131
  const endpointBase =
142
- normalizeEndpointBase(env('SECURENOW_INSTANCE') || env('OTEL_EXPORTER_OTLP_ENDPOINT') || 'https://ingest.securenow.ai');
132
+ normalizeEndpointBase(configValue('config.otel.endpoint', 'https://ingest.securenow.ai'));
143
133
  const tracesUrl =
144
- normalizeSignalEndpoint(env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'), 'traces') || `${endpointBase}/v1/traces`;
145
- const headers = parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS'));
134
+ normalizeSignalEndpoint(configValue('config.otel.tracesEndpoint'), 'traces') || `${endpointBase}/v1/traces`;
135
+ const headers = parseHeaders(toConfigString(configValue('config.otel.headers', {})));
146
136
  const deploymentEnvironment =
147
- env('SECURENOW_DEPLOYMENT_ENVIRONMENT') || env('SECURENOW_ENVIRONMENT') || viteEnv.MODE || 'production';
148
- const browserAppKey = env('SECURENOW_APPID');
149
- if (browserAppKey && !headers['x-api-key']) headers['x-api-key'] = browserAppKey;
137
+ String(configValue('config.runtime.deploymentEnvironment', 'production'));
138
+ const browserAppKey = toConfigString(configValue('app.key'));
139
+ if (browserAppKey && !headers['x-securenow-app-key']) headers['x-securenow-app-key'] = browserAppKey;
150
140
  if (deploymentEnvironment && !headers['x-securenow-environment']) headers['x-securenow-environment'] = deploymentEnvironment;
151
141
 
152
142
  // ---- naming rules (mirrors tracing.js) ----
153
- const rawBase = (env('OTEL_SERVICE_NAME') || env('SECURENOW_APPID') || '').trim().replace(/^['"]|['"]$/g, '');
143
+ const rawBase = (toConfigString(configValue('app.key')) || toConfigString(configValue('app.name')) || '').trim().replace(/^['"]|['"]$/g, '');
154
144
  const baseName = rawBase || null;
155
145
  // Default to no suffix whenever a baseName is resolved: the dashboard filters
156
146
  // service.name by exact match, and browsers have no PM2 cluster problem
157
147
  // (each tab has its own service.instance.id). Explicit config.runtime.noUuid=false
158
148
  // still re-enables the suffix if someone really wants it.
159
- const noUuidEnv = env('SECURENOW_NO_UUID');
149
+ const noUuidEnv = configValue('config.runtime.noUuid');
160
150
  const noUuid =
161
151
  (noUuidEnv !== undefined && noUuidEnv !== '')
162
152
  ? /^(1|true)$/i.test(String(noUuidEnv).trim())
163
153
  : !!baseName;
164
- const strict = String(env('SECURENOW_STRICT')) === '1' || String(env('SECURENOW_STRICT')).toLowerCase() === 'true';
154
+ const strict = boolConfig('config.runtime.strict', false);
155
+ const hostedSecureNowIngest = /^https:\/\/ingest\.securenow\.ai(?:\/|$)/i.test(tracesUrl);
165
156
 
166
157
  function uuidv4() {
167
158
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
@@ -195,6 +186,12 @@ if (baseName) {
195
186
  }
196
187
  }
197
188
 
189
+ if (!disabled && hostedSecureNowIngest) {
190
+ disabled = true;
191
+ // eslint-disable-next-line no-console
192
+ console.warn('[securenow/web-vite] Direct browser OTLP ingestion to SecureNow is disabled in v8.0 until browser-safe auth is available. Use the server-side SDK runtime credentials path.');
193
+ }
194
+
198
195
  const instancePrefix = baseName || 'securenow';
199
196
  const serviceInstanceId = `${instancePrefix}-${uuidv4()}`;
200
197
 
@@ -203,10 +200,10 @@ try {
203
200
  // eslint-disable-next-line no-console
204
201
  console.log(
205
202
  '[securenow] web preload loaded app.key=%s app.name=%s config.runtime.noUuid=%s config.runtime.strict=%s → service.name=%s instance.id=%s',
206
- JSON.stringify(env('SECURENOW_APPID')),
207
- JSON.stringify(env('OTEL_SERVICE_NAME')),
208
- JSON.stringify(env('SECURENOW_NO_UUID')),
209
- JSON.stringify(env('SECURENOW_STRICT')),
203
+ JSON.stringify(configValue('app.key')),
204
+ JSON.stringify(configValue('app.name')),
205
+ JSON.stringify(configValue('config.runtime.noUuid')),
206
+ JSON.stringify(configValue('config.runtime.strict')),
210
207
  serviceName,
211
208
  serviceInstanceId
212
209
  );
@@ -229,7 +226,7 @@ export function startSecurenowWeb() {
229
226
  [S.SERVICE_NAME]: serviceName,
230
227
  [S.SERVICE_INSTANCE_ID]: serviceInstanceId,
231
228
  [S.DEPLOYMENT_ENVIRONMENT]: deploymentEnvironment,
232
- [S.SERVICE_VERSION]: viteEnv.VITE_APP_VERSION || undefined,
229
+ [S.SERVICE_VERSION]: toConfigString(configValue('app.version')) || undefined,
233
230
  }),
234
231
  spanProcessors: [new BatchSpanProcessor(exporter)],
235
232
  });
@@ -250,8 +247,7 @@ export function startSecurenowWeb() {
250
247
  ],
251
248
  });
252
249
 
253
- // Optional smoke span (same flag name)
254
- if (String(env('SECURENOW_TEST_SPAN')) === '1') {
250
+ if (boolConfig('config.runtime.testSpan', false)) {
255
251
  import('@opentelemetry/api').then(api => {
256
252
  const tracer = api.trace.getTracer('securenow-smoke');
257
253
  const span = tracer.startSpan('securenow.startup.smoke.web'); span.end();
@@ -265,7 +261,7 @@ export function startSecurenowWeb() {
265
261
  // ---- Free trial banner (browser DOM injection) ----
266
262
  function injectFreeTrialBanner() {
267
263
  const FREE_TRIAL_HOSTS = ['ingest.securenow.ai', 'freetrial.securenow.ai'];
268
- const hideBanner = String(env('SECURENOW_HIDE_BANNER')) === '1';
264
+ const hideBanner = boolConfig('config.runtime.hideBanner', false);
269
265
  if (hideBanner || !FREE_TRIAL_HOSTS.some(host => endpointBase.includes(host))) return;
270
266
  if (typeof document === 'undefined') return;
271
267