securenow 6.0.2 → 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.
Files changed (88) hide show
  1. package/CONSUMING-APPS-GUIDE.md +455 -0
  2. package/NPM_README.md +2029 -0
  3. package/README.md +297 -40
  4. package/SKILL-API.md +634 -0
  5. package/SKILL-CLI.md +454 -0
  6. package/app-config.js +130 -0
  7. package/cidr.js +83 -0
  8. package/cli/apps.js +608 -0
  9. package/cli/auth.js +298 -0
  10. package/cli/client.js +115 -0
  11. package/cli/config.js +202 -0
  12. package/cli/diagnostics.js +387 -0
  13. package/cli/firewall.js +100 -0
  14. package/cli/fp.js +638 -0
  15. package/cli/init.js +201 -0
  16. package/cli/monitor.js +440 -0
  17. package/cli/run.js +148 -0
  18. package/cli/security.js +980 -0
  19. package/cli/ui.js +386 -0
  20. package/cli/utils.js +127 -0
  21. package/cli.js +469 -455
  22. package/console-instrumentation.js +147 -136
  23. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +1377 -455
  24. package/docs/API-KEYS-GUIDE.md +233 -0
  25. package/docs/ARCHITECTURE.md +3 -3
  26. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  27. package/docs/AUTO-SETUP-SUMMARY.md +331 -0
  28. package/docs/AUTO-SETUP.md +4 -4
  29. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  30. package/docs/BODY-CAPTURE-FIX.md +261 -0
  31. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  32. package/docs/CHANGELOG-NEXTJS.md +1 -35
  33. package/docs/COMPLETION-REPORT.md +408 -0
  34. package/docs/CUSTOMER-GUIDE.md +16 -16
  35. package/docs/EASIEST-SETUP.md +5 -5
  36. package/docs/ENVIRONMENT-VARIABLES.md +880 -652
  37. package/docs/EXPRESS-BODY-CAPTURE.md +13 -12
  38. package/docs/EXPRESS-SETUP-GUIDE.md +719 -720
  39. package/docs/FINAL-SOLUTION.md +335 -0
  40. package/docs/FIREWALL-GUIDE.md +426 -0
  41. package/docs/IMPLEMENTATION-SUMMARY.md +410 -0
  42. package/docs/INDEX.md +22 -4
  43. package/docs/LOGGING-GUIDE.md +701 -708
  44. package/docs/LOGGING-QUICKSTART.md +234 -255
  45. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +323 -0
  46. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  47. package/docs/NEXTJS-GUIDE.md +14 -14
  48. package/docs/NEXTJS-QUICKSTART.md +1 -1
  49. package/docs/NEXTJS-SETUP-COMPLETE.md +795 -0
  50. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  51. package/docs/NUXT-GUIDE.md +166 -0
  52. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  53. package/docs/REDACTION-EXAMPLES.md +1 -1
  54. package/docs/REQUEST-BODY-CAPTURE.md +19 -10
  55. package/docs/SOLUTION-SUMMARY.md +312 -0
  56. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  57. package/examples/README.md +6 -6
  58. package/examples/instrumentation-with-auto-capture.ts +1 -1
  59. package/examples/nextjs-env-example.txt +2 -2
  60. package/examples/nextjs-instrumentation.js +1 -1
  61. package/examples/nextjs-instrumentation.ts +1 -1
  62. package/examples/nextjs-with-logging-example.md +6 -6
  63. package/examples/nextjs-with-options.ts +1 -1
  64. package/examples/test-nextjs-setup.js +1 -1
  65. package/firewall-cloud.js +212 -0
  66. package/firewall-iptables.js +139 -0
  67. package/firewall-only.js +38 -0
  68. package/firewall-tcp.js +74 -0
  69. package/firewall.js +720 -0
  70. package/free-trial-banner.js +174 -0
  71. package/nextjs-auto-capture.js +198 -207
  72. package/nextjs-middleware.js +186 -181
  73. package/nextjs-webpack-config.js +88 -53
  74. package/nextjs-wrapper.js +158 -158
  75. package/nextjs.d.ts +1 -1
  76. package/nextjs.js +638 -647
  77. package/nuxt-server-plugin.mjs +425 -0
  78. package/nuxt.d.ts +60 -0
  79. package/nuxt.mjs +75 -0
  80. package/package.json +172 -164
  81. package/postinstall.js +42 -14
  82. package/register.d.ts +1 -1
  83. package/register.js +39 -4
  84. package/resolve-ip.js +77 -0
  85. package/tracing.d.ts +2 -1
  86. package/tracing.js +318 -45
  87. package/web-vite.mjs +239 -156
  88. package/LICENSE +0 -15
@@ -0,0 +1,425 @@
1
+ /**
2
+ * SecureNow Nitro Server Plugin
3
+ *
4
+ * Initialises the OpenTelemetry SDK and hooks into Nitro's request lifecycle
5
+ * to create spans, capture metadata, and forward logs.
6
+ *
7
+ * This file is registered by the Nuxt module (nuxt.mjs) via addServerPlugin.
8
+ */
9
+
10
+ import { NodeSDK } from '@opentelemetry/sdk-node';
11
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
12
+ import { Resource } from '@opentelemetry/resources';
13
+ import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
14
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
15
+ import {
16
+ context as otelContext,
17
+ trace as otelTrace,
18
+ SpanStatusCode,
19
+ } from '@opentelemetry/api';
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');
25
+
26
+ // ── Helpers ──
27
+
28
+ const env = (k) =>
29
+ process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
30
+
31
+ const parseHeaders = (str) => {
32
+ const out = {};
33
+ if (!str) return out;
34
+ for (const raw of String(str).split(',')) {
35
+ const s = raw.trim();
36
+ if (!s) continue;
37
+ const i = s.indexOf('=');
38
+ if (i === -1) continue;
39
+ out[s.slice(0, i).trim().toLowerCase()] = s.slice(i + 1).trim();
40
+ }
41
+ return out;
42
+ };
43
+
44
+ const DEFAULT_SENSITIVE_FIELDS = [
45
+ 'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
46
+ 'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
47
+ 'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
48
+ ];
49
+
50
+ function redactSensitiveData(obj, fields = DEFAULT_SENSITIVE_FIELDS) {
51
+ if (!obj || typeof obj !== 'object') return obj;
52
+ const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
53
+ for (const key of Object.keys(redacted)) {
54
+ const lower = key.toLowerCase();
55
+ if (fields.some((f) => lower.includes(f.toLowerCase()))) {
56
+ redacted[key] = '[REDACTED]';
57
+ } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
58
+ redacted[key] = redactSensitiveData(redacted[key], fields);
59
+ }
60
+ }
61
+ return redacted;
62
+ }
63
+
64
+ // ── Runtime config helpers ──
65
+
66
+ function getRuntimeOptions() {
67
+ try {
68
+ const cfg = useRuntimeConfig();
69
+ return cfg.securenow || {};
70
+ } catch {
71
+ return {};
72
+ }
73
+ }
74
+
75
+ // ── Plugin ──
76
+
77
+ export default defineNitroPlugin((nitroApp) => {
78
+ const opts = getRuntimeOptions();
79
+
80
+ // Resolution order: opts → env → .securenow/credentials.json → package.json#name
81
+ const resolvedApp = appConfig.resolveAll();
82
+
83
+ // ── Naming ──
84
+ const rawBase = (opts.serviceName || resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
85
+ const baseName = rawBase || null;
86
+ const noUuid =
87
+ opts.noUuid ??
88
+ (String(env('SECURENOW_NO_UUID')) === '1' ||
89
+ String(env('SECURENOW_NO_UUID')).toLowerCase() === 'true');
90
+
91
+ let serviceName;
92
+ if (baseName) {
93
+ serviceName = noUuid ? baseName : `${baseName}-${uuidv4()}`;
94
+ } else {
95
+ serviceName = `nuxt-app-${uuidv4()}`;
96
+ console.warn(
97
+ '[securenow] ⚠️ No app identity resolved. Using fallback: %s',
98
+ serviceName,
99
+ );
100
+ console.warn(
101
+ '[securenow] 💡 Run `npx securenow login` or set SECURENOW_APPID in .env',
102
+ );
103
+ }
104
+
105
+ const serviceInstanceId = `${baseName || 'securenow'}-${uuidv4()}`;
106
+
107
+ // ── Endpoints ──
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
+ }
115
+
116
+ const tracesUrl =
117
+ env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
118
+ const logsUrl =
119
+ env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') || `${endpointBase}/v1/logs`;
120
+ const headers = parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS'));
121
+
122
+ // ── Resource ──
123
+ const resource = new Resource({
124
+ [SemanticResourceAttributes.SERVICE_NAME]: serviceName,
125
+ [SemanticResourceAttributes.SERVICE_INSTANCE_ID]: serviceInstanceId,
126
+ [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]:
127
+ env('NODE_ENV') || 'production',
128
+ [SemanticResourceAttributes.SERVICE_VERSION]:
129
+ process.env.npm_package_version || undefined,
130
+ 'framework': 'nuxt',
131
+ });
132
+
133
+ // ── Body capture config ──
134
+ // Opt-out default: set SECURENOW_CAPTURE_BODY=0 (or opts.captureBody=false) to disable.
135
+ const captureBody =
136
+ opts.captureBody ??
137
+ !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
138
+ const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
139
+ const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '')
140
+ .split(',')
141
+ .map((s) => s.trim())
142
+ .filter(Boolean);
143
+ const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
144
+
145
+ // ── HTTP instrumentation ──
146
+ const httpInstrumentation = new HttpInstrumentation({
147
+ requestHook: (span, request) => {
148
+ try {
149
+ const hdrs = request.headers || {};
150
+ const fwd = hdrs['x-forwarded-for'];
151
+ const clientIp =
152
+ (fwd ? String(fwd).split(',')[0].trim() : null) ||
153
+ hdrs['x-real-ip'] ||
154
+ hdrs['cf-connecting-ip'] ||
155
+ hdrs['x-client-ip'] ||
156
+ request.socket?.remoteAddress ||
157
+ 'unknown';
158
+
159
+ span.setAttributes({
160
+ 'http.client_ip': clientIp,
161
+ 'http.user_agent': hdrs['user-agent'] || '',
162
+ 'http.host': hdrs['x-forwarded-host'] || hdrs['host'] || '',
163
+ 'http.scheme':
164
+ hdrs['x-forwarded-proto'] ||
165
+ (request.socket?.encrypted ? 'https' : 'http'),
166
+ 'http.referer': hdrs['referer'] || '',
167
+ 'http.origin': hdrs['origin'] || '',
168
+ 'http.request_id':
169
+ hdrs['x-request-id'] || hdrs['x-trace-id'] || '',
170
+ });
171
+
172
+ if (hdrs['authorization']) {
173
+ span.setAttribute('http.security.auth_present', 'true');
174
+ }
175
+ if (hdrs['cookie']) {
176
+ span.setAttribute('http.security.cookies_present', 'true');
177
+ }
178
+
179
+ // Body capture via stream listener (same approach as tracing.js)
180
+ if (
181
+ captureBody &&
182
+ request.method &&
183
+ ['POST', 'PUT', 'PATCH'].includes(request.method)
184
+ ) {
185
+ const ct = hdrs['content-type'] || '';
186
+ if (
187
+ ct.includes('application/json') ||
188
+ ct.includes('application/graphql') ||
189
+ ct.includes('application/x-www-form-urlencoded')
190
+ ) {
191
+ const chunks = [];
192
+ let size = 0;
193
+ request.on('data', (chunk) => {
194
+ size += chunk.length;
195
+ if (size <= maxBodySize) chunks.push(chunk);
196
+ });
197
+ request.on('end', () => {
198
+ if (size > maxBodySize) {
199
+ span.setAttribute('http.request.body', `[TOO LARGE: ${size} bytes]`);
200
+ return;
201
+ }
202
+ if (chunks.length === 0) return;
203
+ const raw = Buffer.concat(chunks).toString('utf8');
204
+ try {
205
+ const parsed = JSON.parse(raw);
206
+ const redacted = redactSensitiveData(parsed, allSensitiveFields);
207
+ span.setAttributes({
208
+ 'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
209
+ 'http.request.body.type': ct.includes('graphql') ? 'graphql' : 'json',
210
+ 'http.request.body.size': size,
211
+ });
212
+ } catch {
213
+ span.setAttribute('http.request.body', '[UNPARSEABLE - REDACTED FOR SAFETY]');
214
+ span.setAttribute('http.request.body.parse_error', true);
215
+ }
216
+ });
217
+ }
218
+ }
219
+ } catch {
220
+ // never break the request
221
+ }
222
+ },
223
+ });
224
+
225
+ // ── Trace exporter + SDK ──
226
+ const traceExporter = new OTLPTraceExporter({ url: tracesUrl, headers });
227
+
228
+ const sdk = new NodeSDK({
229
+ traceExporter,
230
+ resource,
231
+ instrumentations: [httpInstrumentation],
232
+ });
233
+
234
+ sdk.start();
235
+ console.log('[securenow] 🚀 Nuxt OTel SDK started → %s', tracesUrl);
236
+ console.log(
237
+ '[securenow] service.name=%s instance.id=%s',
238
+ serviceName,
239
+ serviceInstanceId,
240
+ );
241
+
242
+ // ── Logging ──
243
+ // Opt-out default: set SECURENOW_LOGGING_ENABLED=0 (or opts.logging=false) to disable.
244
+ const loggingEnabled =
245
+ opts.logging ??
246
+ !/^(0|false)$/i.test(String(env('SECURENOW_LOGGING_ENABLED') ?? ''));
247
+
248
+ let loggerProvider = null;
249
+
250
+ if (loggingEnabled) {
251
+ try {
252
+ const { OTLPLogExporter } = await import(
253
+ '@opentelemetry/exporter-logs-otlp-http'
254
+ );
255
+ const { LoggerProvider, BatchLogRecordProcessor } = await import(
256
+ '@opentelemetry/sdk-logs'
257
+ );
258
+
259
+ const logExporter = new OTLPLogExporter({ url: logsUrl, headers });
260
+ loggerProvider = new LoggerProvider({ resource });
261
+ loggerProvider.addLogRecordProcessor(
262
+ new BatchLogRecordProcessor(logExporter),
263
+ );
264
+
265
+ const logger = loggerProvider.getLogger('console', '1.0.0');
266
+ const SEV = { DEBUG: 5, INFO: 9, WARN: 13, ERROR: 17 };
267
+ const orig = {
268
+ log: console.log,
269
+ info: console.info,
270
+ warn: console.warn,
271
+ error: console.error,
272
+ debug: console.debug,
273
+ };
274
+
275
+ function emitLog(sn, st, args) {
276
+ try {
277
+ const ctx = otelContext.active();
278
+ const spanCtx = otelTrace.getSpanContext(ctx);
279
+ logger.emit({
280
+ severityNumber: sn,
281
+ severityText: st,
282
+ body: args
283
+ .map((a) =>
284
+ typeof a === 'object' && a !== null ? JSON.stringify(a) : String(a),
285
+ )
286
+ .join(' '),
287
+ attributes: { 'log.source': 'console', 'log.method': st.toLowerCase() },
288
+ ...(spanCtx && { context: ctx }),
289
+ });
290
+ } catch {
291
+ // swallow
292
+ }
293
+ }
294
+
295
+ console.log = (...a) => { emitLog(SEV.INFO, 'INFO', a); orig.log.apply(console, a); };
296
+ console.info = (...a) => { emitLog(SEV.INFO, 'INFO', a); orig.info.apply(console, a); };
297
+ console.warn = (...a) => { emitLog(SEV.WARN, 'WARN', a); orig.warn.apply(console, a); };
298
+ console.error = (...a) => { emitLog(SEV.ERROR, 'ERROR', a); orig.error.apply(console, a); };
299
+ console.debug = (...a) => { emitLog(SEV.DEBUG, 'DEBUG', a); orig.debug.apply(console, a); };
300
+
301
+ console.log('[securenow] 📋 Logging: ENABLED → %s', logsUrl);
302
+ } catch (e) {
303
+ console.warn('[securenow] ⚠️ Logging setup failed:', e.message);
304
+ }
305
+ } else {
306
+ console.log(
307
+ '[securenow] 📋 Logging: DISABLED (SECURENOW_LOGGING_ENABLED=0)',
308
+ );
309
+ }
310
+
311
+ if (captureBody) {
312
+ console.log(
313
+ '[securenow] 📝 Body capture: ENABLED (max %d bytes, redacting %d fields)',
314
+ maxBodySize,
315
+ allSensitiveFields.length,
316
+ );
317
+ }
318
+
319
+ // ── Free trial banner ──
320
+ try {
321
+ const { isFreeTrial, patchHttpForBanner } = await import('./free-trial-banner.js');
322
+ if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
323
+ patchHttpForBanner();
324
+ }
325
+ } catch {
326
+ // not critical
327
+ }
328
+
329
+ // ── Firewall — runs independently from OTel ──
330
+ const firewallApiKey = env('SECURENOW_API_KEY');
331
+ if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
332
+ try {
333
+ const { init: fwInit } = await import('./firewall.js');
334
+ fwInit({
335
+ apiKey: firewallApiKey,
336
+ apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
337
+ versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
338
+ syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
339
+ failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
340
+ statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
341
+ log: env('SECURENOW_FIREWALL_LOG') !== '0',
342
+ tcp: env('SECURENOW_FIREWALL_TCP') === '1',
343
+ iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
344
+ cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
345
+ });
346
+ } catch (e) {
347
+ console.warn('[securenow] Firewall init failed:', e.message);
348
+ }
349
+ }
350
+
351
+ // ── Graceful shutdown ──
352
+ const shutdown = async (sig) => {
353
+ try {
354
+ await sdk.shutdown?.();
355
+ if (loggerProvider) await loggerProvider.shutdown?.();
356
+ try { const fw = await import('./firewall.js'); fw.shutdown?.(); } catch {}
357
+ console.log(`[securenow] Shut down on ${sig}`);
358
+ } catch {
359
+ // swallow
360
+ }
361
+ };
362
+ process.on('SIGINT', () => shutdown('SIGINT'));
363
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
364
+
365
+ // ── Nitro request hooks for span enrichment ──
366
+ const tracer = otelTrace.getTracer('securenow-nuxt', '1.0.0');
367
+ const spanMap = new WeakMap();
368
+
369
+ nitroApp.hooks.hook('request', (event) => {
370
+ try {
371
+ const req = event.node.req;
372
+ const method = event.method || req.method || 'GET';
373
+ const path = event.path || req.url || '/';
374
+
375
+ const span = tracer.startSpan(`${method} ${path}`, {
376
+ attributes: {
377
+ 'http.method': method,
378
+ 'http.target': path,
379
+ 'http.url': `${req.headers?.['x-forwarded-proto'] || 'http'}://${req.headers?.host || 'localhost'}${path}`,
380
+ 'component': 'nuxt-nitro',
381
+ },
382
+ });
383
+
384
+ spanMap.set(event, span);
385
+ } catch {
386
+ // never break the request
387
+ }
388
+ });
389
+
390
+ nitroApp.hooks.hook('afterResponse', (event) => {
391
+ try {
392
+ const span = spanMap.get(event);
393
+ if (!span) return;
394
+
395
+ const status = event.node.res.statusCode || 200;
396
+ span.setAttribute('http.status_code', status);
397
+
398
+ if (status >= 500) {
399
+ span.setStatus({ code: SpanStatusCode.ERROR, message: `HTTP ${status}` });
400
+ }
401
+
402
+ span.end();
403
+ spanMap.delete(event);
404
+ } catch {
405
+ // swallow
406
+ }
407
+ });
408
+
409
+ nitroApp.hooks.hook('error', (error, { event }) => {
410
+ try {
411
+ const span = event ? spanMap.get(event) : null;
412
+ if (span) {
413
+ span.recordException(error);
414
+ span.setStatus({
415
+ code: SpanStatusCode.ERROR,
416
+ message: error.message || 'Internal Server Error',
417
+ });
418
+ span.end();
419
+ spanMap.delete(event);
420
+ }
421
+ } catch {
422
+ // swallow
423
+ }
424
+ });
425
+ });
package/nuxt.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * SecureNow Nuxt 3 Module TypeScript Declarations
3
+ */
4
+
5
+ export interface SecureNowNuxtOptions {
6
+ /**
7
+ * Service name for OpenTelemetry traces.
8
+ * @default process.env.SECURENOW_APPID || process.env.OTEL_SERVICE_NAME
9
+ */
10
+ serviceName?: string;
11
+
12
+ /**
13
+ * OTLP endpoint base URL.
14
+ * @default process.env.SECURENOW_INSTANCE || 'https://freetrial.securenow.ai:4318'
15
+ */
16
+ endpoint?: string;
17
+
18
+ /**
19
+ * Don't append UUID to service name (useful when running a single instance).
20
+ * @default false
21
+ */
22
+ noUuid?: boolean;
23
+
24
+ /**
25
+ * Capture request bodies (POST/PUT/PATCH) on traced spans.
26
+ * Sensitive fields are automatically redacted.
27
+ * @default false
28
+ */
29
+ captureBody?: boolean;
30
+
31
+ /**
32
+ * Enable console log forwarding as OTLP log records.
33
+ * @default false
34
+ */
35
+ logging?: boolean;
36
+ }
37
+
38
+ declare module 'nuxt/schema' {
39
+ interface NuxtConfig {
40
+ securenow?: SecureNowNuxtOptions;
41
+ }
42
+ interface NuxtOptions {
43
+ securenow?: SecureNowNuxtOptions;
44
+ }
45
+ interface RuntimeConfig {
46
+ securenow?: SecureNowNuxtOptions;
47
+ }
48
+ }
49
+
50
+ declare module '@nuxt/schema' {
51
+ interface NuxtConfig {
52
+ securenow?: SecureNowNuxtOptions;
53
+ }
54
+ interface NuxtOptions {
55
+ securenow?: SecureNowNuxtOptions;
56
+ }
57
+ interface RuntimeConfig {
58
+ securenow?: SecureNowNuxtOptions;
59
+ }
60
+ }
package/nuxt.mjs ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * SecureNow Nuxt 3 Module
3
+ *
4
+ * Usage in nuxt.config.ts:
5
+ *
6
+ * export default defineNuxtConfig({
7
+ * modules: ['securenow/nuxt'],
8
+ * securenow: { // optional overrides
9
+ * serviceName: 'my-nuxt-app',
10
+ * },
11
+ * });
12
+ *
13
+ * Environment variables (same as all SecureNow integrations):
14
+ * SECURENOW_APPID, SECURENOW_INSTANCE, SECURENOW_LOGGING_ENABLED, etc.
15
+ */
16
+
17
+ import { defineNuxtModule, createResolver, addServerPlugin } from '@nuxt/kit';
18
+
19
+ const OTEL_EXTERNALS = [
20
+ 'securenow',
21
+ '@opentelemetry/api',
22
+ '@opentelemetry/api-logs',
23
+ '@opentelemetry/sdk-node',
24
+ '@opentelemetry/sdk-logs',
25
+ '@opentelemetry/auto-instrumentations-node',
26
+ '@opentelemetry/instrumentation',
27
+ '@opentelemetry/instrumentation-http',
28
+ '@opentelemetry/exporter-trace-otlp-http',
29
+ '@opentelemetry/exporter-logs-otlp-http',
30
+ '@opentelemetry/resources',
31
+ '@opentelemetry/semantic-conventions',
32
+ ];
33
+
34
+ export default defineNuxtModule({
35
+ meta: {
36
+ name: 'securenow',
37
+ configKey: 'securenow',
38
+ compatibility: { nuxt: '>=3.0.0' },
39
+ },
40
+
41
+ defaults: {
42
+ serviceName: undefined,
43
+ endpoint: undefined,
44
+ noUuid: undefined,
45
+ captureBody: undefined,
46
+ logging: undefined,
47
+ },
48
+
49
+ setup(options, nuxt) {
50
+ const { resolve } = createResolver(import.meta.url);
51
+
52
+ // ── Externalize OTel packages so Nitro doesn't bundle them ──
53
+ nuxt.hook('nitro:config', (nitroConfig) => {
54
+ nitroConfig.externals = nitroConfig.externals || {};
55
+ nitroConfig.externals.external = [
56
+ ...(nitroConfig.externals.external || []),
57
+ ...OTEL_EXTERNALS,
58
+ ];
59
+ });
60
+
61
+ // ── Pass module options to the server plugin via runtimeConfig ──
62
+ nuxt.options.runtimeConfig.securenow = {
63
+ serviceName: options.serviceName,
64
+ endpoint: options.endpoint,
65
+ noUuid: options.noUuid,
66
+ captureBody: options.captureBody,
67
+ logging: options.logging,
68
+ };
69
+
70
+ // ── Register Nitro server plugin ──
71
+ addServerPlugin(resolve('./nuxt-server-plugin'));
72
+
73
+ console.log('[securenow] Nuxt module loaded — server plugin registered');
74
+ },
75
+ });