securenow 8.20.2 → 8.21.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/NPM_README.md +1 -1
- package/SKILL-CLI.md +15 -0
- package/app-config.js +1 -1
- package/cli/protect.js +137 -0
- package/cli.js +14 -1
- package/mcp/catalog.js +63 -2
- package/package.json +1 -1
- package/resolve-ip.js +26 -2
package/NPM_README.md
CHANGED
|
@@ -1755,7 +1755,7 @@ proxy_set_header X-Real-IP $remote_addr;
|
|
|
1755
1755
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
1756
1756
|
```
|
|
1757
1757
|
|
|
1758
|
-
SecureNow trusts private, loopback, and same-host proxy peers automatically. For external load balancers or public proxy IPs, add them to `config.networking.trustedProxies` in `.securenow/runtime.json` or the mounted runtime credentials file. If no forwarded header reaches the app, SecureNow can detect the attack shape but cannot recover the real visitor IP from traces.
|
|
1758
|
+
SecureNow trusts private, loopback, and same-host proxy peers automatically. For external load balancers or public proxy IPs, add them to `config.networking.trustedProxies` in `.securenow/runtime.json` or the mounted runtime credentials file: exact IPs, IPv4 ranges such as `"203.0.113.0/24"`, or `"cloudflare"` for Cloudflare's published edge ranges (8.21.0+). If no forwarded header reaches the app, SecureNow can detect the attack shape but cannot recover the real visitor IP from traces. Starter protection refuses block mode for an app whose traffic arrives through a proxy it doesn't trust, because a block would hit the proxy and everyone behind it.
|
|
1759
1759
|
|
|
1760
1760
|
**Check 6: Using PM2?**
|
|
1761
1761
|
|
package/SKILL-CLI.md
CHANGED
|
@@ -476,6 +476,21 @@ securenow firewall test-ip <ip> --app <key> --env local # check if IP would be
|
|
|
476
476
|
|
|
477
477
|
**Zero-config setup:** running `securenow app connect` enables the selected app's firewall toggle, auto-mints a runtime API key (scoped `traces:write + logs:write + firewall:read + blocklist:read + allowlist:read`), and writes it to `.securenow/runtime.json` after the app is selected. No `SECURENOW_API_KEY` env var needed. If the user already has a key, `securenow api-key set snk_live_...` achieves the same thing; if they only have admin auth, `securenow api-key create` mints and saves a fresh runtime key. See [the landing firewall page](https://securenow.ai/firewall) for an overview.
|
|
478
478
|
|
|
479
|
+
### Starter Protection — Rules Every App Gets
|
|
480
|
+
|
|
481
|
+
An app gets the starter pack when it is created or first sends traffic: about 20 rules (SQL injection, XSS, traversal, command and template injection, Log4Shell, Spring4Shell, metadata SSRF, CRLF, prototype pollution, `.env`/`.git`/backup probes, exploit kits, attack-tool user agents, body payloads, failed sign-ins, 404 scanning). They start in **monitor** mode: they alert, nothing is blocked by them. In **block** mode the 14 block-ready rules block the address for 6 hours on their exploit signatures (URL, headers, user agent; bodies and the scheduled rules only alert).
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
securenow protect # status of the current app: mode, rules, alerts, ready to block?
|
|
485
|
+
securenow protect status --all --json # every app
|
|
486
|
+
securenow protect enable --app <key> # turn it on (monitor mode)
|
|
487
|
+
securenow protect block --app <key> # a person turns blocking on; refused unless the app sees real client IPs
|
|
488
|
+
securenow protect monitor --app <key> # back to alerts only
|
|
489
|
+
securenow protect disable --app <key> # off; stays off until someone turns it on
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Only a person signed in (`securenow admin login`) can turn blocking on; API keys, MCP connectors and the agent get a 403. Blocking is refused when more than 20% of the app's traffic reaches it through a proxy the SDK doesn't trust: add the proxy to `config.networking.trustedProxies` (IPs, IPv4 ranges or `"cloudflare"`). Tune a noisy starter rule like any rule (exclusions, `alerts rules update`); pack updates never overwrite a rule a person edited.
|
|
493
|
+
|
|
479
494
|
### Blocklist — Block Malicious IPs
|
|
480
495
|
|
|
481
496
|
```bash
|
package/app-config.js
CHANGED
|
@@ -133,7 +133,7 @@ const CONFIG_EXPLANATIONS = Object.freeze({
|
|
|
133
133
|
'config.firewall.cloudflare': 'Cloudflare API token/account id for Layer 4 WAF. Secrets: do not commit.',
|
|
134
134
|
'config.firewall.aws': 'AWS WAF IP set configuration for Layer 4 WAF.',
|
|
135
135
|
'config.firewall.gcp': 'GCP Cloud Armor policy configuration for Layer 4 WAF.',
|
|
136
|
-
'config.networking.trustedProxies': 'Additional
|
|
136
|
+
'config.networking.trustedProxies': 'Additional proxies whose X-Forwarded-For headers should be trusted: IPs, IPv4 ranges (CIDR) or "cloudflare".',
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
function clone(value) {
|
package/cli/protect.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// securenow protect: the starter protection every app gets (API: /protection). Monitor mode alerts; block mode
|
|
4
|
+
// also blocks the exploit signatures of the block-ready rules, and only a person turns it on.
|
|
5
|
+
|
|
6
|
+
const { api, requireAuth } = require('./client');
|
|
7
|
+
const ui = require('./ui');
|
|
8
|
+
|
|
9
|
+
function resolveAppKey(flags) {
|
|
10
|
+
if (flags && flags.app) return String(flags.app);
|
|
11
|
+
try {
|
|
12
|
+
const { resolveAppKey: fromConfig } = require('../app-config');
|
|
13
|
+
return fromConfig() || null;
|
|
14
|
+
} catch (_) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function requireApp(flags) {
|
|
20
|
+
const appKey = resolveAppKey(flags);
|
|
21
|
+
if (!appKey) {
|
|
22
|
+
ui.error('No app selected. Pass --app <key> or run `securenow app connect` / `securenow apps default <key>`.');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
return appKey;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function modeLabel(app) {
|
|
29
|
+
if (app.mode === 'block') return ui.c.green('BLOCK');
|
|
30
|
+
if (app.mode === 'monitor') return ui.c.cyan('MONITOR');
|
|
31
|
+
return ui.c.yellow(app.state === 'unset' ? 'NOT YET ON' : 'OFF');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function printApp(app, { verbose }) {
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(` ${ui.c.bold(app.appName)} ${ui.c.dim(app.appKey)} ${modeLabel(app)}`);
|
|
37
|
+
const summary = app.summary || {};
|
|
38
|
+
ui.keyValue([
|
|
39
|
+
['Rules', `${summary.active || 0} active of ${summary.rules || 0} (${summary.blockReady || 0} can block, ${summary.blocking || 0} blocking)`],
|
|
40
|
+
['Alerts, last 7 days', `${summary.alerts7d || 0} (${summary.falsePositives7d || 0} marked false positive)`],
|
|
41
|
+
['Pack version', `${app.packVersion || '-'} (latest ${app.latestPackVersion})${summary.customized ? `, ${summary.customized} rule(s) edited by a person` : ''}${summary.missing ? `, ${summary.missing} missing` : ''}`],
|
|
42
|
+
]);
|
|
43
|
+
if (app.mode === 'monitor') {
|
|
44
|
+
const readiness = app.readiness || {};
|
|
45
|
+
console.log(` ${ui.c.dim('Ready to block:')} ${readiness.ready ? ui.c.green('yes') : ui.c.yellow('not yet')}`);
|
|
46
|
+
for (const check of readiness.checks || []) {
|
|
47
|
+
const mark = check.ok === true ? ui.c.green('✓') : check.ok === false ? ui.c.yellow('·') : ui.c.dim('?');
|
|
48
|
+
console.log(` ${mark} ${check.detail}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (verbose) {
|
|
52
|
+
ui.table(
|
|
53
|
+
['Rule', 'Kind', 'Mode', 'Status', 'Alerts 7d', 'Blocks'],
|
|
54
|
+
(app.rules || []).map((rule) => [
|
|
55
|
+
ui.truncate(rule.name, 52),
|
|
56
|
+
rule.kind,
|
|
57
|
+
rule.present ? rule.mode : '-',
|
|
58
|
+
rule.present ? `${rule.status}${rule.customized ? ' (edited)' : ''}` : ui.c.dim('missing'),
|
|
59
|
+
String(rule.alerts7d || 0),
|
|
60
|
+
rule.blocking ? ui.c.green('yes') : rule.blockReady ? ui.c.dim('ready') : ui.c.dim('alerts only'),
|
|
61
|
+
])
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function status(args, flags) {
|
|
67
|
+
requireAuth();
|
|
68
|
+
const appKey = flags.app ? String(flags.app) : (flags.all ? null : resolveAppKey(flags));
|
|
69
|
+
const s = ui.spinner('Reading starter protection');
|
|
70
|
+
try {
|
|
71
|
+
const data = await api.get('/protection', { query: appKey ? { app: appKey } : {} });
|
|
72
|
+
s.stop('Starter protection');
|
|
73
|
+
if (flags.json) { ui.json(data); return; }
|
|
74
|
+
const apps = data.apps || [];
|
|
75
|
+
if (apps.length === 0) {
|
|
76
|
+
console.log('');
|
|
77
|
+
ui.info('No applications.');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
for (const app of apps) printApp(app, { verbose: Boolean(appKey) || apps.length === 1 || flags.rules });
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(` ${ui.c.dim('Turn blocking on (a person): securenow protect block --app <key>. Back to alerts only: securenow protect monitor --app <key>.')}`);
|
|
83
|
+
console.log('');
|
|
84
|
+
} catch (err) {
|
|
85
|
+
s.fail('Could not read starter protection');
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function change(path, flags, { verb, confirmText }) {
|
|
91
|
+
requireAuth();
|
|
92
|
+
const appKey = requireApp(flags);
|
|
93
|
+
if (confirmText && !flags.yes) {
|
|
94
|
+
const ok = await ui.confirm(confirmText.replace('{app}', appKey));
|
|
95
|
+
if (!ok) {
|
|
96
|
+
ui.info('Nothing changed.');
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const s = ui.spinner(`${verb} ${appKey}`);
|
|
101
|
+
try {
|
|
102
|
+
const data = await api.post(path, { app: appKey });
|
|
103
|
+
s.stop(data.message || 'Done');
|
|
104
|
+
if (flags.json) { ui.json(data); return data; }
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(` ${data.message || 'Done.'}`);
|
|
107
|
+
console.log('');
|
|
108
|
+
return data;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
s.fail(`${verb} failed`);
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function enable(args, flags) {
|
|
116
|
+
return change('/protection/enable', flags, { verb: 'Turning starter protection on for' });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function disable(args, flags) {
|
|
120
|
+
return change('/protection/disable', flags, {
|
|
121
|
+
verb: 'Turning starter protection off for',
|
|
122
|
+
confirmText: 'Turn starter protection off for {app}? Its rules stop alerting and blocking, and stay off until someone turns them on again.',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function block(args, flags) {
|
|
127
|
+
return change('/protection/block', flags, {
|
|
128
|
+
verb: 'Turning blocking on for',
|
|
129
|
+
confirmText: 'Turn blocking on for {app}? Addresses that send the exploit signatures of the block-ready starter rules are blocked for 6 hours.',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function monitor(args, flags) {
|
|
134
|
+
return change('/protection/monitor', flags, { verb: 'Switching to monitor mode for' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { status, enable, disable, block, monitor };
|
package/cli.js
CHANGED
|
@@ -456,6 +456,19 @@ const COMMANDS = {
|
|
|
456
456
|
},
|
|
457
457
|
defaultSub: 'status',
|
|
458
458
|
},
|
|
459
|
+
protect: {
|
|
460
|
+
desc: 'Starter protection per app: the rules every app gets, monitor or block mode',
|
|
461
|
+
usage: 'securenow protect <status|enable|disable|block|monitor> [--app <key>] [--json]',
|
|
462
|
+
flags: { app: 'App key (defaults to logged-in app)', all: 'With status: every app', rules: 'With status: list each rule', yes: 'Skip the confirmation (block, disable)', json: 'Output as JSON' },
|
|
463
|
+
sub: {
|
|
464
|
+
status: { desc: 'Each app\'s starter protection: mode, rules, alerts and whether it is ready to block', usage: 'securenow protect status [--app <key> | --all] [--rules] [--json]', run: (a, f) => require('./cli/protect').status(a, f) },
|
|
465
|
+
enable: { desc: 'Turn starter protection on for an app (monitor mode: rules alert, nothing is blocked)', usage: 'securenow protect enable [--app <key>]', run: (a, f) => require('./cli/protect').enable(a, f) },
|
|
466
|
+
disable: { desc: 'Turn starter protection off for an app; it stays off until someone turns it on', usage: 'securenow protect disable [--app <key>] [--yes]', run: (a, f) => require('./cli/protect').disable(a, f) },
|
|
467
|
+
block: { desc: 'Turn blocking on (a person signed in): block-ready rules block exploit signatures for 6 hours', usage: 'securenow protect block [--app <key>] [--yes]', run: (a, f) => require('./cli/protect').block(a, f) },
|
|
468
|
+
monitor: { desc: 'Back to monitor mode: the starter rules alert, nothing is blocked by them', usage: 'securenow protect monitor [--app <key>]', run: (a, f) => require('./cli/protect').monitor(a, f) },
|
|
469
|
+
},
|
|
470
|
+
defaultSub: 'status',
|
|
471
|
+
},
|
|
459
472
|
ratelimit: {
|
|
460
473
|
desc: 'Manage soft rate-limit remediation rules',
|
|
461
474
|
usage: 'securenow ratelimit <list|add|parse|from-text|show|test|enable|disable|remove> [options]',
|
|
@@ -896,7 +909,7 @@ function showHelp(commandName) {
|
|
|
896
909
|
'Authentication': ['login', 'logout', 'whoami', 'credentials', 'admin', 'api-key'],
|
|
897
910
|
'Applications': ['apps', 'app', 'init', 'status'],
|
|
898
911
|
'Observe': ['traces', 'logs', 'analytics'],
|
|
899
|
-
'Detect & Respond': ['agent', 'human', 'review', 'notifications', 'alerts', 'fp'],
|
|
912
|
+
'Detect & Respond': ['protect', 'agent', 'human', 'review', 'notifications', 'alerts', 'fp'],
|
|
900
913
|
'Investigate': ['ip', 'forensics'],
|
|
901
914
|
'Firewall': ['firewall'],
|
|
902
915
|
'Remediation': ['automation', 'ratelimit', 'challenge', 'blocklist', 'revoke', 'allowlist', 'trusted'],
|
package/mcp/catalog.js
CHANGED
|
@@ -1302,6 +1302,65 @@ const TOOLS = [
|
|
|
1302
1302
|
...environmentInput,
|
|
1303
1303
|
}),
|
|
1304
1304
|
},
|
|
1305
|
+
{
|
|
1306
|
+
name: 'securenow_protection_status',
|
|
1307
|
+
title: 'Starter Protection Status',
|
|
1308
|
+
description: 'Each app\'s starter protection: the SecureNow rules every app gets when it first sends traffic (SQL injection, XSS, traversal, command and template injection, Log4Shell, metadata SSRF, secret and backup probes, exploit kits, attack-tool user agents, body payloads, failed sign-ins, 404 scanning). Shows the mode (monitor: rules alert only; block: block-ready rules block the address for 6 hours), each rule, alerts in the last 7 days, false positives people marked, and whether the app is ready to block (a week of monitoring without a false positive). Only a person turns blocking on, on the dashboard or with `securenow protect block`.',
|
|
1309
|
+
scope: 'alerts:read',
|
|
1310
|
+
readOnly: true,
|
|
1311
|
+
method: 'GET',
|
|
1312
|
+
endpoint: '/protection',
|
|
1313
|
+
queryFields: ['app'],
|
|
1314
|
+
inputSchema: objectSchema({
|
|
1315
|
+
app: string('Optional application key; every app the caller can see when left out.'),
|
|
1316
|
+
}),
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
name: 'securenow_protection_enable',
|
|
1320
|
+
title: 'Turn Starter Protection On',
|
|
1321
|
+
description: 'Turn starter protection on for an app: creates any missing starter rules and turns them on. An app that was off starts in monitor mode (the rules alert, nothing is blocked). Write action; requires confirmation.',
|
|
1322
|
+
scope: 'alerts:write',
|
|
1323
|
+
readOnly: false,
|
|
1324
|
+
confirm: true,
|
|
1325
|
+
method: 'POST',
|
|
1326
|
+
endpoint: '/protection/enable',
|
|
1327
|
+
bodyFields: ['app'],
|
|
1328
|
+
inputSchema: objectSchema({
|
|
1329
|
+
app: string('Application key.'),
|
|
1330
|
+
...confirmSchema,
|
|
1331
|
+
}, ['app', 'confirm', 'reason']),
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
name: 'securenow_protection_monitor',
|
|
1335
|
+
title: 'Starter Protection: Back to Monitor Mode',
|
|
1336
|
+
description: 'Switch an app\'s starter protection back to monitor mode: its rules keep alerting, none of them blocks. Use it when a starter rule blocked legitimate traffic, then tune or exclude the rule. Write action; requires confirmation.',
|
|
1337
|
+
scope: 'alerts:write',
|
|
1338
|
+
readOnly: false,
|
|
1339
|
+
confirm: true,
|
|
1340
|
+
method: 'POST',
|
|
1341
|
+
endpoint: '/protection/monitor',
|
|
1342
|
+
bodyFields: ['app'],
|
|
1343
|
+
inputSchema: objectSchema({
|
|
1344
|
+
app: string('Application key.'),
|
|
1345
|
+
...confirmSchema,
|
|
1346
|
+
}, ['app', 'confirm', 'reason']),
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
name: 'securenow_protection_disable',
|
|
1350
|
+
title: 'Turn Starter Protection Off',
|
|
1351
|
+
description: 'Turn starter protection off for an app: its starter rules stop alerting and blocking, and stay off until someone turns protection on again. Ask the user first. Write action; requires confirmation.',
|
|
1352
|
+
scope: 'alerts:write',
|
|
1353
|
+
readOnly: false,
|
|
1354
|
+
destructive: true,
|
|
1355
|
+
confirm: true,
|
|
1356
|
+
method: 'POST',
|
|
1357
|
+
endpoint: '/protection/disable',
|
|
1358
|
+
bodyFields: ['app'],
|
|
1359
|
+
inputSchema: objectSchema({
|
|
1360
|
+
app: string('Application key.'),
|
|
1361
|
+
...confirmSchema,
|
|
1362
|
+
}, ['app', 'confirm', 'reason']),
|
|
1363
|
+
},
|
|
1305
1364
|
{
|
|
1306
1365
|
name: 'securenow_challenge_list',
|
|
1307
1366
|
title: 'List Challenges',
|
|
@@ -2650,6 +2709,7 @@ const AGENT_PATROL_RUNBOOK = [
|
|
|
2650
2709
|
' - Benign noise that needs no rule change: close the group with outcome "false_positive" (or "clean").',
|
|
2651
2710
|
' - Rows that disagree with each other: decide them one by one (securenow_alert_review_run_resolve), or leave them.',
|
|
2652
2711
|
'D. When you learn something durable about this org from its people (a correction, a benign actor a person confirmed) or from a rule\'s recurring noise, propose one specific line with securenow_agent_remember; a person approves it before later runs use it. Never propose anything that only telemetry says.',
|
|
2712
|
+
'E. Starter protection: rules named after a threat class ("SQL injection in the URL", "Probe for secrets, source control or backups", ...) are the starter pack every app gets. Their rows follow B and C like any other. When one keeps misfiring on legitimate traffic, prefer an exclusion pinned to the IP (section C) over editing the rule. If calls remain at the end, check securenow_protection_status: when an app says ready to block, say so in your report; only a person turns blocking on.',
|
|
2653
2713
|
'',
|
|
2654
2714
|
'Every write needs confirm true and a reason. Reasons and summaries are read by the org\'s people: plain, specific, with the numbers that decided it (requests, paths, status codes, sign-in failures, risk), never vague.',
|
|
2655
2715
|
'',
|
|
@@ -2973,6 +3033,7 @@ const AGENT_PROFILE_TOOLS = new Set([
|
|
|
2973
3033
|
'securenow_alert_rule_exclusions_list',
|
|
2974
3034
|
'securenow_alert_rule_exclusion_add',
|
|
2975
3035
|
'securenow_alert_rule_request_promotion',
|
|
3036
|
+
'securenow_protection_status',
|
|
2976
3037
|
'securenow_ip_traces',
|
|
2977
3038
|
'securenow_traces_show',
|
|
2978
3039
|
'securenow_logs_for_trace',
|
|
@@ -2986,7 +3047,7 @@ const TOOL_PROFILES = {
|
|
|
2986
3047
|
detection: {
|
|
2987
3048
|
description: 'Write and tune detections: draft, create, test, run and explain alert rules, with the traces, logs, notifications and forensics to check them against.',
|
|
2988
3049
|
includes: (tool) => /^securenow_alert_rules?_/.test(tool.name)
|
|
2989
|
-
|| /^securenow_(traces|logs|forensics|ip)_/.test(tool.name)
|
|
3050
|
+
|| /^securenow_(traces|logs|forensics|ip|protection)_/.test(tool.name)
|
|
2990
3051
|
|| /^securenow_notifications_(list|get|batch_get)$/.test(tool.name),
|
|
2991
3052
|
},
|
|
2992
3053
|
agent: {
|
|
@@ -2995,7 +3056,7 @@ const TOOL_PROFILES = {
|
|
|
2995
3056
|
},
|
|
2996
3057
|
responder: {
|
|
2997
3058
|
description: 'Respond to alerts: notifications, the human action queue and alert reviews, blocks, allowlist, trusted IPs, rate limits, challenges, automations, firewall and IP intelligence.',
|
|
2998
|
-
includes: (tool) => /^securenow_(notifications|human|alert_review|blocklist|allowlist|trusted|rate_limit|challenge|automation|ip|firewall)_/.test(tool.name)
|
|
3059
|
+
includes: (tool) => /^securenow_(notifications|human|alert_review|blocklist|allowlist|trusted|rate_limit|challenge|automation|ip|firewall|protection)_/.test(tool.name)
|
|
2999
3060
|
|| ['securenow_alert_rule_get', 'securenow_alert_rule_runs', 'securenow_alert_rule_explain', 'securenow_traces_list', 'securenow_traces_show', 'securenow_logs_for_trace'].includes(tool.name),
|
|
3000
3061
|
},
|
|
3001
3062
|
};
|
package/package.json
CHANGED
package/resolve-ip.js
CHANGED
|
@@ -2,12 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const appConfig = require('./app-config');
|
|
5
|
+
const { createMatcher } = require('./cidr');
|
|
5
6
|
|
|
6
7
|
const LOOPBACK_RE = /^(127\.|::1$|::ffff:127\.)/;
|
|
7
8
|
const PRIVATE_IP_RE = /^(127\.|::1$|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.|f[cd][0-9a-f]{2}:)/i;
|
|
8
9
|
|
|
10
|
+
// Named proxy presets for config.networking.trustedProxies. Cloudflare's published IPv4 edge ranges
|
|
11
|
+
// (https://www.cloudflare.com/ips-v4): an app behind Cloudflare trusts them with "cloudflare".
|
|
12
|
+
const PROXY_PRESETS = {
|
|
13
|
+
cloudflare: [
|
|
14
|
+
'173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18',
|
|
15
|
+
'108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17',
|
|
16
|
+
'162.158.0.0/15', '104.16.0.0/13', '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22',
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Exact addresses (IPv4 or IPv6), IPv4 CIDR ranges and presets.
|
|
21
|
+
function trustedProxyMatcher(entries) {
|
|
22
|
+
const expanded = [];
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const value = String(entry || '').trim();
|
|
25
|
+
if (!value) continue;
|
|
26
|
+
const preset = PROXY_PRESETS[value.toLowerCase()];
|
|
27
|
+
if (preset) expanded.push(...preset);
|
|
28
|
+
else expanded.push(value.includes('/') ? value : normalizeIp(value));
|
|
29
|
+
}
|
|
30
|
+
return expanded.length ? createMatcher(expanded) : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
9
33
|
const trustedProxies = appConfig.listConfig('networking.trustedProxies');
|
|
10
|
-
const
|
|
34
|
+
const trustedProxyMatch = trustedProxyMatcher(trustedProxies);
|
|
11
35
|
|
|
12
36
|
let _hostIps = null;
|
|
13
37
|
function normalizeIp(value) {
|
|
@@ -47,7 +71,7 @@ function isFromTrustedProxy(socketIp) {
|
|
|
47
71
|
if (!socketIp) return false;
|
|
48
72
|
const normalized = normalizeIp(socketIp);
|
|
49
73
|
if (!normalized) return false;
|
|
50
|
-
if (
|
|
74
|
+
if (trustedProxyMatch && trustedProxyMatch.isBlocked(normalized)) return true;
|
|
51
75
|
if (getHostIps().has(normalized)) return true;
|
|
52
76
|
return PRIVATE_IP_RE.test(normalized);
|
|
53
77
|
}
|