securenow 5.18.0 → 6.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.
Files changed (85) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +40 -239
  3. package/cli.js +455 -415
  4. package/console-instrumentation.js +136 -147
  5. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
  6. package/docs/ARCHITECTURE.md +3 -3
  7. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  8. package/docs/AUTO-SETUP.md +4 -4
  9. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  10. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  11. package/docs/CHANGELOG-NEXTJS.md +1 -1
  12. package/docs/CUSTOMER-GUIDE.md +16 -16
  13. package/docs/EASIEST-SETUP.md +5 -5
  14. package/docs/ENVIRONMENT-VARIABLES.md +652 -880
  15. package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
  16. package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
  17. package/docs/INDEX.md +4 -22
  18. package/docs/LOGGING-GUIDE.md +708 -701
  19. package/docs/LOGGING-QUICKSTART.md +239 -234
  20. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  21. package/docs/NEXTJS-GUIDE.md +14 -14
  22. package/docs/NEXTJS-QUICKSTART.md +1 -1
  23. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  24. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  25. package/docs/REDACTION-EXAMPLES.md +1 -1
  26. package/docs/REQUEST-BODY-CAPTURE.md +10 -19
  27. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  28. package/examples/README.md +6 -6
  29. package/examples/instrumentation-with-auto-capture.ts +1 -1
  30. package/examples/nextjs-env-example.txt +2 -2
  31. package/examples/nextjs-instrumentation.js +1 -1
  32. package/examples/nextjs-instrumentation.ts +1 -1
  33. package/examples/nextjs-with-logging-example.md +6 -6
  34. package/examples/nextjs-with-options.ts +1 -1
  35. package/examples/test-nextjs-setup.js +1 -1
  36. package/nextjs-auto-capture.js +207 -199
  37. package/nextjs-middleware.js +181 -186
  38. package/nextjs-webpack-config.js +53 -88
  39. package/nextjs-wrapper.js +158 -158
  40. package/nextjs.d.ts +1 -1
  41. package/nextjs.js +135 -190
  42. package/package.json +45 -67
  43. package/postinstall.js +6 -6
  44. package/register.d.ts +1 -1
  45. package/register.js +4 -39
  46. package/tracing.d.ts +1 -2
  47. package/tracing.js +22 -287
  48. package/web-vite.mjs +156 -239
  49. package/CONSUMING-APPS-GUIDE.md +0 -455
  50. package/NPM_README.md +0 -1933
  51. package/SKILL-API.md +0 -600
  52. package/SKILL-CLI.md +0 -409
  53. package/cidr.js +0 -83
  54. package/cli/apps.js +0 -585
  55. package/cli/auth.js +0 -280
  56. package/cli/client.js +0 -115
  57. package/cli/config.js +0 -173
  58. package/cli/firewall.js +0 -100
  59. package/cli/fp.js +0 -638
  60. package/cli/init.js +0 -201
  61. package/cli/monitor.js +0 -440
  62. package/cli/run.js +0 -133
  63. package/cli/security.js +0 -1064
  64. package/cli/ui.js +0 -386
  65. package/docs/API-KEYS-GUIDE.md +0 -233
  66. package/docs/AUTO-SETUP-SUMMARY.md +0 -331
  67. package/docs/BODY-CAPTURE-FIX.md +0 -261
  68. package/docs/COMPLETION-REPORT.md +0 -408
  69. package/docs/FINAL-SOLUTION.md +0 -335
  70. package/docs/FIREWALL-GUIDE.md +0 -426
  71. package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
  72. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
  73. package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
  74. package/docs/NUXT-GUIDE.md +0 -166
  75. package/docs/SOLUTION-SUMMARY.md +0 -312
  76. package/firewall-cloud.js +0 -212
  77. package/firewall-iptables.js +0 -139
  78. package/firewall-only.js +0 -38
  79. package/firewall-tcp.js +0 -74
  80. package/firewall.js +0 -720
  81. package/free-trial-banner.js +0 -174
  82. package/nuxt-server-plugin.mjs +0 -423
  83. package/nuxt.d.ts +0 -60
  84. package/nuxt.mjs +0 -75
  85. package/resolve-ip.js +0 -77
@@ -1,174 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Free Trial Banner — auto-injects a visible "Testing Environment" banner
5
- * into HTML pages served by apps using the SecureNow free trial instance.
6
- *
7
- * Opt-out: set SECURENOW_HIDE_BANNER=1
8
- */
9
-
10
- const FREETRIAL_HOST = 'freetrial.securenow.ai';
11
-
12
- function isFreeTrial(endpointBase) {
13
- return !!endpointBase && endpointBase.includes(FREETRIAL_HOST);
14
- }
15
-
16
- /* istanbul ignore next — runs in browser, not Node */
17
- function _bannerClientCode() {
18
- if (window.__snBanner) return;
19
- window.__snBanner = 1;
20
-
21
- function create() {
22
- if (document.getElementById('sn-ft-banner')) return;
23
-
24
- var d = document.createElement('div');
25
- d.id = 'sn-ft-banner';
26
- d.style.cssText =
27
- 'position:fixed;top:0;left:0;right:0;z-index:2147483647;' +
28
- 'background:#FEF3CD;color:#856404;padding:10px 16px;' +
29
- 'font-family:system-ui,-apple-system,sans-serif;font-size:13px;' +
30
- 'text-align:center;border-bottom:2px solid #FFE69C;' +
31
- 'display:flex;align-items:center;justify-content:center;gap:6px;' +
32
- 'box-shadow:0 2px 8px rgba(0,0,0,0.12)';
33
-
34
- var icon = document.createElement('span');
35
- icon.textContent = '\u26a0\ufe0f';
36
- d.appendChild(icon);
37
-
38
- var msg = document.createElement('span');
39
- var strong = document.createElement('strong');
40
- strong.textContent = 'Testing Environment:';
41
- msg.appendChild(strong);
42
- msg.appendChild(document.createTextNode(
43
- ' Only add test applications. For production usage, '
44
- ));
45
-
46
- var link = document.createElement('a');
47
- link.href = 'https://app.securenow.ai/dashboard/settings/instances';
48
- link.target = '_blank';
49
- link.rel = 'noopener';
50
- link.style.cssText = 'color:#664D03;font-weight:600;text-decoration:underline';
51
- link.textContent = 'create a new production instance';
52
- msg.appendChild(link);
53
- msg.appendChild(document.createTextNode('.'));
54
- d.appendChild(msg);
55
-
56
- var upgradeBtn = document.createElement('a');
57
- upgradeBtn.href = 'https://app.securenow.ai/dashboard/settings/instances';
58
- upgradeBtn.target = '_blank';
59
- upgradeBtn.rel = 'noopener';
60
- upgradeBtn.textContent = '\u26a1 Upgrade';
61
- upgradeBtn.style.cssText =
62
- 'display:inline-flex;align-items:center;gap:4px;' +
63
- 'background:#D97706;color:#fff;padding:4px 12px;border-radius:4px;' +
64
- 'font-size:12px;font-weight:600;text-decoration:none;margin-left:10px;' +
65
- 'white-space:nowrap;transition:background 0.15s';
66
- upgradeBtn.onmouseover = function () { upgradeBtn.style.background = '#B45309'; };
67
- upgradeBtn.onmouseout = function () { upgradeBtn.style.background = '#D97706'; };
68
- d.appendChild(upgradeBtn);
69
-
70
- var close = document.createElement('button');
71
- close.textContent = '\u00d7';
72
- close.style.cssText =
73
- 'background:none;border:none;color:#856404;font-size:18px;' +
74
- 'cursor:pointer;margin-left:12px;padding:0 4px;line-height:1';
75
- close.onclick = function () { d.style.display = 'none'; };
76
- d.appendChild(close);
77
-
78
- document.body.prepend(d);
79
- }
80
-
81
- if (document.readyState === 'loading') {
82
- document.addEventListener('DOMContentLoaded', create);
83
- } else {
84
- create();
85
- }
86
- }
87
-
88
- var BANNER_SCRIPT =
89
- '<script data-securenow-banner>(' +
90
- _bannerClientCode.toString() +
91
- ')()</scr' + 'ipt>';
92
-
93
- /**
94
- * Monkey-patch http.ServerResponse to inject the banner script into HTML
95
- * responses. Searches for `<head...>` and inserts the script right after it.
96
- * Skips compressed responses and non-HTML content types.
97
- */
98
- function patchHttpForBanner() {
99
- try {
100
- var http = require('http');
101
- var _origWrite = http.ServerResponse.prototype.write;
102
- var _origEnd = http.ServerResponse.prototype.end;
103
-
104
- function maybeInject(res, chunk) {
105
- if (res._snBannerDone || !chunk) return chunk;
106
-
107
- if (res._snIsHtml === undefined) {
108
- var ct = res.getHeader('content-type');
109
- var ce = res.getHeader('content-encoding');
110
- var csp = res.getHeader('content-security-policy');
111
- res._snIsHtml = !!(ct && String(ct).includes('text/html') && !ce && !csp);
112
- }
113
- if (!res._snIsHtml) {
114
- res._snBannerDone = true;
115
- return chunk;
116
- }
117
-
118
- var isStr = typeof chunk === 'string';
119
- var isBuf = Buffer.isBuffer(chunk);
120
- if (!isStr && !isBuf) return chunk;
121
-
122
- var str = isStr ? chunk : chunk.toString('utf8');
123
- var headIdx = str.indexOf('<head');
124
- if (headIdx === -1) return chunk;
125
-
126
- var gt = str.indexOf('>', headIdx);
127
- if (gt === -1) return chunk;
128
-
129
- res._snBannerDone = true;
130
- var result = str.slice(0, gt + 1) + BANNER_SCRIPT + str.slice(gt + 1);
131
- return isStr ? result : Buffer.from(result, 'utf8');
132
- }
133
-
134
- http.ServerResponse.prototype.write = function (chunk, encoding, cb) {
135
- try {
136
- var modified = maybeInject(this, chunk);
137
- if (modified !== chunk) {
138
- var enc = typeof encoding === 'function' ? 'utf8' : encoding;
139
- var callback = typeof encoding === 'function' ? encoding : cb;
140
- try {
141
- if (this.getHeader('content-length')) {
142
- this.setHeader('content-length', Buffer.byteLength(modified));
143
- }
144
- } catch (_) { /* headers already sent */ }
145
- return _origWrite.call(this, modified, enc, callback);
146
- }
147
- } catch (_) { /* never break the app */ }
148
- return _origWrite.call(this, chunk, encoding, cb);
149
- };
150
-
151
- http.ServerResponse.prototype.end = function (chunk, encoding, cb) {
152
- try {
153
- var modified = maybeInject(this, chunk);
154
- if (modified !== chunk) {
155
- var enc = typeof encoding === 'function' ? 'utf8' : encoding;
156
- var callback = typeof encoding === 'function' ? encoding : cb;
157
- try {
158
- if (this.getHeader('content-length')) {
159
- this.setHeader('content-length', Buffer.byteLength(modified));
160
- }
161
- } catch (_) { /* headers already sent */ }
162
- return _origEnd.call(this, modified, enc, callback);
163
- }
164
- } catch (_) { /* never break the app */ }
165
- return _origEnd.call(this, chunk, encoding, cb);
166
- };
167
-
168
- console.log('[securenow] Free trial banner injection enabled');
169
- } catch (err) {
170
- console.warn('[securenow] Could not setup free trial banner:', err.message);
171
- }
172
- }
173
-
174
- module.exports = { isFreeTrial, patchHttpForBanner, BANNER_SCRIPT };
@@ -1,423 +0,0 @@
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
-
22
- // ── Helpers ──
23
-
24
- const env = (k) =>
25
- process.env[k] ?? process.env[k.toUpperCase()] ?? process.env[k.toLowerCase()];
26
-
27
- const parseHeaders = (str) => {
28
- const out = {};
29
- if (!str) return out;
30
- for (const raw of String(str).split(',')) {
31
- const s = raw.trim();
32
- if (!s) continue;
33
- const i = s.indexOf('=');
34
- if (i === -1) continue;
35
- out[s.slice(0, i).trim().toLowerCase()] = s.slice(i + 1).trim();
36
- }
37
- return out;
38
- };
39
-
40
- const DEFAULT_SENSITIVE_FIELDS = [
41
- 'password', 'passwd', 'pwd', 'secret', 'token', 'api_key', 'apikey',
42
- 'access_token', 'auth', 'credentials', 'mysql_pwd', 'stripeToken',
43
- 'card', 'cardnumber', 'ccv', 'cvc', 'cvv', 'ssn', 'pin',
44
- ];
45
-
46
- function redactSensitiveData(obj, fields = DEFAULT_SENSITIVE_FIELDS) {
47
- if (!obj || typeof obj !== 'object') return obj;
48
- const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
49
- for (const key of Object.keys(redacted)) {
50
- const lower = key.toLowerCase();
51
- if (fields.some((f) => lower.includes(f.toLowerCase()))) {
52
- redacted[key] = '[REDACTED]';
53
- } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {
54
- redacted[key] = redactSensitiveData(redacted[key], fields);
55
- }
56
- }
57
- return redacted;
58
- }
59
-
60
- // ── Runtime config helpers ──
61
-
62
- function getRuntimeOptions() {
63
- try {
64
- const cfg = useRuntimeConfig();
65
- return cfg.securenow || {};
66
- } catch {
67
- return {};
68
- }
69
- }
70
-
71
- // ── Plugin ──
72
-
73
- export default defineNitroPlugin((nitroApp) => {
74
- const opts = getRuntimeOptions();
75
-
76
- // ── Naming ──
77
- const rawBase = (
78
- opts.serviceName ||
79
- env('OTEL_SERVICE_NAME') ||
80
- env('SECURENOW_APPID') ||
81
- ''
82
- ).trim().replace(/^['"]|['"]$/g, '');
83
-
84
- const baseName = rawBase || null;
85
- const noUuid =
86
- opts.noUuid ??
87
- (String(env('SECURENOW_NO_UUID')) === '1' ||
88
- String(env('SECURENOW_NO_UUID')).toLowerCase() === 'true');
89
-
90
- let serviceName;
91
- if (baseName) {
92
- serviceName = noUuid ? baseName : `${baseName}-${uuidv4()}`;
93
- } else {
94
- serviceName = `nuxt-app-${uuidv4()}`;
95
- console.warn(
96
- '[securenow] ⚠️ No SECURENOW_APPID set. Using fallback: %s',
97
- serviceName,
98
- );
99
- console.warn(
100
- '[securenow] 💡 Set SECURENOW_APPID=your-app-name in .env for better tracking',
101
- );
102
- }
103
-
104
- const serviceInstanceId = `${baseName || 'securenow'}-${uuidv4()}`;
105
-
106
- // ── 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(/\/$/, '');
113
-
114
- const tracesUrl =
115
- env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
116
- const logsUrl =
117
- env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') || `${endpointBase}/v1/logs`;
118
- const headers = parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS'));
119
-
120
- // ── Resource ──
121
- const resource = new Resource({
122
- [SemanticResourceAttributes.SERVICE_NAME]: serviceName,
123
- [SemanticResourceAttributes.SERVICE_INSTANCE_ID]: serviceInstanceId,
124
- [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]:
125
- env('NODE_ENV') || 'production',
126
- [SemanticResourceAttributes.SERVICE_VERSION]:
127
- process.env.npm_package_version || undefined,
128
- 'framework': 'nuxt',
129
- });
130
-
131
- // ── Body capture config ──
132
- const captureBody =
133
- opts.captureBody ??
134
- (String(env('SECURENOW_CAPTURE_BODY')) === '1' ||
135
- String(env('SECURENOW_CAPTURE_BODY')).toLowerCase() === 'true');
136
- const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
137
- const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '')
138
- .split(',')
139
- .map((s) => s.trim())
140
- .filter(Boolean);
141
- const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
142
-
143
- // ── HTTP instrumentation ──
144
- const httpInstrumentation = new HttpInstrumentation({
145
- requestHook: (span, request) => {
146
- try {
147
- const hdrs = request.headers || {};
148
- const fwd = hdrs['x-forwarded-for'];
149
- const clientIp =
150
- (fwd ? String(fwd).split(',')[0].trim() : null) ||
151
- hdrs['x-real-ip'] ||
152
- hdrs['cf-connecting-ip'] ||
153
- hdrs['x-client-ip'] ||
154
- request.socket?.remoteAddress ||
155
- 'unknown';
156
-
157
- span.setAttributes({
158
- 'http.client_ip': clientIp,
159
- 'http.user_agent': hdrs['user-agent'] || '',
160
- 'http.host': hdrs['x-forwarded-host'] || hdrs['host'] || '',
161
- 'http.scheme':
162
- hdrs['x-forwarded-proto'] ||
163
- (request.socket?.encrypted ? 'https' : 'http'),
164
- 'http.referer': hdrs['referer'] || '',
165
- 'http.origin': hdrs['origin'] || '',
166
- 'http.request_id':
167
- hdrs['x-request-id'] || hdrs['x-trace-id'] || '',
168
- });
169
-
170
- if (hdrs['authorization']) {
171
- span.setAttribute('http.security.auth_present', 'true');
172
- }
173
- if (hdrs['cookie']) {
174
- span.setAttribute('http.security.cookies_present', 'true');
175
- }
176
-
177
- // Body capture via stream listener (same approach as tracing.js)
178
- if (
179
- captureBody &&
180
- request.method &&
181
- ['POST', 'PUT', 'PATCH'].includes(request.method)
182
- ) {
183
- const ct = hdrs['content-type'] || '';
184
- if (
185
- ct.includes('application/json') ||
186
- ct.includes('application/graphql') ||
187
- ct.includes('application/x-www-form-urlencoded')
188
- ) {
189
- const chunks = [];
190
- let size = 0;
191
- request.on('data', (chunk) => {
192
- size += chunk.length;
193
- if (size <= maxBodySize) chunks.push(chunk);
194
- });
195
- request.on('end', () => {
196
- if (size > maxBodySize) {
197
- span.setAttribute('http.request.body', `[TOO LARGE: ${size} bytes]`);
198
- return;
199
- }
200
- if (chunks.length === 0) return;
201
- const raw = Buffer.concat(chunks).toString('utf8');
202
- try {
203
- const parsed = JSON.parse(raw);
204
- const redacted = redactSensitiveData(parsed, allSensitiveFields);
205
- span.setAttributes({
206
- 'http.request.body': JSON.stringify(redacted).substring(0, maxBodySize),
207
- 'http.request.body.type': ct.includes('graphql') ? 'graphql' : 'json',
208
- 'http.request.body.size': size,
209
- });
210
- } catch {
211
- span.setAttribute('http.request.body', '[UNPARSEABLE - REDACTED FOR SAFETY]');
212
- span.setAttribute('http.request.body.parse_error', true);
213
- }
214
- });
215
- }
216
- }
217
- } catch {
218
- // never break the request
219
- }
220
- },
221
- });
222
-
223
- // ── Trace exporter + SDK ──
224
- const traceExporter = new OTLPTraceExporter({ url: tracesUrl, headers });
225
-
226
- const sdk = new NodeSDK({
227
- traceExporter,
228
- resource,
229
- instrumentations: [httpInstrumentation],
230
- });
231
-
232
- sdk.start();
233
- console.log('[securenow] 🚀 Nuxt OTel SDK started → %s', tracesUrl);
234
- console.log(
235
- '[securenow] service.name=%s instance.id=%s',
236
- serviceName,
237
- serviceInstanceId,
238
- );
239
-
240
- // ── Logging ──
241
- const loggingEnabled =
242
- opts.logging ??
243
- (String(env('SECURENOW_LOGGING_ENABLED')) === '1' ||
244
- String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() === 'true');
245
-
246
- let loggerProvider = null;
247
-
248
- if (loggingEnabled) {
249
- try {
250
- const { OTLPLogExporter } = await import(
251
- '@opentelemetry/exporter-logs-otlp-http'
252
- );
253
- const { LoggerProvider, BatchLogRecordProcessor } = await import(
254
- '@opentelemetry/sdk-logs'
255
- );
256
-
257
- const logExporter = new OTLPLogExporter({ url: logsUrl, headers });
258
- loggerProvider = new LoggerProvider({ resource });
259
- loggerProvider.addLogRecordProcessor(
260
- new BatchLogRecordProcessor(logExporter),
261
- );
262
-
263
- const logger = loggerProvider.getLogger('console', '1.0.0');
264
- const SEV = { DEBUG: 5, INFO: 9, WARN: 13, ERROR: 17 };
265
- const orig = {
266
- log: console.log,
267
- info: console.info,
268
- warn: console.warn,
269
- error: console.error,
270
- debug: console.debug,
271
- };
272
-
273
- function emitLog(sn, st, args) {
274
- try {
275
- const ctx = otelContext.active();
276
- const spanCtx = otelTrace.getSpanContext(ctx);
277
- logger.emit({
278
- severityNumber: sn,
279
- severityText: st,
280
- body: args
281
- .map((a) =>
282
- typeof a === 'object' && a !== null ? JSON.stringify(a) : String(a),
283
- )
284
- .join(' '),
285
- attributes: { 'log.source': 'console', 'log.method': st.toLowerCase() },
286
- ...(spanCtx && { context: ctx }),
287
- });
288
- } catch {
289
- // swallow
290
- }
291
- }
292
-
293
- console.log = (...a) => { emitLog(SEV.INFO, 'INFO', a); orig.log.apply(console, a); };
294
- console.info = (...a) => { emitLog(SEV.INFO, 'INFO', a); orig.info.apply(console, a); };
295
- console.warn = (...a) => { emitLog(SEV.WARN, 'WARN', a); orig.warn.apply(console, a); };
296
- console.error = (...a) => { emitLog(SEV.ERROR, 'ERROR', a); orig.error.apply(console, a); };
297
- console.debug = (...a) => { emitLog(SEV.DEBUG, 'DEBUG', a); orig.debug.apply(console, a); };
298
-
299
- console.log('[securenow] 📋 Logging: ENABLED → %s', logsUrl);
300
- } catch (e) {
301
- console.warn('[securenow] ⚠️ Logging setup failed:', e.message);
302
- }
303
- } else {
304
- console.log(
305
- '[securenow] 📋 Logging: DISABLED (set SECURENOW_LOGGING_ENABLED=1 to enable)',
306
- );
307
- }
308
-
309
- if (captureBody) {
310
- console.log(
311
- '[securenow] 📝 Body capture: ENABLED (max %d bytes, redacting %d fields)',
312
- maxBodySize,
313
- allSensitiveFields.length,
314
- );
315
- }
316
-
317
- // ── Free trial banner ──
318
- try {
319
- const { isFreeTrial, patchHttpForBanner } = await import('./free-trial-banner.js');
320
- if (isFreeTrial(endpointBase) && String(env('SECURENOW_HIDE_BANNER')) !== '1') {
321
- patchHttpForBanner();
322
- }
323
- } catch {
324
- // not critical
325
- }
326
-
327
- // ── Firewall — runs independently from OTel ──
328
- const firewallApiKey = env('SECURENOW_API_KEY');
329
- if (firewallApiKey && env('SECURENOW_FIREWALL_ENABLED') !== '0') {
330
- try {
331
- const { init: fwInit } = await import('./firewall.js');
332
- fwInit({
333
- apiKey: firewallApiKey,
334
- apiUrl: env('SECURENOW_API_URL') || 'https://api.securenow.ai',
335
- versionCheckInterval: parseInt(env('SECURENOW_FIREWALL_VERSION_INTERVAL'), 10) || 10,
336
- syncInterval: parseInt(env('SECURENOW_FIREWALL_SYNC_INTERVAL'), 10) || 300,
337
- failMode: env('SECURENOW_FIREWALL_FAIL_MODE') || 'open',
338
- statusCode: parseInt(env('SECURENOW_FIREWALL_STATUS_CODE'), 10) || 403,
339
- log: env('SECURENOW_FIREWALL_LOG') !== '0',
340
- tcp: env('SECURENOW_FIREWALL_TCP') === '1',
341
- iptables: env('SECURENOW_FIREWALL_IPTABLES') === '1',
342
- cloud: env('SECURENOW_FIREWALL_CLOUD') || null,
343
- });
344
- } catch (e) {
345
- console.warn('[securenow] Firewall init failed:', e.message);
346
- }
347
- }
348
-
349
- // ── Graceful shutdown ──
350
- const shutdown = async (sig) => {
351
- try {
352
- await sdk.shutdown?.();
353
- if (loggerProvider) await loggerProvider.shutdown?.();
354
- try { const fw = await import('./firewall.js'); fw.shutdown?.(); } catch {}
355
- console.log(`[securenow] Shut down on ${sig}`);
356
- } catch {
357
- // swallow
358
- }
359
- };
360
- process.on('SIGINT', () => shutdown('SIGINT'));
361
- process.on('SIGTERM', () => shutdown('SIGTERM'));
362
-
363
- // ── Nitro request hooks for span enrichment ──
364
- const tracer = otelTrace.getTracer('securenow-nuxt', '1.0.0');
365
- const spanMap = new WeakMap();
366
-
367
- nitroApp.hooks.hook('request', (event) => {
368
- try {
369
- const req = event.node.req;
370
- const method = event.method || req.method || 'GET';
371
- const path = event.path || req.url || '/';
372
-
373
- const span = tracer.startSpan(`${method} ${path}`, {
374
- attributes: {
375
- 'http.method': method,
376
- 'http.target': path,
377
- 'http.url': `${req.headers?.['x-forwarded-proto'] || 'http'}://${req.headers?.host || 'localhost'}${path}`,
378
- 'component': 'nuxt-nitro',
379
- },
380
- });
381
-
382
- spanMap.set(event, span);
383
- } catch {
384
- // never break the request
385
- }
386
- });
387
-
388
- nitroApp.hooks.hook('afterResponse', (event) => {
389
- try {
390
- const span = spanMap.get(event);
391
- if (!span) return;
392
-
393
- const status = event.node.res.statusCode || 200;
394
- span.setAttribute('http.status_code', status);
395
-
396
- if (status >= 500) {
397
- span.setStatus({ code: SpanStatusCode.ERROR, message: `HTTP ${status}` });
398
- }
399
-
400
- span.end();
401
- spanMap.delete(event);
402
- } catch {
403
- // swallow
404
- }
405
- });
406
-
407
- nitroApp.hooks.hook('error', (error, { event }) => {
408
- try {
409
- const span = event ? spanMap.get(event) : null;
410
- if (span) {
411
- span.recordException(error);
412
- span.setStatus({
413
- code: SpanStatusCode.ERROR,
414
- message: error.message || 'Internal Server Error',
415
- });
416
- span.end();
417
- spanMap.delete(event);
418
- }
419
- } catch {
420
- // swallow
421
- }
422
- });
423
- });
package/nuxt.d.ts DELETED
@@ -1,60 +0,0 @@
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
- }