@sentriflow/cli 0.3.0 → 0.3.2
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 +35 -0
- package/dist/index.js +274 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -151,6 +151,41 @@ sentriflow --show-machine-id
|
|
|
151
151
|
# Output: Machine ID: a1b2c3d4...
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
+
### Cloud Licensing Commands
|
|
155
|
+
|
|
156
|
+
Cloud licensing features require the `@sentriflow/licensing` package, which is provided to customers after purchasing a license. Visit [sentriflow.com.au/pricing](https://sentriflow.com.au/pricing) for more information.
|
|
157
|
+
|
|
158
|
+
| Command | Description |
|
|
159
|
+
|---------|-------------|
|
|
160
|
+
| `sentriflow activate --license-key <key>` | Activate license and download entitled packs |
|
|
161
|
+
| `sentriflow update` | Check for and download pack updates |
|
|
162
|
+
| `sentriflow offline --bundle <path>` | Create offline bundle for air-gapped environments |
|
|
163
|
+
| `sentriflow license` | Show license status and entitled feeds |
|
|
164
|
+
|
|
165
|
+
**Activate a license:**
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Activate license and download all entitled packs
|
|
169
|
+
sentriflow activate --license-key eyJhbGciOiJIUzI1Ni...
|
|
170
|
+
|
|
171
|
+
# Or use environment variable
|
|
172
|
+
export SENTRIFLOW_LICENSE_KEY=eyJhbGciOiJIUzI1Ni...
|
|
173
|
+
sentriflow activate
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Check for updates:**
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Check and download available pack updates
|
|
180
|
+
sentriflow update
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Offline mode:**
|
|
184
|
+
|
|
185
|
+
Downloaded packs are cached in `~/.sentriflow/cache/` and work offline for 72 hours (entitlement cache). The pack files themselves work indefinitely once downloaded.
|
|
186
|
+
|
|
187
|
+
If `@sentriflow/licensing` is not installed, these commands display a message with information on how to obtain access.
|
|
188
|
+
|
|
154
189
|
### Directory Scanning
|
|
155
190
|
|
|
156
191
|
| Option | Description |
|
package/dist/index.js
CHANGED
|
@@ -12129,6 +12129,178 @@ function extractIPSummary(content, options = {}) {
|
|
|
12129
12129
|
};
|
|
12130
12130
|
}
|
|
12131
12131
|
|
|
12132
|
+
// ../core/src/ip/classifier.ts
|
|
12133
|
+
var DEFAULT_FILTER_OPTIONS = {
|
|
12134
|
+
keepPublic: true,
|
|
12135
|
+
keepPrivate: true,
|
|
12136
|
+
keepLoopback: false,
|
|
12137
|
+
keepLinkLocal: false,
|
|
12138
|
+
keepMulticast: false,
|
|
12139
|
+
keepReserved: false,
|
|
12140
|
+
keepUnspecified: false,
|
|
12141
|
+
keepBroadcast: false,
|
|
12142
|
+
keepDocumentation: false,
|
|
12143
|
+
keepCgnat: true
|
|
12144
|
+
};
|
|
12145
|
+
function ipv4ToNumber2(ip) {
|
|
12146
|
+
const parts = ip.split(".");
|
|
12147
|
+
if (parts.length !== 4) return 0;
|
|
12148
|
+
let result = 0;
|
|
12149
|
+
for (let i = 0; i < 4; i++) {
|
|
12150
|
+
const octet = parseInt(parts[i] ?? "0", 10);
|
|
12151
|
+
if (isNaN(octet) || octet < 0 || octet > 255) return 0;
|
|
12152
|
+
result = (result << 8) + octet;
|
|
12153
|
+
}
|
|
12154
|
+
return result >>> 0;
|
|
12155
|
+
}
|
|
12156
|
+
function isInIPv4Range(ip, network, prefix) {
|
|
12157
|
+
const ipNum = ipv4ToNumber2(ip);
|
|
12158
|
+
const netNum = ipv4ToNumber2(network);
|
|
12159
|
+
const mask = prefix === 0 ? 0 : ~0 << 32 - prefix >>> 0;
|
|
12160
|
+
return (ipNum & mask) === (netNum & mask);
|
|
12161
|
+
}
|
|
12162
|
+
function classifyIPv4(ip) {
|
|
12163
|
+
if (ip === "0.0.0.0") return "unspecified";
|
|
12164
|
+
if (ip === "255.255.255.255") return "broadcast";
|
|
12165
|
+
if (isInIPv4Range(ip, "0.0.0.0", 8)) return "unspecified";
|
|
12166
|
+
if (isInIPv4Range(ip, "127.0.0.0", 8)) return "loopback";
|
|
12167
|
+
if (isInIPv4Range(ip, "169.254.0.0", 16)) return "link-local";
|
|
12168
|
+
if (isInIPv4Range(ip, "10.0.0.0", 8)) return "private";
|
|
12169
|
+
if (isInIPv4Range(ip, "172.16.0.0", 12)) return "private";
|
|
12170
|
+
if (isInIPv4Range(ip, "192.168.0.0", 16)) return "private";
|
|
12171
|
+
if (isInIPv4Range(ip, "100.64.0.0", 10)) return "cgnat";
|
|
12172
|
+
if (isInIPv4Range(ip, "192.0.2.0", 24)) return "documentation";
|
|
12173
|
+
if (isInIPv4Range(ip, "198.51.100.0", 24)) return "documentation";
|
|
12174
|
+
if (isInIPv4Range(ip, "203.0.113.0", 24)) return "documentation";
|
|
12175
|
+
if (isInIPv4Range(ip, "224.0.0.0", 4)) return "multicast";
|
|
12176
|
+
if (isInIPv4Range(ip, "240.0.0.0", 4)) return "reserved";
|
|
12177
|
+
return "public";
|
|
12178
|
+
}
|
|
12179
|
+
function classifyIPv4Subnet(subnet) {
|
|
12180
|
+
const slashIndex = subnet.lastIndexOf("/");
|
|
12181
|
+
if (slashIndex === -1) return classifyIPv4(subnet);
|
|
12182
|
+
const network = subnet.substring(0, slashIndex);
|
|
12183
|
+
return classifyIPv4(network);
|
|
12184
|
+
}
|
|
12185
|
+
function expandIPv62(ip) {
|
|
12186
|
+
const zoneIndex = ip.indexOf("%");
|
|
12187
|
+
const addr = zoneIndex !== -1 ? ip.substring(0, zoneIndex) : ip;
|
|
12188
|
+
const parts = addr.split(":");
|
|
12189
|
+
const result = [];
|
|
12190
|
+
for (let i = 0; i < parts.length; i++) {
|
|
12191
|
+
const part = parts[i] ?? "";
|
|
12192
|
+
if (part === "" && i > 0 && i < parts.length - 1) {
|
|
12193
|
+
const nonEmpty = parts.filter((p) => p !== "").length;
|
|
12194
|
+
const zeros = 8 - nonEmpty;
|
|
12195
|
+
for (let j = 0; j < zeros; j++) {
|
|
12196
|
+
result.push(0);
|
|
12197
|
+
}
|
|
12198
|
+
} else if (part !== "") {
|
|
12199
|
+
result.push(parseInt(part, 16) || 0);
|
|
12200
|
+
} else if (i === 0 && (parts[1] ?? "") === "") {
|
|
12201
|
+
const nonEmpty = parts.filter((p) => p !== "").length;
|
|
12202
|
+
const zeros = 8 - nonEmpty;
|
|
12203
|
+
for (let j = 0; j < zeros; j++) {
|
|
12204
|
+
result.push(0);
|
|
12205
|
+
}
|
|
12206
|
+
}
|
|
12207
|
+
}
|
|
12208
|
+
while (result.length < 8) {
|
|
12209
|
+
result.push(0);
|
|
12210
|
+
}
|
|
12211
|
+
return result.slice(0, 8);
|
|
12212
|
+
}
|
|
12213
|
+
function classifyIPv6(ip) {
|
|
12214
|
+
const parts = expandIPv62(ip);
|
|
12215
|
+
if (parts.every((p) => p === 0)) return "unspecified";
|
|
12216
|
+
if (parts.slice(0, 7).every((p) => p === 0) && parts[7] === 1) return "loopback";
|
|
12217
|
+
if ((parts[0] ?? 0) >= 65152 && (parts[0] ?? 0) <= 65215) return "link-local";
|
|
12218
|
+
if (((parts[0] ?? 0) & 65280) === 65280) return "multicast";
|
|
12219
|
+
if (parts[0] === 8193 && parts[1] === 3512) return "documentation";
|
|
12220
|
+
if (((parts[0] ?? 0) & 65024) === 64512) return "private";
|
|
12221
|
+
return "public";
|
|
12222
|
+
}
|
|
12223
|
+
function classifyIPv6Subnet(subnet) {
|
|
12224
|
+
const slashIndex = subnet.lastIndexOf("/");
|
|
12225
|
+
if (slashIndex === -1) return classifyIPv6(subnet);
|
|
12226
|
+
const network = subnet.substring(0, slashIndex);
|
|
12227
|
+
return classifyIPv6(network);
|
|
12228
|
+
}
|
|
12229
|
+
function shouldKeepClassification(classification, options) {
|
|
12230
|
+
switch (classification) {
|
|
12231
|
+
case "public":
|
|
12232
|
+
return options.keepPublic;
|
|
12233
|
+
case "private":
|
|
12234
|
+
return options.keepPrivate;
|
|
12235
|
+
case "loopback":
|
|
12236
|
+
return options.keepLoopback;
|
|
12237
|
+
case "link-local":
|
|
12238
|
+
return options.keepLinkLocal;
|
|
12239
|
+
case "multicast":
|
|
12240
|
+
return options.keepMulticast;
|
|
12241
|
+
case "reserved":
|
|
12242
|
+
return options.keepReserved;
|
|
12243
|
+
case "unspecified":
|
|
12244
|
+
return options.keepUnspecified;
|
|
12245
|
+
case "broadcast":
|
|
12246
|
+
return options.keepBroadcast;
|
|
12247
|
+
case "documentation":
|
|
12248
|
+
return options.keepDocumentation;
|
|
12249
|
+
case "cgnat":
|
|
12250
|
+
return options.keepCgnat;
|
|
12251
|
+
default:
|
|
12252
|
+
return true;
|
|
12253
|
+
}
|
|
12254
|
+
}
|
|
12255
|
+
function filterIPv4Addresses(addresses, options = {}) {
|
|
12256
|
+
const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
|
|
12257
|
+
return addresses.filter((ip) => {
|
|
12258
|
+
const classification = classifyIPv4(ip);
|
|
12259
|
+
return shouldKeepClassification(classification, opts);
|
|
12260
|
+
});
|
|
12261
|
+
}
|
|
12262
|
+
function filterIPv6Addresses(addresses, options = {}) {
|
|
12263
|
+
const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
|
|
12264
|
+
return addresses.filter((ip) => {
|
|
12265
|
+
const classification = classifyIPv6(ip);
|
|
12266
|
+
return shouldKeepClassification(classification, opts);
|
|
12267
|
+
});
|
|
12268
|
+
}
|
|
12269
|
+
function filterIPv4Subnets(subnets, options = {}) {
|
|
12270
|
+
const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
|
|
12271
|
+
return subnets.filter((subnet) => {
|
|
12272
|
+
const classification = classifyIPv4Subnet(subnet);
|
|
12273
|
+
return shouldKeepClassification(classification, opts);
|
|
12274
|
+
});
|
|
12275
|
+
}
|
|
12276
|
+
function filterIPv6Subnets(subnets, options = {}) {
|
|
12277
|
+
const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
|
|
12278
|
+
return subnets.filter((subnet) => {
|
|
12279
|
+
const classification = classifyIPv6Subnet(subnet);
|
|
12280
|
+
return shouldKeepClassification(classification, opts);
|
|
12281
|
+
});
|
|
12282
|
+
}
|
|
12283
|
+
function filterIPSummary(summary, options = {}) {
|
|
12284
|
+
const ipv4Addresses = filterIPv4Addresses(summary.ipv4Addresses, options);
|
|
12285
|
+
const ipv6Addresses = filterIPv6Addresses(summary.ipv6Addresses, options);
|
|
12286
|
+
const ipv4Subnets = filterIPv4Subnets(summary.ipv4Subnets, options);
|
|
12287
|
+
const ipv6Subnets = filterIPv6Subnets(summary.ipv6Subnets, options);
|
|
12288
|
+
const counts = {
|
|
12289
|
+
ipv4: ipv4Addresses.length,
|
|
12290
|
+
ipv6: ipv6Addresses.length,
|
|
12291
|
+
ipv4Subnets: ipv4Subnets.length,
|
|
12292
|
+
ipv6Subnets: ipv6Subnets.length,
|
|
12293
|
+
total: ipv4Addresses.length + ipv6Addresses.length + ipv4Subnets.length + ipv6Subnets.length
|
|
12294
|
+
};
|
|
12295
|
+
return {
|
|
12296
|
+
ipv4Addresses,
|
|
12297
|
+
ipv6Addresses,
|
|
12298
|
+
ipv4Subnets,
|
|
12299
|
+
ipv6Subnets,
|
|
12300
|
+
counts
|
|
12301
|
+
};
|
|
12302
|
+
}
|
|
12303
|
+
|
|
12132
12304
|
// ../core/src/validation/rule-validation.ts
|
|
12133
12305
|
function validateRule2(rule) {
|
|
12134
12306
|
if (typeof rule !== "object" || rule === null) {
|
|
@@ -12328,7 +12500,7 @@ function generateSarif(results, filePath, rules, options = {}, ipSummary) {
|
|
|
12328
12500
|
tool: {
|
|
12329
12501
|
driver: {
|
|
12330
12502
|
name: "Sentriflow",
|
|
12331
|
-
version: "0.3.
|
|
12503
|
+
version: "0.3.2",
|
|
12332
12504
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
12333
12505
|
rules: sarifRules,
|
|
12334
12506
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -12488,7 +12660,7 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
|
|
|
12488
12660
|
tool: {
|
|
12489
12661
|
driver: {
|
|
12490
12662
|
name: "Sentriflow",
|
|
12491
|
-
version: "0.3.
|
|
12663
|
+
version: "0.3.2",
|
|
12492
12664
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
12493
12665
|
rules: sarifRules,
|
|
12494
12666
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -15418,7 +15590,7 @@ function getRulesByCumulusVendor() {
|
|
|
15418
15590
|
|
|
15419
15591
|
// ../rules-default/src/json/cisco-json-rules.json
|
|
15420
15592
|
var cisco_json_rules_default = {
|
|
15421
|
-
$schema: "https://sentriflow.
|
|
15593
|
+
$schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
|
|
15422
15594
|
version: "1.0",
|
|
15423
15595
|
meta: {
|
|
15424
15596
|
name: "Cisco IOS JSON Rules",
|
|
@@ -15615,7 +15787,7 @@ var cisco_json_rules_default = {
|
|
|
15615
15787
|
|
|
15616
15788
|
// ../rules-default/src/json/common-json-rules.json
|
|
15617
15789
|
var common_json_rules_default = {
|
|
15618
|
-
$schema: "https://sentriflow.
|
|
15790
|
+
$schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
|
|
15619
15791
|
version: "1.0",
|
|
15620
15792
|
meta: {
|
|
15621
15793
|
name: "Common JSON Rules",
|
|
@@ -15663,7 +15835,7 @@ var common_json_rules_default = {
|
|
|
15663
15835
|
|
|
15664
15836
|
// ../rules-default/src/json/juniper-json-rules.json
|
|
15665
15837
|
var juniper_json_rules_default = {
|
|
15666
|
-
$schema: "https://sentriflow.
|
|
15838
|
+
$schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
|
|
15667
15839
|
version: "1.0",
|
|
15668
15840
|
meta: {
|
|
15669
15841
|
name: "Juniper JunOS JSON Rules",
|
|
@@ -16172,6 +16344,9 @@ function isValidSentriflowConfig(config) {
|
|
|
16172
16344
|
return false;
|
|
16173
16345
|
}
|
|
16174
16346
|
}
|
|
16347
|
+
if (obj.filterSpecialIps !== void 0 && typeof obj.filterSpecialIps !== "boolean") {
|
|
16348
|
+
return false;
|
|
16349
|
+
}
|
|
16175
16350
|
return true;
|
|
16176
16351
|
}
|
|
16177
16352
|
function isValidDirectoryConfig(config) {
|
|
@@ -16967,7 +17142,7 @@ function enrichResultsWithRuleMetadata(results, rules) {
|
|
|
16967
17142
|
});
|
|
16968
17143
|
}
|
|
16969
17144
|
var program = new Command();
|
|
16970
|
-
program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.3.
|
|
17145
|
+
program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.3.2").argument("[files...]", "Path(s) to configuration file(s) (supports multiple files)").option("--ast", "Output the AST instead of rule results").option("-f, --format <format>", "Output format (json, sarif)", "json").option("-q, --quiet", "Only output failures (suppress passed results)").option("-c, --config <path>", "Path to config file (default: auto-detect)").option("--no-config", "Ignore config file").option("-r, --rules <path>", "Additional rules file to load (legacy)").option(
|
|
16971
17146
|
"--pack <path...>",
|
|
16972
17147
|
"Path(s) to rule pack(s) (auto-detects format: .grx2, .grpx, or unencrypted)"
|
|
16973
17148
|
).option(
|
|
@@ -17025,7 +17200,10 @@ program.name("sentriflow").description("SentriFlow Network Configuration Validat
|
|
|
17025
17200
|
"--max-depth <number>",
|
|
17026
17201
|
"Maximum recursion depth for directory scanning (use with -R)",
|
|
17027
17202
|
(val) => parseInt(val, 10)
|
|
17028
|
-
).option("--progress", "Show progress during directory scanning").
|
|
17203
|
+
).option("--progress", "Show progress during directory scanning").option(
|
|
17204
|
+
"--filter-special-ips",
|
|
17205
|
+
"Filter out special IP ranges (loopback, multicast, reserved, broadcast) from IP summary"
|
|
17206
|
+
).action(async (files, options) => {
|
|
17029
17207
|
try {
|
|
17030
17208
|
if (options.showMachineId) {
|
|
17031
17209
|
try {
|
|
@@ -17099,6 +17277,19 @@ Use: sentriflow --vendor <vendor> <file>`);
|
|
|
17099
17277
|
allowedBaseDirs
|
|
17100
17278
|
// SEC-011: Pass allowed base dirs for rule file validation
|
|
17101
17279
|
});
|
|
17280
|
+
let filterSpecialIps = options.filterSpecialIps ?? false;
|
|
17281
|
+
if (!options.filterSpecialIps && options.config !== false) {
|
|
17282
|
+
const configPath = options.config ?? findConfigFile(configSearchDir);
|
|
17283
|
+
if (configPath) {
|
|
17284
|
+
try {
|
|
17285
|
+
const config = await loadConfigFile(configPath, allowedBaseDirs);
|
|
17286
|
+
if (config.filterSpecialIps) {
|
|
17287
|
+
filterSpecialIps = true;
|
|
17288
|
+
}
|
|
17289
|
+
} catch {
|
|
17290
|
+
}
|
|
17291
|
+
}
|
|
17292
|
+
}
|
|
17102
17293
|
if (options.listCategories) {
|
|
17103
17294
|
const counts = /* @__PURE__ */ new Map();
|
|
17104
17295
|
for (const rule of rules) {
|
|
@@ -17338,7 +17529,21 @@ Parsing complete: ${allAsts.length} files`);
|
|
|
17338
17529
|
const passed = results2.filter((r) => r.passed).length;
|
|
17339
17530
|
totalFailures += failures;
|
|
17340
17531
|
totalPassed += passed;
|
|
17341
|
-
|
|
17532
|
+
let fileIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
|
|
17533
|
+
if (filterSpecialIps) {
|
|
17534
|
+
fileIpSummary = filterIPSummary(fileIpSummary, {
|
|
17535
|
+
keepPublic: true,
|
|
17536
|
+
keepPrivate: true,
|
|
17537
|
+
keepCgnat: true,
|
|
17538
|
+
keepLoopback: false,
|
|
17539
|
+
keepLinkLocal: false,
|
|
17540
|
+
keepMulticast: false,
|
|
17541
|
+
keepReserved: false,
|
|
17542
|
+
keepUnspecified: false,
|
|
17543
|
+
keepBroadcast: false,
|
|
17544
|
+
keepDocumentation: false
|
|
17545
|
+
});
|
|
17546
|
+
}
|
|
17342
17547
|
allFileResults.push({
|
|
17343
17548
|
filePath: filePath2,
|
|
17344
17549
|
results: results2,
|
|
@@ -17454,6 +17659,20 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
17454
17659
|
let stdinIpSummary;
|
|
17455
17660
|
try {
|
|
17456
17661
|
stdinIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
|
|
17662
|
+
if (filterSpecialIps) {
|
|
17663
|
+
stdinIpSummary = filterIPSummary(stdinIpSummary, {
|
|
17664
|
+
keepPublic: true,
|
|
17665
|
+
keepPrivate: true,
|
|
17666
|
+
keepCgnat: true,
|
|
17667
|
+
keepLoopback: false,
|
|
17668
|
+
keepLinkLocal: false,
|
|
17669
|
+
keepMulticast: false,
|
|
17670
|
+
keepReserved: false,
|
|
17671
|
+
keepUnspecified: false,
|
|
17672
|
+
keepBroadcast: false,
|
|
17673
|
+
keepDocumentation: false
|
|
17674
|
+
});
|
|
17675
|
+
}
|
|
17457
17676
|
} catch (error) {
|
|
17458
17677
|
if (error instanceof InputValidationError) {
|
|
17459
17678
|
console.error(`Input validation error: ${error.message}`);
|
|
@@ -17531,7 +17750,21 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
17531
17750
|
const passed = results2.filter((r) => r.passed).length;
|
|
17532
17751
|
totalFailures += failures;
|
|
17533
17752
|
totalPassed += passed;
|
|
17534
|
-
|
|
17753
|
+
let fileIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
|
|
17754
|
+
if (filterSpecialIps) {
|
|
17755
|
+
fileIpSummary = filterIPSummary(fileIpSummary, {
|
|
17756
|
+
keepPublic: true,
|
|
17757
|
+
keepPrivate: true,
|
|
17758
|
+
keepCgnat: true,
|
|
17759
|
+
keepLoopback: false,
|
|
17760
|
+
keepLinkLocal: false,
|
|
17761
|
+
keepMulticast: false,
|
|
17762
|
+
keepReserved: false,
|
|
17763
|
+
keepUnspecified: false,
|
|
17764
|
+
keepBroadcast: false,
|
|
17765
|
+
keepDocumentation: false
|
|
17766
|
+
});
|
|
17767
|
+
}
|
|
17535
17768
|
allFileResults.push({
|
|
17536
17769
|
filePath: filePath2,
|
|
17537
17770
|
results: results2,
|
|
@@ -17647,7 +17880,21 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
17647
17880
|
if (options.quiet) {
|
|
17648
17881
|
results = results.filter((r) => !r.passed);
|
|
17649
17882
|
}
|
|
17650
|
-
|
|
17883
|
+
let ipSummary = extractIPSummary(content, { includeSubnetNetworks: true });
|
|
17884
|
+
if (filterSpecialIps) {
|
|
17885
|
+
ipSummary = filterIPSummary(ipSummary, {
|
|
17886
|
+
keepPublic: true,
|
|
17887
|
+
keepPrivate: true,
|
|
17888
|
+
keepCgnat: true,
|
|
17889
|
+
keepLoopback: false,
|
|
17890
|
+
keepLinkLocal: false,
|
|
17891
|
+
keepMulticast: false,
|
|
17892
|
+
keepReserved: false,
|
|
17893
|
+
keepUnspecified: false,
|
|
17894
|
+
keepBroadcast: false,
|
|
17895
|
+
keepDocumentation: false
|
|
17896
|
+
});
|
|
17897
|
+
}
|
|
17651
17898
|
if (options.format === "sarif") {
|
|
17652
17899
|
const sarifOptions = {
|
|
17653
17900
|
relativePaths: options.relativePaths,
|
|
@@ -17693,6 +17940,23 @@ async function loadLicensingExtension() {
|
|
|
17693
17940
|
licensing.registerCommands(program);
|
|
17694
17941
|
}
|
|
17695
17942
|
} catch {
|
|
17943
|
+
const licensingMessage = `
|
|
17944
|
+
Cloud licensing features require the @sentriflow/licensing package.
|
|
17945
|
+
|
|
17946
|
+
This package is provided to customers after purchasing a license.
|
|
17947
|
+
Visit https://sentriflow.com.au/pricing for more information.
|
|
17948
|
+
|
|
17949
|
+
Once you have a license, you'll receive access to the private package
|
|
17950
|
+
and can enable cloud features like pack downloads and license activation.
|
|
17951
|
+
`.trim();
|
|
17952
|
+
const fallbackAction = () => {
|
|
17953
|
+
console.log(licensingMessage);
|
|
17954
|
+
process.exit(0);
|
|
17955
|
+
};
|
|
17956
|
+
program.command("activate").description("Activate your SentriFlow license (requires @sentriflow/licensing)").action(fallbackAction);
|
|
17957
|
+
program.command("update").description("Check and download pack updates (requires @sentriflow/licensing)").action(fallbackAction);
|
|
17958
|
+
program.command("offline").description("Manage offline bundles (requires @sentriflow/licensing)").action(fallbackAction);
|
|
17959
|
+
program.command("license").description("Show license status (requires @sentriflow/licensing)").action(fallbackAction);
|
|
17696
17960
|
}
|
|
17697
17961
|
}
|
|
17698
17962
|
loadLicensingExtension().finally(() => {
|