securenow 6.1.0 → 7.0.0-anas

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/app-config.js ADDED
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Shared app-configuration resolver.
5
+ *
6
+ * Used by both the SDK (tracing.js, nextjs.js, nuxt-server-plugin.mjs) and
7
+ * the CLI to answer three questions with a single source of truth:
8
+ *
9
+ * - Which app key routes this telemetry? (resolveAppKey)
10
+ * - What service.name label to show? (resolveAppId)
11
+ * - Which OTLP collector to hit? (resolveInstance)
12
+ *
13
+ * Resolution order (first non-empty wins):
14
+ *
15
+ * 1. Explicit environment variable
16
+ * (SECURENOW_API_KEY / SECURENOW_APPID / SECURENOW_INSTANCE)
17
+ * 2. Project-local credentials (./.securenow/credentials.json)
18
+ * 3. Global credentials (~/.securenow/credentials.json)
19
+ * 4. package.json#name (for appId only)
20
+ * 5. Hard default / null
21
+ */
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+ const os = require('os');
26
+
27
+ const FREE_TRIAL_INSTANCE = 'https://freetrial.securenow.ai:4318';
28
+
29
+ function readJsonSafe(filepath) {
30
+ try {
31
+ return JSON.parse(fs.readFileSync(filepath, 'utf8'));
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ function loadLocalCredentials() {
38
+ try {
39
+ const cwd = typeof process !== 'undefined' && process.cwd ? process.cwd() : '.';
40
+ return readJsonSafe(path.join(cwd, '.securenow', 'credentials.json'));
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function loadGlobalCredentials() {
47
+ try {
48
+ return readJsonSafe(path.join(os.homedir(), '.securenow', 'credentials.json'));
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ function loadCredentials() {
55
+ return loadLocalCredentials() || loadGlobalCredentials() || null;
56
+ }
57
+
58
+ function loadPackageJsonName() {
59
+ try {
60
+ const cwd = typeof process !== 'undefined' && process.cwd ? process.cwd() : '.';
61
+ const pkg = readJsonSafe(path.join(cwd, 'package.json'));
62
+ if (pkg && typeof pkg.name === 'string' && pkg.name.trim()) {
63
+ return pkg.name.trim().replace(/^@[^/]+\//, '');
64
+ }
65
+ } catch {}
66
+ return null;
67
+ }
68
+
69
+ function pick(value) {
70
+ if (value === undefined || value === null) return null;
71
+ const str = String(value).trim();
72
+ return str.length > 0 ? str : null;
73
+ }
74
+
75
+ function resolveAppKey() {
76
+ const fromEnv =
77
+ pick(process.env.SECURENOW_API_KEY) ||
78
+ pick(process.env.securenow);
79
+ if (fromEnv) return fromEnv;
80
+
81
+ const creds = loadCredentials();
82
+ if (creds && creds.app && pick(creds.app.key)) return pick(creds.app.key);
83
+ return null;
84
+ }
85
+
86
+ function resolveAppId() {
87
+ const fromEnv =
88
+ pick(process.env.OTEL_SERVICE_NAME) ||
89
+ pick(process.env.SECURENOW_APPID);
90
+ if (fromEnv) return fromEnv;
91
+
92
+ const creds = loadCredentials();
93
+ if (creds && creds.app && pick(creds.app.id)) return pick(creds.app.id);
94
+
95
+ return loadPackageJsonName();
96
+ }
97
+
98
+ function resolveInstance() {
99
+ const fromEnv =
100
+ pick(process.env.SECURENOW_INSTANCE) ||
101
+ pick(process.env.securenow_instance) ||
102
+ pick(process.env.OTEL_EXPORTER_OTLP_ENDPOINT);
103
+ if (fromEnv) return fromEnv.replace(/\/$/, '');
104
+
105
+ const creds = loadCredentials();
106
+ if (creds && creds.app && pick(creds.app.instance)) {
107
+ return pick(creds.app.instance).replace(/\/$/, '');
108
+ }
109
+ return FREE_TRIAL_INSTANCE;
110
+ }
111
+
112
+ function resolveAll() {
113
+ return {
114
+ appKey: resolveAppKey(),
115
+ appId: resolveAppId(),
116
+ instance: resolveInstance(),
117
+ };
118
+ }
119
+
120
+ module.exports = {
121
+ FREE_TRIAL_INSTANCE,
122
+ resolveAppKey,
123
+ resolveAppId,
124
+ resolveInstance,
125
+ resolveAll,
126
+ loadCredentials,
127
+ loadLocalCredentials,
128
+ loadGlobalCredentials,
129
+ loadPackageJsonName,
130
+ };
package/cli/apps.js CHANGED
@@ -295,7 +295,30 @@ async function setDefault(args) {
295
295
  process.exit(1);
296
296
  }
297
297
  config.setConfigValue('defaultApp', key);
298
- ui.success(`Default application set to ${ui.c.bold(key)}`);
298
+
299
+ // Mirror into credentials.json so the SDK picks this app up with zero env vars.
300
+ try {
301
+ requireAuth();
302
+ const appData = await api.get(`/applications/${key}`);
303
+ const app = appData.application || appData;
304
+ let inst = null;
305
+ if (app.instanceId) {
306
+ try {
307
+ const instData = await api.get(`/instances/${app.instanceId}`);
308
+ inst = instData.instance || null;
309
+ } catch {}
310
+ }
311
+ config.setApp({
312
+ key: app.key,
313
+ id: app.name || app.key,
314
+ instance: instanceUrl(inst),
315
+ });
316
+ ui.success(`Default application set to ${ui.c.bold(app.name || key)}`);
317
+ ui.info(`SDK will use ${ui.c.dim(instanceUrl(inst))} for telemetry`);
318
+ } catch {
319
+ // If we can't reach the API (offline / invalid key), still set the CLI default.
320
+ ui.success(`Default application set to ${ui.c.bold(key)}`);
321
+ }
299
322
  }
300
323
 
301
324
  function extractRootDomains(hosts) {
package/cli/auth.js CHANGED
@@ -44,6 +44,7 @@ async function loginWithBrowser() {
44
44
 
45
45
  return new Promise((resolve, reject) => {
46
46
  let pendingToken = null;
47
+ let pendingApp = null;
47
48
 
48
49
  const server = http.createServer((req, res) => {
49
50
  const url = new URL(req.url, `http://127.0.0.1`);
@@ -52,6 +53,9 @@ async function loginWithBrowser() {
52
53
  const token = url.searchParams.get('token');
53
54
  const error = url.searchParams.get('error');
54
55
  const returnedState = url.searchParams.get('state');
56
+ const appKey = url.searchParams.get('app_key');
57
+ const appId = url.searchParams.get('app_id');
58
+ const appInstance = url.searchParams.get('app_instance');
55
59
 
56
60
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
57
61
 
@@ -71,6 +75,13 @@ async function loginWithBrowser() {
71
75
 
72
76
  if (token) {
73
77
  pendingToken = token;
78
+ if (appKey || appId || appInstance) {
79
+ pendingApp = {
80
+ key: appKey || null,
81
+ id: appId || null,
82
+ instance: appInstance || null,
83
+ };
84
+ }
74
85
  const payload = decodeJwtPayload(token);
75
86
  const email = payload?.email || 'unknown account';
76
87
  const safeEmail = email.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -127,9 +138,11 @@ async function loginWithBrowser() {
127
138
  res.writeHead(200, { 'Content-Type': 'application/json' });
128
139
  res.end('{"ok":true}');
129
140
  const token = pendingToken;
141
+ const app = pendingApp;
130
142
  pendingToken = null;
143
+ pendingApp = null;
131
144
  server.close();
132
- resolve(token);
145
+ resolve({ token, app });
133
146
  return;
134
147
  }
135
148
 
@@ -183,7 +196,8 @@ async function loginWithToken(token) {
183
196
  }
184
197
 
185
198
  async function login(args, flags) {
186
- const local = !!flags.local;
199
+ // Default is project-local. Pass --global to save to ~/.securenow/ instead.
200
+ const local = flags.global ? false : true;
187
201
 
188
202
  if (flags.token) {
189
203
  const token = flags.token;
@@ -196,7 +210,7 @@ async function login(args, flags) {
196
210
  if (local) config.ensureLocalGitignore();
197
211
  console.log('');
198
212
  ui.success(`Logged in as ${ui.c.bold(email)}`);
199
- if (local) ui.info('Credentials saved to project .securenow/ (local)');
213
+ ui.info(local ? 'Credentials saved to project .securenow/ (local)' : 'Credentials saved to ~/.securenow/ (global)');
200
214
  if (exp) {
201
215
  const days = Math.ceil((exp - Date.now()) / (1000 * 60 * 60 * 24));
202
216
  ui.info(`Session expires in ${days} days`);
@@ -205,16 +219,19 @@ async function login(args, flags) {
205
219
  }
206
220
 
207
221
  try {
208
- const token = await loginWithBrowser();
222
+ const { token, app } = await loginWithBrowser();
209
223
  const payload = decodeJwtPayload(token);
210
224
  const email = payload?.email || 'unknown';
211
225
  const exp = payload?.exp ? payload.exp * 1000 : null;
212
226
 
213
- config.setAuth(token, email, exp, { local });
227
+ config.setAuth(token, email, exp, { local, app });
214
228
  if (local) config.ensureLocalGitignore();
215
229
  console.log('');
216
230
  ui.success(`Logged in as ${ui.c.bold(email)}`);
217
- if (local) ui.info('Credentials saved to project .securenow/ (local)');
231
+ ui.info(local ? 'Credentials saved to project .securenow/ (local)' : 'Credentials saved to ~/.securenow/ (global)');
232
+ if (app && app.id) {
233
+ ui.info(`Linked to app ${ui.c.bold(app.id)}${app.key ? ` (${ui.c.dim(app.key)})` : ''}`);
234
+ }
218
235
  if (exp) {
219
236
  const days = Math.ceil((exp - Date.now()) / (1000 * 60 * 60 * 24));
220
237
  ui.info(`Session expires in ${days} days`);
@@ -235,7 +252,8 @@ async function login(args, flags) {
235
252
  }
236
253
 
237
254
  async function logout(args, flags) {
238
- const local = flags ? flags.local : undefined;
255
+ // Default: clear project-local. --global clears ~/.securenow/.
256
+ const local = !(flags && flags.global);
239
257
  const creds = config.loadCredentials();
240
258
  config.clearCredentials({ local });
241
259
  if (creds.email) {
@@ -243,7 +261,7 @@ async function logout(args, flags) {
243
261
  } else {
244
262
  ui.success('Logged out');
245
263
  }
246
- if (local) ui.info('Cleared project-local credentials');
264
+ ui.info(local ? 'Cleared project-local credentials (.securenow/)' : 'Cleared global credentials (~/.securenow/)');
247
265
  }
248
266
 
249
267
  async function whoami() {
package/cli/config.js CHANGED
@@ -118,8 +118,35 @@ function getToken() {
118
118
  return creds.token;
119
119
  }
120
120
 
121
- function setAuth(token, email, expiresAt, { local = false } = {}) {
122
- saveCredentials({ token, email, expiresAt }, { local });
121
+ function setAuth(token, email, expiresAt, { local = false, app = null } = {}) {
122
+ const payload = { token, email, expiresAt };
123
+ if (app && (app.key || app.id || app.instance)) {
124
+ payload.app = {
125
+ key: app.key || null,
126
+ id: app.id || null,
127
+ instance: app.instance || null,
128
+ };
129
+ }
130
+ saveCredentials(payload, { local });
131
+ }
132
+
133
+ function getApp() {
134
+ const creds = loadCredentials();
135
+ return creds && creds.app ? creds.app : null;
136
+ }
137
+
138
+ function setApp(app, { local } = {}) {
139
+ const useLocal = local === true || (local == null && hasLocalCredentials());
140
+ const targetFile = useLocal ? LOCAL_CREDENTIALS_FILE : CREDENTIALS_FILE;
141
+ const existing = loadJSON(targetFile);
142
+ saveJSON(targetFile, {
143
+ ...existing,
144
+ app: {
145
+ key: app.key || null,
146
+ id: app.id || null,
147
+ instance: app.instance || null,
148
+ },
149
+ });
123
150
  }
124
151
 
125
152
  function ensureLocalGitignore() {
@@ -164,6 +191,8 @@ module.exports = {
164
191
  clearCredentials,
165
192
  getToken,
166
193
  setAuth,
194
+ getApp,
195
+ setApp,
167
196
  getAuthSource,
168
197
  hasLocalCredentials,
169
198
  ensureLocalGitignore,
@@ -27,8 +27,8 @@ function resolvedConfig() {
27
27
  const headers = process.env.OTEL_EXPORTER_OTLP_HEADERS || '';
28
28
  const apiKey = process.env.SECURENOW_API_KEY || '';
29
29
  const apiUrl = config.getApiUrl();
30
- const loggingEnabled = process.env.SECURENOW_LOGGING_ENABLED !== '0';
31
- const captureBody = process.env.SECURENOW_CAPTURE_BODY === '1';
30
+ const loggingEnabled = !/^(0|false)$/i.test(String(process.env.SECURENOW_LOGGING_ENABLED ?? ''));
31
+ const captureBody = !/^(0|false)$/i.test(String(process.env.SECURENOW_CAPTURE_BODY ?? ''));
32
32
  const firewallEnabled =
33
33
  !!apiKey && process.env.SECURENOW_FIREWALL_ENABLED !== '0';
34
34
 
package/cli.js CHANGED
@@ -54,15 +54,18 @@ const COMMANDS = {
54
54
  run: (a, f) => require('./cli/init').init(a, f),
55
55
  },
56
56
  login: {
57
- desc: 'Authenticate with SecureNow',
58
- usage: 'securenow login [--token <TOKEN>] [--local]',
59
- flags: { token: 'Authenticate with a token directly', local: 'Save credentials to this project only (.securenow/)' },
57
+ desc: 'Authenticate with SecureNow (saves to project .securenow/ by default)',
58
+ usage: 'securenow login [--token <TOKEN>] [--global]',
59
+ flags: {
60
+ token: 'Authenticate with a token directly',
61
+ global: 'Save credentials to ~/.securenow/ (shared across all projects)',
62
+ },
60
63
  run: (a, f) => require('./cli/auth').login(a, f),
61
64
  },
62
65
  logout: {
63
66
  desc: 'Clear stored credentials',
64
- usage: 'securenow logout [--local]',
65
- flags: { local: 'Clear project-local credentials only' },
67
+ usage: 'securenow logout [--global]',
68
+ flags: { global: 'Clear global credentials (~/.securenow/) instead of project-local' },
66
69
  run: (a, f) => require('./cli/auth').logout(a, f),
67
70
  },
68
71
  whoami: {
@@ -24,7 +24,7 @@
24
24
  const tracing = require('./tracing');
25
25
 
26
26
  if (!tracing.isLoggingEnabled()) {
27
- console.warn('[securenow] Console instrumentation loaded but logging is not enabled. Set SECURENOW_LOGGING_ENABLED=1 to enable.');
27
+ console.warn('[securenow] Console instrumentation loaded but logging is disabled (SECURENOW_LOGGING_ENABLED=0).');
28
28
  }
29
29
 
30
30
  // Get a logger instance
@@ -125,9 +125,8 @@ async function safeBodyCapture(request, span) {
125
125
  * Check if body capture is enabled
126
126
  */
127
127
  function isBodyCaptureEnabled() {
128
- const enabled = String(process.env.SECURENOW_CAPTURE_BODY) === '1' ||
129
- String(process.env.SECURENOW_CAPTURE_BODY).toLowerCase() === 'true';
130
- return enabled;
128
+ // Opt-out default: set SECURENOW_CAPTURE_BODY=0 to disable.
129
+ return !/^(0|false)$/i.test(String(process.env.SECURENOW_CAPTURE_BODY ?? ''));
131
130
  }
132
131
 
133
132
  /**
@@ -184,7 +183,7 @@ if (isBodyCaptureEnabled()) {
184
183
  console.warn('[securenow] 💡 Body capture disabled. Use manual approach if needed.');
185
184
  }
186
185
  } else {
187
- console.log('[securenow] 📝 Automatic body capture: DISABLED (set SECURENOW_CAPTURE_BODY=1 to enable)');
186
+ console.log('[securenow] 📝 Automatic body capture: DISABLED (SECURENOW_CAPTURE_BODY=0)');
188
187
  }
189
188
 
190
189
  module.exports = {
package/nextjs-wrapper.js CHANGED
@@ -49,8 +49,8 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
49
49
  * Capture body from Request object (clone to avoid consuming)
50
50
  */
51
51
  async function captureRequestBody(request) {
52
- const captureBody = String(process.env.SECURENOW_CAPTURE_BODY) === '1' ||
53
- String(process.env.SECURENOW_CAPTURE_BODY).toLowerCase() === 'true';
52
+ // Opt-out default: set SECURENOW_CAPTURE_BODY=0 to disable.
53
+ const captureBody = !/^(0|false)$/i.test(String(process.env.SECURENOW_CAPTURE_BODY ?? ''));
54
54
 
55
55
  if (!captureBody) return;
56
56
  if (!['POST', 'PUT', 'PATCH'].includes(request.method)) return;
package/nextjs.js CHANGED
@@ -144,13 +144,11 @@ function registerSecureNow(options = {}) {
144
144
  console.log('[securenow] Next.js integration loading (pid=%d)', process.pid);
145
145
 
146
146
  // -------- Configuration --------
147
- const rawBase = (
148
- options.serviceName ||
149
- env('OTEL_SERVICE_NAME') ||
150
- env('SECURENOW_APPID') ||
151
- ''
152
- ).trim().replace(/^['"]|['"]$/g, '');
153
-
147
+ // Resolution order: explicit options → env → .securenow/credentials.json → package.json#name
148
+ const appConfig = require('./app-config');
149
+ const resolvedApp = appConfig.resolveAll();
150
+
151
+ const rawBase = (options.serviceName || resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
154
152
  const baseName = rawBase || null;
155
153
  const noUuid = options.noUuid ?? (String(env('SECURENOW_NO_UUID')) === '1' || String(env('SECURENOW_NO_UUID')).toLowerCase() === 'true');
156
154
 
@@ -160,17 +158,18 @@ function registerSecureNow(options = {}) {
160
158
  serviceName = noUuid ? baseName : `${baseName}-${randomUUID()}`;
161
159
  } else {
162
160
  serviceName = `nextjs-app-${randomUUID()}`;
163
- console.warn('[securenow] ⚠️ No SECURENOW_APPID or OTEL_SERVICE_NAME provided. Using fallback: %s', serviceName);
164
- console.warn('[securenow] 💡 Set SECURENOW_APPID=your-app-name in .env.local for better tracking');
161
+ console.warn('[securenow] ⚠️ No app identity resolved. Using fallback: %s', serviceName);
162
+ console.warn('[securenow] 💡 Run `npx securenow login` or set SECURENOW_APPID in .env.local');
165
163
  }
166
164
 
167
165
  // -------- Endpoint Configuration --------
168
- const endpointBase = (
169
- options.endpoint ||
170
- env('SECURENOW_INSTANCE') ||
171
- env('OTEL_EXPORTER_OTLP_ENDPOINT') ||
172
- 'https://freetrial.securenow.ai:4318'
173
- ).replace(/\/$/, '');
166
+ const endpointBase = (options.endpoint || resolvedApp.instance).replace(/\/$/, '');
167
+
168
+ // If credentials file provided an app key, surface it via x-api-key for collector routing.
169
+ if (resolvedApp.appKey && !env('OTEL_EXPORTER_OTLP_HEADERS') && !env('SECURENOW_API_KEY')) {
170
+ process.env.SECURENOW_API_KEY = resolvedApp.appKey;
171
+ process.env.OTEL_EXPORTER_OTLP_HEADERS = `x-api-key=${resolvedApp.appKey}`;
172
+ }
174
173
 
175
174
  const tracesUrl = env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
176
175
  const logsUrl = env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') || `${endpointBase}/v1/logs`;
@@ -182,9 +181,8 @@ function registerSecureNow(options = {}) {
182
181
  console.log('[securenow] 🚀 Next.js App → service.name=%s', serviceName);
183
182
 
184
183
  // -------- Body Capture Configuration --------
185
- const captureBody = String(env('SECURENOW_CAPTURE_BODY')) === '1' ||
186
- String(env('SECURENOW_CAPTURE_BODY')).toLowerCase() === 'true' ||
187
- options.captureBody === true;
184
+ // Opt-out default: set SECURENOW_CAPTURE_BODY=0 (or options.captureBody=false) to disable.
185
+ const captureBody = options.captureBody ?? !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
188
186
  const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
189
187
  const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
190
188
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
@@ -461,7 +459,8 @@ function registerSecureNow(options = {}) {
461
459
  console.log('[securenow] 🎯 Vanilla SDK initialized for self-hosted environment');
462
460
 
463
461
  // -------- Logging (self-hosted only) --------
464
- const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) === '1' || String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() === 'true';
462
+ // Opt-out default: set SECURENOW_LOGGING_ENABLED=0 to disable.
463
+ const loggingEnabled = !/^(0|false)$/i.test(String(env('SECURENOW_LOGGING_ENABLED') ?? ''));
465
464
  if (loggingEnabled) {
466
465
  try {
467
466
  const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
@@ -565,7 +564,7 @@ function registerSecureNow(options = {}) {
565
564
  console.warn('[securenow] ⚠️ Logging setup failed (missing @opentelemetry/exporter-logs-otlp-http or @opentelemetry/sdk-logs):', e.message);
566
565
  }
567
566
  } else {
568
- console.log('[securenow] 📋 Logging: DISABLED (set SECURENOW_LOGGING_ENABLED=1 to enable)');
567
+ console.log('[securenow] 📋 Logging: DISABLED (SECURENOW_LOGGING_ENABLED=0)');
569
568
  }
570
569
  }
571
570
 
@@ -18,6 +18,10 @@ import {
18
18
  SpanStatusCode,
19
19
  } from '@opentelemetry/api';
20
20
  import { v4 as uuidv4 } from 'uuid';
21
+ import { createRequire } from 'node:module';
22
+
23
+ const nodeRequire = createRequire(import.meta.url);
24
+ const appConfig = nodeRequire('./app-config');
21
25
 
22
26
  // ── Helpers ──
23
27
 
@@ -73,14 +77,11 @@ function getRuntimeOptions() {
73
77
  export default defineNitroPlugin((nitroApp) => {
74
78
  const opts = getRuntimeOptions();
75
79
 
76
- // ── Naming ──
77
- const rawBase = (
78
- opts.serviceName ||
79
- env('OTEL_SERVICE_NAME') ||
80
- env('SECURENOW_APPID') ||
81
- ''
82
- ).trim().replace(/^['"]|['"]$/g, '');
80
+ // Resolution order: opts → env → .securenow/credentials.json → package.json#name
81
+ const resolvedApp = appConfig.resolveAll();
83
82
 
83
+ // ── Naming ──
84
+ const rawBase = (opts.serviceName || resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
84
85
  const baseName = rawBase || null;
85
86
  const noUuid =
86
87
  opts.noUuid ??
@@ -93,23 +94,24 @@ export default defineNitroPlugin((nitroApp) => {
93
94
  } else {
94
95
  serviceName = `nuxt-app-${uuidv4()}`;
95
96
  console.warn(
96
- '[securenow] ⚠️ No SECURENOW_APPID set. Using fallback: %s',
97
+ '[securenow] ⚠️ No app identity resolved. Using fallback: %s',
97
98
  serviceName,
98
99
  );
99
100
  console.warn(
100
- '[securenow] 💡 Set SECURENOW_APPID=your-app-name in .env for better tracking',
101
+ '[securenow] 💡 Run `npx securenow login` or set SECURENOW_APPID in .env',
101
102
  );
102
103
  }
103
104
 
104
105
  const serviceInstanceId = `${baseName || 'securenow'}-${uuidv4()}`;
105
106
 
106
107
  // ── Endpoints ──
107
- const endpointBase = (
108
- opts.endpoint ||
109
- env('SECURENOW_INSTANCE') ||
110
- env('OTEL_EXPORTER_OTLP_ENDPOINT') ||
111
- 'https://freetrial.securenow.ai:4318'
112
- ).replace(/\/$/, '');
108
+ const endpointBase = (opts.endpoint || resolvedApp.instance).replace(/\/$/, '');
109
+
110
+ // Surface credentials-file app key as x-api-key for collector routing.
111
+ if (resolvedApp.appKey && !env('OTEL_EXPORTER_OTLP_HEADERS') && !env('SECURENOW_API_KEY')) {
112
+ process.env.SECURENOW_API_KEY = resolvedApp.appKey;
113
+ process.env.OTEL_EXPORTER_OTLP_HEADERS = `x-api-key=${resolvedApp.appKey}`;
114
+ }
113
115
 
114
116
  const tracesUrl =
115
117
  env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
@@ -129,10 +131,10 @@ export default defineNitroPlugin((nitroApp) => {
129
131
  });
130
132
 
131
133
  // ── Body capture config ──
134
+ // Opt-out default: set SECURENOW_CAPTURE_BODY=0 (or opts.captureBody=false) to disable.
132
135
  const captureBody =
133
136
  opts.captureBody ??
134
- (String(env('SECURENOW_CAPTURE_BODY')) === '1' ||
135
- String(env('SECURENOW_CAPTURE_BODY')).toLowerCase() === 'true');
137
+ !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
136
138
  const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
137
139
  const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '')
138
140
  .split(',')
@@ -238,10 +240,10 @@ export default defineNitroPlugin((nitroApp) => {
238
240
  );
239
241
 
240
242
  // ── Logging ──
243
+ // Opt-out default: set SECURENOW_LOGGING_ENABLED=0 (or opts.logging=false) to disable.
241
244
  const loggingEnabled =
242
245
  opts.logging ??
243
- (String(env('SECURENOW_LOGGING_ENABLED')) === '1' ||
244
- String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() === 'true');
246
+ !/^(0|false)$/i.test(String(env('SECURENOW_LOGGING_ENABLED') ?? ''));
245
247
 
246
248
  let loggerProvider = null;
247
249
 
@@ -302,7 +304,7 @@ export default defineNitroPlugin((nitroApp) => {
302
304
  }
303
305
  } else {
304
306
  console.log(
305
- '[securenow] 📋 Logging: DISABLED (set SECURENOW_LOGGING_ENABLED=1 to enable)',
307
+ '[securenow] 📋 Logging: DISABLED (SECURENOW_LOGGING_ENABLED=0)',
306
308
  );
307
309
  }
308
310
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "6.1.0",
3
+ "version": "7.0.0-anas",
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",
@@ -94,6 +94,9 @@
94
94
  "./web-vite": {
95
95
  "import": "./web-vite.mjs",
96
96
  "default": "./web-vite.mjs"
97
+ },
98
+ "./app-config": {
99
+ "default": "./app-config.js"
97
100
  }
98
101
  },
99
102
  "files": [
@@ -127,6 +130,7 @@
127
130
  "postinstall.js",
128
131
  "register-vite.js",
129
132
  "web-vite.mjs",
133
+ "app-config.js",
130
134
  "examples/",
131
135
  "docs/",
132
136
  "README.md",
@@ -153,29 +157,11 @@
153
157
  "@opentelemetry/sdk-node": "0.47.0",
154
158
  "@opentelemetry/sdk-trace-web": "1.20.0",
155
159
  "@opentelemetry/semantic-conventions": "1.20.0",
156
- "@vercel/otel": "^1.14.0",
157
160
  "dotenv": "^17.2.1",
158
161
  "uuid": "^9.0.0"
159
162
  },
160
- "peerDependencies": {
161
- "next": ">=13.0.0",
162
- "nuxt": ">=3.0.0",
163
- "@aws-sdk/client-wafv2": ">=3.0.0",
164
- "@google-cloud/compute": ">=4.0.0"
165
- },
166
- "peerDependenciesMeta": {
167
- "next": {
168
- "optional": true
169
- },
170
- "nuxt": {
171
- "optional": true
172
- },
173
- "@aws-sdk/client-wafv2": {
174
- "optional": true
175
- },
176
- "@google-cloud/compute": {
177
- "optional": true
178
- }
163
+ "optionalDependencies": {
164
+ "@vercel/otel": "^1.14.0"
179
165
  },
180
166
  "overrides": {
181
167
  "@opentelemetry/api": "1.7.0",
package/postinstall.js CHANGED
@@ -11,6 +11,35 @@ const fs = require('fs');
11
11
  const path = require('path');
12
12
  const readline = require('readline');
13
13
 
14
+ // Make sure `.securenow/` is in the project's .gitignore so credentials never get committed.
15
+ function ensureGitignore() {
16
+ try {
17
+ // Skip if we're not in an npm install of a user project
18
+ // (e.g., securenow's own CI, or nested install under another node_modules).
19
+ const cwd = process.cwd();
20
+ if (!fs.existsSync(path.join(cwd, 'package.json'))) return;
21
+ if (cwd.includes(`${path.sep}node_modules${path.sep}`)) return;
22
+
23
+ const gitignorePath = path.join(cwd, '.gitignore');
24
+ const entry = '.securenow/';
25
+ const header = '# SecureNow local credentials';
26
+
27
+ if (fs.existsSync(gitignorePath)) {
28
+ const content = fs.readFileSync(gitignorePath, 'utf8');
29
+ const alreadyListed = content.split('\n').some((line) => line.trim() === entry);
30
+ if (!alreadyListed) {
31
+ const prefix = content.endsWith('\n') ? '' : '\n';
32
+ fs.appendFileSync(gitignorePath, `${prefix}\n${header}\n${entry}\n`);
33
+ }
34
+ } else if (fs.existsSync(path.join(cwd, '.git'))) {
35
+ // Only create a new .gitignore if this is actually a git repo.
36
+ fs.writeFileSync(gitignorePath, `${header}\n${entry}\n`);
37
+ }
38
+ } catch {
39
+ // Non-fatal
40
+ }
41
+ }
42
+
14
43
  // Check if we're in a Next.js project
15
44
  function isNextJsProject() {
16
45
  try {
@@ -81,9 +110,8 @@ export function register() {
81
110
  * OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
82
111
  * OTEL_LOG_LEVEL=info
83
112
  *
84
- * Optional: Enable request body capture
85
- * SECURENOW_CAPTURE_BODY=1
86
- * (Also create middleware.ts to activate - run: npx securenow init)
113
+ * Optional: Disable request body capture (enabled by default)
114
+ * SECURENOW_CAPTURE_BODY=0
87
115
  */
88
116
  `;
89
117
 
@@ -160,8 +188,8 @@ SECURENOW_INSTANCE=http://your-otlp-backend:4318
160
188
  # Optional: Log level (debug|info|warn|error)
161
189
  # OTEL_LOG_LEVEL=info
162
190
 
163
- # Optional: Enable request body capture (requires middleware.ts)
164
- # SECURENOW_CAPTURE_BODY=1
191
+ # Optional: Disable request body capture (enabled by default)
192
+ # SECURENOW_CAPTURE_BODY=0
165
193
  # SECURENOW_MAX_BODY_SIZE=10240
166
194
  # SECURENOW_SENSITIVE_FIELDS=email,phone
167
195
  `;
@@ -176,6 +204,9 @@ function isTypeScriptProject() {
176
204
 
177
205
  // Main setup function
178
206
  async function setup() {
207
+ // Always make sure .securenow/ is gitignored (cheap, non-destructive).
208
+ ensureGitignore();
209
+
179
210
  // Skip if not in Next.js project
180
211
  if (!isNextJsProject()) {
181
212
  console.log('[securenow] Not a Next.js project, skipping auto-setup');
@@ -271,9 +302,6 @@ async function setup() {
271
302
  console.log('│ 1. Edit .env.local and set: │');
272
303
  console.log('│ SECURENOW_APPID=your-app-name │');
273
304
  console.log('│ SECURENOW_INSTANCE=http://your-otlp-backend:4318 │');
274
- if (shouldCreateMiddleware) {
275
- console.log('│ SECURENOW_CAPTURE_BODY=1 │');
276
- }
277
305
  console.log('│ │');
278
306
  console.log('│ 2. Run your app: npm run dev │');
279
307
  console.log('│ │');
package/tracing.js CHANGED
@@ -268,14 +268,25 @@ const diagLevel = (env('OTEL_LOG_LEVEL') || '').toLowerCase();
268
268
  console.log('[securenow] preload loaded pid=%d', process.pid);
269
269
  })();
270
270
 
271
- // -------- endpoints --------
272
- const endpointBase = (env('SECURENOW_INSTANCE') || env('OTEL_EXPORTER_OTLP_ENDPOINT') || 'https://freetrial.securenow.ai:4318').replace(/\/$/, '');
271
+ // -------- endpoints & app resolution --------
272
+ // Resolution order for endpoint/appId/apiKey: env .securenow/credentials.json → package.json#name → defaults.
273
+ const appConfig = require('./app-config');
274
+ const resolvedApp = appConfig.resolveAll();
275
+
276
+ const endpointBase = resolvedApp.instance.replace(/\/$/, '');
273
277
  const tracesUrl = env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
274
278
  const logsUrl = env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') || `${endpointBase}/v1/logs`;
279
+
280
+ // If the credentials file provided an app key and no OTLP headers are set,
281
+ // surface it as x-api-key so the collector can route telemetry to the right app bucket.
282
+ if (resolvedApp.appKey && !env('OTEL_EXPORTER_OTLP_HEADERS') && !env('SECURENOW_API_KEY')) {
283
+ process.env.SECURENOW_API_KEY = resolvedApp.appKey;
284
+ process.env.OTEL_EXPORTER_OTLP_HEADERS = `x-api-key=${resolvedApp.appKey}`;
285
+ }
275
286
  const headers = parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS'));
276
287
 
277
288
  // -------- naming rules --------
278
- const rawBase = (env('OTEL_SERVICE_NAME') || env('SECURENOW_APPID') || '').trim().replace(/^['"]|['"]$/g, '');
289
+ const rawBase = (resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
279
290
  const baseName = rawBase || null;
280
291
  const noUuid = String(env('SECURENOW_NO_UUID')) === '1' || String(env('SECURENOW_NO_UUID')).toLowerCase() === 'true';
281
292
  const strict = String(env('SECURENOW_STRICT')) === '1' || String(env('SECURENOW_STRICT')).toLowerCase() === 'true';
@@ -302,12 +313,11 @@ const instancePrefix = baseName || 'securenow';
302
313
  const serviceInstanceId = `${instancePrefix}-${uuidv4()}`;
303
314
 
304
315
  // Loud line per worker to prove what was used
305
- console.log('[securenow] pid=%d SECURENOW_APPID=%s OTEL_SERVICE_NAME=%s SECURENOW_NO_UUID=%s SECURENOW_STRICT=%s → service.name=%s instance.id=%s',
316
+ console.log('[securenow] pid=%d appId=%s instance=%s apiKey=%s → service.name=%s instance.id=%s',
306
317
  process.pid,
307
- JSON.stringify(env('SECURENOW_APPID')),
308
- JSON.stringify(env('OTEL_SERVICE_NAME')),
309
- JSON.stringify(env('SECURENOW_NO_UUID')),
310
- JSON.stringify(env('SECURENOW_STRICT')),
318
+ JSON.stringify(baseName),
319
+ JSON.stringify(endpointBase),
320
+ resolvedApp.appKey ? 'set' : 'none',
311
321
  serviceName,
312
322
  serviceInstanceId
313
323
  );
@@ -319,12 +329,13 @@ for (const n of (env('SECURENOW_DISABLE_INSTRUMENTATIONS') || '').split(',').map
319
329
  }
320
330
 
321
331
  // -------- Body Capture Configuration --------
322
- const captureBody = String(env('SECURENOW_CAPTURE_BODY')) === '1' || String(env('SECURENOW_CAPTURE_BODY')).toLowerCase() === 'true';
332
+ // Opt-out defaults: set =0 or =false to disable.
333
+ const captureBody = !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
323
334
  const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
324
335
  const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
325
336
  const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
326
337
 
327
- const captureMultipart = String(env('SECURENOW_CAPTURE_MULTIPART')) === '1' || String(env('SECURENOW_CAPTURE_MULTIPART')).toLowerCase() === 'true';
338
+ const captureMultipart = !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_MULTIPART') ?? ''));
328
339
 
329
340
  // -------- Trusted proxy IP resolution --------
330
341
  const { resolveClientIp, isFromTrustedProxy, LOOPBACK_RE } = require('./resolve-ip');
@@ -445,7 +456,7 @@ const httpInstrumentation = new HttpInstrumentation({
445
456
  } else {
446
457
  span.setAttribute('http.request.body', '[MULTIPART - NOT CAPTURED]');
447
458
  span.setAttribute('http.request.body.type', 'multipart');
448
- span.setAttribute('http.request.body.note', 'Set SECURENOW_CAPTURE_MULTIPART=1 to enable');
459
+ span.setAttribute('http.request.body.note', 'Multipart capture disabled (SECURENOW_CAPTURE_MULTIPART=0)');
449
460
  }
450
461
  }
451
462
  }
@@ -456,7 +467,8 @@ const httpInstrumentation = new HttpInstrumentation({
456
467
  });
457
468
 
458
469
  // -------- Logging Configuration --------
459
- const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) === '1' || String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() === 'true';
470
+ // Opt-out default: set =0 or =false to disable.
471
+ const loggingEnabled = !/^(0|false)$/i.test(String(env('SECURENOW_LOGGING_ENABLED') ?? ''));
460
472
 
461
473
  // Create shared resource for both traces and logs
462
474
  const sharedResource = new Resource({
@@ -572,7 +584,7 @@ const sdk = new NodeSDK({
572
584
  if (loggingEnabled) {
573
585
  console.log('[securenow] 📋 Logging: ENABLED → %s', logsUrl);
574
586
  } else {
575
- console.log('[securenow] 📋 Logging: DISABLED (set SECURENOW_LOGGING_ENABLED=1 to enable)');
587
+ console.log('[securenow] 📋 Logging: DISABLED (SECURENOW_LOGGING_ENABLED=0)');
576
588
  }
577
589
  if (captureBody) {
578
590
  console.log('[securenow] 📝 Request body capture: ENABLED (max: %d bytes, redacting %d sensitive fields)', maxBodySize, allSensitiveFields.length);
@@ -636,7 +648,7 @@ module.exports = {
636
648
  loggerProvider,
637
649
  getLogger: (name = 'default', version = '1.0.0') => {
638
650
  if (!loggerProvider) {
639
- console.warn('[securenow] Logging is not enabled. Set SECURENOW_LOGGING_ENABLED=1 to enable logging.');
651
+ console.warn('[securenow] Logging is disabled (SECURENOW_LOGGING_ENABLED=0). Remove the override to enable.');
640
652
  return null;
641
653
  }
642
654
  return loggerProvider.getLogger(name, version);