@sentriflow/cli 0.2.1 → 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 +1202 -373
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3811,8 +3811,6 @@ var MAX_NESTING_DEPTH = 50;
|
|
|
3811
3811
|
var MAX_LINE_COUNT = 1e5;
|
|
3812
3812
|
var MAX_TRAVERSAL_DEPTH = 20;
|
|
3813
3813
|
var ALLOWED_CONFIG_EXTENSIONS = [".js", ".ts", ".mjs", ".cjs"];
|
|
3814
|
-
var ALLOWED_ENCRYPTED_PACK_EXTENSIONS = [".grpx"];
|
|
3815
|
-
var ALLOWED_GRX2_PACK_EXTENSIONS = [".grx2"];
|
|
3816
3814
|
var ALLOWED_JSON_RULES_EXTENSIONS = [".json"];
|
|
3817
3815
|
var MAX_CONFIG_FILE_SIZE = 1024 * 1024;
|
|
3818
3816
|
var MAX_ENCRYPTED_PACK_SIZE = 5 * 1024 * 1024;
|
|
@@ -10012,16 +10010,16 @@ var hasFloodProtection = (zppNode) => {
|
|
|
10012
10010
|
const udp = findStanza6(flood, "udp");
|
|
10013
10011
|
const icmp = findStanza6(flood, "icmp");
|
|
10014
10012
|
const otherIp = findStanza6(flood, "other-ip");
|
|
10015
|
-
const
|
|
10013
|
+
const isEnabled6 = (stanza) => {
|
|
10016
10014
|
if (!stanza) return false;
|
|
10017
10015
|
const enableCmd = getChildCommand(stanza, "enable");
|
|
10018
10016
|
return enableCmd?.id.toLowerCase().includes("yes") ?? false;
|
|
10019
10017
|
};
|
|
10020
10018
|
return {
|
|
10021
|
-
hasSyn:
|
|
10022
|
-
hasUdp:
|
|
10023
|
-
hasIcmp:
|
|
10024
|
-
hasOtherIp:
|
|
10019
|
+
hasSyn: isEnabled6(tcpSyn),
|
|
10020
|
+
hasUdp: isEnabled6(udp),
|
|
10021
|
+
hasIcmp: isEnabled6(icmp),
|
|
10022
|
+
hasOtherIp: isEnabled6(otherIp)
|
|
10025
10023
|
};
|
|
10026
10024
|
};
|
|
10027
10025
|
var hasReconProtection = (zppNode) => {
|
|
@@ -10631,6 +10629,62 @@ function validatePackFormat(packData) {
|
|
|
10631
10629
|
return true;
|
|
10632
10630
|
}
|
|
10633
10631
|
|
|
10632
|
+
// ../core/src/pack-loader/format-detector.ts
|
|
10633
|
+
import { open } from "node:fs/promises";
|
|
10634
|
+
import { resolve } from "node:path";
|
|
10635
|
+
var FORMAT_PRIORITIES = {
|
|
10636
|
+
unknown: 0,
|
|
10637
|
+
unencrypted: 100,
|
|
10638
|
+
grpx: 200,
|
|
10639
|
+
grx2: 300
|
|
10640
|
+
};
|
|
10641
|
+
var MAGIC_BYTES = {
|
|
10642
|
+
GRX2: Buffer.from("GRX2", "ascii"),
|
|
10643
|
+
GRPX: Buffer.from("GRPX", "ascii")
|
|
10644
|
+
};
|
|
10645
|
+
var MAGIC_BYTES_LENGTH = 4;
|
|
10646
|
+
async function detectPackFormat(filePath) {
|
|
10647
|
+
const absolutePath = resolve(filePath);
|
|
10648
|
+
let fileHandle;
|
|
10649
|
+
try {
|
|
10650
|
+
fileHandle = await open(absolutePath, "r");
|
|
10651
|
+
const stats = await fileHandle.stat();
|
|
10652
|
+
if (stats.size < MAGIC_BYTES_LENGTH) {
|
|
10653
|
+
return "unencrypted";
|
|
10654
|
+
}
|
|
10655
|
+
const buffer = Buffer.alloc(MAGIC_BYTES_LENGTH);
|
|
10656
|
+
const { bytesRead } = await fileHandle.read(
|
|
10657
|
+
buffer,
|
|
10658
|
+
0,
|
|
10659
|
+
MAGIC_BYTES_LENGTH,
|
|
10660
|
+
0
|
|
10661
|
+
);
|
|
10662
|
+
if (bytesRead < MAGIC_BYTES_LENGTH) {
|
|
10663
|
+
return "unencrypted";
|
|
10664
|
+
}
|
|
10665
|
+
if (buffer.equals(MAGIC_BYTES.GRX2)) {
|
|
10666
|
+
return "grx2";
|
|
10667
|
+
}
|
|
10668
|
+
if (buffer.equals(MAGIC_BYTES.GRPX)) {
|
|
10669
|
+
return "grpx";
|
|
10670
|
+
}
|
|
10671
|
+
return "unencrypted";
|
|
10672
|
+
} catch (error) {
|
|
10673
|
+
if (error instanceof Error) {
|
|
10674
|
+
const nodeError = error;
|
|
10675
|
+
if (nodeError.code === "ENOENT") {
|
|
10676
|
+
throw new Error(`Pack file not found: ${absolutePath}`);
|
|
10677
|
+
}
|
|
10678
|
+
if (nodeError.code === "EACCES") {
|
|
10679
|
+
throw new Error(`Permission denied reading pack file: ${absolutePath}`);
|
|
10680
|
+
}
|
|
10681
|
+
}
|
|
10682
|
+
throw error;
|
|
10683
|
+
} finally {
|
|
10684
|
+
await fileHandle?.close();
|
|
10685
|
+
}
|
|
10686
|
+
}
|
|
10687
|
+
|
|
10634
10688
|
// ../core/src/grx2-loader/types.ts
|
|
10635
10689
|
import { homedir } from "node:os";
|
|
10636
10690
|
import { join } from "node:path";
|
|
@@ -12075,10 +12129,301 @@ function extractIPSummary(content, options = {}) {
|
|
|
12075
12129
|
};
|
|
12076
12130
|
}
|
|
12077
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
|
+
|
|
12304
|
+
// ../core/src/validation/rule-validation.ts
|
|
12305
|
+
function validateRule2(rule) {
|
|
12306
|
+
if (typeof rule !== "object" || rule === null) {
|
|
12307
|
+
return "Rule is not an object";
|
|
12308
|
+
}
|
|
12309
|
+
const obj = rule;
|
|
12310
|
+
if (typeof obj.id !== "string") {
|
|
12311
|
+
return "Rule id is not a string";
|
|
12312
|
+
}
|
|
12313
|
+
if (!RULE_ID_PATTERN.test(obj.id)) {
|
|
12314
|
+
return `Rule id "${obj.id}" does not match pattern ${RULE_ID_PATTERN}`;
|
|
12315
|
+
}
|
|
12316
|
+
if (typeof obj.check !== "function") {
|
|
12317
|
+
return `Rule ${obj.id}: check is not a function (got ${typeof obj.check})`;
|
|
12318
|
+
}
|
|
12319
|
+
if (obj.selector !== void 0 && typeof obj.selector !== "string") {
|
|
12320
|
+
return `Rule ${obj.id}: selector is not a string`;
|
|
12321
|
+
}
|
|
12322
|
+
if (obj.vendor !== void 0) {
|
|
12323
|
+
if (Array.isArray(obj.vendor)) {
|
|
12324
|
+
for (const v of obj.vendor) {
|
|
12325
|
+
if (typeof v !== "string") {
|
|
12326
|
+
return `Rule ${obj.id}: vendor array contains non-string`;
|
|
12327
|
+
}
|
|
12328
|
+
if (!isValidVendorId(v)) {
|
|
12329
|
+
return `Rule ${obj.id}: invalid vendor "${v}"`;
|
|
12330
|
+
}
|
|
12331
|
+
}
|
|
12332
|
+
} else if (typeof obj.vendor !== "string") {
|
|
12333
|
+
return `Rule ${obj.id}: vendor is not a string`;
|
|
12334
|
+
} else if (!isValidVendorId(obj.vendor)) {
|
|
12335
|
+
return `Rule ${obj.id}: invalid vendor "${obj.vendor}"`;
|
|
12336
|
+
}
|
|
12337
|
+
}
|
|
12338
|
+
if (typeof obj.metadata !== "object" || obj.metadata === null) {
|
|
12339
|
+
return `Rule ${obj.id}: metadata is not an object`;
|
|
12340
|
+
}
|
|
12341
|
+
const metadata = obj.metadata;
|
|
12342
|
+
if (!["error", "warning", "info"].includes(metadata.level)) {
|
|
12343
|
+
return `Rule ${obj.id}: invalid metadata.level "${metadata.level}"`;
|
|
12344
|
+
}
|
|
12345
|
+
return null;
|
|
12346
|
+
}
|
|
12347
|
+
function isValidRule(rule) {
|
|
12348
|
+
return validateRule2(rule) === null;
|
|
12349
|
+
}
|
|
12350
|
+
function validateRulePack(pack, reservedPackName) {
|
|
12351
|
+
if (typeof pack !== "object" || pack === null) {
|
|
12352
|
+
return "Pack is not an object";
|
|
12353
|
+
}
|
|
12354
|
+
const obj = pack;
|
|
12355
|
+
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
12356
|
+
return "Pack name is missing or empty";
|
|
12357
|
+
}
|
|
12358
|
+
if (reservedPackName && obj.name === reservedPackName) {
|
|
12359
|
+
return `Pack name "${obj.name}" is reserved`;
|
|
12360
|
+
}
|
|
12361
|
+
if (typeof obj.version !== "string" || obj.version.length === 0) {
|
|
12362
|
+
return "Pack version is missing or empty";
|
|
12363
|
+
}
|
|
12364
|
+
if (typeof obj.publisher !== "string" || obj.publisher.length === 0) {
|
|
12365
|
+
return "Pack publisher is missing or empty";
|
|
12366
|
+
}
|
|
12367
|
+
if (typeof obj.priority !== "number" || obj.priority < 0) {
|
|
12368
|
+
return `Pack priority is invalid (got ${obj.priority})`;
|
|
12369
|
+
}
|
|
12370
|
+
if (!Array.isArray(obj.rules)) {
|
|
12371
|
+
return "Pack rules is not an array";
|
|
12372
|
+
}
|
|
12373
|
+
for (let i = 0; i < obj.rules.length; i++) {
|
|
12374
|
+
const ruleError = validateRule2(obj.rules[i]);
|
|
12375
|
+
if (ruleError) {
|
|
12376
|
+
return `Rule[${i}]: ${ruleError}`;
|
|
12377
|
+
}
|
|
12378
|
+
}
|
|
12379
|
+
if (obj.disables !== void 0) {
|
|
12380
|
+
if (typeof obj.disables !== "object" || obj.disables === null) {
|
|
12381
|
+
return "Pack disables is not an object";
|
|
12382
|
+
}
|
|
12383
|
+
const disables = obj.disables;
|
|
12384
|
+
if (disables.all !== void 0 && typeof disables.all !== "boolean") {
|
|
12385
|
+
return "Pack disables.all is not a boolean";
|
|
12386
|
+
}
|
|
12387
|
+
if (disables.vendors !== void 0) {
|
|
12388
|
+
if (!Array.isArray(disables.vendors)) {
|
|
12389
|
+
return "Pack disables.vendors is not an array";
|
|
12390
|
+
}
|
|
12391
|
+
for (const v of disables.vendors) {
|
|
12392
|
+
if (typeof v !== "string" || !isValidVendorId(v)) {
|
|
12393
|
+
return `Pack disables.vendors contains invalid vendor "${v}"`;
|
|
12394
|
+
}
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
if (disables.rules !== void 0) {
|
|
12398
|
+
if (!Array.isArray(disables.rules)) {
|
|
12399
|
+
return "Pack disables.rules is not an array";
|
|
12400
|
+
}
|
|
12401
|
+
for (const r of disables.rules) {
|
|
12402
|
+
if (typeof r !== "string") {
|
|
12403
|
+
return "Pack disables.rules contains non-string";
|
|
12404
|
+
}
|
|
12405
|
+
}
|
|
12406
|
+
}
|
|
12407
|
+
}
|
|
12408
|
+
return null;
|
|
12409
|
+
}
|
|
12410
|
+
function isValidRulePack(pack, reservedPackName) {
|
|
12411
|
+
return validateRulePack(pack, reservedPackName) === null;
|
|
12412
|
+
}
|
|
12413
|
+
function ruleAppliesToVendor(rule, vendorId) {
|
|
12414
|
+
if (!rule.vendor) {
|
|
12415
|
+
return true;
|
|
12416
|
+
}
|
|
12417
|
+
if (Array.isArray(rule.vendor)) {
|
|
12418
|
+
return rule.vendor.includes("common") || rule.vendor.includes(vendorId);
|
|
12419
|
+
}
|
|
12420
|
+
return rule.vendor === "common" || rule.vendor === vendorId;
|
|
12421
|
+
}
|
|
12422
|
+
|
|
12078
12423
|
// index.ts
|
|
12079
12424
|
import { readFile as readFile2 } from "fs/promises";
|
|
12080
12425
|
import { statSync as statSync2 } from "fs";
|
|
12081
|
-
import { resolve as
|
|
12426
|
+
import { resolve as resolve6, dirname as dirname2, basename } from "path";
|
|
12082
12427
|
|
|
12083
12428
|
// src/sarif.ts
|
|
12084
12429
|
import { relative } from "path";
|
|
@@ -12155,7 +12500,7 @@ function generateSarif(results, filePath, rules, options = {}, ipSummary) {
|
|
|
12155
12500
|
tool: {
|
|
12156
12501
|
driver: {
|
|
12157
12502
|
name: "Sentriflow",
|
|
12158
|
-
version: "0.2
|
|
12503
|
+
version: "0.3.2",
|
|
12159
12504
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
12160
12505
|
rules: sarifRules,
|
|
12161
12506
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -12315,7 +12660,7 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
|
|
|
12315
12660
|
tool: {
|
|
12316
12661
|
driver: {
|
|
12317
12662
|
name: "Sentriflow",
|
|
12318
|
-
version: "0.2
|
|
12663
|
+
version: "0.3.2",
|
|
12319
12664
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
12320
12665
|
rules: sarifRules,
|
|
12321
12666
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -12529,10 +12874,10 @@ var InterfaceDescriptionRequired = {
|
|
|
12529
12874
|
loc: node.loc
|
|
12530
12875
|
};
|
|
12531
12876
|
}
|
|
12532
|
-
const
|
|
12877
|
+
const hasDescription9 = node.children.some(
|
|
12533
12878
|
(child) => startsWithIgnoreCase(child.id, "description")
|
|
12534
12879
|
);
|
|
12535
|
-
if (!
|
|
12880
|
+
if (!hasDescription9) {
|
|
12536
12881
|
return {
|
|
12537
12882
|
passed: false,
|
|
12538
12883
|
message: `Interface "${node.params.slice(1).join(" ")}" is missing a description.`,
|
|
@@ -12557,6 +12902,59 @@ var allCommonRules = [
|
|
|
12557
12902
|
InterfaceDescriptionRequired
|
|
12558
12903
|
];
|
|
12559
12904
|
|
|
12905
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/common/validation.ts
|
|
12906
|
+
var equalsIgnoreCase2 = (a, b) => a != null && b != null && a.toLowerCase() === b.toLowerCase();
|
|
12907
|
+
var includesIgnoreCase2 = (haystack, needle) => haystack != null && needle != null && haystack.toLowerCase().includes(needle.toLowerCase());
|
|
12908
|
+
var startsWithIgnoreCase2 = (str, prefix) => str != null && prefix != null && str.toLowerCase().startsWith(prefix.toLowerCase());
|
|
12909
|
+
|
|
12910
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/common/helpers.ts
|
|
12911
|
+
var hasChildCommand5 = (node, prefix) => {
|
|
12912
|
+
if (!node?.children || !prefix) return false;
|
|
12913
|
+
return node.children.some(
|
|
12914
|
+
(child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase()) ?? false
|
|
12915
|
+
);
|
|
12916
|
+
};
|
|
12917
|
+
var getChildCommand5 = (node, prefix) => {
|
|
12918
|
+
if (!node?.children || !prefix) return void 0;
|
|
12919
|
+
return node.children.find(
|
|
12920
|
+
(child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase()) ?? false
|
|
12921
|
+
);
|
|
12922
|
+
};
|
|
12923
|
+
var isShutdown6 = (node) => {
|
|
12924
|
+
if (!node?.children) return false;
|
|
12925
|
+
return node.children.some((child) => {
|
|
12926
|
+
const id = child?.id?.toLowerCase().trim();
|
|
12927
|
+
return id === "shutdown" || id === "disable";
|
|
12928
|
+
});
|
|
12929
|
+
};
|
|
12930
|
+
|
|
12931
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/cisco/helpers.ts
|
|
12932
|
+
var isShutdown7 = (node) => {
|
|
12933
|
+
if (!node?.children) return false;
|
|
12934
|
+
return node.children.some((child) => {
|
|
12935
|
+
return child?.id && equalsIgnoreCase2(child.id.trim(), "shutdown");
|
|
12936
|
+
});
|
|
12937
|
+
};
|
|
12938
|
+
var isPhysicalPort4 = (interfaceName) => {
|
|
12939
|
+
return !includesIgnoreCase2(interfaceName, "loopback") && !includesIgnoreCase2(interfaceName, "null") && !includesIgnoreCase2(interfaceName, "vlan") && !includesIgnoreCase2(interfaceName, "tunnel") && !includesIgnoreCase2(interfaceName, "port-channel") && !includesIgnoreCase2(interfaceName, "bvi") && !includesIgnoreCase2(interfaceName, "nve");
|
|
12940
|
+
};
|
|
12941
|
+
var isTrunkPort4 = (node) => {
|
|
12942
|
+
return hasChildCommand5(node, "switchport mode trunk");
|
|
12943
|
+
};
|
|
12944
|
+
var isTrunkToNonCisco2 = (node) => {
|
|
12945
|
+
const desc = getChildCommand5(node, "description");
|
|
12946
|
+
if (desc) {
|
|
12947
|
+
const descText = desc.rawText;
|
|
12948
|
+
if (includesIgnoreCase2(descText, "server:") || includesIgnoreCase2(descText, "storage:") || includesIgnoreCase2(descText, "esx") || includesIgnoreCase2(descText, "vmware") || includesIgnoreCase2(descText, "hyperv") || includesIgnoreCase2(descText, "hyper-v") || includesIgnoreCase2(descText, "linux") || includesIgnoreCase2(descText, "appliance") || includesIgnoreCase2(descText, "firewall") || includesIgnoreCase2(descText, "loadbalancer") || includesIgnoreCase2(descText, "lb:") || includesIgnoreCase2(descText, "nas:") || includesIgnoreCase2(descText, "san:")) {
|
|
12949
|
+
return true;
|
|
12950
|
+
}
|
|
12951
|
+
if (includesIgnoreCase2(descText, "uplink:") || includesIgnoreCase2(descText, "downlink:") || includesIgnoreCase2(descText, "isl:") || includesIgnoreCase2(descText, "po-member:")) {
|
|
12952
|
+
return false;
|
|
12953
|
+
}
|
|
12954
|
+
}
|
|
12955
|
+
return false;
|
|
12956
|
+
};
|
|
12957
|
+
|
|
12560
12958
|
// ../rules-default/src/cisco/ios-rules.ts
|
|
12561
12959
|
var TrunkNoDTP = {
|
|
12562
12960
|
id: "NET-TRUNK-001",
|
|
@@ -12570,16 +12968,16 @@ var TrunkNoDTP = {
|
|
|
12570
12968
|
remediation: 'Add "switchport nonegotiate" to disable DTP on trunk ports connected to non-Cisco devices.'
|
|
12571
12969
|
},
|
|
12572
12970
|
check: (node) => {
|
|
12573
|
-
if (!
|
|
12971
|
+
if (!isPhysicalPort4(node.id) || isShutdown7(node)) {
|
|
12574
12972
|
return { passed: true, message: "Not applicable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12575
12973
|
}
|
|
12576
|
-
if (!
|
|
12974
|
+
if (!isTrunkPort4(node)) {
|
|
12577
12975
|
return { passed: true, message: "Not a trunk port.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12578
12976
|
}
|
|
12579
|
-
if (!
|
|
12977
|
+
if (!isTrunkToNonCisco2(node)) {
|
|
12580
12978
|
return { passed: true, message: "Trunk to Cisco device - DTP acceptable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12581
12979
|
}
|
|
12582
|
-
if (!
|
|
12980
|
+
if (!hasChildCommand5(node, "switchport nonegotiate")) {
|
|
12583
12981
|
return {
|
|
12584
12982
|
passed: false,
|
|
12585
12983
|
message: `Trunk port "${node.params.slice(1).join(" ")}" connected to non-Cisco device needs "switchport nonegotiate".`,
|
|
@@ -12604,11 +13002,11 @@ var AccessExplicitMode = {
|
|
|
12604
13002
|
remediation: 'Add "switchport mode access" to explicitly configure access mode.'
|
|
12605
13003
|
},
|
|
12606
13004
|
check: (node) => {
|
|
12607
|
-
if (!
|
|
13005
|
+
if (!isPhysicalPort4(node.id) || isShutdown7(node)) {
|
|
12608
13006
|
return { passed: true, message: "Not applicable.", ruleId: "NET-ACCESS-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12609
13007
|
}
|
|
12610
|
-
const hasAccessVlan =
|
|
12611
|
-
const hasExplicitMode =
|
|
13008
|
+
const hasAccessVlan = hasChildCommand5(node, "switchport access vlan");
|
|
13009
|
+
const hasExplicitMode = hasChildCommand5(node, "switchport mode");
|
|
12612
13010
|
if (hasAccessVlan && !hasExplicitMode) {
|
|
12613
13011
|
return {
|
|
12614
13012
|
passed: false,
|
|
@@ -12746,6 +13144,38 @@ var allCiscoRules = [
|
|
|
12746
13144
|
EnableSecretStrong
|
|
12747
13145
|
];
|
|
12748
13146
|
|
|
13147
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/juniper/helpers.ts
|
|
13148
|
+
var findStanza8 = (node, stanzaName) => {
|
|
13149
|
+
if (!node?.children) return void 0;
|
|
13150
|
+
return node.children.find(
|
|
13151
|
+
(child) => child?.id && equalsIgnoreCase2(child.id, stanzaName)
|
|
13152
|
+
);
|
|
13153
|
+
};
|
|
13154
|
+
var findStanzas7 = (node, pattern) => {
|
|
13155
|
+
if (!node?.children) return [];
|
|
13156
|
+
return node.children.filter((child) => child?.id && pattern.test(child.id));
|
|
13157
|
+
};
|
|
13158
|
+
var isFilterTermDrop2 = (termNode) => {
|
|
13159
|
+
if (!termNode?.children) return false;
|
|
13160
|
+
for (const child of termNode.children) {
|
|
13161
|
+
const id = child?.id?.trim();
|
|
13162
|
+
if (!id) continue;
|
|
13163
|
+
if (equalsIgnoreCase2(id, "then discard") || equalsIgnoreCase2(id, "then discard;") || equalsIgnoreCase2(id, "then reject") || equalsIgnoreCase2(id, "then reject;")) {
|
|
13164
|
+
return true;
|
|
13165
|
+
}
|
|
13166
|
+
}
|
|
13167
|
+
const thenStanza = findStanza8(termNode, "then");
|
|
13168
|
+
if (!thenStanza?.children) return false;
|
|
13169
|
+
for (const child of thenStanza.children) {
|
|
13170
|
+
const id = child?.id?.trim();
|
|
13171
|
+
if (!id) continue;
|
|
13172
|
+
if (equalsIgnoreCase2(id, "discard") || equalsIgnoreCase2(id, "discard;") || equalsIgnoreCase2(id, "reject") || equalsIgnoreCase2(id, "reject;")) {
|
|
13173
|
+
return true;
|
|
13174
|
+
}
|
|
13175
|
+
}
|
|
13176
|
+
return false;
|
|
13177
|
+
};
|
|
13178
|
+
|
|
12749
13179
|
// ../rules-default/src/juniper/junos-rules.ts
|
|
12750
13180
|
var RootAuthRequired = {
|
|
12751
13181
|
id: "JUN-SYS-001",
|
|
@@ -12759,7 +13189,7 @@ var RootAuthRequired = {
|
|
|
12759
13189
|
remediation: 'Configure "root-authentication" under system stanza with encrypted-password or ssh-rsa.'
|
|
12760
13190
|
},
|
|
12761
13191
|
check: (node) => {
|
|
12762
|
-
const rootAuth =
|
|
13192
|
+
const rootAuth = findStanza8(node, "root-authentication");
|
|
12763
13193
|
if (!rootAuth) {
|
|
12764
13194
|
return {
|
|
12765
13195
|
passed: false,
|
|
@@ -12770,8 +13200,8 @@ var RootAuthRequired = {
|
|
|
12770
13200
|
loc: node.loc
|
|
12771
13201
|
};
|
|
12772
13202
|
}
|
|
12773
|
-
const hasPassword =
|
|
12774
|
-
const hasSshKey =
|
|
13203
|
+
const hasPassword = hasChildCommand5(rootAuth, "encrypted-password");
|
|
13204
|
+
const hasSshKey = hasChildCommand5(rootAuth, "ssh-rsa") || hasChildCommand5(rootAuth, "ssh-ecdsa");
|
|
12775
13205
|
if (!hasPassword && !hasSshKey) {
|
|
12776
13206
|
return {
|
|
12777
13207
|
passed: false,
|
|
@@ -12804,7 +13234,7 @@ var JunosBgpRouterId = {
|
|
|
12804
13234
|
remediation: 'Configure "router-id" under routing-options stanza.'
|
|
12805
13235
|
},
|
|
12806
13236
|
check: (node) => {
|
|
12807
|
-
const hasRouterId =
|
|
13237
|
+
const hasRouterId = hasChildCommand5(node, "router-id");
|
|
12808
13238
|
if (!hasRouterId) {
|
|
12809
13239
|
return {
|
|
12810
13240
|
passed: false,
|
|
@@ -12855,7 +13285,7 @@ var JunosFirewallDefaultDeny = {
|
|
|
12855
13285
|
}
|
|
12856
13286
|
const issues = [];
|
|
12857
13287
|
for (const filter of filters) {
|
|
12858
|
-
const terms =
|
|
13288
|
+
const terms = findStanzas7(filter, /^term/i);
|
|
12859
13289
|
if (terms.length === 0) {
|
|
12860
13290
|
continue;
|
|
12861
13291
|
}
|
|
@@ -12863,7 +13293,7 @@ var JunosFirewallDefaultDeny = {
|
|
|
12863
13293
|
if (!lastTerm) {
|
|
12864
13294
|
continue;
|
|
12865
13295
|
}
|
|
12866
|
-
if (!
|
|
13296
|
+
if (!isFilterTermDrop2(lastTerm)) {
|
|
12867
13297
|
issues.push(`Filter "${filter.id}" does not end with a deny term.`);
|
|
12868
13298
|
}
|
|
12869
13299
|
}
|
|
@@ -12896,6 +13326,69 @@ var allJuniperRules = [
|
|
|
12896
13326
|
JunosFirewallDefaultDeny
|
|
12897
13327
|
];
|
|
12898
13328
|
|
|
13329
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/aruba/helpers.ts
|
|
13330
|
+
var getInterfaceName4 = (node) => {
|
|
13331
|
+
if (!node?.id) return void 0;
|
|
13332
|
+
const match = node.id.match(/interface\s+(.+)/i);
|
|
13333
|
+
const ifName = match?.[1]?.trim();
|
|
13334
|
+
return ifName && ifName.length > 0 ? ifName : void 0;
|
|
13335
|
+
};
|
|
13336
|
+
var isAosCxPhysicalPort2 = (interfaceName) => {
|
|
13337
|
+
return /^\d+\/\d+\/\d+$/.test(interfaceName.trim());
|
|
13338
|
+
};
|
|
13339
|
+
var isAosCxLag2 = (interfaceName) => {
|
|
13340
|
+
return /^lag\s*\d+$/i.test(interfaceName.trim());
|
|
13341
|
+
};
|
|
13342
|
+
var isAosCxTrunk2 = (node) => {
|
|
13343
|
+
return hasChildCommand5(node, "vlan trunk");
|
|
13344
|
+
};
|
|
13345
|
+
var getAosCxTrunkAllowed2 = (node) => {
|
|
13346
|
+
const cmd = getChildCommand5(node, "vlan trunk allowed");
|
|
13347
|
+
if (!cmd) return [];
|
|
13348
|
+
const match = cmd.id.match(/vlan\s+trunk\s+allowed\s+([\d,]+)/i);
|
|
13349
|
+
const vlanList = match?.[1];
|
|
13350
|
+
if (!vlanList) return [];
|
|
13351
|
+
return vlanList.split(",").map((v) => parseInt(v.trim(), 10)).filter((v) => !isNaN(v));
|
|
13352
|
+
};
|
|
13353
|
+
var getAosSwitchVlanName2 = (node) => {
|
|
13354
|
+
const cmd = getChildCommand5(node, "name");
|
|
13355
|
+
if (!cmd) return void 0;
|
|
13356
|
+
const match = cmd.id.match(/name\s+["']?([^"']+)["']?/i);
|
|
13357
|
+
const name = match?.[1];
|
|
13358
|
+
return name?.trim();
|
|
13359
|
+
};
|
|
13360
|
+
var getWlanEncryption2 = (node) => {
|
|
13361
|
+
const cmd = getChildCommand5(node, "opmode");
|
|
13362
|
+
if (!cmd) return null;
|
|
13363
|
+
const match = cmd.id.match(/opmode\s+(\S+)/i);
|
|
13364
|
+
const mode = match?.[1];
|
|
13365
|
+
return mode ? mode.toLowerCase() : null;
|
|
13366
|
+
};
|
|
13367
|
+
var hasSecureEncryption2 = (node) => {
|
|
13368
|
+
const opmode = getWlanEncryption2(node);
|
|
13369
|
+
if (!opmode) return false;
|
|
13370
|
+
return opmode.includes("wpa2") || opmode.includes("wpa3") || opmode.includes("aes");
|
|
13371
|
+
};
|
|
13372
|
+
var isOpenSsid2 = (node) => {
|
|
13373
|
+
const opmode = getWlanEncryption2(node);
|
|
13374
|
+
return opmode === "opensystem" || opmode === "open";
|
|
13375
|
+
};
|
|
13376
|
+
var getRadiusHost2 = (node) => {
|
|
13377
|
+
const cmd = getChildCommand5(node, "host");
|
|
13378
|
+
if (!cmd) return void 0;
|
|
13379
|
+
const match = cmd.id.match(/host\s+(\S+)/i);
|
|
13380
|
+
const host = match?.[1];
|
|
13381
|
+
return host?.trim();
|
|
13382
|
+
};
|
|
13383
|
+
var extractProfileName2 = (nodeId) => {
|
|
13384
|
+
const match = nodeId.match(/(?:ssid-profile|virtual-ap|profile|server-group|ap-group|arm-profile)\s+["']?([^"'\n]+)["']?$/i);
|
|
13385
|
+
const profile = match?.[1];
|
|
13386
|
+
return profile ? profile.trim() : void 0;
|
|
13387
|
+
};
|
|
13388
|
+
var hasDescription5 = (node) => {
|
|
13389
|
+
return hasChildCommand5(node, "description");
|
|
13390
|
+
};
|
|
13391
|
+
|
|
12899
13392
|
// ../rules-default/src/aruba/common-rules.ts
|
|
12900
13393
|
var SshEnabled = {
|
|
12901
13394
|
id: "ARU-SEC-001",
|
|
@@ -12983,17 +13476,17 @@ var AosCxInterfaceDescription = {
|
|
|
12983
13476
|
remediation: "Add a description to physical interfaces for documentation."
|
|
12984
13477
|
},
|
|
12985
13478
|
check: (node) => {
|
|
12986
|
-
const ifName =
|
|
13479
|
+
const ifName = getInterfaceName4(node);
|
|
12987
13480
|
if (!ifName) {
|
|
12988
13481
|
return { passed: true, message: "Not an interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12989
13482
|
}
|
|
12990
|
-
if (!
|
|
13483
|
+
if (!isAosCxPhysicalPort2(ifName) && !isAosCxLag2(ifName)) {
|
|
12991
13484
|
return { passed: true, message: "Not a physical interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12992
13485
|
}
|
|
12993
|
-
if (
|
|
13486
|
+
if (isShutdown6(node)) {
|
|
12994
13487
|
return { passed: true, message: "Interface is shutdown.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12995
13488
|
}
|
|
12996
|
-
if (!
|
|
13489
|
+
if (!hasDescription5(node)) {
|
|
12997
13490
|
return {
|
|
12998
13491
|
passed: false,
|
|
12999
13492
|
message: `Interface ${ifName} missing description.`,
|
|
@@ -13025,17 +13518,17 @@ var AosCxTrunkAllowedVlans = {
|
|
|
13025
13518
|
remediation: 'Configure "vlan trunk allowed <vlans>" on trunk interfaces.'
|
|
13026
13519
|
},
|
|
13027
13520
|
check: (node) => {
|
|
13028
|
-
const ifName =
|
|
13521
|
+
const ifName = getInterfaceName4(node);
|
|
13029
13522
|
if (!ifName) {
|
|
13030
13523
|
return { passed: true, message: "Not an interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13031
13524
|
}
|
|
13032
|
-
if (!
|
|
13525
|
+
if (!isAosCxPhysicalPort2(ifName) && !isAosCxLag2(ifName)) {
|
|
13033
13526
|
return { passed: true, message: "Not a switchport interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13034
13527
|
}
|
|
13035
|
-
if (!
|
|
13528
|
+
if (!isAosCxTrunk2(node)) {
|
|
13036
13529
|
return { passed: true, message: "Not a trunk port.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13037
13530
|
}
|
|
13038
|
-
const allowedVlans =
|
|
13531
|
+
const allowedVlans = getAosCxTrunkAllowed2(node);
|
|
13039
13532
|
if (allowedVlans.length === 0) {
|
|
13040
13533
|
return {
|
|
13041
13534
|
passed: false,
|
|
@@ -13085,7 +13578,7 @@ var AosSwitchVlanName = {
|
|
|
13085
13578
|
if (vlanId === null || isDefaultVlan(vlanId)) {
|
|
13086
13579
|
return { passed: true, message: "Default VLAN 1.", ruleId: "AOSSW-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13087
13580
|
}
|
|
13088
|
-
const vlanName =
|
|
13581
|
+
const vlanName = getAosSwitchVlanName2(node);
|
|
13089
13582
|
if (!vlanName) {
|
|
13090
13583
|
return {
|
|
13091
13584
|
passed: false,
|
|
@@ -13159,8 +13652,8 @@ var WlcSsidEncryption = {
|
|
|
13159
13652
|
remediation: 'Configure "opmode wpa2-aes" or "opmode wpa3-sae-aes" for secure encryption.'
|
|
13160
13653
|
},
|
|
13161
13654
|
check: (node) => {
|
|
13162
|
-
const profileName =
|
|
13163
|
-
const opmode =
|
|
13655
|
+
const profileName = extractProfileName2(node.id);
|
|
13656
|
+
const opmode = getWlanEncryption2(node);
|
|
13164
13657
|
if (!opmode) {
|
|
13165
13658
|
return {
|
|
13166
13659
|
passed: false,
|
|
@@ -13171,7 +13664,7 @@ var WlcSsidEncryption = {
|
|
|
13171
13664
|
loc: node.loc
|
|
13172
13665
|
};
|
|
13173
13666
|
}
|
|
13174
|
-
if (
|
|
13667
|
+
if (isOpenSsid2(node)) {
|
|
13175
13668
|
return {
|
|
13176
13669
|
passed: false,
|
|
13177
13670
|
message: `SSID profile "${profileName}" is open/unencrypted. Use WPA2 or WPA3.`,
|
|
@@ -13181,7 +13674,7 @@ var WlcSsidEncryption = {
|
|
|
13181
13674
|
loc: node.loc
|
|
13182
13675
|
};
|
|
13183
13676
|
}
|
|
13184
|
-
if (!
|
|
13677
|
+
if (!hasSecureEncryption2(node)) {
|
|
13185
13678
|
return {
|
|
13186
13679
|
passed: false,
|
|
13187
13680
|
message: `SSID profile "${profileName}" uses weak encryption (${opmode}). Use WPA2 or WPA3.`,
|
|
@@ -13213,8 +13706,8 @@ var WlcRadiusHost = {
|
|
|
13213
13706
|
remediation: 'Configure "host <ip-address>" for the RADIUS server.'
|
|
13214
13707
|
},
|
|
13215
13708
|
check: (node) => {
|
|
13216
|
-
const profileName =
|
|
13217
|
-
const host =
|
|
13709
|
+
const profileName = extractProfileName2(node.id);
|
|
13710
|
+
const host = getRadiusHost2(node);
|
|
13218
13711
|
if (!host) {
|
|
13219
13712
|
return {
|
|
13220
13713
|
passed: false,
|
|
@@ -13262,20 +13755,48 @@ function getRulesByArubaVendor(vendorId) {
|
|
|
13262
13755
|
}
|
|
13263
13756
|
}
|
|
13264
13757
|
|
|
13265
|
-
//
|
|
13266
|
-
var
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13758
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/paloalto/helpers.ts
|
|
13759
|
+
var findStanza9 = (node, stanzaName) => {
|
|
13760
|
+
if (!node?.children) return void 0;
|
|
13761
|
+
return node.children.find(
|
|
13762
|
+
(child) => child?.id?.toLowerCase() === stanzaName.toLowerCase()
|
|
13763
|
+
);
|
|
13764
|
+
};
|
|
13765
|
+
var hasLogging3 = (ruleNode) => {
|
|
13766
|
+
const logStart = hasChildCommand5(ruleNode, "log-start");
|
|
13767
|
+
const logEnd = hasChildCommand5(ruleNode, "log-end");
|
|
13768
|
+
return { logStart, logEnd };
|
|
13769
|
+
};
|
|
13770
|
+
var isRuleDisabled2 = (ruleNode) => {
|
|
13771
|
+
const disabled = getChildCommand5(ruleNode, "disabled");
|
|
13772
|
+
if (!disabled) return false;
|
|
13773
|
+
return disabled.id.toLowerCase().includes("yes") || disabled.id.toLowerCase().includes("true");
|
|
13774
|
+
};
|
|
13775
|
+
var getSecurityRules2 = (rulebaseNode) => {
|
|
13776
|
+
const security = findStanza9(rulebaseNode, "security");
|
|
13777
|
+
if (!security) return [];
|
|
13778
|
+
const rules = findStanza9(security, "rules");
|
|
13779
|
+
if (!rules?.children) return [];
|
|
13780
|
+
return rules.children;
|
|
13781
|
+
};
|
|
13782
|
+
var hasZoneProtectionProfile2 = (zoneNode) => {
|
|
13783
|
+
return hasChildCommand5(zoneNode, "zone-protection-profile");
|
|
13784
|
+
};
|
|
13785
|
+
|
|
13786
|
+
// ../rules-default/src/paloalto/panos-rules.ts
|
|
13787
|
+
var HostnameRequired = {
|
|
13788
|
+
id: "PAN-SYS-001",
|
|
13789
|
+
selector: "deviceconfig",
|
|
13790
|
+
vendor: "paloalto-panos",
|
|
13791
|
+
category: "Documentation",
|
|
13792
|
+
metadata: {
|
|
13793
|
+
level: "warning",
|
|
13794
|
+
obu: "Network Engineering",
|
|
13795
|
+
owner: "NetOps",
|
|
13796
|
+
remediation: "Configure hostname under deviceconfig > system."
|
|
13797
|
+
},
|
|
13798
|
+
check: (node) => {
|
|
13799
|
+
const system = findStanza9(node, "system");
|
|
13279
13800
|
if (!system) {
|
|
13280
13801
|
return {
|
|
13281
13802
|
passed: false,
|
|
@@ -13286,7 +13807,7 @@ var HostnameRequired = {
|
|
|
13286
13807
|
loc: node.loc
|
|
13287
13808
|
};
|
|
13288
13809
|
}
|
|
13289
|
-
const hasHostname =
|
|
13810
|
+
const hasHostname = hasChildCommand5(system, "hostname");
|
|
13290
13811
|
if (!hasHostname) {
|
|
13291
13812
|
return {
|
|
13292
13813
|
passed: false,
|
|
@@ -13319,7 +13840,7 @@ var SecurityRuleLogging = {
|
|
|
13319
13840
|
remediation: "Enable log-end (and optionally log-start) on all security rules."
|
|
13320
13841
|
},
|
|
13321
13842
|
check: (node) => {
|
|
13322
|
-
const rules =
|
|
13843
|
+
const rules = getSecurityRules2(node);
|
|
13323
13844
|
if (rules.length === 0) {
|
|
13324
13845
|
return {
|
|
13325
13846
|
passed: true,
|
|
@@ -13332,8 +13853,8 @@ var SecurityRuleLogging = {
|
|
|
13332
13853
|
}
|
|
13333
13854
|
const issues = [];
|
|
13334
13855
|
for (const rule of rules) {
|
|
13335
|
-
if (
|
|
13336
|
-
const logging =
|
|
13856
|
+
if (isRuleDisabled2(rule)) continue;
|
|
13857
|
+
const logging = hasLogging3(rule);
|
|
13337
13858
|
if (!logging.logEnd) {
|
|
13338
13859
|
issues.push(`Rule "${rule.id}" does not have log-end enabled.`);
|
|
13339
13860
|
}
|
|
@@ -13372,7 +13893,7 @@ var ZoneProtectionRequired = {
|
|
|
13372
13893
|
check: (node) => {
|
|
13373
13894
|
const issues = [];
|
|
13374
13895
|
for (const zone of node.children) {
|
|
13375
|
-
if (!
|
|
13896
|
+
if (!hasZoneProtectionProfile2(zone)) {
|
|
13376
13897
|
issues.push(`Zone "${zone.id}" does not have a Zone Protection Profile applied.`);
|
|
13377
13898
|
}
|
|
13378
13899
|
}
|
|
@@ -13420,6 +13941,34 @@ function getRulesByPaloAltoVendor() {
|
|
|
13420
13941
|
return [...allCommonRules, ...allPaloAltoRules];
|
|
13421
13942
|
}
|
|
13422
13943
|
|
|
13944
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/arista/helpers.ts
|
|
13945
|
+
var checkMlagRequirements2 = (mlagNode) => {
|
|
13946
|
+
return {
|
|
13947
|
+
hasDomainId: hasChildCommand5(mlagNode, "domain-id"),
|
|
13948
|
+
hasPeerLink: hasChildCommand5(mlagNode, "peer-link"),
|
|
13949
|
+
hasPeerAddress: hasChildCommand5(mlagNode, "peer-address"),
|
|
13950
|
+
hasLocalInterface: hasChildCommand5(mlagNode, "local-interface")
|
|
13951
|
+
};
|
|
13952
|
+
};
|
|
13953
|
+
var isShutdown8 = (interfaceNode) => {
|
|
13954
|
+
if (!interfaceNode?.children) return false;
|
|
13955
|
+
const hasShutdown = interfaceNode.children.some(
|
|
13956
|
+
(child) => child?.id && equalsIgnoreCase2(child.id, "shutdown")
|
|
13957
|
+
);
|
|
13958
|
+
const hasNoShutdown = interfaceNode.children.some(
|
|
13959
|
+
(child) => child?.id && equalsIgnoreCase2(child.id, "no shutdown")
|
|
13960
|
+
);
|
|
13961
|
+
return hasShutdown && !hasNoShutdown;
|
|
13962
|
+
};
|
|
13963
|
+
var getInterfaceDescription2 = (interfaceNode) => {
|
|
13964
|
+
if (!interfaceNode?.children) return void 0;
|
|
13965
|
+
const descCmd = interfaceNode.children.find(
|
|
13966
|
+
(child) => child?.id && startsWithIgnoreCase2(child.id, "description ")
|
|
13967
|
+
);
|
|
13968
|
+
if (!descCmd) return void 0;
|
|
13969
|
+
return descCmd.id.replace(/^description\s+/i, "").trim();
|
|
13970
|
+
};
|
|
13971
|
+
|
|
13423
13972
|
// ../rules-default/src/arista/eos-rules.ts
|
|
13424
13973
|
var HostnameRequired2 = {
|
|
13425
13974
|
id: "ARI-SYS-001",
|
|
@@ -13468,7 +14017,7 @@ var MlagConfigComplete = {
|
|
|
13468
14017
|
remediation: "MLAG configuration requires: domain-id, peer-link, peer-address, and local-interface."
|
|
13469
14018
|
},
|
|
13470
14019
|
check: (node, context) => {
|
|
13471
|
-
const requirements =
|
|
14020
|
+
const requirements = checkMlagRequirements2(node);
|
|
13472
14021
|
const issues = [];
|
|
13473
14022
|
if (!requirements.hasDomainId) {
|
|
13474
14023
|
issues.push("domain-id");
|
|
@@ -13514,7 +14063,7 @@ var InterfaceDescription = {
|
|
|
13514
14063
|
remediation: "Add description to interface: description <text>"
|
|
13515
14064
|
},
|
|
13516
14065
|
check: (node, context) => {
|
|
13517
|
-
if (
|
|
14066
|
+
if (isShutdown8(node)) {
|
|
13518
14067
|
return {
|
|
13519
14068
|
passed: true,
|
|
13520
14069
|
message: "Interface is shutdown, description not required.",
|
|
@@ -13524,7 +14073,7 @@ var InterfaceDescription = {
|
|
|
13524
14073
|
loc: node.loc
|
|
13525
14074
|
};
|
|
13526
14075
|
}
|
|
13527
|
-
const description =
|
|
14076
|
+
const description = getInterfaceDescription2(node);
|
|
13528
14077
|
if (!description) {
|
|
13529
14078
|
return {
|
|
13530
14079
|
passed: false,
|
|
@@ -13559,6 +14108,37 @@ function getRulesByAristaVendor() {
|
|
|
13559
14108
|
return [...allCommonRules, ...allAristaRules];
|
|
13560
14109
|
}
|
|
13561
14110
|
|
|
14111
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/vyos/helpers.ts
|
|
14112
|
+
var isDisabled4 = (node) => {
|
|
14113
|
+
if (!node?.children) return false;
|
|
14114
|
+
return node.children.some((child) => child?.id?.toLowerCase().trim() === "disable");
|
|
14115
|
+
};
|
|
14116
|
+
var findStanzasByPrefix3 = (node, prefix) => {
|
|
14117
|
+
if (!node?.children) return [];
|
|
14118
|
+
return node.children.filter(
|
|
14119
|
+
(child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase())
|
|
14120
|
+
);
|
|
14121
|
+
};
|
|
14122
|
+
var getEthernetInterfaces2 = (interfacesNode) => {
|
|
14123
|
+
if (!interfacesNode?.children) return [];
|
|
14124
|
+
return interfacesNode.children.filter(
|
|
14125
|
+
(child) => child?.id?.toLowerCase().startsWith("ethernet eth")
|
|
14126
|
+
);
|
|
14127
|
+
};
|
|
14128
|
+
var getFirewallDefaultAction2 = (rulesetNode) => {
|
|
14129
|
+
if (!rulesetNode?.children) return void 0;
|
|
14130
|
+
for (const child of rulesetNode.children) {
|
|
14131
|
+
const id = child?.id?.toLowerCase().trim();
|
|
14132
|
+
if (!id) continue;
|
|
14133
|
+
if (id.startsWith("default-action")) {
|
|
14134
|
+
if (id.includes("drop")) return "drop";
|
|
14135
|
+
if (id.includes("accept")) return "accept";
|
|
14136
|
+
if (id.includes("reject")) return "reject";
|
|
14137
|
+
}
|
|
14138
|
+
}
|
|
14139
|
+
return void 0;
|
|
14140
|
+
};
|
|
14141
|
+
|
|
13562
14142
|
// ../rules-default/src/vyos/vyos-rules.ts
|
|
13563
14143
|
var VyosHostnameRequired = {
|
|
13564
14144
|
id: "VYOS-SYS-001",
|
|
@@ -13572,7 +14152,7 @@ var VyosHostnameRequired = {
|
|
|
13572
14152
|
remediation: 'Configure "host-name" under system stanza.'
|
|
13573
14153
|
},
|
|
13574
14154
|
check: (node) => {
|
|
13575
|
-
const hasHostname =
|
|
14155
|
+
const hasHostname = hasChildCommand5(node, "host-name");
|
|
13576
14156
|
if (!hasHostname) {
|
|
13577
14157
|
return {
|
|
13578
14158
|
passed: false,
|
|
@@ -13641,12 +14221,12 @@ var VyosInterfaceDescription = {
|
|
|
13641
14221
|
},
|
|
13642
14222
|
check: (node) => {
|
|
13643
14223
|
const issues = [];
|
|
13644
|
-
const ethernetInterfaces =
|
|
14224
|
+
const ethernetInterfaces = getEthernetInterfaces2(node);
|
|
13645
14225
|
for (const iface of ethernetInterfaces) {
|
|
13646
|
-
if (
|
|
14226
|
+
if (isDisabled4(iface)) {
|
|
13647
14227
|
continue;
|
|
13648
14228
|
}
|
|
13649
|
-
const hasDesc =
|
|
14229
|
+
const hasDesc = hasChildCommand5(iface, "description");
|
|
13650
14230
|
if (!hasDesc) {
|
|
13651
14231
|
const ifaceName = iface.id.split(/\s+/).pop() || iface.id;
|
|
13652
14232
|
issues.push(`Interface "${ifaceName}" missing description.`);
|
|
@@ -13684,7 +14264,7 @@ var VyosFirewallDefaultAction = {
|
|
|
13684
14264
|
remediation: 'Set "default-action drop" or "default-action reject" for each firewall ruleset.'
|
|
13685
14265
|
},
|
|
13686
14266
|
check: (node) => {
|
|
13687
|
-
const rulesets =
|
|
14267
|
+
const rulesets = findStanzasByPrefix3(node, "name");
|
|
13688
14268
|
if (rulesets.length === 0) {
|
|
13689
14269
|
return {
|
|
13690
14270
|
passed: true,
|
|
@@ -13697,7 +14277,7 @@ var VyosFirewallDefaultAction = {
|
|
|
13697
14277
|
}
|
|
13698
14278
|
const issues = [];
|
|
13699
14279
|
for (const ruleset of rulesets) {
|
|
13700
|
-
const defaultAction =
|
|
14280
|
+
const defaultAction = getFirewallDefaultAction2(ruleset);
|
|
13701
14281
|
if (!defaultAction) {
|
|
13702
14282
|
const rulesetName = ruleset.id.split(/\s+/)[1] || ruleset.id;
|
|
13703
14283
|
issues.push(`Firewall ruleset "${rulesetName}" has no default-action configured.`);
|
|
@@ -13739,6 +14319,65 @@ function getRulesByVyosVendor() {
|
|
|
13739
14319
|
return [...allCommonRules, ...allVyosRules];
|
|
13740
14320
|
}
|
|
13741
14321
|
|
|
14322
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/fortinet/helpers.ts
|
|
14323
|
+
var getEditEntries2 = (configSection) => {
|
|
14324
|
+
if (!configSection?.children) return [];
|
|
14325
|
+
return configSection.children.filter(
|
|
14326
|
+
(child) => child?.id?.toLowerCase().startsWith("edit ")
|
|
14327
|
+
);
|
|
14328
|
+
};
|
|
14329
|
+
var getEditEntryName2 = (editEntry) => {
|
|
14330
|
+
const match = editEntry.id.match(/^edit\s+["']?([^"']+)["']?$/i);
|
|
14331
|
+
const entryName = match?.[1];
|
|
14332
|
+
return entryName ?? editEntry.id;
|
|
14333
|
+
};
|
|
14334
|
+
var getSetValue2 = (node, paramName) => {
|
|
14335
|
+
if (!node?.children) return void 0;
|
|
14336
|
+
const normalizedParam = paramName.toLowerCase();
|
|
14337
|
+
for (const child of node.children) {
|
|
14338
|
+
const childId = child?.id?.toLowerCase();
|
|
14339
|
+
if (!childId) continue;
|
|
14340
|
+
const match = childId.match(new RegExp(`^set\\s+${normalizedParam}\\s+(.+)$`, "i"));
|
|
14341
|
+
const value = match?.[1];
|
|
14342
|
+
if (value) {
|
|
14343
|
+
return value.replace(/^["']|["']$/g, "").trim();
|
|
14344
|
+
}
|
|
14345
|
+
}
|
|
14346
|
+
return void 0;
|
|
14347
|
+
};
|
|
14348
|
+
var isPolicyDisabled2 = (policyNode) => {
|
|
14349
|
+
const status = getSetValue2(policyNode, "status");
|
|
14350
|
+
return status?.toLowerCase() === "disable";
|
|
14351
|
+
};
|
|
14352
|
+
var hasLogging4 = (policyNode) => {
|
|
14353
|
+
const logtraffic = getSetValue2(policyNode, "logtraffic");
|
|
14354
|
+
const logtrafficStart = getSetValue2(policyNode, "logtraffic-start");
|
|
14355
|
+
return {
|
|
14356
|
+
logtraffic,
|
|
14357
|
+
logtrafficStart: logtrafficStart?.toLowerCase() === "enable"
|
|
14358
|
+
};
|
|
14359
|
+
};
|
|
14360
|
+
var getAdminProfile2 = (adminNode) => {
|
|
14361
|
+
return getSetValue2(adminNode, "accprofile");
|
|
14362
|
+
};
|
|
14363
|
+
var isSuperAdmin2 = (adminNode) => {
|
|
14364
|
+
const profile = getAdminProfile2(adminNode);
|
|
14365
|
+
return profile?.toLowerCase() === "super_admin";
|
|
14366
|
+
};
|
|
14367
|
+
var getAdminTrustedHosts2 = (adminNode) => {
|
|
14368
|
+
const trustedHosts = [];
|
|
14369
|
+
for (let i = 1; i <= 10; i++) {
|
|
14370
|
+
const host = getSetValue2(adminNode, `trusthost${i}`);
|
|
14371
|
+
if (host && host !== "0.0.0.0 0.0.0.0") {
|
|
14372
|
+
trustedHosts.push(host);
|
|
14373
|
+
}
|
|
14374
|
+
}
|
|
14375
|
+
return trustedHosts;
|
|
14376
|
+
};
|
|
14377
|
+
var hasAdminTrustedHosts2 = (adminNode) => {
|
|
14378
|
+
return getAdminTrustedHosts2(adminNode).length > 0;
|
|
14379
|
+
};
|
|
14380
|
+
|
|
13742
14381
|
// ../rules-default/src/fortinet/fortigate-rules.ts
|
|
13743
14382
|
var HostnameRequired3 = {
|
|
13744
14383
|
id: "FGT-SYS-001",
|
|
@@ -13752,7 +14391,7 @@ var HostnameRequired3 = {
|
|
|
13752
14391
|
remediation: 'Configure hostname under "config system global" using "set hostname <name>".'
|
|
13753
14392
|
},
|
|
13754
14393
|
check: (node) => {
|
|
13755
|
-
const hostname =
|
|
14394
|
+
const hostname = getSetValue2(node, "hostname");
|
|
13756
14395
|
if (!hostname) {
|
|
13757
14396
|
return {
|
|
13758
14397
|
passed: false,
|
|
@@ -13785,7 +14424,7 @@ var AdminTrustedHostRequired = {
|
|
|
13785
14424
|
remediation: 'Configure trusted hosts for each admin user using "set trusthost1", "set trusthost2", etc.'
|
|
13786
14425
|
},
|
|
13787
14426
|
check: (node) => {
|
|
13788
|
-
const admins =
|
|
14427
|
+
const admins = getEditEntries2(node);
|
|
13789
14428
|
if (admins.length === 0) {
|
|
13790
14429
|
return {
|
|
13791
14430
|
passed: true,
|
|
@@ -13798,11 +14437,11 @@ var AdminTrustedHostRequired = {
|
|
|
13798
14437
|
}
|
|
13799
14438
|
const issues = [];
|
|
13800
14439
|
for (const admin of admins) {
|
|
13801
|
-
const adminName =
|
|
13802
|
-
if (
|
|
14440
|
+
const adminName = getEditEntryName2(admin);
|
|
14441
|
+
if (hasAdminTrustedHosts2(admin)) {
|
|
13803
14442
|
continue;
|
|
13804
14443
|
}
|
|
13805
|
-
if (
|
|
14444
|
+
if (isSuperAdmin2(admin)) {
|
|
13806
14445
|
issues.push(`Super admin "${adminName}" has no trusted host restrictions. This is a critical security issue.`);
|
|
13807
14446
|
} else {
|
|
13808
14447
|
issues.push(`Admin "${adminName}" has no trusted host restrictions.`);
|
|
@@ -13840,7 +14479,7 @@ var PolicyLoggingRequired = {
|
|
|
13840
14479
|
remediation: 'Enable logging on all firewall policies using "set logtraffic all" or "set logtraffic utm".'
|
|
13841
14480
|
},
|
|
13842
14481
|
check: (node) => {
|
|
13843
|
-
const policies =
|
|
14482
|
+
const policies = getEditEntries2(node);
|
|
13844
14483
|
if (policies.length === 0) {
|
|
13845
14484
|
return {
|
|
13846
14485
|
passed: true,
|
|
@@ -13853,10 +14492,10 @@ var PolicyLoggingRequired = {
|
|
|
13853
14492
|
}
|
|
13854
14493
|
const issues = [];
|
|
13855
14494
|
for (const policy of policies) {
|
|
13856
|
-
if (
|
|
13857
|
-
const logging =
|
|
14495
|
+
if (isPolicyDisabled2(policy)) continue;
|
|
14496
|
+
const logging = hasLogging4(policy);
|
|
13858
14497
|
if (!logging.logtraffic || isFeatureDisabled(logging.logtraffic)) {
|
|
13859
|
-
const policyId =
|
|
14498
|
+
const policyId = getEditEntryName2(policy);
|
|
13860
14499
|
issues.push(`Policy ${policyId} does not have logging enabled.`);
|
|
13861
14500
|
}
|
|
13862
14501
|
}
|
|
@@ -13894,6 +14533,44 @@ function getRulesByFortinetVendor() {
|
|
|
13894
14533
|
return [...allCommonRules, ...allFortinetRules];
|
|
13895
14534
|
}
|
|
13896
14535
|
|
|
14536
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/extreme/helpers.ts
|
|
14537
|
+
var getExosVlanName2 = (node) => {
|
|
14538
|
+
const match = node.id.match(/^(?:create|configure)\s+vlan\s+["']?(\w+)["']?/i);
|
|
14539
|
+
const vlanName = match?.[1];
|
|
14540
|
+
return vlanName?.trim();
|
|
14541
|
+
};
|
|
14542
|
+
var isVossVlanCreate2 = (node) => {
|
|
14543
|
+
return /^vlan\s+create\s+\d+/i.test(node.id);
|
|
14544
|
+
};
|
|
14545
|
+
var getVossVlanId2 = (node) => {
|
|
14546
|
+
const match = node.id.match(/^vlan\s+(?:create|members|i-sid)\s+(\d+)/i);
|
|
14547
|
+
const vlanId = match?.[1];
|
|
14548
|
+
return vlanId ? parseInt(vlanId, 10) : void 0;
|
|
14549
|
+
};
|
|
14550
|
+
var isVossGigabitEthernet2 = (node) => {
|
|
14551
|
+
return /^interface\s+GigabitEthernet\s+\d+\/\d+/i.test(node.id);
|
|
14552
|
+
};
|
|
14553
|
+
var isVossShutdown2 = (node) => {
|
|
14554
|
+
if (!node?.children) return false;
|
|
14555
|
+
const hasShutdown = node.children.some(
|
|
14556
|
+
(child) => child?.id?.toLowerCase() === "shutdown"
|
|
14557
|
+
);
|
|
14558
|
+
const hasNoShutdown = node.children.some(
|
|
14559
|
+
(child) => child?.id?.toLowerCase() === "no shutdown"
|
|
14560
|
+
);
|
|
14561
|
+
return hasShutdown && !hasNoShutdown;
|
|
14562
|
+
};
|
|
14563
|
+
var getVossDefaultVlan2 = (node) => {
|
|
14564
|
+
if (!node?.children) return void 0;
|
|
14565
|
+
const defaultVlan = node.children.find(
|
|
14566
|
+
(child) => child?.id && /^default-vlan-id\s+\d+/i.test(child.id)
|
|
14567
|
+
);
|
|
14568
|
+
if (!defaultVlan?.id) return void 0;
|
|
14569
|
+
const match = defaultVlan.id.match(/default-vlan-id\s+(\d+)/i);
|
|
14570
|
+
const vlanId = match?.[1];
|
|
14571
|
+
return vlanId ? parseInt(vlanId, 10) : void 0;
|
|
14572
|
+
};
|
|
14573
|
+
|
|
13897
14574
|
// ../rules-default/src/extreme/exos-rules.ts
|
|
13898
14575
|
var ExosSysnameRequired = {
|
|
13899
14576
|
id: "EXOS-SYS-001",
|
|
@@ -13973,7 +14650,7 @@ var ExosVlanNaming = {
|
|
|
13973
14650
|
remediation: 'Use descriptive VLAN names: create vlan "<meaningful-name>" tag <id>'
|
|
13974
14651
|
},
|
|
13975
14652
|
check: (node, context) => {
|
|
13976
|
-
const vlanName =
|
|
14653
|
+
const vlanName = getExosVlanName2(node);
|
|
13977
14654
|
if (!vlanName) {
|
|
13978
14655
|
return {
|
|
13979
14656
|
passed: false,
|
|
@@ -14064,7 +14741,7 @@ var VossVlanIsidRequired = {
|
|
|
14064
14741
|
remediation: "Configure I-SID for VLAN: vlan i-sid <vlan-id> <isid>"
|
|
14065
14742
|
},
|
|
14066
14743
|
check: (node, context) => {
|
|
14067
|
-
if (!
|
|
14744
|
+
if (!isVossVlanCreate2(node)) {
|
|
14068
14745
|
return {
|
|
14069
14746
|
passed: true,
|
|
14070
14747
|
message: "Not a VLAN create command.",
|
|
@@ -14074,7 +14751,7 @@ var VossVlanIsidRequired = {
|
|
|
14074
14751
|
loc: node.loc
|
|
14075
14752
|
};
|
|
14076
14753
|
}
|
|
14077
|
-
const vlanId =
|
|
14754
|
+
const vlanId = getVossVlanId2(node);
|
|
14078
14755
|
if (!vlanId) {
|
|
14079
14756
|
return {
|
|
14080
14757
|
passed: false,
|
|
@@ -14117,7 +14794,7 @@ var VossInterfaceDefaultVlan = {
|
|
|
14117
14794
|
remediation: "Configure default VLAN: default-vlan-id <vlan-id>"
|
|
14118
14795
|
},
|
|
14119
14796
|
check: (node, context) => {
|
|
14120
|
-
if (!
|
|
14797
|
+
if (!isVossGigabitEthernet2(node)) {
|
|
14121
14798
|
return {
|
|
14122
14799
|
passed: true,
|
|
14123
14800
|
message: "Not a GigabitEthernet interface.",
|
|
@@ -14127,7 +14804,7 @@ var VossInterfaceDefaultVlan = {
|
|
|
14127
14804
|
loc: node.loc
|
|
14128
14805
|
};
|
|
14129
14806
|
}
|
|
14130
|
-
if (
|
|
14807
|
+
if (isVossShutdown2(node)) {
|
|
14131
14808
|
return {
|
|
14132
14809
|
passed: true,
|
|
14133
14810
|
message: "Interface is shutdown.",
|
|
@@ -14137,7 +14814,7 @@ var VossInterfaceDefaultVlan = {
|
|
|
14137
14814
|
loc: node.loc
|
|
14138
14815
|
};
|
|
14139
14816
|
}
|
|
14140
|
-
const defaultVlan =
|
|
14817
|
+
const defaultVlan = getVossDefaultVlan2(node);
|
|
14141
14818
|
if (!defaultVlan) {
|
|
14142
14819
|
return {
|
|
14143
14820
|
passed: false,
|
|
@@ -14189,6 +14866,22 @@ function getRulesByExtremeVendor(vendorId) {
|
|
|
14189
14866
|
}
|
|
14190
14867
|
}
|
|
14191
14868
|
|
|
14869
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/huawei/helpers.ts
|
|
14870
|
+
var isEnabled4 = (node) => {
|
|
14871
|
+
if (!node?.children) return false;
|
|
14872
|
+
return node.children.some((child) => {
|
|
14873
|
+
const rawText = child?.rawText?.toLowerCase().trim();
|
|
14874
|
+
return rawText === "undo shutdown";
|
|
14875
|
+
});
|
|
14876
|
+
};
|
|
14877
|
+
var isPhysicalPort5 = (interfaceName) => {
|
|
14878
|
+
const name = interfaceName.toLowerCase();
|
|
14879
|
+
return !name.includes("vlanif") && !name.includes("loopback") && !name.includes("null") && !name.includes("tunnel") && !name.includes("eth-trunk") && !name.includes("nve") && !name.includes("vbdif");
|
|
14880
|
+
};
|
|
14881
|
+
var hasDescription6 = (node) => {
|
|
14882
|
+
return hasChildCommand5(node, "description");
|
|
14883
|
+
};
|
|
14884
|
+
|
|
14192
14885
|
// ../rules-default/src/huawei/vrp-rules.ts
|
|
14193
14886
|
var SysnameRequired = {
|
|
14194
14887
|
id: "HUAWEI-SYS-001",
|
|
@@ -14236,7 +14929,7 @@ var InterfaceDescriptionRequired2 = {
|
|
|
14236
14929
|
},
|
|
14237
14930
|
check: (node) => {
|
|
14238
14931
|
const interfaceName = node.id.replace(/^interface\s+/i, "").trim();
|
|
14239
|
-
if (!
|
|
14932
|
+
if (!isPhysicalPort5(interfaceName)) {
|
|
14240
14933
|
return {
|
|
14241
14934
|
passed: true,
|
|
14242
14935
|
message: "Non-physical interface, description optional.",
|
|
@@ -14246,7 +14939,7 @@ var InterfaceDescriptionRequired2 = {
|
|
|
14246
14939
|
loc: node.loc
|
|
14247
14940
|
};
|
|
14248
14941
|
}
|
|
14249
|
-
if (!
|
|
14942
|
+
if (!isEnabled4(node)) {
|
|
14250
14943
|
return {
|
|
14251
14944
|
passed: true,
|
|
14252
14945
|
message: "Interface is shutdown, description optional.",
|
|
@@ -14256,7 +14949,7 @@ var InterfaceDescriptionRequired2 = {
|
|
|
14256
14949
|
loc: node.loc
|
|
14257
14950
|
};
|
|
14258
14951
|
}
|
|
14259
|
-
if (!
|
|
14952
|
+
if (!hasDescription6(node)) {
|
|
14260
14953
|
return {
|
|
14261
14954
|
passed: false,
|
|
14262
14955
|
message: `Interface ${interfaceName} is enabled but has no description.`,
|
|
@@ -14340,6 +15033,55 @@ function getRulesByHuaweiVendor() {
|
|
|
14340
15033
|
return [...allCommonRules, ...allHuaweiRules];
|
|
14341
15034
|
}
|
|
14342
15035
|
|
|
15036
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/mikrotik/helpers.ts
|
|
15037
|
+
var parseProperty2 = (commandStr, propertyName) => {
|
|
15038
|
+
const regex = new RegExp(`\\b${propertyName}=(?:"([^"]+)"|'([^']+)'|(\\S+))`, "i");
|
|
15039
|
+
const match = commandStr.match(regex);
|
|
15040
|
+
if (match) {
|
|
15041
|
+
return match[1] || match[2] || match[3];
|
|
15042
|
+
}
|
|
15043
|
+
return void 0;
|
|
15044
|
+
};
|
|
15045
|
+
var getFirewallChain2 = (nodeOrCommand) => {
|
|
15046
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
15047
|
+
return parseProperty2(str, "chain");
|
|
15048
|
+
};
|
|
15049
|
+
var getFirewallAction2 = (nodeOrCommand) => {
|
|
15050
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
15051
|
+
return parseProperty2(str, "action");
|
|
15052
|
+
};
|
|
15053
|
+
var getName2 = (nodeOrCommand) => {
|
|
15054
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
15055
|
+
return parseProperty2(str, "name");
|
|
15056
|
+
};
|
|
15057
|
+
var isAddCommand2 = (nodeOrCommand) => {
|
|
15058
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
15059
|
+
return /^add\s+/i.test(str.trim());
|
|
15060
|
+
};
|
|
15061
|
+
var isSetCommand2 = (nodeOrCommand) => {
|
|
15062
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
15063
|
+
return /^set\s+/i.test(str.trim());
|
|
15064
|
+
};
|
|
15065
|
+
var getAddCommands2 = (node) => {
|
|
15066
|
+
if (!node?.children) return [];
|
|
15067
|
+
return node.children.filter((child) => isAddCommand2(child));
|
|
15068
|
+
};
|
|
15069
|
+
var isServiceDisabled2 = (nodeOrCommand) => {
|
|
15070
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
15071
|
+
const disabled = parseProperty2(str, "disabled");
|
|
15072
|
+
return disabled?.toLowerCase() === "yes";
|
|
15073
|
+
};
|
|
15074
|
+
var getSystemIdentity2 = (node) => {
|
|
15075
|
+
if (!node?.children) return void 0;
|
|
15076
|
+
for (const child of node.children) {
|
|
15077
|
+
if (isSetCommand2(child)) {
|
|
15078
|
+
const name = getName2(child);
|
|
15079
|
+
if (name) return name;
|
|
15080
|
+
}
|
|
15081
|
+
}
|
|
15082
|
+
return void 0;
|
|
15083
|
+
};
|
|
15084
|
+
|
|
14343
15085
|
// ../rules-default/src/mikrotik/routeros-rules.ts
|
|
14344
15086
|
var MikrotikSystemIdentity = {
|
|
14345
15087
|
id: "MIK-SYS-001",
|
|
@@ -14353,7 +15095,7 @@ var MikrotikSystemIdentity = {
|
|
|
14353
15095
|
remediation: "Configure system identity: /system identity set name=MyRouter"
|
|
14354
15096
|
},
|
|
14355
15097
|
check: (node) => {
|
|
14356
|
-
const identity =
|
|
15098
|
+
const identity = getSystemIdentity2(node);
|
|
14357
15099
|
if (!identity || equalsIgnoreCase(identity, "mikrotik") || equalsIgnoreCase(identity, "routerboard")) {
|
|
14358
15100
|
return {
|
|
14359
15101
|
passed: false,
|
|
@@ -14391,8 +15133,8 @@ var MikrotikDisableUnusedServices = {
|
|
|
14391
15133
|
for (const child of node.children) {
|
|
14392
15134
|
const childId = child.id.toLowerCase();
|
|
14393
15135
|
for (const service of dangerousServices) {
|
|
14394
|
-
if (childId.includes(service) && !
|
|
14395
|
-
const disabled =
|
|
15136
|
+
if (childId.includes(service) && !isServiceDisabled2(child)) {
|
|
15137
|
+
const disabled = parseProperty2(child.id, "disabled");
|
|
14396
15138
|
if (!disabled || !equalsIgnoreCase(disabled, "yes")) {
|
|
14397
15139
|
issues.push(`Service '${service}' is enabled. Consider disabling it.`);
|
|
14398
15140
|
}
|
|
@@ -14431,11 +15173,11 @@ var MikrotikInputChainDrop = {
|
|
|
14431
15173
|
remediation: "Add drop rule for input chain: add chain=input action=drop"
|
|
14432
15174
|
},
|
|
14433
15175
|
check: (node) => {
|
|
14434
|
-
const addCommands =
|
|
15176
|
+
const addCommands = getAddCommands2(node);
|
|
14435
15177
|
let hasInputDrop = false;
|
|
14436
15178
|
for (const cmd of addCommands) {
|
|
14437
|
-
const chain =
|
|
14438
|
-
const action =
|
|
15179
|
+
const chain = getFirewallChain2(cmd);
|
|
15180
|
+
const action = getFirewallAction2(cmd);
|
|
14439
15181
|
if (chain && equalsIgnoreCase(chain, "input") && action && equalsIgnoreCase(action, "drop")) {
|
|
14440
15182
|
hasInputDrop = true;
|
|
14441
15183
|
break;
|
|
@@ -14472,6 +15214,60 @@ function getRulesByMikroTikVendor() {
|
|
|
14472
15214
|
return [...allCommonRules, ...allMikroTikRules];
|
|
14473
15215
|
}
|
|
14474
15216
|
|
|
15217
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/nokia/helpers.ts
|
|
15218
|
+
var isAdminStateDisabled2 = (node) => {
|
|
15219
|
+
if (!node?.children) return false;
|
|
15220
|
+
const directCheck = node.children.some((child) => {
|
|
15221
|
+
const rawText = child?.rawText?.toLowerCase().trim();
|
|
15222
|
+
return rawText === "admin-state disable";
|
|
15223
|
+
});
|
|
15224
|
+
if (directCheck) return true;
|
|
15225
|
+
return node?.rawText?.toLowerCase().includes("admin-state disable") ?? false;
|
|
15226
|
+
};
|
|
15227
|
+
var isPhysicalPort6 = (portName) => {
|
|
15228
|
+
const name = portName.toLowerCase();
|
|
15229
|
+
return /^\d+\/\d+\/\d+/.test(name);
|
|
15230
|
+
};
|
|
15231
|
+
var hasDescription7 = (node) => {
|
|
15232
|
+
return hasChildCommand5(node, "description");
|
|
15233
|
+
};
|
|
15234
|
+
var getDescription4 = (node) => {
|
|
15235
|
+
const descCmd = getChildCommand5(node, "description");
|
|
15236
|
+
if (descCmd?.rawText) {
|
|
15237
|
+
const match = descCmd.rawText.match(/description\s+"([^"]+)"|description\s+(\S+)/i);
|
|
15238
|
+
if (match) {
|
|
15239
|
+
return match[1] || match[2];
|
|
15240
|
+
}
|
|
15241
|
+
}
|
|
15242
|
+
return void 0;
|
|
15243
|
+
};
|
|
15244
|
+
var getSystemName2 = (node) => {
|
|
15245
|
+
if (!node?.children) return void 0;
|
|
15246
|
+
const nameCmd = node.children.find((child) => {
|
|
15247
|
+
return child?.id?.toLowerCase().startsWith("name");
|
|
15248
|
+
});
|
|
15249
|
+
if (nameCmd?.rawText) {
|
|
15250
|
+
const match = nameCmd.rawText.match(/name\s+"([^"]+)"/i);
|
|
15251
|
+
if (match) {
|
|
15252
|
+
return match[1];
|
|
15253
|
+
}
|
|
15254
|
+
}
|
|
15255
|
+
return void 0;
|
|
15256
|
+
};
|
|
15257
|
+
var hasBgpRouterId3 = (node) => {
|
|
15258
|
+
return hasChildCommand5(node, "router-id");
|
|
15259
|
+
};
|
|
15260
|
+
var getBgpRouterId2 = (node) => {
|
|
15261
|
+
const routerIdCmd = getChildCommand5(node, "router-id");
|
|
15262
|
+
if (routerIdCmd?.rawText) {
|
|
15263
|
+
const match = routerIdCmd.rawText.match(/router-id\s+([\d.]+)/i);
|
|
15264
|
+
if (match) {
|
|
15265
|
+
return match[1];
|
|
15266
|
+
}
|
|
15267
|
+
}
|
|
15268
|
+
return void 0;
|
|
15269
|
+
};
|
|
15270
|
+
|
|
14475
15271
|
// ../rules-default/src/nokia/sros-rules.ts
|
|
14476
15272
|
var SystemNameRequired = {
|
|
14477
15273
|
id: "NOKIA-SYS-001",
|
|
@@ -14485,7 +15281,7 @@ var SystemNameRequired = {
|
|
|
14485
15281
|
remediation: 'Configure system name using: system > name "<hostname>"'
|
|
14486
15282
|
},
|
|
14487
15283
|
check: (node) => {
|
|
14488
|
-
const name =
|
|
15284
|
+
const name = getSystemName2(node);
|
|
14489
15285
|
if (!name || name.length === 0) {
|
|
14490
15286
|
return {
|
|
14491
15287
|
passed: false,
|
|
@@ -14519,7 +15315,7 @@ var PortDescriptionRequired = {
|
|
|
14519
15315
|
},
|
|
14520
15316
|
check: (node) => {
|
|
14521
15317
|
const portName = node.id.replace(/^port\s+/i, "").trim();
|
|
14522
|
-
if (!
|
|
15318
|
+
if (!isPhysicalPort6(portName)) {
|
|
14523
15319
|
return {
|
|
14524
15320
|
passed: true,
|
|
14525
15321
|
message: "Not a physical port, description optional.",
|
|
@@ -14529,7 +15325,7 @@ var PortDescriptionRequired = {
|
|
|
14529
15325
|
loc: node.loc
|
|
14530
15326
|
};
|
|
14531
15327
|
}
|
|
14532
|
-
if (
|
|
15328
|
+
if (isAdminStateDisabled2(node)) {
|
|
14533
15329
|
return {
|
|
14534
15330
|
passed: true,
|
|
14535
15331
|
message: "Port is disabled, description optional.",
|
|
@@ -14539,7 +15335,7 @@ var PortDescriptionRequired = {
|
|
|
14539
15335
|
loc: node.loc
|
|
14540
15336
|
};
|
|
14541
15337
|
}
|
|
14542
|
-
if (!
|
|
15338
|
+
if (!hasDescription7(node)) {
|
|
14543
15339
|
return {
|
|
14544
15340
|
passed: false,
|
|
14545
15341
|
message: `Port ${portName} is enabled but has no description.`,
|
|
@@ -14551,7 +15347,7 @@ var PortDescriptionRequired = {
|
|
|
14551
15347
|
}
|
|
14552
15348
|
return {
|
|
14553
15349
|
passed: true,
|
|
14554
|
-
message: `Port ${portName} has description: ${
|
|
15350
|
+
message: `Port ${portName} has description: ${getDescription4(node)}`,
|
|
14555
15351
|
ruleId: "NOKIA-PORT-001",
|
|
14556
15352
|
nodeId: node.id,
|
|
14557
15353
|
level: "info",
|
|
@@ -14571,7 +15367,7 @@ var BgpRouterIdRequired = {
|
|
|
14571
15367
|
remediation: "Configure BGP router-id: bgp > router-id <ip-address>"
|
|
14572
15368
|
},
|
|
14573
15369
|
check: (node) => {
|
|
14574
|
-
if (
|
|
15370
|
+
if (isAdminStateDisabled2(node)) {
|
|
14575
15371
|
return {
|
|
14576
15372
|
passed: true,
|
|
14577
15373
|
message: "BGP is disabled.",
|
|
@@ -14581,7 +15377,7 @@ var BgpRouterIdRequired = {
|
|
|
14581
15377
|
loc: node.loc
|
|
14582
15378
|
};
|
|
14583
15379
|
}
|
|
14584
|
-
if (!
|
|
15380
|
+
if (!hasBgpRouterId3(node)) {
|
|
14585
15381
|
return {
|
|
14586
15382
|
passed: false,
|
|
14587
15383
|
message: "BGP router-id is not configured. Configure a stable router-id.",
|
|
@@ -14593,7 +15389,7 @@ var BgpRouterIdRequired = {
|
|
|
14593
15389
|
}
|
|
14594
15390
|
return {
|
|
14595
15391
|
passed: true,
|
|
14596
|
-
message: `BGP router-id is configured: ${
|
|
15392
|
+
message: `BGP router-id is configured: ${getBgpRouterId2(node)}`,
|
|
14597
15393
|
ruleId: "NOKIA-BGP-001",
|
|
14598
15394
|
nodeId: node.id,
|
|
14599
15395
|
level: "info",
|
|
@@ -14615,6 +15411,40 @@ function getRulesByNokiaVendor() {
|
|
|
14615
15411
|
return [...allCommonRules, ...allNokiaRules];
|
|
14616
15412
|
}
|
|
14617
15413
|
|
|
15414
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/cumulus/helpers.ts
|
|
15415
|
+
var isSwitchPort2 = (interfaceName) => {
|
|
15416
|
+
const name = interfaceName.toLowerCase();
|
|
15417
|
+
return /swp\d+/.test(name);
|
|
15418
|
+
};
|
|
15419
|
+
var isBridgeInterface4 = (interfaceName) => {
|
|
15420
|
+
const name = interfaceName.toLowerCase();
|
|
15421
|
+
return name.includes("bridge") || name === "br_default" || /^br\d+$/.test(name);
|
|
15422
|
+
};
|
|
15423
|
+
var isVlanAwareBridge2 = (node) => {
|
|
15424
|
+
return node.children.some(
|
|
15425
|
+
(child) => child.id.toLowerCase().includes("bridge-vlan-aware") && child.id.toLowerCase().includes("yes")
|
|
15426
|
+
);
|
|
15427
|
+
};
|
|
15428
|
+
var getInterfaceName6 = (node) => {
|
|
15429
|
+
const parts = node.id.split(/\s+/);
|
|
15430
|
+
return parts[1] || node.id;
|
|
15431
|
+
};
|
|
15432
|
+
var hasDescription8 = (node) => {
|
|
15433
|
+
return node.children.some(
|
|
15434
|
+
(child) => child.id.toLowerCase().startsWith("alias ")
|
|
15435
|
+
);
|
|
15436
|
+
};
|
|
15437
|
+
var hasBridgeVids2 = (node) => {
|
|
15438
|
+
return node.children.some(
|
|
15439
|
+
(child) => child.id.toLowerCase().startsWith("bridge-vids ")
|
|
15440
|
+
);
|
|
15441
|
+
};
|
|
15442
|
+
var hasBgpRouterId4 = (node) => {
|
|
15443
|
+
return node.children.some(
|
|
15444
|
+
(child) => child.id.toLowerCase().startsWith("bgp router-id ")
|
|
15445
|
+
);
|
|
15446
|
+
};
|
|
15447
|
+
|
|
14618
15448
|
// ../rules-default/src/cumulus/cumulus-rules.ts
|
|
14619
15449
|
var CumulusInterfaceDescription = {
|
|
14620
15450
|
id: "CUM-IF-001",
|
|
@@ -14628,8 +15458,8 @@ var CumulusInterfaceDescription = {
|
|
|
14628
15458
|
remediation: 'Add "alias <description>" under the interface stanza.'
|
|
14629
15459
|
},
|
|
14630
15460
|
check: (node) => {
|
|
14631
|
-
const ifaceName =
|
|
14632
|
-
if (!
|
|
15461
|
+
const ifaceName = getInterfaceName6(node);
|
|
15462
|
+
if (!isSwitchPort2(ifaceName)) {
|
|
14633
15463
|
return {
|
|
14634
15464
|
passed: true,
|
|
14635
15465
|
message: "Not a switch port interface.",
|
|
@@ -14639,7 +15469,7 @@ var CumulusInterfaceDescription = {
|
|
|
14639
15469
|
loc: node.loc
|
|
14640
15470
|
};
|
|
14641
15471
|
}
|
|
14642
|
-
if (!
|
|
15472
|
+
if (!hasDescription8(node)) {
|
|
14643
15473
|
return {
|
|
14644
15474
|
passed: false,
|
|
14645
15475
|
message: `Switch port "${ifaceName}" missing description (alias).`,
|
|
@@ -14671,8 +15501,8 @@ var CumulusBridgeVlans = {
|
|
|
14671
15501
|
remediation: 'Add "bridge-vids <vlan-ids>" to define allowed VLANs on the bridge.'
|
|
14672
15502
|
},
|
|
14673
15503
|
check: (node) => {
|
|
14674
|
-
const ifaceName =
|
|
14675
|
-
if (!
|
|
15504
|
+
const ifaceName = getInterfaceName6(node);
|
|
15505
|
+
if (!isBridgeInterface4(ifaceName)) {
|
|
14676
15506
|
return {
|
|
14677
15507
|
passed: true,
|
|
14678
15508
|
message: "Not a bridge interface.",
|
|
@@ -14682,7 +15512,7 @@ var CumulusBridgeVlans = {
|
|
|
14682
15512
|
loc: node.loc
|
|
14683
15513
|
};
|
|
14684
15514
|
}
|
|
14685
|
-
if (!
|
|
15515
|
+
if (!isVlanAwareBridge2(node)) {
|
|
14686
15516
|
return {
|
|
14687
15517
|
passed: true,
|
|
14688
15518
|
message: "Not a VLAN-aware bridge.",
|
|
@@ -14692,7 +15522,7 @@ var CumulusBridgeVlans = {
|
|
|
14692
15522
|
loc: node.loc
|
|
14693
15523
|
};
|
|
14694
15524
|
}
|
|
14695
|
-
if (!
|
|
15525
|
+
if (!hasBridgeVids2(node)) {
|
|
14696
15526
|
return {
|
|
14697
15527
|
passed: false,
|
|
14698
15528
|
message: `VLAN-aware bridge "${ifaceName}" has no VLANs (bridge-vids) configured.`,
|
|
@@ -14724,7 +15554,7 @@ var CumulusBgpRouterId = {
|
|
|
14724
15554
|
remediation: 'Add "bgp router-id <ip>" to explicitly set router ID.'
|
|
14725
15555
|
},
|
|
14726
15556
|
check: (node) => {
|
|
14727
|
-
if (!
|
|
15557
|
+
if (!hasBgpRouterId4(node)) {
|
|
14728
15558
|
return {
|
|
14729
15559
|
passed: false,
|
|
14730
15560
|
message: "BGP missing explicit router-id configuration.",
|
|
@@ -14760,7 +15590,7 @@ function getRulesByCumulusVendor() {
|
|
|
14760
15590
|
|
|
14761
15591
|
// ../rules-default/src/json/cisco-json-rules.json
|
|
14762
15592
|
var cisco_json_rules_default = {
|
|
14763
|
-
$schema: "https://sentriflow.
|
|
15593
|
+
$schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
|
|
14764
15594
|
version: "1.0",
|
|
14765
15595
|
meta: {
|
|
14766
15596
|
name: "Cisco IOS JSON Rules",
|
|
@@ -14957,7 +15787,7 @@ var cisco_json_rules_default = {
|
|
|
14957
15787
|
|
|
14958
15788
|
// ../rules-default/src/json/common-json-rules.json
|
|
14959
15789
|
var common_json_rules_default = {
|
|
14960
|
-
$schema: "https://sentriflow.
|
|
15790
|
+
$schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
|
|
14961
15791
|
version: "1.0",
|
|
14962
15792
|
meta: {
|
|
14963
15793
|
name: "Common JSON Rules",
|
|
@@ -15005,7 +15835,7 @@ var common_json_rules_default = {
|
|
|
15005
15835
|
|
|
15006
15836
|
// ../rules-default/src/json/juniper-json-rules.json
|
|
15007
15837
|
var juniper_json_rules_default = {
|
|
15008
|
-
$schema: "https://sentriflow.
|
|
15838
|
+
$schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
|
|
15009
15839
|
version: "1.0",
|
|
15010
15840
|
meta: {
|
|
15011
15841
|
name: "Juniper JunOS JSON Rules",
|
|
@@ -15250,11 +16080,11 @@ function getRulesByVendor(vendorId) {
|
|
|
15250
16080
|
// src/config.ts
|
|
15251
16081
|
import { existsSync as existsSync2 } from "fs";
|
|
15252
16082
|
import { readFile as readFileAsync } from "fs/promises";
|
|
15253
|
-
import { resolve as
|
|
16083
|
+
import { resolve as resolve4, dirname } from "path";
|
|
15254
16084
|
|
|
15255
16085
|
// src/security/pathValidator.ts
|
|
15256
16086
|
import { existsSync, statSync, realpathSync } from "fs";
|
|
15257
|
-
import { extname, resolve } from "path";
|
|
16087
|
+
import { extname, resolve as resolve2 } from "path";
|
|
15258
16088
|
function normalizeSeparators(p) {
|
|
15259
16089
|
return p.replace(/\\/g, "/");
|
|
15260
16090
|
}
|
|
@@ -15272,7 +16102,7 @@ function validatePath(inputPath, options = {}) {
|
|
|
15272
16102
|
error: "Network (UNC) paths are not allowed"
|
|
15273
16103
|
};
|
|
15274
16104
|
}
|
|
15275
|
-
const absolutePath =
|
|
16105
|
+
const absolutePath = resolve2(inputPath);
|
|
15276
16106
|
const ext = extname(absolutePath).toLowerCase();
|
|
15277
16107
|
if (allowedExtensions.length > 0 && !allowedExtensions.includes(ext)) {
|
|
15278
16108
|
return {
|
|
@@ -15315,7 +16145,7 @@ function validatePath(inputPath, options = {}) {
|
|
|
15315
16145
|
const normalizedCanonical = normalizeSeparators(canonicalPath);
|
|
15316
16146
|
const isWithinBounds2 = allowedBaseDirs.some((baseDir) => {
|
|
15317
16147
|
try {
|
|
15318
|
-
const canonicalBase = realpathSync(
|
|
16148
|
+
const canonicalBase = realpathSync(resolve2(baseDir));
|
|
15319
16149
|
const normalizedBase = normalizeSeparators(canonicalBase);
|
|
15320
16150
|
return normalizedCanonical === normalizedBase || normalizedCanonical.startsWith(normalizedBase + "/");
|
|
15321
16151
|
} catch {
|
|
@@ -15345,22 +16175,6 @@ function validateConfigPath(configPath, baseDirs) {
|
|
|
15345
16175
|
mustExist: true
|
|
15346
16176
|
});
|
|
15347
16177
|
}
|
|
15348
|
-
function validateEncryptedPackPath(packPath, baseDirs) {
|
|
15349
|
-
return validatePath(packPath, {
|
|
15350
|
-
allowedBaseDirs: baseDirs,
|
|
15351
|
-
allowedExtensions: ALLOWED_ENCRYPTED_PACK_EXTENSIONS,
|
|
15352
|
-
maxFileSize: MAX_ENCRYPTED_PACK_SIZE,
|
|
15353
|
-
mustExist: true
|
|
15354
|
-
});
|
|
15355
|
-
}
|
|
15356
|
-
function validateGrx2PackPath(packPath, baseDirs) {
|
|
15357
|
-
return validatePath(packPath, {
|
|
15358
|
-
allowedBaseDirs: baseDirs,
|
|
15359
|
-
allowedExtensions: ALLOWED_GRX2_PACK_EXTENSIONS,
|
|
15360
|
-
maxFileSize: MAX_ENCRYPTED_PACK_SIZE,
|
|
15361
|
-
mustExist: true
|
|
15362
|
-
});
|
|
15363
|
-
}
|
|
15364
16178
|
function validateJsonRulesPath(jsonPath, baseDirs) {
|
|
15365
16179
|
return validatePath(jsonPath, {
|
|
15366
16180
|
allowedBaseDirs: baseDirs,
|
|
@@ -15378,6 +16192,40 @@ function validateInputFilePath(filePath, maxSize = 10 * 1024 * 1024, baseDirs) {
|
|
|
15378
16192
|
mustExist: true
|
|
15379
16193
|
});
|
|
15380
16194
|
}
|
|
16195
|
+
function validatePackPath(packPath, baseDirs) {
|
|
16196
|
+
return validatePath(packPath, {
|
|
16197
|
+
allowedBaseDirs: baseDirs,
|
|
16198
|
+
allowedExtensions: [],
|
|
16199
|
+
// Allow any extension - format detected via magic bytes
|
|
16200
|
+
maxFileSize: MAX_ENCRYPTED_PACK_SIZE,
|
|
16201
|
+
mustExist: true
|
|
16202
|
+
});
|
|
16203
|
+
}
|
|
16204
|
+
|
|
16205
|
+
// src/loaders/pack-detector.ts
|
|
16206
|
+
import { resolve as resolve3 } from "node:path";
|
|
16207
|
+
async function createPackDescriptor(filePath, orderIndex) {
|
|
16208
|
+
const absolutePath = resolve3(filePath);
|
|
16209
|
+
const format = await detectPackFormat(absolutePath);
|
|
16210
|
+
const basePriority = FORMAT_PRIORITIES[format];
|
|
16211
|
+
return {
|
|
16212
|
+
path: absolutePath,
|
|
16213
|
+
format,
|
|
16214
|
+
basePriority,
|
|
16215
|
+
priority: basePriority + orderIndex
|
|
16216
|
+
};
|
|
16217
|
+
}
|
|
16218
|
+
async function createPackDescriptors(packPaths) {
|
|
16219
|
+
const descriptors = [];
|
|
16220
|
+
for (let i = 0; i < packPaths.length; i++) {
|
|
16221
|
+
const packPath = packPaths[i];
|
|
16222
|
+
if (packPath !== void 0) {
|
|
16223
|
+
const descriptor = await createPackDescriptor(packPath, i);
|
|
16224
|
+
descriptors.push(descriptor);
|
|
16225
|
+
}
|
|
16226
|
+
}
|
|
16227
|
+
return descriptors;
|
|
16228
|
+
}
|
|
15381
16229
|
|
|
15382
16230
|
// src/loaders/index.ts
|
|
15383
16231
|
function validatePathOrThrow(path, pathValidator, errorContext, baseDirs) {
|
|
@@ -15424,11 +16272,11 @@ var CONFIG_FILES = [
|
|
|
15424
16272
|
".sentriflowrc.js"
|
|
15425
16273
|
];
|
|
15426
16274
|
function findConfigFile(startDir) {
|
|
15427
|
-
let currentDir =
|
|
16275
|
+
let currentDir = resolve4(startDir);
|
|
15428
16276
|
let depth = 0;
|
|
15429
16277
|
while (depth < MAX_TRAVERSAL_DEPTH) {
|
|
15430
16278
|
for (const configFile of CONFIG_FILES) {
|
|
15431
|
-
const configPath =
|
|
16279
|
+
const configPath = resolve4(currentDir, configFile);
|
|
15432
16280
|
if (existsSync2(configPath)) {
|
|
15433
16281
|
const validation = validateConfigPath(configPath);
|
|
15434
16282
|
if (validation.valid) {
|
|
@@ -15496,6 +16344,9 @@ function isValidSentriflowConfig(config) {
|
|
|
15496
16344
|
return false;
|
|
15497
16345
|
}
|
|
15498
16346
|
}
|
|
16347
|
+
if (obj.filterSpecialIps !== void 0 && typeof obj.filterSpecialIps !== "boolean") {
|
|
16348
|
+
return false;
|
|
16349
|
+
}
|
|
15499
16350
|
return true;
|
|
15500
16351
|
}
|
|
15501
16352
|
function isValidDirectoryConfig(config) {
|
|
@@ -15595,93 +16446,6 @@ function mergeDirectoryOptions(cliOptions, configOptions) {
|
|
|
15595
16446
|
}
|
|
15596
16447
|
return result;
|
|
15597
16448
|
}
|
|
15598
|
-
function isValidRule(rule) {
|
|
15599
|
-
if (typeof rule !== "object" || rule === null) {
|
|
15600
|
-
return false;
|
|
15601
|
-
}
|
|
15602
|
-
const obj = rule;
|
|
15603
|
-
if (typeof obj.id !== "string" || !RULE_ID_PATTERN.test(obj.id)) {
|
|
15604
|
-
return false;
|
|
15605
|
-
}
|
|
15606
|
-
if (typeof obj.check !== "function") {
|
|
15607
|
-
return false;
|
|
15608
|
-
}
|
|
15609
|
-
if (obj.vendor !== void 0) {
|
|
15610
|
-
if (Array.isArray(obj.vendor)) {
|
|
15611
|
-
for (const v of obj.vendor) {
|
|
15612
|
-
if (typeof v !== "string" || !isValidVendorId(v)) {
|
|
15613
|
-
return false;
|
|
15614
|
-
}
|
|
15615
|
-
}
|
|
15616
|
-
} else if (typeof obj.vendor !== "string" || !isValidVendorId(obj.vendor)) {
|
|
15617
|
-
return false;
|
|
15618
|
-
}
|
|
15619
|
-
}
|
|
15620
|
-
if (typeof obj.metadata !== "object" || obj.metadata === null) {
|
|
15621
|
-
return false;
|
|
15622
|
-
}
|
|
15623
|
-
const metadata = obj.metadata;
|
|
15624
|
-
if (!["error", "warning", "info"].includes(metadata.level)) {
|
|
15625
|
-
return false;
|
|
15626
|
-
}
|
|
15627
|
-
return true;
|
|
15628
|
-
}
|
|
15629
|
-
function isValidRulePack(pack) {
|
|
15630
|
-
if (typeof pack !== "object" || pack === null) {
|
|
15631
|
-
return false;
|
|
15632
|
-
}
|
|
15633
|
-
const obj = pack;
|
|
15634
|
-
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
15635
|
-
return false;
|
|
15636
|
-
}
|
|
15637
|
-
if (typeof obj.version !== "string" || obj.version.length === 0) {
|
|
15638
|
-
return false;
|
|
15639
|
-
}
|
|
15640
|
-
if (typeof obj.publisher !== "string" || obj.publisher.length === 0) {
|
|
15641
|
-
return false;
|
|
15642
|
-
}
|
|
15643
|
-
if (typeof obj.priority !== "number" || obj.priority < 0) {
|
|
15644
|
-
return false;
|
|
15645
|
-
}
|
|
15646
|
-
if (!Array.isArray(obj.rules)) {
|
|
15647
|
-
return false;
|
|
15648
|
-
}
|
|
15649
|
-
for (const rule of obj.rules) {
|
|
15650
|
-
if (!isValidRule(rule)) {
|
|
15651
|
-
return false;
|
|
15652
|
-
}
|
|
15653
|
-
}
|
|
15654
|
-
if (obj.disables !== void 0) {
|
|
15655
|
-
if (typeof obj.disables !== "object" || obj.disables === null) {
|
|
15656
|
-
return false;
|
|
15657
|
-
}
|
|
15658
|
-
const disables = obj.disables;
|
|
15659
|
-
if (disables.all !== void 0 && typeof disables.all !== "boolean") {
|
|
15660
|
-
return false;
|
|
15661
|
-
}
|
|
15662
|
-
if (disables.vendors !== void 0) {
|
|
15663
|
-
if (!Array.isArray(disables.vendors)) {
|
|
15664
|
-
return false;
|
|
15665
|
-
}
|
|
15666
|
-
for (const v of disables.vendors) {
|
|
15667
|
-
if (typeof v !== "string" || !isValidVendorId(v)) {
|
|
15668
|
-
return false;
|
|
15669
|
-
}
|
|
15670
|
-
}
|
|
15671
|
-
}
|
|
15672
|
-
if (disables.rules !== void 0) {
|
|
15673
|
-
if (!Array.isArray(disables.rules)) {
|
|
15674
|
-
return false;
|
|
15675
|
-
}
|
|
15676
|
-
for (const r of disables.rules) {
|
|
15677
|
-
if (typeof r !== "string") {
|
|
15678
|
-
return false;
|
|
15679
|
-
}
|
|
15680
|
-
}
|
|
15681
|
-
}
|
|
15682
|
-
}
|
|
15683
|
-
return true;
|
|
15684
|
-
}
|
|
15685
16449
|
async function loadConfigFile(configPath, baseDirs) {
|
|
15686
16450
|
return loadAndValidate({
|
|
15687
16451
|
path: configPath,
|
|
@@ -15755,13 +16519,6 @@ ${errors}`);
|
|
|
15755
16519
|
return compiledRules;
|
|
15756
16520
|
}, "JSON rules");
|
|
15757
16521
|
}
|
|
15758
|
-
function ruleAppliesToVendor(rule, vendorId) {
|
|
15759
|
-
if (!rule.vendor) return true;
|
|
15760
|
-
if (Array.isArray(rule.vendor)) {
|
|
15761
|
-
return rule.vendor.includes("common") || rule.vendor.includes(vendorId);
|
|
15762
|
-
}
|
|
15763
|
-
return rule.vendor === "common" || rule.vendor === vendorId;
|
|
15764
|
-
}
|
|
15765
16522
|
function isDefaultRuleDisabled(ruleId, vendorId, packs, legacyDisableIds) {
|
|
15766
16523
|
if (legacyDisableIds.has(ruleId)) return true;
|
|
15767
16524
|
for (const pack of packs) {
|
|
@@ -15800,7 +16557,7 @@ function mapPackLoadError(error) {
|
|
|
15800
16557
|
async function loadEncryptedRulePack(packPath, licenseKey, baseDirs) {
|
|
15801
16558
|
const canonicalPath = validatePathOrThrow(
|
|
15802
16559
|
packPath,
|
|
15803
|
-
|
|
16560
|
+
validatePackPath,
|
|
15804
16561
|
"encrypted pack",
|
|
15805
16562
|
baseDirs
|
|
15806
16563
|
);
|
|
@@ -15829,13 +16586,9 @@ async function resolveRules(options = {}) {
|
|
|
15829
16586
|
configPath,
|
|
15830
16587
|
noConfig = false,
|
|
15831
16588
|
rulesPath,
|
|
15832
|
-
|
|
15833
|
-
encryptedPackPaths,
|
|
16589
|
+
packPaths,
|
|
15834
16590
|
licenseKey,
|
|
15835
16591
|
strictPacks = false,
|
|
15836
|
-
// SEC-012: Default to graceful handling
|
|
15837
|
-
grx2PackPaths,
|
|
15838
|
-
strictGrx2 = false,
|
|
15839
16592
|
// Default to graceful handling
|
|
15840
16593
|
jsonRulesPaths,
|
|
15841
16594
|
disableIds = [],
|
|
@@ -15844,9 +16597,8 @@ async function resolveRules(options = {}) {
|
|
|
15844
16597
|
allowedBaseDirs
|
|
15845
16598
|
// SEC-011: Allowed base directories for file path validation
|
|
15846
16599
|
} = options;
|
|
15847
|
-
const packPathsArray =
|
|
16600
|
+
const packPathsArray = packPaths ? Array.isArray(packPaths) ? packPaths : [packPaths] : [];
|
|
15848
16601
|
const jsonPathsArray = jsonRulesPaths ? Array.isArray(jsonRulesPaths) ? jsonRulesPaths : [jsonRulesPaths] : [];
|
|
15849
|
-
const grx2PathsArray = grx2PackPaths ? Array.isArray(grx2PackPaths) ? grx2PackPaths : [grx2PackPaths] : [];
|
|
15850
16602
|
let config = { includeDefaults: true };
|
|
15851
16603
|
if (!noConfig) {
|
|
15852
16604
|
const foundConfigPath = configPath ?? findConfigFile(cwd);
|
|
@@ -15879,10 +16631,6 @@ async function resolveRules(options = {}) {
|
|
|
15879
16631
|
});
|
|
15880
16632
|
}
|
|
15881
16633
|
}
|
|
15882
|
-
if (rulePackPath) {
|
|
15883
|
-
const cliPack = await loadRulePackFile(rulePackPath, allowedBaseDirs);
|
|
15884
|
-
allPacks.push(cliPack);
|
|
15885
|
-
}
|
|
15886
16634
|
if (config.jsonRules && config.jsonRules.length > 0) {
|
|
15887
16635
|
for (const jsonPath of config.jsonRules) {
|
|
15888
16636
|
try {
|
|
@@ -15927,118 +16675,128 @@ async function resolveRules(options = {}) {
|
|
|
15927
16675
|
}
|
|
15928
16676
|
}
|
|
15929
16677
|
if (packPathsArray.length > 0) {
|
|
15930
|
-
|
|
15931
|
-
|
|
16678
|
+
let packDescriptors;
|
|
16679
|
+
try {
|
|
16680
|
+
packDescriptors = await createPackDescriptors(packPathsArray);
|
|
16681
|
+
} catch (error) {
|
|
16682
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
15932
16683
|
if (strictPacks) {
|
|
15933
|
-
throw new SentriflowConfigError(errorMsg);
|
|
15934
|
-
}
|
|
15935
|
-
console.error(`Warning: ${errorMsg}`);
|
|
15936
|
-
console.error(
|
|
15937
|
-
`Warning: Skipping ${packPathsArray.length} encrypted pack(s)`
|
|
15938
|
-
);
|
|
15939
|
-
} else {
|
|
15940
|
-
for (let i = 0; i < packPathsArray.length; i++) {
|
|
15941
|
-
const packPath = packPathsArray[i];
|
|
15942
|
-
if (!packPath) continue;
|
|
15943
|
-
try {
|
|
15944
|
-
const encryptedPack = await loadEncryptedRulePack(
|
|
15945
|
-
packPath,
|
|
15946
|
-
licenseKey,
|
|
15947
|
-
allowedBaseDirs
|
|
15948
|
-
);
|
|
15949
|
-
encryptedPack.priority = 200 + i;
|
|
15950
|
-
allPacks.push(encryptedPack);
|
|
15951
|
-
} catch (error) {
|
|
15952
|
-
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
15953
|
-
if (strictPacks) {
|
|
15954
|
-
throw error;
|
|
15955
|
-
}
|
|
15956
|
-
console.error(`Warning: Failed to load encrypted pack: ${errorMsg}`);
|
|
15957
|
-
console.error(`Warning: Skipping encrypted pack: ${packPath}`);
|
|
15958
|
-
}
|
|
16684
|
+
throw new SentriflowConfigError(`Pack detection failed: ${errorMsg}`);
|
|
15959
16685
|
}
|
|
16686
|
+
console.error(`Warning: Pack detection failed: ${errorMsg}`);
|
|
16687
|
+
packDescriptors = [];
|
|
15960
16688
|
}
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
16689
|
+
const hasEncryptedPacks = packDescriptors.some(
|
|
16690
|
+
(d) => d.format === "grx2" || d.format === "grpx"
|
|
16691
|
+
);
|
|
16692
|
+
if (hasEncryptedPacks && !licenseKey) {
|
|
16693
|
+
const errorMsg = "License key required for encrypted packs (use --license-key or set SENTRIFLOW_LICENSE_KEY)";
|
|
16694
|
+
if (strictPacks) {
|
|
15966
16695
|
throw new SentriflowConfigError(errorMsg);
|
|
15967
16696
|
}
|
|
15968
16697
|
console.error(`Warning: ${errorMsg}`);
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
let machineId2;
|
|
16698
|
+
}
|
|
16699
|
+
let machineId2;
|
|
16700
|
+
const hasGrx2Packs = packDescriptors.some((d) => d.format === "grx2");
|
|
16701
|
+
if (hasGrx2Packs && licenseKey) {
|
|
15974
16702
|
try {
|
|
15975
16703
|
machineId2 = await getMachineId();
|
|
15976
16704
|
} catch (error) {
|
|
15977
16705
|
const errorMsg = "Failed to retrieve machine ID for license validation";
|
|
15978
|
-
if (
|
|
16706
|
+
if (strictPacks) {
|
|
15979
16707
|
throw new SentriflowConfigError(errorMsg);
|
|
15980
16708
|
}
|
|
15981
16709
|
console.error(`Warning: ${errorMsg}`);
|
|
15982
|
-
console.error(
|
|
15983
|
-
`Warning: Skipping ${grx2PathsArray.length} GRX2 pack(s)`
|
|
15984
|
-
);
|
|
15985
|
-
machineId2 = "";
|
|
15986
16710
|
}
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
16711
|
+
}
|
|
16712
|
+
let loadedCount = 0;
|
|
16713
|
+
let totalRules = 0;
|
|
16714
|
+
const failedPacks = [];
|
|
16715
|
+
for (const desc of packDescriptors) {
|
|
16716
|
+
try {
|
|
16717
|
+
const validation = validatePackPath(desc.path, allowedBaseDirs);
|
|
16718
|
+
if (!validation.valid) {
|
|
16719
|
+
throw new SentriflowConfigError(validation.error ?? "Invalid pack path");
|
|
16720
|
+
}
|
|
16721
|
+
let loadedPack;
|
|
16722
|
+
switch (desc.format) {
|
|
16723
|
+
case "grx2": {
|
|
16724
|
+
if (!licenseKey) {
|
|
16725
|
+
console.error(`Warning: Skipping GRX2 pack (no license key): ${desc.path}`);
|
|
16726
|
+
continue;
|
|
16727
|
+
}
|
|
16728
|
+
if (!machineId2) {
|
|
16729
|
+
console.error(`Warning: Skipping GRX2 pack (no machine ID): ${desc.path}`);
|
|
16730
|
+
continue;
|
|
15998
16731
|
}
|
|
15999
|
-
|
|
16732
|
+
loadedPack = await loadExtendedPack(
|
|
16000
16733
|
validation.canonicalPath,
|
|
16001
16734
|
licenseKey,
|
|
16002
16735
|
machineId2
|
|
16003
16736
|
);
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
const messages = {
|
|
16012
|
-
LICENSE_MISSING: "Invalid or missing license key",
|
|
16013
|
-
LICENSE_EXPIRED: "License has expired",
|
|
16014
|
-
LICENSE_INVALID: "License key is invalid for this pack",
|
|
16015
|
-
DECRYPTION_FAILED: "Failed to decrypt pack (invalid key or corrupted data)",
|
|
16016
|
-
MACHINE_MISMATCH: "License is not valid for this machine",
|
|
16017
|
-
PACK_CORRUPTED: "Pack file is corrupted or invalid",
|
|
16018
|
-
NOT_EXTENDED_FORMAT: "Pack is not in extended GRX2 format"
|
|
16019
|
-
};
|
|
16020
|
-
errorMsg = messages[error.code] ?? `Pack load error: ${error.message}`;
|
|
16021
|
-
} else {
|
|
16022
|
-
errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
16023
|
-
}
|
|
16024
|
-
if (strictGrx2) {
|
|
16025
|
-
throw new SentriflowConfigError(
|
|
16026
|
-
`Failed to load GRX2 pack '${packPath}': ${errorMsg}`
|
|
16027
|
-
);
|
|
16737
|
+
loadedPack.priority = desc.priority;
|
|
16738
|
+
break;
|
|
16739
|
+
}
|
|
16740
|
+
case "grpx": {
|
|
16741
|
+
if (!licenseKey) {
|
|
16742
|
+
console.error(`Warning: Skipping GRPX pack (no license key): ${desc.path}`);
|
|
16743
|
+
continue;
|
|
16028
16744
|
}
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16745
|
+
loadedPack = await loadEncryptedRulePack(
|
|
16746
|
+
validation.canonicalPath,
|
|
16747
|
+
licenseKey,
|
|
16748
|
+
allowedBaseDirs
|
|
16749
|
+
);
|
|
16750
|
+
loadedPack.priority = desc.priority;
|
|
16751
|
+
break;
|
|
16032
16752
|
}
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16753
|
+
case "unencrypted": {
|
|
16754
|
+
loadedPack = await loadRulePackFile(
|
|
16755
|
+
validation.canonicalPath,
|
|
16756
|
+
allowedBaseDirs
|
|
16757
|
+
);
|
|
16758
|
+
loadedPack.priority = desc.priority;
|
|
16759
|
+
break;
|
|
16040
16760
|
}
|
|
16761
|
+
default:
|
|
16762
|
+
console.error(`Warning: Unknown pack format, skipping: ${desc.path}`);
|
|
16763
|
+
continue;
|
|
16041
16764
|
}
|
|
16765
|
+
allPacks.push(loadedPack);
|
|
16766
|
+
loadedCount++;
|
|
16767
|
+
totalRules += loadedPack.rules.length;
|
|
16768
|
+
} catch (error) {
|
|
16769
|
+
let errorMsg;
|
|
16770
|
+
if (error instanceof EncryptedPackError) {
|
|
16771
|
+
const messages = {
|
|
16772
|
+
LICENSE_MISSING: "Invalid or missing license key",
|
|
16773
|
+
LICENSE_EXPIRED: "License has expired",
|
|
16774
|
+
LICENSE_INVALID: "License key is invalid for this pack",
|
|
16775
|
+
DECRYPTION_FAILED: "Failed to decrypt pack (invalid key or corrupted data)",
|
|
16776
|
+
MACHINE_MISMATCH: "License is not valid for this machine",
|
|
16777
|
+
PACK_CORRUPTED: "Pack file is corrupted or invalid",
|
|
16778
|
+
NOT_EXTENDED_FORMAT: "Pack is not in extended GRX2 format"
|
|
16779
|
+
};
|
|
16780
|
+
errorMsg = messages[error.code] ?? `Pack load error: ${error.message}`;
|
|
16781
|
+
} else {
|
|
16782
|
+
errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
16783
|
+
}
|
|
16784
|
+
if (strictPacks) {
|
|
16785
|
+
throw new SentriflowConfigError(
|
|
16786
|
+
`Failed to load pack '${desc.path}': ${errorMsg}`
|
|
16787
|
+
);
|
|
16788
|
+
}
|
|
16789
|
+
console.error(`Warning: Failed to load pack: ${errorMsg}`);
|
|
16790
|
+
console.error(`Warning: Skipping pack: ${desc.path}`);
|
|
16791
|
+
failedPacks.push(desc.path);
|
|
16792
|
+
}
|
|
16793
|
+
}
|
|
16794
|
+
if (packPathsArray.length > 0) {
|
|
16795
|
+
const successMsg = `Packs: ${loadedCount} of ${packPathsArray.length} loaded (${totalRules} rules)`;
|
|
16796
|
+
if (failedPacks.length > 0) {
|
|
16797
|
+
console.error(`${successMsg}, ${failedPacks.length} failed`);
|
|
16798
|
+
} else if (loadedCount > 0) {
|
|
16799
|
+
console.error(successMsg);
|
|
16042
16800
|
}
|
|
16043
16801
|
}
|
|
16044
16802
|
}
|
|
@@ -16072,7 +16830,7 @@ async function resolveRules(options = {}) {
|
|
|
16072
16830
|
|
|
16073
16831
|
// src/scanner/DirectoryScanner.ts
|
|
16074
16832
|
import { readdir as readdir2, stat } from "fs/promises";
|
|
16075
|
-
import { join as join2, resolve as
|
|
16833
|
+
import { join as join2, resolve as resolve5, extname as extname2 } from "path";
|
|
16076
16834
|
import { realpathSync as realpathSync2, existsSync as existsSync3 } from "fs";
|
|
16077
16835
|
var DEFAULT_CONFIG_EXTENSIONS = [
|
|
16078
16836
|
"txt",
|
|
@@ -16101,7 +16859,7 @@ function isWithinBounds(filePath, allowedBaseDirs) {
|
|
|
16101
16859
|
const normalizedPath = normalizeSeparators2(filePath);
|
|
16102
16860
|
return allowedBaseDirs.some((baseDir) => {
|
|
16103
16861
|
try {
|
|
16104
|
-
const canonicalBase = realpathSync2(
|
|
16862
|
+
const canonicalBase = realpathSync2(resolve5(baseDir));
|
|
16105
16863
|
const normalizedBase = normalizeSeparators2(canonicalBase);
|
|
16106
16864
|
return normalizedPath === normalizedBase || normalizedPath.startsWith(normalizedBase + "/");
|
|
16107
16865
|
} catch (error) {
|
|
@@ -16157,7 +16915,7 @@ async function scanDirectory(dirPath, options = {}) {
|
|
|
16157
16915
|
};
|
|
16158
16916
|
let canonicalDir;
|
|
16159
16917
|
try {
|
|
16160
|
-
canonicalDir = realpathSync2(
|
|
16918
|
+
canonicalDir = realpathSync2(resolve5(dirPath));
|
|
16161
16919
|
} catch {
|
|
16162
16920
|
result.errors.push({
|
|
16163
16921
|
path: dirPath,
|
|
@@ -16257,7 +17015,7 @@ function validateDirectoryPath(dirPath, allowedBaseDirs) {
|
|
|
16257
17015
|
error: "Network (UNC) paths are not allowed"
|
|
16258
17016
|
};
|
|
16259
17017
|
}
|
|
16260
|
-
const absolutePath =
|
|
17018
|
+
const absolutePath = resolve5(dirPath);
|
|
16261
17019
|
if (!existsSync3(absolutePath)) {
|
|
16262
17020
|
return { valid: false, error: "Directory not found" };
|
|
16263
17021
|
}
|
|
@@ -16286,14 +17044,14 @@ function validateDirectoryPath(dirPath, allowedBaseDirs) {
|
|
|
16286
17044
|
|
|
16287
17045
|
// src/loaders/stdin.ts
|
|
16288
17046
|
async function readStdin() {
|
|
16289
|
-
return new Promise((
|
|
17047
|
+
return new Promise((resolve7) => {
|
|
16290
17048
|
const stdin = process.stdin;
|
|
16291
17049
|
const chunks = [];
|
|
16292
17050
|
let totalSize = 0;
|
|
16293
17051
|
let sizeLimitExceeded = false;
|
|
16294
17052
|
stdin.setEncoding("utf8");
|
|
16295
17053
|
if (stdin.isTTY) {
|
|
16296
|
-
|
|
17054
|
+
resolve7({
|
|
16297
17055
|
success: false,
|
|
16298
17056
|
error: "No input received from stdin"
|
|
16299
17057
|
});
|
|
@@ -16305,7 +17063,7 @@ async function readStdin() {
|
|
|
16305
17063
|
totalSize += buffer.length;
|
|
16306
17064
|
if (totalSize > MAX_CONFIG_SIZE) {
|
|
16307
17065
|
sizeLimitExceeded = true;
|
|
16308
|
-
|
|
17066
|
+
resolve7({
|
|
16309
17067
|
success: false,
|
|
16310
17068
|
error: `Input exceeds maximum size (${totalSize} > ${MAX_CONFIG_SIZE} bytes)`
|
|
16311
17069
|
});
|
|
@@ -16318,19 +17076,19 @@ async function readStdin() {
|
|
|
16318
17076
|
if (sizeLimitExceeded) return;
|
|
16319
17077
|
const content = Buffer.concat(chunks).toString("utf8");
|
|
16320
17078
|
if (content.length === 0) {
|
|
16321
|
-
|
|
17079
|
+
resolve7({
|
|
16322
17080
|
success: false,
|
|
16323
17081
|
error: "No input received from stdin"
|
|
16324
17082
|
});
|
|
16325
17083
|
return;
|
|
16326
17084
|
}
|
|
16327
|
-
|
|
17085
|
+
resolve7({
|
|
16328
17086
|
success: true,
|
|
16329
17087
|
content
|
|
16330
17088
|
});
|
|
16331
17089
|
});
|
|
16332
17090
|
stdin.on("error", (err) => {
|
|
16333
|
-
|
|
17091
|
+
resolve7({
|
|
16334
17092
|
success: false,
|
|
16335
17093
|
error: `Failed to read from stdin: ${err.message}`
|
|
16336
17094
|
});
|
|
@@ -16338,7 +17096,7 @@ async function readStdin() {
|
|
|
16338
17096
|
const timeout = setTimeout(() => {
|
|
16339
17097
|
if (chunks.length === 0 && !sizeLimitExceeded) {
|
|
16340
17098
|
stdin.destroy();
|
|
16341
|
-
|
|
17099
|
+
resolve7({
|
|
16342
17100
|
success: false,
|
|
16343
17101
|
error: "No input received from stdin (timeout)"
|
|
16344
17102
|
});
|
|
@@ -16384,21 +17142,15 @@ function enrichResultsWithRuleMetadata(results, rules) {
|
|
|
16384
17142
|
});
|
|
16385
17143
|
}
|
|
16386
17144
|
var program = new Command();
|
|
16387
|
-
program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.2
|
|
16388
|
-
"--
|
|
16389
|
-
"
|
|
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(
|
|
17146
|
+
"--pack <path...>",
|
|
17147
|
+
"Path(s) to rule pack(s) (auto-detects format: .grx2, .grpx, or unencrypted)"
|
|
16390
17148
|
).option(
|
|
16391
17149
|
"--license-key <key>",
|
|
16392
|
-
"
|
|
17150
|
+
"License key for encrypted rule packs (or set SENTRIFLOW_LICENSE_KEY)"
|
|
16393
17151
|
).option(
|
|
16394
17152
|
"--strict-packs",
|
|
16395
|
-
"Fail if
|
|
16396
|
-
).option(
|
|
16397
|
-
"--grx2-pack <path...>",
|
|
16398
|
-
"Path(s) to extended encrypted rule pack(s) (.grx2), can specify multiple"
|
|
16399
|
-
).option(
|
|
16400
|
-
"--strict-grx2",
|
|
16401
|
-
"Fail immediately if any GRX2 pack cannot be loaded"
|
|
17153
|
+
"Fail immediately if any pack cannot be loaded (default: warn and continue)"
|
|
16402
17154
|
).option(
|
|
16403
17155
|
"--show-machine-id",
|
|
16404
17156
|
"Display the current machine ID (for license binding support)"
|
|
@@ -16448,7 +17200,10 @@ program.name("sentriflow").description("SentriFlow Network Configuration Validat
|
|
|
16448
17200
|
"--max-depth <number>",
|
|
16449
17201
|
"Maximum recursion depth for directory scanning (use with -R)",
|
|
16450
17202
|
(val) => parseInt(val, 10)
|
|
16451
|
-
).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) => {
|
|
16452
17207
|
try {
|
|
16453
17208
|
if (options.showMachineId) {
|
|
16454
17209
|
try {
|
|
@@ -16502,23 +17257,18 @@ Use: sentriflow --vendor <vendor> <file>`);
|
|
|
16502
17257
|
}
|
|
16503
17258
|
const licenseKey = options.licenseKey || process.env.SENTRIFLOW_LICENSE_KEY;
|
|
16504
17259
|
const firstFile = files.length > 0 ? files[0] : void 0;
|
|
16505
|
-
const configSearchDir = firstFile ? dirname2(
|
|
17260
|
+
const configSearchDir = firstFile ? dirname2(resolve6(firstFile)) : workingDir;
|
|
16506
17261
|
const rules = await resolveRules({
|
|
16507
17262
|
configPath: options.config,
|
|
16508
17263
|
noConfig: options.config === false,
|
|
16509
17264
|
// --no-config sets this to false
|
|
16510
17265
|
rulesPath: options.rules,
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
// SEC-012: Now supports array
|
|
17266
|
+
packPaths: options.pack,
|
|
17267
|
+
// Unified pack loading with auto-format detection
|
|
16514
17268
|
licenseKey,
|
|
16515
|
-
//
|
|
17269
|
+
// From CLI or SENTRIFLOW_LICENSE_KEY env var
|
|
16516
17270
|
strictPacks: options.strictPacks,
|
|
16517
|
-
//
|
|
16518
|
-
grx2PackPaths: options.grx2Pack,
|
|
16519
|
-
// Extended GRX2 packs
|
|
16520
|
-
strictGrx2: options.strictGrx2,
|
|
16521
|
-
// Fail on GRX2 pack load errors
|
|
17271
|
+
// Fail on pack load errors
|
|
16522
17272
|
jsonRulesPaths: options.jsonRules,
|
|
16523
17273
|
// JSON rules files
|
|
16524
17274
|
disableIds: options.disable ?? [],
|
|
@@ -16527,6 +17277,19 @@ Use: sentriflow --vendor <vendor> <file>`);
|
|
|
16527
17277
|
allowedBaseDirs
|
|
16528
17278
|
// SEC-011: Pass allowed base dirs for rule file validation
|
|
16529
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
|
+
}
|
|
16530
17293
|
if (options.listCategories) {
|
|
16531
17294
|
const counts = /* @__PURE__ */ new Map();
|
|
16532
17295
|
for (const rule of rules) {
|
|
@@ -16766,7 +17529,21 @@ Parsing complete: ${allAsts.length} files`);
|
|
|
16766
17529
|
const passed = results2.filter((r) => r.passed).length;
|
|
16767
17530
|
totalFailures += failures;
|
|
16768
17531
|
totalPassed += passed;
|
|
16769
|
-
|
|
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
|
+
}
|
|
16770
17547
|
allFileResults.push({
|
|
16771
17548
|
filePath: filePath2,
|
|
16772
17549
|
results: results2,
|
|
@@ -16855,12 +17632,9 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
16855
17632
|
configPath: options.config,
|
|
16856
17633
|
noConfig: options.config === false,
|
|
16857
17634
|
rulesPath: options.rules,
|
|
16858
|
-
|
|
16859
|
-
encryptedPackPaths: options.encryptedPack,
|
|
17635
|
+
packPaths: options.pack,
|
|
16860
17636
|
licenseKey,
|
|
16861
17637
|
strictPacks: options.strictPacks,
|
|
16862
|
-
grx2PackPaths: options.grx2Pack,
|
|
16863
|
-
strictGrx2: options.strictGrx2,
|
|
16864
17638
|
jsonRulesPaths: options.jsonRules,
|
|
16865
17639
|
disableIds: options.disable ?? [],
|
|
16866
17640
|
vendorId: vendor2.id,
|
|
@@ -16885,6 +17659,20 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
16885
17659
|
let stdinIpSummary;
|
|
16886
17660
|
try {
|
|
16887
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
|
+
}
|
|
16888
17676
|
} catch (error) {
|
|
16889
17677
|
if (error instanceof InputValidationError) {
|
|
16890
17678
|
console.error(`Input validation error: ${error.message}`);
|
|
@@ -16962,7 +17750,21 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
16962
17750
|
const passed = results2.filter((r) => r.passed).length;
|
|
16963
17751
|
totalFailures += failures;
|
|
16964
17752
|
totalPassed += passed;
|
|
16965
|
-
|
|
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
|
+
}
|
|
16966
17768
|
allFileResults.push({
|
|
16967
17769
|
filePath: filePath2,
|
|
16968
17770
|
results: results2,
|
|
@@ -17050,14 +17852,10 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
17050
17852
|
configPath: options.config,
|
|
17051
17853
|
noConfig: options.config === false,
|
|
17052
17854
|
rulesPath: options.rules,
|
|
17053
|
-
|
|
17054
|
-
encryptedPackPaths: options.encryptedPack,
|
|
17855
|
+
packPaths: options.pack,
|
|
17055
17856
|
licenseKey,
|
|
17056
17857
|
strictPacks: options.strictPacks,
|
|
17057
|
-
grx2PackPaths: options.grx2Pack,
|
|
17058
|
-
strictGrx2: options.strictGrx2,
|
|
17059
17858
|
jsonRulesPaths: options.jsonRules,
|
|
17060
|
-
// JSON rules files
|
|
17061
17859
|
disableIds: options.disable ?? [],
|
|
17062
17860
|
vendorId: vendor.id,
|
|
17063
17861
|
// Now we have the actual detected vendor
|
|
@@ -17082,7 +17880,21 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
17082
17880
|
if (options.quiet) {
|
|
17083
17881
|
results = results.filter((r) => !r.passed);
|
|
17084
17882
|
}
|
|
17085
|
-
|
|
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
|
+
}
|
|
17086
17898
|
if (options.format === "sarif") {
|
|
17087
17899
|
const sarifOptions = {
|
|
17088
17900
|
relativePaths: options.relativePaths,
|
|
@@ -17128,6 +17940,23 @@ async function loadLicensingExtension() {
|
|
|
17128
17940
|
licensing.registerCommands(program);
|
|
17129
17941
|
}
|
|
17130
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);
|
|
17131
17960
|
}
|
|
17132
17961
|
}
|
|
17133
17962
|
loadLicensingExtension().finally(() => {
|