securenow 7.5.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONSUMING-APPS-GUIDE.md +2 -0
- package/NPM_README.md +201 -237
- package/README.md +73 -26
- package/SKILL-API.md +209 -205
- package/SKILL-CLI.md +71 -64
- package/app-config.js +479 -83
- package/cli/apiKey.js +1 -1
- package/cli/apps.js +1 -1
- package/cli/config.js +31 -12
- package/cli/credentials.js +88 -0
- package/cli/diagnostics.js +81 -98
- package/cli/firewall.js +29 -14
- package/cli/init.js +246 -201
- package/cli/monitor.js +107 -43
- package/cli/security.js +24 -12
- package/cli/ui.js +22 -12
- package/cli/utils.js +2 -1
- package/cli.js +71 -39
- package/console-instrumentation.js +1 -1
- package/docs/ENVIRONMENT-VARIABLES.md +137 -863
- package/docs/ENVIRONMENTS.md +60 -0
- package/docs/EXPRESS-SETUP-GUIDE.md +3 -0
- package/docs/FIREWALL-GUIDE.md +3 -0
- package/docs/INDEX.md +6 -8
- package/docs/LOGGING-GUIDE.md +3 -0
- package/docs/MCP-GUIDE.md +8 -0
- package/docs/NEXTJS-GUIDE.md +3 -0
- package/docs/NEXTJS-QUICKSTART.md +24 -16
- package/docs/NUXT-GUIDE.md +3 -0
- package/docs/QUICKSTART-BODY-CAPTURE.md +3 -0
- package/docs/REQUEST-BODY-CAPTURE.md +3 -0
- package/firewall-cloud.js +10 -10
- package/firewall-only.js +25 -23
- package/firewall.js +47 -29
- package/free-trial-banner.js +1 -1
- package/mcp/catalog.js +104 -17
- package/nextjs-auto-capture.d.ts +7 -4
- package/nextjs-auto-capture.js +7 -7
- package/nextjs-middleware.js +4 -3
- package/nextjs-wrapper.js +6 -6
- package/nextjs.d.ts +36 -25
- package/nextjs.js +47 -55
- package/nuxt-server-plugin.mjs +35 -51
- package/nuxt.d.ts +29 -23
- package/package.json +1 -1
- package/postinstall.js +27 -61
- package/register.d.ts +19 -33
- package/register.js +8 -8
- package/resolve-ip.js +4 -5
- package/tracing.d.ts +21 -19
- package/tracing.js +34 -42
package/mcp/catalog.js
CHANGED
|
@@ -5,7 +5,77 @@ const path = require('path');
|
|
|
5
5
|
|
|
6
6
|
const TEXT = 'text/plain';
|
|
7
7
|
const JSON_MIME = 'application/json';
|
|
8
|
-
const MARKDOWN = 'text/markdown';
|
|
8
|
+
const MARKDOWN = 'text/markdown';
|
|
9
|
+
|
|
10
|
+
const UNIVERSAL_SECURENOW_SETUP_PROMPT = `You are working in an existing JavaScript or TypeScript app. Set up SecureNow end-to-end for the framework/runtime already used by this repo. Treat this as a real onboarding, not just a package install.
|
|
11
|
+
|
|
12
|
+
Primary goals:
|
|
13
|
+
- Use the latest published SecureNow npm package. Require securenow@7.5.1 or newer.
|
|
14
|
+
- By default, enable tracing, logs, POST request body capture, multipart metadata capture, and the SecureNow firewall.
|
|
15
|
+
- If I explicitly ask for firewall-only mode, keep the same install/login/verification gates, but use firewall-only preload and do not add tracing, logging, or OTel instrumentation.
|
|
16
|
+
- The firewall must protect the selected SecureNow app, use SecureNow's own blocklist/allowlist/IPDB data, and respect that app's SecureNow IPDB confidence threshold. Do not add custom IP reputation providers or custom auto-blocking.
|
|
17
|
+
|
|
18
|
+
Safety rules:
|
|
19
|
+
- Do not print full API keys, JWTs, tokens, or .securenow/credentials.json. Mask secrets.
|
|
20
|
+
- Do not commit secrets. Ensure .securenow/ is in .gitignore.
|
|
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
|
+
- If the browser says "Missing callback parameter", you opened the wrong URL: rerun npx securenow login from the project root.
|
|
23
|
+
- Do not skip login, app selection, firewall connection, or verification unless I explicitly say to.
|
|
24
|
+
- Preserve existing middleware, proxy, instrumentation, Docker, PM2, and start scripts.
|
|
25
|
+
|
|
26
|
+
Runbook:
|
|
27
|
+
1. Identify the project root, package manager, framework, start/build/test scripts, process manager files, Docker files, and existing middleware/proxy/instrumentation.
|
|
28
|
+
2. Install or upgrade SecureNow with the detected package manager, using securenow@latest. Verify the actual installed version with:
|
|
29
|
+
node -p "require('./node_modules/securenow/package.json').version"
|
|
30
|
+
npx securenow version
|
|
31
|
+
Stop and fix the install if either is below 7.5.1 or npx still resolves an older local package.
|
|
32
|
+
3. Read the installed package surface before editing files: node_modules/securenow/package.json, README/NPM_README, SKILL-API, SKILL-CLI, docs/MCP-GUIDE.md if present, npx securenow help, and relevant subcommand help for login/init/firewall/doctor/env/test-span/log/mcp.
|
|
33
|
+
4. Mandatory auth gate:
|
|
34
|
+
- Run npx securenow whoami from the project root.
|
|
35
|
+
- If not logged in, run npx securenow login from the project root and wait for the browser flow.
|
|
36
|
+
- After the CLI exits, rerun npx securenow whoami.
|
|
37
|
+
- Do not proceed to app edits or verification until whoami succeeds.
|
|
38
|
+
5. Validate project-local credentials without exposing secrets:
|
|
39
|
+
- Confirm .securenow/credentials.json exists.
|
|
40
|
+
- Confirm it has SecureNow's default config/explanations block.
|
|
41
|
+
- Confirm it has an app key/name/instance and a firewall API key after login/app selection.
|
|
42
|
+
- Confirm .securenow/ is ignored by git.
|
|
43
|
+
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.
|
|
44
|
+
7. Configure the least invasive framework-specific integration:
|
|
45
|
+
- 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.
|
|
46
|
+
- Nuxt/Nitro: use the documented securenow/nuxt module or Nitro server plugin.
|
|
47
|
+
- Express/Fastify/NestJS/Koa/Hapi/Hono/raw Node: preload securenow/register through existing scripts, NODE_OPTIONS, PM2 node_args, Docker CMD, or the process manager already used.
|
|
48
|
+
- Firewall-only: preload securenow/firewall-only or use the documented securenow run --firewall-only command. Do not add OTel/tracing/logging in this mode.
|
|
49
|
+
- Vite/browser-only: use only documented browser integration and state that server firewall protection requires a server runtime.
|
|
50
|
+
8. Do not create or require a .env file for local development or production. The SDK reads defaults from .securenow/credentials.json:
|
|
51
|
+
- config.logging.enabled: true
|
|
52
|
+
- config.capture.body: true
|
|
53
|
+
- config.capture.multipart: true
|
|
54
|
+
- config.firewall.enabled: true
|
|
55
|
+
- config.firewall.failMode: "open"
|
|
56
|
+
- config.capture.maxBodySize: 10240
|
|
57
|
+
For production, run npx securenow credentials runtime --env production, store the resulting JSON as a deployment secret file, and mount/copy it to <app-root>/.securenow/credentials.json. Do not recommend env vars unless the user explicitly asks for legacy fallbacks.
|
|
58
|
+
Local credentials should use config.runtime.deploymentEnvironment="local". Production runtime credentials should use "production". The app key stays the same; traces, logs, firewall status, forensics, and CLI/MCP queries are scoped by environment.
|
|
59
|
+
9. Verify firewall and threshold:
|
|
60
|
+
- Run npx securenow firewall apps and npx securenow firewall status.
|
|
61
|
+
- Confirm the selected app is present, firewallEnabled is true, and the SecureNow IPDB confidence threshold is visible.
|
|
62
|
+
- If firewallEnabled is false, run the documented per-app enable command, for example npx securenow firewall enable --app <appKey>, then verify again.
|
|
63
|
+
10. End-to-end proof:
|
|
64
|
+
- Run npx securenow doctor.
|
|
65
|
+
- Run npx securenow env and confirm loggingEnabled, captureBody, captureMultipart, and firewallEnabled resolve to true or 1 from credentials/defaults, unless I explicitly requested firewall-only.
|
|
66
|
+
- If available and not in firewall-only mode, send telemetry:
|
|
67
|
+
npx securenow test-span securenow.onboarding
|
|
68
|
+
npx securenow log send "SecureNow onboarding test" --level info
|
|
69
|
+
- Run the repo build/test command if available.
|
|
70
|
+
- For MCP-capable clients, optionally smoke-test npx securenow mcp with the securenow_auth_status tool.
|
|
71
|
+
|
|
72
|
+
Final response:
|
|
73
|
+
- List every changed file.
|
|
74
|
+
- Summarize installed SecureNow version and linked app name/key, masking secrets.
|
|
75
|
+
- Show verification commands and pass/fail result.
|
|
76
|
+
- Mention skipped checks and why.
|
|
77
|
+
- Provide exact command(s) to start the protected app.`;
|
|
78
|
+
|
|
9
79
|
|
|
10
80
|
function string(description) {
|
|
11
81
|
return { type: 'string', description };
|
|
@@ -83,6 +153,10 @@ const timeRangeInput = {
|
|
|
83
153
|
to: string('End time as ISO 8601, optional.'),
|
|
84
154
|
};
|
|
85
155
|
|
|
156
|
+
const environmentInput = {
|
|
157
|
+
environment: string('Deployment environment scope: production, staging, preview, local, test, or all. Default for investigations is production.'),
|
|
158
|
+
};
|
|
159
|
+
|
|
86
160
|
const TOOLS = [
|
|
87
161
|
{
|
|
88
162
|
name: 'securenow_auth_status',
|
|
@@ -151,7 +225,8 @@ const TOOLS = [
|
|
|
151
225
|
readOnly: true,
|
|
152
226
|
method: 'GET',
|
|
153
227
|
endpoint: '/firewall/status',
|
|
154
|
-
|
|
228
|
+
queryFields: ['environment'],
|
|
229
|
+
inputSchema: objectSchema({ ...environmentInput }),
|
|
155
230
|
},
|
|
156
231
|
{
|
|
157
232
|
name: 'securenow_firewall_enable',
|
|
@@ -163,9 +238,11 @@ const TOOLS = [
|
|
|
163
238
|
method: 'PATCH',
|
|
164
239
|
endpoint: '/firewall/app/:appKey',
|
|
165
240
|
pathParams: ['appKey'],
|
|
241
|
+
bodyFields: ['environment'],
|
|
166
242
|
fixedBody: { enabled: true },
|
|
167
243
|
inputSchema: objectSchema({
|
|
168
244
|
appKey: string('Application key UUID.'),
|
|
245
|
+
...environmentInput,
|
|
169
246
|
...confirmSchema,
|
|
170
247
|
}, ['appKey', 'confirm', 'reason']),
|
|
171
248
|
},
|
|
@@ -180,9 +257,11 @@ const TOOLS = [
|
|
|
180
257
|
method: 'PATCH',
|
|
181
258
|
endpoint: '/firewall/app/:appKey',
|
|
182
259
|
pathParams: ['appKey'],
|
|
260
|
+
bodyFields: ['environment'],
|
|
183
261
|
fixedBody: { enabled: false },
|
|
184
262
|
inputSchema: objectSchema({
|
|
185
263
|
appKey: string('Application key UUID.'),
|
|
264
|
+
...environmentInput,
|
|
186
265
|
...confirmSchema,
|
|
187
266
|
}, ['appKey', 'confirm', 'reason']),
|
|
188
267
|
},
|
|
@@ -196,10 +275,11 @@ const TOOLS = [
|
|
|
196
275
|
method: 'PATCH',
|
|
197
276
|
endpoint: '/firewall/app/:appKey',
|
|
198
277
|
pathParams: ['appKey'],
|
|
199
|
-
bodyFields: ['confidenceMinimum'],
|
|
278
|
+
bodyFields: ['confidenceMinimum', 'environment'],
|
|
200
279
|
inputSchema: objectSchema({
|
|
201
280
|
appKey: string('Application key UUID.'),
|
|
202
281
|
confidenceMinimum: number('Minimum SecureNow IPDB abuse confidence score.', { minimum: 0, maximum: 100 }),
|
|
282
|
+
...environmentInput,
|
|
203
283
|
...confirmSchema,
|
|
204
284
|
}, ['appKey', 'confidenceMinimum', 'confirm', 'reason']),
|
|
205
285
|
},
|
|
@@ -212,8 +292,10 @@ const TOOLS = [
|
|
|
212
292
|
method: 'GET',
|
|
213
293
|
endpoint: '/firewall/check/:ip',
|
|
214
294
|
pathParams: ['ip'],
|
|
295
|
+
queryFields: ['environment'],
|
|
215
296
|
inputSchema: objectSchema({
|
|
216
297
|
ip: string('IPv4 address to test.'),
|
|
298
|
+
...environmentInput,
|
|
217
299
|
}, ['ip']),
|
|
218
300
|
},
|
|
219
301
|
{
|
|
@@ -224,10 +306,11 @@ const TOOLS = [
|
|
|
224
306
|
readOnly: true,
|
|
225
307
|
method: 'GET',
|
|
226
308
|
endpoint: '/traces',
|
|
227
|
-
queryFields: ['appKeys', 'from', 'to', 'limit'],
|
|
309
|
+
queryFields: ['appKeys', 'environment', 'from', 'to', 'limit'],
|
|
228
310
|
normalize: { appKeys: normalizeAppKeys },
|
|
229
311
|
inputSchema: objectSchema({
|
|
230
312
|
...appKeysInput,
|
|
313
|
+
...environmentInput,
|
|
231
314
|
...timeRangeInput,
|
|
232
315
|
limit: number('Maximum traces to return.', { minimum: 1, maximum: 200 }),
|
|
233
316
|
}, ['appKeys']),
|
|
@@ -241,11 +324,12 @@ const TOOLS = [
|
|
|
241
324
|
method: 'GET',
|
|
242
325
|
endpoint: '/traces/:traceId',
|
|
243
326
|
pathParams: ['traceId'],
|
|
244
|
-
queryFields: ['appKeys'],
|
|
327
|
+
queryFields: ['appKeys', 'environment'],
|
|
245
328
|
normalize: { appKeys: normalizeAppKeys },
|
|
246
329
|
inputSchema: objectSchema({
|
|
247
330
|
traceId: string('Trace id.'),
|
|
248
331
|
...appKeysInput,
|
|
332
|
+
...environmentInput,
|
|
249
333
|
}, ['traceId', 'appKeys']),
|
|
250
334
|
},
|
|
251
335
|
{
|
|
@@ -256,10 +340,11 @@ const TOOLS = [
|
|
|
256
340
|
readOnly: true,
|
|
257
341
|
method: 'GET',
|
|
258
342
|
endpoint: '/logs',
|
|
259
|
-
queryFields: ['appKeys', 'from', 'to', 'severity', 'limit'],
|
|
343
|
+
queryFields: ['appKeys', 'environment', 'from', 'to', 'severity', 'limit'],
|
|
260
344
|
normalize: { appKeys: normalizeAppKeys },
|
|
261
345
|
inputSchema: objectSchema({
|
|
262
346
|
...appKeysInput,
|
|
347
|
+
...environmentInput,
|
|
263
348
|
...timeRangeInput,
|
|
264
349
|
severity: string('Optional severity filter.'),
|
|
265
350
|
limit: number('Maximum logs to return.', { minimum: 1, maximum: 500 }),
|
|
@@ -274,11 +359,12 @@ const TOOLS = [
|
|
|
274
359
|
method: 'GET',
|
|
275
360
|
endpoint: '/logs/trace/:traceId',
|
|
276
361
|
pathParams: ['traceId'],
|
|
277
|
-
queryFields: ['appKeys'],
|
|
362
|
+
queryFields: ['appKeys', 'environment'],
|
|
278
363
|
normalize: { appKeys: normalizeAppKeys },
|
|
279
364
|
inputSchema: objectSchema({
|
|
280
365
|
traceId: string('Trace id.'),
|
|
281
366
|
...appKeysInput,
|
|
367
|
+
...environmentInput,
|
|
282
368
|
}, ['traceId', 'appKeys']),
|
|
283
369
|
},
|
|
284
370
|
{
|
|
@@ -339,8 +425,12 @@ const TOOLS = [
|
|
|
339
425
|
method: 'GET',
|
|
340
426
|
endpoint: '/ip/:ip/traces',
|
|
341
427
|
pathParams: ['ip'],
|
|
428
|
+
queryFields: ['appKeys', 'environment'],
|
|
429
|
+
normalize: { appKeys: normalizeAppKeys },
|
|
342
430
|
inputSchema: objectSchema({
|
|
343
431
|
ip: string('IPv4 address.'),
|
|
432
|
+
...appKeysInput,
|
|
433
|
+
...environmentInput,
|
|
344
434
|
}, ['ip']),
|
|
345
435
|
},
|
|
346
436
|
{
|
|
@@ -352,11 +442,12 @@ const TOOLS = [
|
|
|
352
442
|
confirm: true,
|
|
353
443
|
method: 'POST',
|
|
354
444
|
endpoint: '/forensics/query',
|
|
355
|
-
bodyFields: ['query', 'applicationId', 'instanceId'],
|
|
445
|
+
bodyFields: ['query', 'applicationId', 'instanceId', 'environment'],
|
|
356
446
|
inputSchema: objectSchema({
|
|
357
447
|
query: string('Natural-language forensic query.'),
|
|
358
448
|
applicationId: string('Optional application database id.'),
|
|
359
449
|
instanceId: string('Optional ClickHouse instance id.'),
|
|
450
|
+
...environmentInput,
|
|
360
451
|
...confirmSchema,
|
|
361
452
|
}, ['query', 'confirm', 'reason']),
|
|
362
453
|
},
|
|
@@ -596,6 +687,7 @@ const PROMPTS = [
|
|
|
596
687
|
arguments: [
|
|
597
688
|
{ name: 'ip', description: 'IP address to investigate.', required: true },
|
|
598
689
|
{ name: 'appKeys', description: 'Optional comma-separated app keys.', required: false },
|
|
690
|
+
{ name: 'environment', description: 'Environment to investigate. Defaults to production; use all only when explicitly needed.', required: false },
|
|
599
691
|
],
|
|
600
692
|
},
|
|
601
693
|
];
|
|
@@ -608,15 +700,9 @@ function promptMessages(name, args = {}) {
|
|
|
608
700
|
content: {
|
|
609
701
|
type: 'text',
|
|
610
702
|
text: [
|
|
611
|
-
'Set up SecureNow end-to-end for this JavaScript/TypeScript project.',
|
|
612
703
|
args.projectRoot ? `Project root: ${args.projectRoot}` : null,
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
'After login, verify whoami succeeds and that .securenow/credentials.json contains the selected app and firewall API key.',
|
|
616
|
-
'Use the least invasive integration for the detected framework.',
|
|
617
|
-
'Keep tracing, logs, POST body capture, multipart metadata capture, and the per-app firewall enabled by default.',
|
|
618
|
-
'Verify with build/test plus npx securenow firewall apps/status and npx securenow env/doctor when available.',
|
|
619
|
-
].filter(Boolean).join('\n'),
|
|
704
|
+
UNIVERSAL_SECURENOW_SETUP_PROMPT,
|
|
705
|
+
].filter(Boolean).join('\n\n'),
|
|
620
706
|
},
|
|
621
707
|
},
|
|
622
708
|
];
|
|
@@ -632,7 +718,7 @@ function promptMessages(name, args = {}) {
|
|
|
632
718
|
'Verify SecureNow default-on protection for this project.',
|
|
633
719
|
args.appKey ? `App key: ${args.appKey}` : null,
|
|
634
720
|
'Check npx securenow whoami, npx securenow api-key show, npx securenow firewall apps, and npx securenow firewall status.',
|
|
635
|
-
'Confirm traces, logs,
|
|
721
|
+
'Confirm traces, logs, capture.body, capture.multipart, and firewall.enabled are enabled by .securenow/credentials.json defaults unless explicitly set false.',
|
|
636
722
|
'Do not print full tokens or API keys.',
|
|
637
723
|
].filter(Boolean).join('\n'),
|
|
638
724
|
},
|
|
@@ -649,6 +735,7 @@ function promptMessages(name, args = {}) {
|
|
|
649
735
|
text: [
|
|
650
736
|
`Investigate IP ${args.ip || '<ip>'} with SecureNow.`,
|
|
651
737
|
args.appKeys ? `Scope to app keys: ${args.appKeys}` : null,
|
|
738
|
+
`Environment scope: ${args.environment || 'production'}.`,
|
|
652
739
|
'Use IP intelligence first, then related traces/logs, then recommend remediation.',
|
|
653
740
|
'Only block, allow, or trust the IP after explicit user confirmation.',
|
|
654
741
|
].filter(Boolean).join('\n'),
|
package/nextjs-auto-capture.d.ts
CHANGED
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
* }
|
|
16
16
|
* ```
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
18
|
+
* Configuration:
|
|
19
|
+
* - config.capture.body=false - Disable body capture (default: enabled)
|
|
20
|
+
* - config.capture.maxBodySize=10240 - Max body size in bytes (default: 10KB)
|
|
21
|
+
* - config.capture.sensitiveFields=["field"] - Additional sensitive fields to redact
|
|
22
|
+
*
|
|
23
|
+
* The SDK reads these from .securenow/credentials.json in local development
|
|
24
|
+
* and production. Legacy env vars are fallback-only for existing installs.
|
|
22
25
|
*
|
|
23
26
|
* Features:
|
|
24
27
|
* - Zero code changes required in API routes
|
package/nextjs-auto-capture.js
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
const { trace } = require('@opentelemetry/api');
|
|
20
|
+
const appConfig = require('./app-config');
|
|
21
|
+
const env = appConfig.env;
|
|
20
22
|
|
|
21
23
|
// Default sensitive fields to redact
|
|
22
24
|
const DEFAULT_SENSITIVE_FIELDS = [
|
|
@@ -54,8 +56,8 @@ async function safeBodyCapture(request, span) {
|
|
|
54
56
|
|
|
55
57
|
try {
|
|
56
58
|
const contentType = request.headers.get('content-type') || '';
|
|
57
|
-
const maxBodySize = Math.max(1024, parseInt(
|
|
58
|
-
const customSensitiveFields = (
|
|
59
|
+
const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
|
|
60
|
+
const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
59
61
|
const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
|
|
60
62
|
|
|
61
63
|
// Only for supported types
|
|
@@ -125,8 +127,8 @@ async function safeBodyCapture(request, span) {
|
|
|
125
127
|
* Check if body capture is enabled
|
|
126
128
|
*/
|
|
127
129
|
function isBodyCaptureEnabled() {
|
|
128
|
-
// Opt-out default: set
|
|
129
|
-
return !/^(0|false)$/i.test(String(
|
|
130
|
+
// Opt-out default: set config.capture.body=false to disable.
|
|
131
|
+
return !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
/**
|
|
@@ -183,7 +185,7 @@ if (isBodyCaptureEnabled()) {
|
|
|
183
185
|
console.warn('[securenow] 💡 Body capture disabled. Use manual approach if needed.');
|
|
184
186
|
}
|
|
185
187
|
} else {
|
|
186
|
-
console.log('[securenow]
|
|
188
|
+
console.log('[securenow] Automatic body capture: DISABLED (config.capture.body=false)');
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
module.exports = {
|
|
@@ -194,5 +196,3 @@ module.exports = {
|
|
|
194
196
|
};
|
|
195
197
|
|
|
196
198
|
|
|
197
|
-
|
|
198
|
-
|
package/nextjs-middleware.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const { trace, context, SpanStatusCode } = require('@opentelemetry/api');
|
|
19
|
+
const appConfig = require('./app-config');
|
|
20
|
+
const env = appConfig.env;
|
|
19
21
|
|
|
20
22
|
// Default sensitive fields to redact
|
|
21
23
|
const DEFAULT_SENSITIVE_FIELDS = [
|
|
@@ -99,8 +101,8 @@ async function middleware(request) {
|
|
|
99
101
|
|
|
100
102
|
try {
|
|
101
103
|
const contentType = request.headers.get('content-type') || '';
|
|
102
|
-
const maxBodySize = Math.max(1024, parseInt(
|
|
103
|
-
const customSensitiveFields = (
|
|
104
|
+
const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
|
|
105
|
+
const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
104
106
|
const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
|
|
105
107
|
|
|
106
108
|
// Only capture supported types
|
|
@@ -183,4 +185,3 @@ module.exports = {
|
|
|
183
185
|
|
|
184
186
|
|
|
185
187
|
|
|
186
|
-
|
package/nextjs-wrapper.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const { trace } = require('@opentelemetry/api');
|
|
19
|
+
const appConfig = require('./app-config');
|
|
20
|
+
const env = appConfig.env;
|
|
19
21
|
|
|
20
22
|
// Default sensitive fields to redact
|
|
21
23
|
const DEFAULT_SENSITIVE_FIELDS = [
|
|
@@ -49,8 +51,8 @@ function redactSensitiveData(obj, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
49
51
|
* Capture body from Request object (clone to avoid consuming)
|
|
50
52
|
*/
|
|
51
53
|
async function captureRequestBody(request) {
|
|
52
|
-
// Opt-out default: set
|
|
53
|
-
const captureBody = !/^(0|false)$/i.test(String(
|
|
54
|
+
// Opt-out default: set config.capture.body=false to disable.
|
|
55
|
+
const captureBody = !/^(0|false)$/i.test(String(env('SECURENOW_CAPTURE_BODY') ?? ''));
|
|
54
56
|
|
|
55
57
|
if (!captureBody) return;
|
|
56
58
|
if (!['POST', 'PUT', 'PATCH'].includes(request.method)) return;
|
|
@@ -60,8 +62,8 @@ async function captureRequestBody(request) {
|
|
|
60
62
|
|
|
61
63
|
try {
|
|
62
64
|
const contentType = request.headers.get('content-type') || '';
|
|
63
|
-
const maxBodySize = Math.max(1024, parseInt(
|
|
64
|
-
const customSensitiveFields = (
|
|
65
|
+
const maxBodySize = Math.max(1024, parseInt(env('SECURENOW_MAX_BODY_SIZE'), 10) || 10240);
|
|
66
|
+
const customSensitiveFields = (env('SECURENOW_SENSITIVE_FIELDS') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
65
67
|
const allSensitiveFields = [...DEFAULT_SENSITIVE_FIELDS, ...customSensitiveFields];
|
|
66
68
|
|
|
67
69
|
// Only for supported types
|
|
@@ -154,5 +156,3 @@ module.exports = {
|
|
|
154
156
|
};
|
|
155
157
|
|
|
156
158
|
|
|
157
|
-
|
|
158
|
-
|
package/nextjs.d.ts
CHANGED
|
@@ -3,17 +3,23 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export interface RegisterOptions {
|
|
6
|
-
/**
|
|
7
|
-
* Service name for OpenTelemetry traces
|
|
8
|
-
* @default
|
|
9
|
-
*/
|
|
10
|
-
serviceName?: string;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* OTLP endpoint for traces
|
|
14
|
-
* @default
|
|
15
|
-
*/
|
|
16
|
-
endpoint?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Service name for OpenTelemetry traces
|
|
8
|
+
* @default .securenow/credentials.json app.key/app.name
|
|
9
|
+
*/
|
|
10
|
+
serviceName?: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* OTLP endpoint for traces
|
|
14
|
+
* @default .securenow/credentials.json app.instance, or https://freetrial.securenow.ai:4318
|
|
15
|
+
*/
|
|
16
|
+
endpoint?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Deployment environment sent as OpenTelemetry deployment.environment.
|
|
20
|
+
* @default .securenow/credentials.json config.runtime.deploymentEnvironment
|
|
21
|
+
*/
|
|
22
|
+
environment?: string;
|
|
17
23
|
|
|
18
24
|
/**
|
|
19
25
|
* Don't append UUID to service name
|
|
@@ -21,12 +27,12 @@ export interface RegisterOptions {
|
|
|
21
27
|
*/
|
|
22
28
|
noUuid?: boolean;
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
* Enable request body capture
|
|
26
|
-
* @default
|
|
27
|
-
*/
|
|
28
|
-
captureBody?: boolean;
|
|
29
|
-
}
|
|
30
|
+
/**
|
|
31
|
+
* Enable request body capture
|
|
32
|
+
* @default true from .securenow/credentials.json secure defaults
|
|
33
|
+
*/
|
|
34
|
+
captureBody?: boolean;
|
|
35
|
+
}
|
|
30
36
|
|
|
31
37
|
/**
|
|
32
38
|
* Register SecureNow OpenTelemetry instrumentation for Next.js
|
|
@@ -34,14 +40,19 @@ export interface RegisterOptions {
|
|
|
34
40
|
* @param options - Optional configuration options
|
|
35
41
|
*
|
|
36
42
|
* @example
|
|
37
|
-
* ```typescript
|
|
38
|
-
* // instrumentation.ts
|
|
39
|
-
* import {
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
43
|
+
* ```typescript
|
|
44
|
+
* // instrumentation.ts
|
|
45
|
+
* import { createRequire } from 'node:module';
|
|
46
|
+
*
|
|
47
|
+
* const require = createRequire(import.meta.url);
|
|
48
|
+
*
|
|
49
|
+
* export async function register() {
|
|
50
|
+
* if (process.env.NEXT_RUNTIME !== 'nodejs') return;
|
|
51
|
+
* const { registerSecureNow } = require('securenow/nextjs');
|
|
52
|
+
* registerSecureNow({ captureBody: true });
|
|
53
|
+
* require('securenow/nextjs-auto-capture');
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
45
56
|
*
|
|
46
57
|
* @example
|
|
47
58
|
* ```typescript
|