securenow 8.12.1 → 8.12.3
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/README.md +2 -0
- package/cli/automation.js +4 -2
- package/cli/threatModel.js +80 -0
- package/cli/ui.js +18 -1
- package/cli.js +7 -1
- package/console-instrumentation.js +1 -12
- package/events.js +1 -0
- package/firewall-only.js +2 -0
- package/firewall-paths.js +46 -0
- package/firewall.js +2 -10
- package/log-format.js +28 -0
- package/nextjs.js +1 -0
- package/package.json +5 -2
- package/register.js +2 -0
- package/sdk-disabled.js +18 -0
- package/sessions.js +2 -0
- package/tracing.js +3 -1
package/README.md
CHANGED
|
@@ -111,6 +111,8 @@ npx securenow doctor # diagnose config + connectivity
|
|
|
111
111
|
npx securenow status # dashboard summary
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
**Tests** — test runs otherwise report to your real app with the project's `.securenow` credentials. `SECURENOW_DISABLED=1 npm test` turns the SDK off for that process: no traces, logs, events, firewall or session sync, and `guard()` just calls `next()`. This is the only setting read from the environment.
|
|
115
|
+
|
|
114
116
|
---
|
|
115
117
|
|
|
116
118
|
## Connect your coding agent (MCP)
|
package/cli/automation.js
CHANGED
|
@@ -198,18 +198,20 @@ async function dryRun(args, flags) {
|
|
|
198
198
|
ui.keyValue([
|
|
199
199
|
['Scanned IPs', String(results.scanned ?? 0)],
|
|
200
200
|
['Matched IPs', String(results.matched ?? 0)],
|
|
201
|
+
...(results.testModeMatched != null ? [['Test-mode matches', `${results.testModeMatched} (preview only: never acted on until the rule is promoted)`]] : []),
|
|
201
202
|
['Sample count', String((results.samples || []).length)],
|
|
202
203
|
]);
|
|
203
204
|
if ((results.samples || []).length) {
|
|
204
205
|
console.log('');
|
|
205
206
|
const rows = results.samples.map((sample) => [
|
|
206
207
|
sample.ip,
|
|
208
|
+
ui.truncate([].concat(sample.alertName || []).slice(-1).join(''), 40),
|
|
207
209
|
ui.truncate((sample.path || []).join(', '), 32),
|
|
208
|
-
ui.truncate((sample.attackType || []).join(', '), 28),
|
|
209
210
|
(sample.environment || []).join(', ') || '-',
|
|
210
211
|
String(sample.riskScore ?? '-'),
|
|
212
|
+
sample.testMode ? 'test' : 'prod',
|
|
211
213
|
]);
|
|
212
|
-
ui.table(['IP', '
|
|
214
|
+
ui.table(['IP', 'Alert', 'Paths', 'Env', 'Risk', 'Mode'], rows);
|
|
213
215
|
}
|
|
214
216
|
console.log('');
|
|
215
217
|
} catch (err) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// `securenow threat-model <category>` — print the copy-paste threat-modeling prompt for a category,
|
|
4
|
+
// fetched from the published set at securenow.ai (the same prompt shown on securenow.ai/threat-modeling).
|
|
5
|
+
// Pipe it straight into your coding agent:
|
|
6
|
+
// claude -p "$(npx securenow threat-model authentication)"
|
|
7
|
+
// or save it: npx securenow threat-model authentication --out prompt.md
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const ui = require('./ui');
|
|
10
|
+
|
|
11
|
+
const BASE = process.env.SECURENOW_THREAT_PROMPTS_URL || 'https://securenow.ai/threat-prompts';
|
|
12
|
+
|
|
13
|
+
// Published categories (slugs). Aliases map common shorthands to the slug.
|
|
14
|
+
const CATEGORIES = [
|
|
15
|
+
'authentication', 'authorization', 'xss-csrf-cors', 'client-side-supply-chain', 'injection',
|
|
16
|
+
'oauth-sso-integrations', 'admin-support-tools', 'payment-business-logic', 'file-upload-download',
|
|
17
|
+
'messaging-notifications', 'rate-limits-and-abuse', 'legacy-endpoints', 'api-security', 'graphql-security',
|
|
18
|
+
'webhooks', 'realtime-websocket-sse', 'serverless-and-edge', 'cloud-infrastructure', 'secrets-and-cloud-iam',
|
|
19
|
+
'dns-tls-certificates', 'supply-chain-cicd', 'storage-and-logs', 'data-privacy-pii', 'ai-llm-features',
|
|
20
|
+
'secrets-and-api-key-hygiene',
|
|
21
|
+
];
|
|
22
|
+
const ALIASES = { auth: 'authentication', authz: 'authorization', oauth: 'oauth-sso-integrations', sso: 'oauth-sso-integrations', api: 'api-security', graphql: 'graphql-security', uploads: 'file-upload-download', upload: 'file-upload-download', xss: 'xss-csrf-cors', csrf: 'xss-csrf-cors', cors: 'xss-csrf-cors', rate: 'rate-limits-and-abuse', 'rate-limits': 'rate-limits-and-abuse', secrets: 'secrets-and-api-key-hygiene', privacy: 'data-privacy-pii', pii: 'data-privacy-pii', ai: 'ai-llm-features', llm: 'ai-llm-features' };
|
|
23
|
+
|
|
24
|
+
function resolveSlug(input) {
|
|
25
|
+
const raw = String(input || '').trim().toLowerCase().replace(/\.md$/, '').replace(/^\d+[-_]/, '');
|
|
26
|
+
if (!raw) return null;
|
|
27
|
+
if (CATEGORIES.includes(raw)) return raw;
|
|
28
|
+
if (ALIASES[raw]) return ALIASES[raw];
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function listCategories() {
|
|
33
|
+
ui.heading('SecureNow threat-modeling prompts');
|
|
34
|
+
console.log(' Copy-paste a prompt into your coding agent, or run it with the SecureNow CLI.\n');
|
|
35
|
+
for (const slug of CATEGORIES) console.log(' ' + slug);
|
|
36
|
+
console.log('\n Print one: securenow threat-model authentication');
|
|
37
|
+
console.log(' Pipe it: claude -p "$(securenow threat-model authentication)"');
|
|
38
|
+
console.log(' Save it: securenow threat-model authentication --out prompt.md');
|
|
39
|
+
console.log(' On the web: securenow.ai/threat-modeling');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function run(args, flags = {}) {
|
|
43
|
+
const category = args[0];
|
|
44
|
+
if (flags.list || (!category && !flags.all)) {
|
|
45
|
+
if (!category && !flags.list) ui.info('Pick a category (or --list to see them all):\n');
|
|
46
|
+
listCategories();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const slug = resolveSlug(category);
|
|
51
|
+
if (!slug) {
|
|
52
|
+
ui.error(`Unknown category "${category}". Run "securenow threat-model --list" to see the ${CATEGORIES.length} categories.`);
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const url = `${BASE}/${slug}.md`;
|
|
58
|
+
let text;
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(url, { headers: { accept: 'text/markdown, text/plain' } });
|
|
61
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
62
|
+
text = await res.text();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
ui.error(`Could not fetch the ${slug} prompt (${err.message}). Open securenow.ai/threat-modeling/${slug} in a browser instead.`);
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (flags.out) {
|
|
70
|
+
fs.writeFileSync(flags.out, text);
|
|
71
|
+
ui.success(`Saved the ${slug} threat-model prompt to ${flags.out} (${text.length.toLocaleString()} chars).`);
|
|
72
|
+
ui.info('Open it with your coding agent at the root of a project that has the securenow SDK installed.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Default: raw prompt to stdout so it pipes cleanly into an agent.
|
|
77
|
+
process.stdout.write(text.endsWith('\n') ? text : text + '\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { run, resolveSlug, CATEGORIES, ALIASES };
|
package/cli/ui.js
CHANGED
|
@@ -182,10 +182,19 @@ function prompt(question, opts = {}) {
|
|
|
182
182
|
|
|
183
183
|
return new Promise((resolve) => {
|
|
184
184
|
const q = opts.secret ? question : ` ${c.cyan('?')} ${question} `;
|
|
185
|
+
let answered = false;
|
|
185
186
|
rl.question(q, (answer) => {
|
|
187
|
+
answered = true;
|
|
186
188
|
rl.close();
|
|
187
189
|
resolve(answer.trim());
|
|
188
190
|
});
|
|
191
|
+
// stdin closed without an answer (EOF, /dev/null, a coding agent's shell): resolve empty instead of
|
|
192
|
+
// leaving the promise pending until the process silently exits.
|
|
193
|
+
rl.once('close', () => {
|
|
194
|
+
if (answered) return;
|
|
195
|
+
process.stderr.write('\n');
|
|
196
|
+
resolve('');
|
|
197
|
+
});
|
|
189
198
|
|
|
190
199
|
if (opts.secret) {
|
|
191
200
|
const onData = (char) => {
|
|
@@ -202,7 +211,15 @@ function prompt(question, opts = {}) {
|
|
|
202
211
|
async function confirm(question, defaultYes = false) {
|
|
203
212
|
const hint = defaultYes ? 'Y/n' : 'y/N';
|
|
204
213
|
const answer = await prompt(`${question} ${c.dim(`(${hint})`)}`);
|
|
205
|
-
if (!answer)
|
|
214
|
+
if (!answer) {
|
|
215
|
+
// A script or coding agent has no terminal to answer from: say so and exit non-zero instead of
|
|
216
|
+
// silently cancelling the write with exit code 0.
|
|
217
|
+
if (!defaultYes && !process.stdin.isTTY) {
|
|
218
|
+
error('No confirmation received (non-interactive shell). Re-run with --yes to confirm.');
|
|
219
|
+
process.exitCode = 1;
|
|
220
|
+
}
|
|
221
|
+
return defaultYes;
|
|
222
|
+
}
|
|
206
223
|
return answer.toLowerCase().startsWith('y');
|
|
207
224
|
}
|
|
208
225
|
|
package/cli.js
CHANGED
|
@@ -671,6 +671,12 @@ const COMMANDS = {
|
|
|
671
671
|
rawArgv: true,
|
|
672
672
|
run: (rawArgs) => require('./cli/run').run(rawArgs),
|
|
673
673
|
},
|
|
674
|
+
'threat-model': {
|
|
675
|
+
desc: 'Print a copy-paste threat-modeling prompt for a category (pipe it into your coding agent)',
|
|
676
|
+
usage: 'securenow threat-model <authentication|authorization|…|--list> [--out prompt.md]',
|
|
677
|
+
flags: { list: 'List all threat-modeling categories', out: 'Write the prompt to a file instead of stdout' },
|
|
678
|
+
run: (a, f) => require('./cli/threatModel').run(a, f),
|
|
679
|
+
},
|
|
674
680
|
redact: {
|
|
675
681
|
desc: 'Redact sensitive fields and OTP codes from a JSON payload',
|
|
676
682
|
usage: "securenow redact '<json>' [--fields f1,f2] [--json]",
|
|
@@ -822,7 +828,7 @@ function showHelp(commandName) {
|
|
|
822
828
|
'Firewall': ['firewall'],
|
|
823
829
|
'Remediation': ['automation', 'ratelimit', 'challenge', 'blocklist', 'revoke', 'allowlist', 'trusted'],
|
|
824
830
|
'Telemetry': ['telemetry', 'log', 'event', 'test-span'],
|
|
825
|
-
'Utilities': ['redact', 'cidr', 'doctor', 'env', 'mcp'],
|
|
831
|
+
'Utilities': ['threat-model', 'redact', 'cidr', 'doctor', 'env', 'mcp'],
|
|
826
832
|
'Settings': ['instances', 'config', 'version'],
|
|
827
833
|
};
|
|
828
834
|
|
|
@@ -64,18 +64,7 @@ const SeverityNumber = {
|
|
|
64
64
|
* Format arguments into a log message
|
|
65
65
|
*/
|
|
66
66
|
function formatMessage(args) {
|
|
67
|
-
return args
|
|
68
|
-
.map((arg) => {
|
|
69
|
-
if (typeof arg === 'object' && arg !== null) {
|
|
70
|
-
try {
|
|
71
|
-
return JSON.stringify(arg);
|
|
72
|
-
} catch (e) {
|
|
73
|
-
return String(arg);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return String(arg);
|
|
77
|
-
})
|
|
78
|
-
.join(' ');
|
|
67
|
+
return require('./log-format').formatLogArgs(args);
|
|
79
68
|
}
|
|
80
69
|
|
|
81
70
|
const { context, trace } = require('@opentelemetry/api');
|
package/events.js
CHANGED
package/firewall-only.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// Do nothing inside Node's internal ESM-hooks thread (see internal-thread.js).
|
|
4
4
|
if (require('./internal-thread').inInternalThread()) return;
|
|
5
|
+
// SECURENOW_DISABLED=1 turns the SDK off for this process (test runs).
|
|
6
|
+
if (require('./sdk-disabled').sdkDisabled('firewall')) return;
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Standalone firewall preload — no OpenTelemetry, no tracing.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Route matching for firewall block, rate-limit and challenge rules.
|
|
4
|
+
//
|
|
5
|
+
// Many servers answer more than one spelling of a path: Express 5 routes case-insensitively and ignores
|
|
6
|
+
// a trailing slash, so POST /API/AUTH/LOGIN/ reaches the /api/auth/login handler. A rule matched only
|
|
7
|
+
// against the raw path let those aliases walk past a route-scoped block. Rules now also match the
|
|
8
|
+
// canonical form (percent-decoded, lower-case, duplicate slashes collapsed; exact rules also ignore a
|
|
9
|
+
// trailing slash). Every caller enforces (block / rate-limit / challenge), so matching more spellings
|
|
10
|
+
// is the safe direction.
|
|
11
|
+
|
|
12
|
+
function normalizePrefixPattern(pattern) {
|
|
13
|
+
const value = String(pattern || '').trim();
|
|
14
|
+
if (!value) return '';
|
|
15
|
+
return value.endsWith('*') ? value.slice(0, -1) : value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function canonicalPath(path, { stripTrailingSlash = false } = {}) {
|
|
19
|
+
let value = String(path || '/');
|
|
20
|
+
try { value = decodeURIComponent(value); } catch (_) { /* keep the raw spelling */ }
|
|
21
|
+
value = value.toLowerCase().replace(/\/{2,}/g, '/');
|
|
22
|
+
if (stripTrailingSlash && value.length > 1) value = value.replace(/\/+$/, '') || '/';
|
|
23
|
+
return value || '/';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function rulePathMatches(rule, path) {
|
|
27
|
+
const pattern = String((rule && rule.pathPattern) || '').trim();
|
|
28
|
+
if (!pattern) return true;
|
|
29
|
+
const mode = (rule && rule.pathMatchMode) || 'prefix';
|
|
30
|
+
if (mode === 'regex') {
|
|
31
|
+
try {
|
|
32
|
+
const re = new RegExp(pattern);
|
|
33
|
+
return re.test(path) || re.test(canonicalPath(path, { stripTrailingSlash: true }));
|
|
34
|
+
} catch (_) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (mode === 'exact') {
|
|
39
|
+
return path === pattern
|
|
40
|
+
|| canonicalPath(path, { stripTrailingSlash: true }) === canonicalPath(pattern, { stripTrailingSlash: true });
|
|
41
|
+
}
|
|
42
|
+
const prefix = normalizePrefixPattern(pattern);
|
|
43
|
+
return path.startsWith(prefix) || canonicalPath(path).startsWith(canonicalPath(prefix));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { canonicalPath, normalizePrefixPattern, rulePathMatches };
|
package/firewall.js
CHANGED
|
@@ -911,16 +911,8 @@ function ruleIpMatches(rule, ip) {
|
|
|
911
911
|
}
|
|
912
912
|
}
|
|
913
913
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
if (!pattern) return true;
|
|
917
|
-
const mode = rule.pathMatchMode || 'prefix';
|
|
918
|
-
if (mode === 'exact') return path === pattern;
|
|
919
|
-
if (mode === 'regex') {
|
|
920
|
-
try { return new RegExp(pattern).test(path); } catch (_) { return false; }
|
|
921
|
-
}
|
|
922
|
-
return path.startsWith(normalizePrefixPattern(pattern));
|
|
923
|
-
}
|
|
914
|
+
// Matches the raw path and its canonical aliases (case, trailing/duplicate slashes, percent-encoding).
|
|
915
|
+
const { rulePathMatches } = require('./firewall-paths');
|
|
924
916
|
|
|
925
917
|
function rateLimitKey(rule, ip) {
|
|
926
918
|
const id = rule.id || rule._id || `${rule.ip || '*'}:${rule.method || 'ALL'}:${rule.pathPattern || '*'}`;
|
package/log-format.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Body of an exported console log record, matching what the terminal shows.
|
|
4
|
+
// - A first string argument with printf-style placeholders (%s %d %i %f %j %o %O %c %%) is formatted
|
|
5
|
+
// like console.log does (util.format), so "%d users" becomes "3 users" instead of "%d users 3".
|
|
6
|
+
// - Errors keep their stack (JSON.stringify(new Error('x')) is "{}").
|
|
7
|
+
// - Other objects stay JSON, as before, so structured logs remain parseable.
|
|
8
|
+
const util = require('util');
|
|
9
|
+
|
|
10
|
+
const PLACEHOLDER = /%[sdifjoOc%]/;
|
|
11
|
+
|
|
12
|
+
function renderArg(arg) {
|
|
13
|
+
if (arg instanceof Error) return arg.stack || `${arg.name}: ${arg.message}`;
|
|
14
|
+
if (typeof arg === 'object' && arg !== null) {
|
|
15
|
+
try { return JSON.stringify(arg); } catch (_) { return util.inspect(arg, { depth: 4, breakLength: Infinity }); }
|
|
16
|
+
}
|
|
17
|
+
return String(arg);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatLogArgs(args) {
|
|
21
|
+
if (!Array.isArray(args) || args.length === 0) return '';
|
|
22
|
+
if (typeof args[0] === 'string' && PLACEHOLDER.test(args[0])) {
|
|
23
|
+
try { return util.format(...args); } catch (_) { /* fall through */ }
|
|
24
|
+
}
|
|
25
|
+
return args.map(renderArg).join(' ');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { formatLogArgs };
|
package/nextjs.js
CHANGED
|
@@ -221,6 +221,7 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
221
221
|
* @param {boolean} options.noUuid - Don't append UUID to service name
|
|
222
222
|
*/
|
|
223
223
|
function registerSecureNow(options = {}) {
|
|
224
|
+
if (require('./sdk-disabled').sdkDisabled('nextjs')) return;
|
|
224
225
|
// Only register once
|
|
225
226
|
if (isRegistered) {
|
|
226
227
|
console.log('[securenow] Already registered, skipping...');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securenow",
|
|
3
|
-
"version": "8.12.
|
|
3
|
+
"version": "8.12.3",
|
|
4
4
|
"description": "OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt - Send traces and logs to any OTLP-compatible backend",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "register.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"securenow-mcp": "mcp/server.js"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "node test-app-config.js && node test-resolve-ip.js && node test-otel-defaults.js && node test-telemetry.js && node test-legacy-semconv.js && node test-otp-redaction.js && node test-header-redaction.js && node test-internal-thread.js && node test-package-contents.js"
|
|
13
|
+
"test": "node test-app-config.js && node test-resolve-ip.js && node test-otel-defaults.js && node test-telemetry.js && node test-legacy-semconv.js && node test-otp-redaction.js && node test-header-redaction.js && node test-firewall-paths.js && node test-log-format.js && node test-sdk-disabled.js && node test-internal-thread.js && node test-package-contents.js"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"opentelemetry",
|
|
@@ -162,6 +162,9 @@
|
|
|
162
162
|
"legacy-semconv.js",
|
|
163
163
|
"otp-redaction.js",
|
|
164
164
|
"header-redaction.js",
|
|
165
|
+
"firewall-paths.js",
|
|
166
|
+
"log-format.js",
|
|
167
|
+
"sdk-disabled.js",
|
|
165
168
|
"rate-limits.js",
|
|
166
169
|
"rate-limits.d.ts",
|
|
167
170
|
"firewall-only.js",
|
package/register.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
// Do nothing inside Node's internal ESM-hooks thread (see internal-thread.js).
|
|
10
10
|
if (require('./internal-thread').inInternalThread()) return;
|
|
11
|
+
// SECURENOW_DISABLED=1 turns the SDK off for this process (test runs).
|
|
12
|
+
if (require('./sdk-disabled').sdkDisabled('register')) return;
|
|
11
13
|
|
|
12
14
|
// 1. Auto-register the ESM loader hook so customers never need --import
|
|
13
15
|
(() => {
|
package/sdk-disabled.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Kill switch: SECURENOW_DISABLED=1 (or true / yes / on) turns the SDK off for one process. Tracing, log
|
|
4
|
+
// export, the firewall, track() events and the sessions guard's sync and session.seen all stay idle, and
|
|
5
|
+
// the guard just calls next(). Meant for unit and integration test runs, which otherwise report to the
|
|
6
|
+
// real app with the project's .securenow credentials.
|
|
7
|
+
let _announced = false;
|
|
8
|
+
|
|
9
|
+
function sdkDisabled(component) {
|
|
10
|
+
const on = /^(1|true|yes|on)$/i.test(String(process.env.SECURENOW_DISABLED || '').trim());
|
|
11
|
+
if (on && component && !_announced) {
|
|
12
|
+
_announced = true;
|
|
13
|
+
try { console.log(`[securenow] disabled by SECURENOW_DISABLED (${component} not started)`); } catch (_) { /* ignore */ }
|
|
14
|
+
}
|
|
15
|
+
return on;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { sdkDisabled };
|
package/sessions.js
CHANGED
|
@@ -130,6 +130,7 @@ function isRevoked(input) {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
function start(options = {}) {
|
|
133
|
+
if (require('./sdk-disabled').sdkDisabled()) return;
|
|
133
134
|
if (options.syncIntervalMs) _syncIntervalMs = options.syncIntervalMs;
|
|
134
135
|
if (_started) return;
|
|
135
136
|
_started = true;
|
|
@@ -277,6 +278,7 @@ function defaultOnRevoked(req, res) {
|
|
|
277
278
|
* @param {number} [options.syncIntervalMs]
|
|
278
279
|
*/
|
|
279
280
|
function guard(options = {}) {
|
|
281
|
+
if (require('./sdk-disabled').sdkDisabled('sessions guard')) return function securenowSessionGuardDisabled(req, res, next) { return next(); };
|
|
280
282
|
start(options);
|
|
281
283
|
if (options.captureWindowMs) _captureWindowMs = options.captureWindowMs;
|
|
282
284
|
const { getSessionId, getUserId } = options;
|
package/tracing.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// Do nothing inside Node's internal ESM-hooks thread (see internal-thread.js).
|
|
4
4
|
if (require('./internal-thread').inInternalThread()) return;
|
|
5
|
+
// SECURENOW_DISABLED=1 turns the SDK off for this process (test runs).
|
|
6
|
+
if (require('./sdk-disabled').sdkDisabled('tracing')) return;
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Preload with: node --require securenow/register app.js
|
|
@@ -665,7 +667,7 @@ if (loggingEnabled) {
|
|
|
665
667
|
_logger.emit({
|
|
666
668
|
severityNumber: sn,
|
|
667
669
|
severityText: st,
|
|
668
|
-
body:
|
|
670
|
+
body: require('./log-format').formatLogArgs(args),
|
|
669
671
|
attributes: { 'log.source': 'console', 'log.method': st.toLowerCase() },
|
|
670
672
|
...(spanCtx && { context: activeCtx }),
|
|
671
673
|
});
|