securenow 7.7.14 โ 7.7.15
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/NPM_README.md +65 -121
- package/README.md +19 -24
- package/SKILL-API.md +490 -489
- package/SKILL-CLI.md +8 -8
- package/app-config.js +99 -42
- package/cli/apps.js +589 -597
- package/cli/auth.js +1 -3
- package/cli/config.js +37 -9
- package/cli/credentials.js +1 -1
- package/cli/diagnostics.js +10 -6
- package/cli/init.js +1 -0
- package/free-trial-banner.js +2 -2
- package/mcp/catalog.js +2 -2
- package/nextjs.d.ts +67 -63
- package/nextjs.js +48 -42
- package/nuxt-server-plugin.mjs +6 -11
- package/nuxt.d.ts +42 -38
- package/nuxt.mjs +1 -1
- package/package.json +1 -1
- package/tracing.d.ts +2 -1
- package/tracing.js +31 -47
- package/web-vite.mjs +102 -15
package/cli/auth.js
CHANGED
|
@@ -75,7 +75,6 @@ async function loginWithBrowser() {
|
|
|
75
75
|
const returnedState = url.searchParams.get('state');
|
|
76
76
|
const appKey = url.searchParams.get('app_key');
|
|
77
77
|
const appName = url.searchParams.get('app_name');
|
|
78
|
-
const appInstance = url.searchParams.get('app_instance');
|
|
79
78
|
const apiKey = url.searchParams.get('api_key');
|
|
80
79
|
|
|
81
80
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
@@ -96,11 +95,10 @@ async function loginWithBrowser() {
|
|
|
96
95
|
|
|
97
96
|
if (token) {
|
|
98
97
|
pendingToken = token;
|
|
99
|
-
if (appKey || appName
|
|
98
|
+
if (appKey || appName) {
|
|
100
99
|
pendingApp = {
|
|
101
100
|
key: appKey || null,
|
|
102
101
|
name: appName || null,
|
|
103
|
-
instance: appInstance || null,
|
|
104
102
|
};
|
|
105
103
|
}
|
|
106
104
|
if (apiKey && apiKey.startsWith('snk_live_')) {
|
package/cli/config.js
CHANGED
|
@@ -153,11 +153,10 @@ function getToken() {
|
|
|
153
153
|
function setAuth(token, email, expiresAt, { local = false, app = null, enableFirewall = false } = {}) {
|
|
154
154
|
const targetFile = credentialsFileForLocal(local);
|
|
155
155
|
const payload = { ...loadJSON(targetFile), token, email, expiresAt };
|
|
156
|
-
if (app && (app.key || app.name
|
|
156
|
+
if (app && (app.key || app.name)) {
|
|
157
157
|
payload.app = {
|
|
158
158
|
key: app.key || null,
|
|
159
159
|
name: app.name || null,
|
|
160
|
-
instance: app.instance || null,
|
|
161
160
|
};
|
|
162
161
|
}
|
|
163
162
|
saveJSON(
|
|
@@ -201,23 +200,52 @@ function setApp(app, { local } = {}) {
|
|
|
201
200
|
app: {
|
|
202
201
|
key: app.key || null,
|
|
203
202
|
name: app.name || null,
|
|
204
|
-
instance: app.instance || null,
|
|
205
203
|
},
|
|
206
204
|
}) || {});
|
|
207
205
|
}
|
|
208
206
|
|
|
209
207
|
function ensureLocalGitignore() {
|
|
210
208
|
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
211
|
-
const
|
|
209
|
+
const legacyEntry = '.securenow/';
|
|
210
|
+
const entries = [
|
|
211
|
+
'.securenow/credentials.json',
|
|
212
|
+
'.securenow/credentials.*.json',
|
|
213
|
+
'!.securenow/credentials.example.json',
|
|
214
|
+
'!.securenow/credentials.*.example.json',
|
|
215
|
+
];
|
|
212
216
|
try {
|
|
217
|
+
let content = '';
|
|
213
218
|
if (fs.existsSync(gitignorePath)) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
219
|
+
content = fs.readFileSync(gitignorePath, 'utf8');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const lines = content ? content.split(/\r?\n/) : [];
|
|
223
|
+
let replacedLegacyEntry = false;
|
|
224
|
+
const nextLines = [];
|
|
225
|
+
|
|
226
|
+
for (const line of lines) {
|
|
227
|
+
if (line.trim() === legacyEntry) {
|
|
228
|
+
if (!replacedLegacyEntry) {
|
|
229
|
+
nextLines.push(...entries);
|
|
230
|
+
replacedLegacyEntry = true;
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
217
233
|
}
|
|
218
|
-
|
|
219
|
-
|
|
234
|
+
nextLines.push(line);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const hasLine = (entry) => nextLines.some(line => line.trim() === entry);
|
|
238
|
+
const missing = entries.filter(entry => !hasLine(entry));
|
|
239
|
+
if (!replacedLegacyEntry && missing.length === 0) return;
|
|
240
|
+
|
|
241
|
+
let nextContent = nextLines.join('\n').replace(/\s*$/, '');
|
|
242
|
+
|
|
243
|
+
if (missing.length > 0) {
|
|
244
|
+
const block = ['# SecureNow local credential files', '# Keep .securenow/ itself trackable for repo-owned docs/templates.', ...missing].join('\n');
|
|
245
|
+
nextContent = nextContent ? `${nextContent}\n\n${block}` : block;
|
|
220
246
|
}
|
|
247
|
+
|
|
248
|
+
fs.writeFileSync(gitignorePath, `${nextContent}\n`);
|
|
221
249
|
} catch {}
|
|
222
250
|
}
|
|
223
251
|
|
package/cli/credentials.js
CHANGED
|
@@ -25,7 +25,6 @@ function buildRuntimeCredentials(options = {}) {
|
|
|
25
25
|
app: {
|
|
26
26
|
key: creds.app?.key || null,
|
|
27
27
|
name: creds.app?.name || null,
|
|
28
|
-
instance: creds.app?.instance || appConfig.FREE_TRIAL_INSTANCE,
|
|
29
28
|
},
|
|
30
29
|
config: {
|
|
31
30
|
...(creds.config || {}),
|
|
@@ -41,6 +40,7 @@ function buildRuntimeCredentials(options = {}) {
|
|
|
41
40
|
_securenow: {
|
|
42
41
|
...(creds._securenow || {}),
|
|
43
42
|
note: 'Runtime SecureNow credentials and SDK defaults. Mount or copy this JSON as .securenow/credentials.json or .securenow/credentials.<environment>.json in production. Do not commit it.',
|
|
43
|
+
routing: 'Telemetry is sent to the default SecureNow ingestion gateway. The gateway routes by app.key, so runtime credentials do not expose per-instance collector URLs.',
|
|
44
44
|
runtimeOnly: 'This file intentionally omits CLI OAuth fields: token, email, and expiresAt.',
|
|
45
45
|
production: 'Production can use this same file shape instead of environment variables.',
|
|
46
46
|
},
|
package/cli/diagnostics.js
CHANGED
|
@@ -36,10 +36,10 @@ function resolvedConfig(options = {}) {
|
|
|
36
36
|
apiKey,
|
|
37
37
|
firewallLocalEnabled,
|
|
38
38
|
apiUrl: config.getApiUrl(),
|
|
39
|
-
loggingEnabled: appConfig.
|
|
40
|
-
captureBody: appConfig.
|
|
41
|
-
captureMultipart: appConfig.
|
|
42
|
-
otelLogLevel:
|
|
39
|
+
loggingEnabled: appConfig.boolConfig('logging.enabled', true),
|
|
40
|
+
captureBody: appConfig.boolConfig('capture.body', true),
|
|
41
|
+
captureMultipart: appConfig.boolConfig('capture.multipart', true),
|
|
42
|
+
otelLogLevel: appConfig.configValue('otel.logLevel', 'error') || 'error',
|
|
43
43
|
firewallEnabled,
|
|
44
44
|
firewallLayers: {
|
|
45
45
|
http: firewallEnabled,
|
|
@@ -467,8 +467,12 @@ async function doctor(_args, flags) {
|
|
|
467
467
|
if (!cfg.appKey) {
|
|
468
468
|
warnings.push('No app key resolved. Run `npx securenow login` or set app.key in .securenow/credentials.json.');
|
|
469
469
|
}
|
|
470
|
-
if (cfg.instance === 'https://
|
|
471
|
-
warnings.push('Using the
|
|
470
|
+
if (cfg.instance === 'https://ingest.securenow.ai') {
|
|
471
|
+
warnings.push('Using the SecureNow ingest gateway. Dedicated instances are routed server-side after policy checks.');
|
|
472
|
+
} else if (cfg.instance === 'https://api.securenow.ai/api/otlp') {
|
|
473
|
+
warnings.push('Using the legacy SecureNow telemetry gateway path. Regenerate credentials so telemetry uses https://ingest.securenow.ai.');
|
|
474
|
+
} else if (cfg.instance === 'https://freetrial.securenow.ai:4318') {
|
|
475
|
+
warnings.push('Using the legacy free-trial collector directly. Regenerate credentials so telemetry flows through https://ingest.securenow.ai.');
|
|
472
476
|
}
|
|
473
477
|
if (!cfg.apiKey && token) {
|
|
474
478
|
warnings.push('CLI/MCP is authenticated with your SecureNow session token. Runtime firewall enforcement key is missing. Run `npx securenow login` or `npx securenow api-key set snk_live_...` to refresh .securenow/credentials.json.');
|
package/cli/init.js
CHANGED
|
@@ -234,6 +234,7 @@ function printAgentPrompt(kind, filename, major, project) {
|
|
|
234
234
|
console.log([
|
|
235
235
|
'Set up SecureNow in this existing Next.js project without using .env files.',
|
|
236
236
|
'Use .securenow/credentials.json for local and production configuration; do not add .env files.',
|
|
237
|
+
'Ignore only .securenow/credentials.json and .securenow/credentials.*.json in git; keep the .securenow/ directory itself trackable for repo-owned files.',
|
|
237
238
|
commands
|
|
238
239
|
? `Use the project scripts for verification when appropriate: ${commands}. Ask before starting long-running dev/start servers, and ask which command to use if these scripts are not the right customer workflow.`
|
|
239
240
|
: 'Ask the customer which command starts, builds, and tests this app because package.json does not expose an obvious script.',
|
package/free-trial-banner.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* Opt-out: set config.runtime.hideBanner=true in .securenow/credentials.json
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const FREE_TRIAL_HOSTS = ['ingest.securenow.ai', 'freetrial.securenow.ai'];
|
|
11
11
|
|
|
12
12
|
function isFreeTrial(endpointBase) {
|
|
13
|
-
return !!endpointBase && endpointBase.includes(
|
|
13
|
+
return !!endpointBase && FREE_TRIAL_HOSTS.some((host) => endpointBase.includes(host));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/* istanbul ignore next โ runs in browser, not Node */
|
package/mcp/catalog.js
CHANGED
|
@@ -17,7 +17,7 @@ Primary goals:
|
|
|
17
17
|
|
|
18
18
|
Safety rules:
|
|
19
19
|
- Do not print full API keys, JWTs, tokens, or .securenow/credentials.json. Mask secrets.
|
|
20
|
-
- Do not commit secrets.
|
|
20
|
+
- Do not commit secrets. Ignore only local SecureNow credential files (.securenow/credentials.json and .securenow/credentials.*.json); keep the .securenow/ directory itself trackable for repo-owned docs/templates.
|
|
21
21
|
- Do not manually browse to a SecureNow auth URL. Always start auth with npx securenow login so the CLI generates the required callback and state.
|
|
22
22
|
- If the browser says "Missing callback parameter", you opened the wrong URL: rerun npx securenow login from the project root.
|
|
23
23
|
- Do not skip login, app selection, firewall connection, or verification unless I explicitly say to.
|
|
@@ -42,7 +42,7 @@ Runbook:
|
|
|
42
42
|
- Confirm .securenow/credentials.json exists.
|
|
43
43
|
- Confirm it has SecureNow's default config/explanations block.
|
|
44
44
|
- Confirm it has an app key/name/instance and a firewall API key after login/app selection.
|
|
45
|
-
- Confirm .securenow/
|
|
45
|
+
- Confirm .securenow/credentials.json and any .securenow/credentials.*.json runtime files are ignored by git, without ignoring the entire .securenow/ directory.
|
|
46
46
|
6. Run npx securenow init. If it fails with ui.header is not a function or another CLI bug, upgrade to securenow@latest, verify >=7.5.1, and retry. Do not silently ignore init failures.
|
|
47
47
|
7. Configure the least invasive framework-specific integration:
|
|
48
48
|
- Next.js: preserve instrumentation.js/ts. Register securenow/nextjs only when NEXT_RUNTIME is nodejs. In ESM files, use createRequire before require("securenow/nextjs"). Include require("securenow/nextjs-auto-capture") for body capture. For Next 15+, add securenow to serverExternalPackages. For older Next.js, use experimental.serverComponentsExternalPackages. Preserve proxy.js/middleware.js.
|
package/nextjs.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SecureNow Next.js Integration TypeScript Declarations
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface RegisterOptions {
|
|
1
|
+
/**
|
|
2
|
+
* SecureNow Next.js Integration TypeScript Declarations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface RegisterOptions {
|
|
6
6
|
/**
|
|
7
7
|
* Service name for OpenTelemetry traces
|
|
8
8
|
* @default .securenow/credentials.json app.key/app.name
|
|
@@ -11,7 +11,11 @@ export interface RegisterOptions {
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* OTLP endpoint for traces
|
|
14
|
-
* @default
|
|
14
|
+
* @default https://ingest.securenow.ai
|
|
15
|
+
*
|
|
16
|
+
* Advanced OTLP endpoint override. Normal SecureNow apps should leave this
|
|
17
|
+
* unset so the ingest gateway can route by app.key to the dashboard-selected
|
|
18
|
+
* instance.
|
|
15
19
|
*/
|
|
16
20
|
endpoint?: string;
|
|
17
21
|
|
|
@@ -20,26 +24,26 @@ export interface RegisterOptions {
|
|
|
20
24
|
* @default .securenow/credentials.json config.runtime.deploymentEnvironment
|
|
21
25
|
*/
|
|
22
26
|
environment?: string;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Don't append UUID to service name
|
|
26
|
-
* @default false
|
|
27
|
-
*/
|
|
28
|
-
noUuid?: boolean;
|
|
29
|
-
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Don't append UUID to service name
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
noUuid?: boolean;
|
|
33
|
+
|
|
30
34
|
/**
|
|
31
35
|
* Enable request body capture
|
|
32
36
|
* @default true from .securenow/credentials.json secure defaults
|
|
33
37
|
*/
|
|
34
38
|
captureBody?: boolean;
|
|
35
39
|
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Register SecureNow OpenTelemetry instrumentation for Next.js
|
|
39
|
-
*
|
|
40
|
-
* @param options - Optional configuration options
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Register SecureNow OpenTelemetry instrumentation for Next.js
|
|
43
|
+
*
|
|
44
|
+
* @param options - Optional configuration options
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
43
47
|
* ```typescript
|
|
44
48
|
* // instrumentation.ts
|
|
45
49
|
* import { createRequire } from 'node:module';
|
|
@@ -53,46 +57,46 @@ export interface RegisterOptions {
|
|
|
53
57
|
* require('securenow/nextjs-auto-capture');
|
|
54
58
|
* }
|
|
55
59
|
* ```
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* ```typescript
|
|
59
|
-
* // With custom options
|
|
60
|
-
* import { registerSecureNow } from 'securenow/nextjs';
|
|
61
|
-
*
|
|
62
|
-
* export function register() {
|
|
63
|
-
* registerSecureNow({
|
|
64
|
-
* serviceName: 'my-nextjs-app',
|
|
65
|
-
* endpoint: 'http://your-otlp-backend.example.com:4318',
|
|
66
|
-
* noUuid: true,
|
|
67
|
-
* });
|
|
68
|
-
* }
|
|
69
|
-
* ```
|
|
70
|
-
*/
|
|
71
|
-
export function registerSecureNow(options?: RegisterOptions): void;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Default sensitive fields that are automatically redacted from traces
|
|
75
|
-
*/
|
|
76
|
-
export const DEFAULT_SENSITIVE_FIELDS: readonly string[];
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Redact sensitive fields from an object
|
|
80
|
-
* @param obj - Object to redact
|
|
81
|
-
* @param sensitiveFields - Array of field names to redact (case-insensitive substring match)
|
|
82
|
-
* @returns Redacted copy of the object
|
|
83
|
-
*/
|
|
84
|
-
export function redactSensitiveData<T = any>(
|
|
85
|
-
obj: T,
|
|
86
|
-
sensitiveFields?: string[]
|
|
87
|
-
): T;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Redact sensitive data from GraphQL query strings
|
|
91
|
-
* @param query - GraphQL query string
|
|
92
|
-
* @param sensitiveFields - Array of field names to redact
|
|
93
|
-
* @returns Redacted query string
|
|
94
|
-
*/
|
|
95
|
-
export function redactGraphQLQuery(
|
|
96
|
-
query: string,
|
|
97
|
-
sensitiveFields?: string[]
|
|
98
|
-
): string;
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* // With custom options
|
|
64
|
+
* import { registerSecureNow } from 'securenow/nextjs';
|
|
65
|
+
*
|
|
66
|
+
* export function register() {
|
|
67
|
+
* registerSecureNow({
|
|
68
|
+
* serviceName: 'my-nextjs-app',
|
|
69
|
+
* endpoint: 'http://your-otlp-backend.example.com:4318',
|
|
70
|
+
* noUuid: true,
|
|
71
|
+
* });
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function registerSecureNow(options?: RegisterOptions): void;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Default sensitive fields that are automatically redacted from traces
|
|
79
|
+
*/
|
|
80
|
+
export const DEFAULT_SENSITIVE_FIELDS: readonly string[];
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Redact sensitive fields from an object
|
|
84
|
+
* @param obj - Object to redact
|
|
85
|
+
* @param sensitiveFields - Array of field names to redact (case-insensitive substring match)
|
|
86
|
+
* @returns Redacted copy of the object
|
|
87
|
+
*/
|
|
88
|
+
export function redactSensitiveData<T = any>(
|
|
89
|
+
obj: T,
|
|
90
|
+
sensitiveFields?: string[]
|
|
91
|
+
): T;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Redact sensitive data from GraphQL query strings
|
|
95
|
+
* @param query - GraphQL query string
|
|
96
|
+
* @param sensitiveFields - Array of field names to redact
|
|
97
|
+
* @returns Redacted query string
|
|
98
|
+
*/
|
|
99
|
+
export function redactGraphQLQuery(
|
|
100
|
+
query: string,
|
|
101
|
+
sensitiveFields?: string[]
|
|
102
|
+
): string;
|
package/nextjs.js
CHANGED
|
@@ -35,8 +35,6 @@ const appConfig = require('./app-config');
|
|
|
35
35
|
const { resolveClientIpWithDetails } = require('./resolve-ip');
|
|
36
36
|
const otelResources = require('@opentelemetry/resources');
|
|
37
37
|
|
|
38
|
-
const env = appConfig.env;
|
|
39
|
-
|
|
40
38
|
let isRegistered = false;
|
|
41
39
|
|
|
42
40
|
function requireRuntimeModule(name) {
|
|
@@ -126,7 +124,7 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
126
124
|
* Register SecureNow OpenTelemetry for Next.js using @vercel/otel
|
|
127
125
|
* @param {Object} options - Optional configuration
|
|
128
126
|
* @param {string} options.serviceName - Service name (defaults to .securenow/credentials.json app.key/app.name)
|
|
129
|
-
* @param {string} options.endpoint -
|
|
127
|
+
* @param {string} options.endpoint - Advanced OTLP endpoint override (defaults to the SecureNow ingest gateway)
|
|
130
128
|
* @param {string} options.environment - deployment.environment override (defaults to config.runtime.deploymentEnvironment)
|
|
131
129
|
* @param {boolean} options.noUuid - Don't append UUID to service name
|
|
132
130
|
*/
|
|
@@ -144,24 +142,26 @@ function registerSecureNow(options = {}) {
|
|
|
144
142
|
}
|
|
145
143
|
|
|
146
144
|
// Detect environment outside try block for error handling
|
|
147
|
-
const isVercel = !!(env
|
|
145
|
+
const isVercel = !!(process.env.VERCEL || process.env.VERCEL_ENV || process.env.VERCEL_URL);
|
|
148
146
|
let deploymentEnvironment = appConfig.resolveDeploymentEnvironment();
|
|
149
147
|
|
|
150
148
|
try {
|
|
151
149
|
console.log('[securenow] Next.js integration loading (pid=%d)', process.pid);
|
|
152
150
|
|
|
153
151
|
// -------- Configuration --------
|
|
154
|
-
// Resolution order: explicit options -> .securenow/credentials.json ->
|
|
152
|
+
// Resolution order: explicit options -> .securenow/credentials.json -> package.json#name.
|
|
153
|
+
// Telemetry goes to the stable ingest gateway by default; the API gateway
|
|
154
|
+
// routes by app.key to the dashboard-selected instance.
|
|
155
155
|
const resolvedApp = appConfig.resolveAll();
|
|
156
156
|
|
|
157
157
|
const rawBase = (options.serviceName || resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '');
|
|
158
158
|
const baseName = rawBase || null;
|
|
159
159
|
// Default: auto-disable suffix when logged in (appId is the routing UUID
|
|
160
|
-
// and the dashboard does exact match). opts.noUuid or
|
|
161
|
-
// override.
|
|
160
|
+
// and the dashboard does exact match). opts.noUuid or config.runtime.noUuid
|
|
161
|
+
// can still override.
|
|
162
162
|
const noUuid = appConfig.resolveNoUuid({ noUuid: options.noUuid });
|
|
163
163
|
deploymentEnvironment = appConfig.normalizeDeploymentEnvironment(
|
|
164
|
-
options.environment || resolvedApp.deploymentEnvironment
|
|
164
|
+
options.environment || resolvedApp.deploymentEnvironment
|
|
165
165
|
);
|
|
166
166
|
|
|
167
167
|
// service.name
|
|
@@ -170,7 +170,7 @@ function registerSecureNow(options = {}) {
|
|
|
170
170
|
serviceName = noUuid ? baseName : `${baseName}-${randomUUID()}`;
|
|
171
171
|
} else {
|
|
172
172
|
serviceName = `nextjs-app-${randomUUID()}`;
|
|
173
|
-
console.warn('[securenow]
|
|
173
|
+
console.warn('[securenow] No app identity resolved. Using fallback: %s', serviceName);
|
|
174
174
|
console.warn('[securenow] Run `npx securenow login` and `npx securenow init` to write .securenow/credentials.json');
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -180,24 +180,30 @@ function registerSecureNow(options = {}) {
|
|
|
180
180
|
const tracesUrl = resolvedEndpoints.tracesUrl;
|
|
181
181
|
const logsUrl = resolvedEndpoints.logsUrl;
|
|
182
182
|
const headers = resolvedEndpoints.headers;
|
|
183
|
+
const otelLogLevel = String(appConfig.configValue('otel.logLevel', '') || '').toLowerCase();
|
|
184
|
+
const isDevelopmentRuntime = process.env.NODE_ENV === 'development';
|
|
183
185
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (
|
|
186
|
+
// @vercel/otel still reads OTel process variables internally. These are
|
|
187
|
+
// derived from credentials JSON; customers do not set them.
|
|
188
|
+
if (isVercel) {
|
|
189
|
+
if (!process.env.OTEL_SERVICE_NAME) process.env.OTEL_SERVICE_NAME = serviceName;
|
|
190
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_ENDPOINT = endpointBase;
|
|
191
|
+
if (!process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = tracesUrl;
|
|
192
|
+
}
|
|
187
193
|
|
|
188
194
|
console.log('[securenow] Next.js App -> service.name=%s', serviceName);
|
|
189
195
|
console.log('[securenow] Environment: %s', deploymentEnvironment);
|
|
190
196
|
|
|
191
197
|
// -------- Body Capture Configuration --------
|
|
192
198
|
// Opt-out default: set config.capture.body=false (or options.captureBody=false) to disable.
|
|
193
|
-
const captureBody = options.captureBody ??
|
|
194
|
-
const maxBodySize =
|
|
195
|
-
const customSensitiveFields = (
|
|
199
|
+
const captureBody = options.captureBody ?? appConfig.boolConfig('capture.body', true);
|
|
200
|
+
const maxBodySize = appConfig.numberConfig('capture.maxBodySize', 10240, 1024);
|
|
201
|
+
const customSensitiveFields = appConfig.listConfig('capture.sensitiveFields');
|
|
196
202
|
const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
|
|
197
203
|
|
|
198
204
|
// -------- Log environment detection --------
|
|
199
205
|
if (!isVercel) {
|
|
200
|
-
console.log('[securenow]
|
|
206
|
+
console.log('[securenow] Self-hosted environment detected (EC2/PM2) - using vanilla SDK');
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
// -------- Use different initialization based on environment --------
|
|
@@ -331,8 +337,8 @@ function registerSecureNow(options = {}) {
|
|
|
331
337
|
}
|
|
332
338
|
|
|
333
339
|
// Debug log in development
|
|
334
|
-
if (
|
|
335
|
-
console.log('[securenow]
|
|
340
|
+
if (isDevelopmentRuntime || otelLogLevel === 'debug') {
|
|
341
|
+
console.log('[securenow] Captured IP: %s (from: %s)',
|
|
336
342
|
primaryIp,
|
|
337
343
|
ipDetails.source || 'unknown'
|
|
338
344
|
);
|
|
@@ -345,8 +351,8 @@ function registerSecureNow(options = {}) {
|
|
|
345
351
|
|
|
346
352
|
} catch (error) {
|
|
347
353
|
// Silently fail to not break the request
|
|
348
|
-
if (
|
|
349
|
-
console.error('[securenow]
|
|
354
|
+
if (otelLogLevel === 'debug') {
|
|
355
|
+
console.error('[securenow] Error in requestHook:', error.message);
|
|
350
356
|
}
|
|
351
357
|
}
|
|
352
358
|
},
|
|
@@ -383,7 +389,7 @@ function registerSecureNow(options = {}) {
|
|
|
383
389
|
return /OTLPTraceExporter|OTLPLogExporter|otlp|exporter.*http|BatchSpanProcessor|BatchLogRecordProcessor/i.test(s)
|
|
384
390
|
|| /node:_http_client|ClientRequest|TLSSocket/i.test(s);
|
|
385
391
|
}
|
|
386
|
-
const _diagDebug =
|
|
392
|
+
const _diagDebug = otelLogLevel === 'debug';
|
|
387
393
|
process.on('uncaughtException', (err, origin) => {
|
|
388
394
|
if (_isOtlpTransientError(err) && _looksLikeOtlpStack(err)) {
|
|
389
395
|
if (_diagDebug) {
|
|
@@ -455,11 +461,11 @@ function registerSecureNow(options = {}) {
|
|
|
455
461
|
});
|
|
456
462
|
|
|
457
463
|
sdk.start();
|
|
458
|
-
console.log('[securenow]
|
|
464
|
+
console.log('[securenow] Vanilla SDK initialized for self-hosted environment');
|
|
459
465
|
|
|
460
466
|
// -------- Logging (self-hosted only) --------
|
|
461
467
|
// Opt-out default: set config.logging.enabled=false to disable.
|
|
462
|
-
const loggingEnabled =
|
|
468
|
+
const loggingEnabled = appConfig.boolConfig('logging.enabled', true);
|
|
463
469
|
if (loggingEnabled) {
|
|
464
470
|
try {
|
|
465
471
|
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
|
|
@@ -511,7 +517,7 @@ function registerSecureNow(options = {}) {
|
|
|
511
517
|
_emitLog(SeverityNumber.ERROR, 'ERROR', args);
|
|
512
518
|
};
|
|
513
519
|
|
|
514
|
-
console.log('[securenow]
|
|
520
|
+
console.log('[securenow] Logging: ENABLED -> %s', logsUrl);
|
|
515
521
|
|
|
516
522
|
// Auto-log every incoming HTTP request/response
|
|
517
523
|
try {
|
|
@@ -559,51 +565,51 @@ function registerSecureNow(options = {}) {
|
|
|
559
565
|
}
|
|
560
566
|
return originalEmit.apply(this, arguments);
|
|
561
567
|
};
|
|
562
|
-
console.log('[securenow]
|
|
568
|
+
console.log('[securenow] HTTP request logging: ENABLED');
|
|
563
569
|
} catch (_) {}
|
|
564
570
|
|
|
565
571
|
// Graceful shutdown for logs
|
|
566
572
|
process.on('SIGTERM', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { requireRuntimeModule('./firewall').shutdown(); } catch (_) {} });
|
|
567
573
|
process.on('SIGINT', async () => { try { await loggerProvider.shutdown(); } catch (_) {} try { requireRuntimeModule('./firewall').shutdown(); } catch (_) {} });
|
|
568
574
|
} catch (e) {
|
|
569
|
-
console.warn('[securenow]
|
|
575
|
+
console.warn('[securenow] Logging setup failed (missing @opentelemetry/exporter-logs-otlp-http or @opentelemetry/sdk-logs):', e.message);
|
|
570
576
|
}
|
|
571
577
|
} else {
|
|
572
|
-
console.log('[securenow]
|
|
578
|
+
console.log('[securenow] Logging: DISABLED (config.logging.enabled=false)');
|
|
573
579
|
}
|
|
574
580
|
}
|
|
575
581
|
|
|
576
582
|
isRegistered = true;
|
|
577
583
|
|
|
578
|
-
// Free trial banner (optional
|
|
584
|
+
// Free trial banner (optional - may not be bundled in standalone builds)
|
|
579
585
|
try {
|
|
580
586
|
const { isFreeTrial, patchHttpForBanner } = requireRuntimeModule('./free-trial-banner');
|
|
581
|
-
if (isFreeTrial(endpointBase) &&
|
|
587
|
+
if (isFreeTrial(endpointBase) && !appConfig.boolConfig('runtime.hideBanner', false)) {
|
|
582
588
|
patchHttpForBanner();
|
|
583
589
|
}
|
|
584
590
|
} catch (_) {}
|
|
585
591
|
|
|
586
|
-
console.log('[securenow]
|
|
587
|
-
console.log('[securenow]
|
|
588
|
-
console.log('[securenow]
|
|
589
|
-
console.log('[securenow]
|
|
590
|
-
console.log('[securenow]
|
|
591
|
-
console.log('[securenow]
|
|
592
|
-
console.log('[securenow]
|
|
593
|
-
console.log('[securenow]
|
|
594
|
-
console.log('[securenow]
|
|
592
|
+
console.log('[securenow] OpenTelemetry started for Next.js -> %s', tracesUrl);
|
|
593
|
+
console.log('[securenow] Auto-capturing comprehensive request metadata:');
|
|
594
|
+
console.log('[securenow] - IP addresses (x-forwarded-for, x-real-ip, socket)');
|
|
595
|
+
console.log('[securenow] - User-Agent, Referer, Origin, Accept headers');
|
|
596
|
+
console.log('[securenow] - Protocol, Host, Port (proxy-aware)');
|
|
597
|
+
console.log('[securenow] - Geographic data (Vercel/Cloudflare)');
|
|
598
|
+
console.log('[securenow] - Request IDs, CSRF tokens, Auth presence');
|
|
599
|
+
console.log('[securenow] - Response status, content-type, content-length');
|
|
600
|
+
console.log('[securenow] Body capture DISABLED at HTTP instrumentation level (prevents Next.js conflicts)');
|
|
595
601
|
if (captureBody) {
|
|
596
|
-
console.log('[securenow]
|
|
602
|
+
console.log('[securenow] For body capture in Next.js, use: import "securenow/nextjs-auto-capture"');
|
|
597
603
|
}
|
|
598
604
|
|
|
599
605
|
// Optional test span
|
|
600
|
-
if (
|
|
606
|
+
if (appConfig.boolConfig('runtime.testSpan', false)) {
|
|
601
607
|
const api = require('@opentelemetry/api');
|
|
602
608
|
const tracer = api.trace.getTracer('securenow-nextjs');
|
|
603
609
|
const span = tracer.startSpan('securenow.nextjs.startup');
|
|
604
610
|
span.setAttribute('next.runtime', process.env.NEXT_RUNTIME || 'nodejs');
|
|
605
611
|
span.end();
|
|
606
|
-
console.log('[securenow]
|
|
612
|
+
console.log('[securenow] Test span created');
|
|
607
613
|
}
|
|
608
614
|
|
|
609
615
|
} catch (error) {
|
|
@@ -615,7 +621,7 @@ function registerSecureNow(options = {}) {
|
|
|
615
621
|
}
|
|
616
622
|
}
|
|
617
623
|
|
|
618
|
-
// Firewall
|
|
624
|
+
// Firewall - runs independently from OTel so it works even if tracing fails.
|
|
619
625
|
// Key and environment come from .securenow/credentials.json (written by
|
|
620
626
|
// login/init or credentials runtime), so no .env entry is needed.
|
|
621
627
|
const firewallOptions = appConfig.resolveFirewallOptions();
|
package/nuxt-server-plugin.mjs
CHANGED
|
@@ -26,8 +26,6 @@ const { resolveClientIpWithDetails } = nodeRequire('./resolve-ip');
|
|
|
26
26
|
|
|
27
27
|
// โโ Helpers โโ
|
|
28
28
|
|
|
29
|
-
const env = appConfig.env;
|
|
30
|
-
|
|
31
29
|
function createResource(attributes) {
|
|
32
30
|
if (typeof otelResources.resourceFromAttributes === 'function') {
|
|
33
31
|
return otelResources.resourceFromAttributes(attributes);
|
|
@@ -74,7 +72,7 @@ function getRuntimeOptions() {
|
|
|
74
72
|
export default defineNitroPlugin(async (nitroApp) => {
|
|
75
73
|
const opts = getRuntimeOptions();
|
|
76
74
|
|
|
77
|
-
// Resolution order: opts -> .securenow/credentials.json ->
|
|
75
|
+
// Resolution order: opts -> .securenow/credentials.json -> package.json#name
|
|
78
76
|
const resolvedApp = appConfig.resolveAll();
|
|
79
77
|
|
|
80
78
|
// โโ Naming โโ
|
|
@@ -125,12 +123,9 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
125
123
|
// Opt-out default: set config.capture.body=false (or opts.captureBody=false) to disable.
|
|
126
124
|
const captureBody =
|
|
127
125
|
opts.captureBody ??
|
|
128
|
-
|
|
129
|
-
const maxBodySize =
|
|
130
|
-
const customSensitiveFields = (
|
|
131
|
-
.split(',')
|
|
132
|
-
.map((s) => s.trim())
|
|
133
|
-
.filter(Boolean);
|
|
126
|
+
appConfig.boolConfig('capture.body', true);
|
|
127
|
+
const maxBodySize = appConfig.numberConfig('capture.maxBodySize', 10240, 1024);
|
|
128
|
+
const customSensitiveFields = appConfig.listConfig('capture.sensitiveFields');
|
|
134
129
|
const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
|
|
135
130
|
|
|
136
131
|
// โโ HTTP instrumentation โโ
|
|
@@ -238,7 +233,7 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
238
233
|
// Opt-out default: set config.logging.enabled=false (or opts.logging=false) to disable.
|
|
239
234
|
const loggingEnabled =
|
|
240
235
|
opts.logging ??
|
|
241
|
-
|
|
236
|
+
appConfig.boolConfig('logging.enabled', true);
|
|
242
237
|
|
|
243
238
|
let loggerProvider = null;
|
|
244
239
|
|
|
@@ -314,7 +309,7 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
314
309
|
// โโ Free trial banner โโ
|
|
315
310
|
try {
|
|
316
311
|
const { isFreeTrial, patchHttpForBanner } = await import('./free-trial-banner.js');
|
|
317
|
-
if (isFreeTrial(endpointBase) &&
|
|
312
|
+
if (isFreeTrial(endpointBase) && !appConfig.boolConfig('runtime.hideBanner', false)) {
|
|
318
313
|
patchHttpForBanner();
|
|
319
314
|
}
|
|
320
315
|
} catch {
|