@sentriflow/cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +926 -361
- 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,129 @@ function extractIPSummary(content, options = {}) {
|
|
|
12075
12129
|
};
|
|
12076
12130
|
}
|
|
12077
12131
|
|
|
12132
|
+
// ../core/src/validation/rule-validation.ts
|
|
12133
|
+
function validateRule2(rule) {
|
|
12134
|
+
if (typeof rule !== "object" || rule === null) {
|
|
12135
|
+
return "Rule is not an object";
|
|
12136
|
+
}
|
|
12137
|
+
const obj = rule;
|
|
12138
|
+
if (typeof obj.id !== "string") {
|
|
12139
|
+
return "Rule id is not a string";
|
|
12140
|
+
}
|
|
12141
|
+
if (!RULE_ID_PATTERN.test(obj.id)) {
|
|
12142
|
+
return `Rule id "${obj.id}" does not match pattern ${RULE_ID_PATTERN}`;
|
|
12143
|
+
}
|
|
12144
|
+
if (typeof obj.check !== "function") {
|
|
12145
|
+
return `Rule ${obj.id}: check is not a function (got ${typeof obj.check})`;
|
|
12146
|
+
}
|
|
12147
|
+
if (obj.selector !== void 0 && typeof obj.selector !== "string") {
|
|
12148
|
+
return `Rule ${obj.id}: selector is not a string`;
|
|
12149
|
+
}
|
|
12150
|
+
if (obj.vendor !== void 0) {
|
|
12151
|
+
if (Array.isArray(obj.vendor)) {
|
|
12152
|
+
for (const v of obj.vendor) {
|
|
12153
|
+
if (typeof v !== "string") {
|
|
12154
|
+
return `Rule ${obj.id}: vendor array contains non-string`;
|
|
12155
|
+
}
|
|
12156
|
+
if (!isValidVendorId(v)) {
|
|
12157
|
+
return `Rule ${obj.id}: invalid vendor "${v}"`;
|
|
12158
|
+
}
|
|
12159
|
+
}
|
|
12160
|
+
} else if (typeof obj.vendor !== "string") {
|
|
12161
|
+
return `Rule ${obj.id}: vendor is not a string`;
|
|
12162
|
+
} else if (!isValidVendorId(obj.vendor)) {
|
|
12163
|
+
return `Rule ${obj.id}: invalid vendor "${obj.vendor}"`;
|
|
12164
|
+
}
|
|
12165
|
+
}
|
|
12166
|
+
if (typeof obj.metadata !== "object" || obj.metadata === null) {
|
|
12167
|
+
return `Rule ${obj.id}: metadata is not an object`;
|
|
12168
|
+
}
|
|
12169
|
+
const metadata = obj.metadata;
|
|
12170
|
+
if (!["error", "warning", "info"].includes(metadata.level)) {
|
|
12171
|
+
return `Rule ${obj.id}: invalid metadata.level "${metadata.level}"`;
|
|
12172
|
+
}
|
|
12173
|
+
return null;
|
|
12174
|
+
}
|
|
12175
|
+
function isValidRule(rule) {
|
|
12176
|
+
return validateRule2(rule) === null;
|
|
12177
|
+
}
|
|
12178
|
+
function validateRulePack(pack, reservedPackName) {
|
|
12179
|
+
if (typeof pack !== "object" || pack === null) {
|
|
12180
|
+
return "Pack is not an object";
|
|
12181
|
+
}
|
|
12182
|
+
const obj = pack;
|
|
12183
|
+
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
12184
|
+
return "Pack name is missing or empty";
|
|
12185
|
+
}
|
|
12186
|
+
if (reservedPackName && obj.name === reservedPackName) {
|
|
12187
|
+
return `Pack name "${obj.name}" is reserved`;
|
|
12188
|
+
}
|
|
12189
|
+
if (typeof obj.version !== "string" || obj.version.length === 0) {
|
|
12190
|
+
return "Pack version is missing or empty";
|
|
12191
|
+
}
|
|
12192
|
+
if (typeof obj.publisher !== "string" || obj.publisher.length === 0) {
|
|
12193
|
+
return "Pack publisher is missing or empty";
|
|
12194
|
+
}
|
|
12195
|
+
if (typeof obj.priority !== "number" || obj.priority < 0) {
|
|
12196
|
+
return `Pack priority is invalid (got ${obj.priority})`;
|
|
12197
|
+
}
|
|
12198
|
+
if (!Array.isArray(obj.rules)) {
|
|
12199
|
+
return "Pack rules is not an array";
|
|
12200
|
+
}
|
|
12201
|
+
for (let i = 0; i < obj.rules.length; i++) {
|
|
12202
|
+
const ruleError = validateRule2(obj.rules[i]);
|
|
12203
|
+
if (ruleError) {
|
|
12204
|
+
return `Rule[${i}]: ${ruleError}`;
|
|
12205
|
+
}
|
|
12206
|
+
}
|
|
12207
|
+
if (obj.disables !== void 0) {
|
|
12208
|
+
if (typeof obj.disables !== "object" || obj.disables === null) {
|
|
12209
|
+
return "Pack disables is not an object";
|
|
12210
|
+
}
|
|
12211
|
+
const disables = obj.disables;
|
|
12212
|
+
if (disables.all !== void 0 && typeof disables.all !== "boolean") {
|
|
12213
|
+
return "Pack disables.all is not a boolean";
|
|
12214
|
+
}
|
|
12215
|
+
if (disables.vendors !== void 0) {
|
|
12216
|
+
if (!Array.isArray(disables.vendors)) {
|
|
12217
|
+
return "Pack disables.vendors is not an array";
|
|
12218
|
+
}
|
|
12219
|
+
for (const v of disables.vendors) {
|
|
12220
|
+
if (typeof v !== "string" || !isValidVendorId(v)) {
|
|
12221
|
+
return `Pack disables.vendors contains invalid vendor "${v}"`;
|
|
12222
|
+
}
|
|
12223
|
+
}
|
|
12224
|
+
}
|
|
12225
|
+
if (disables.rules !== void 0) {
|
|
12226
|
+
if (!Array.isArray(disables.rules)) {
|
|
12227
|
+
return "Pack disables.rules is not an array";
|
|
12228
|
+
}
|
|
12229
|
+
for (const r of disables.rules) {
|
|
12230
|
+
if (typeof r !== "string") {
|
|
12231
|
+
return "Pack disables.rules contains non-string";
|
|
12232
|
+
}
|
|
12233
|
+
}
|
|
12234
|
+
}
|
|
12235
|
+
}
|
|
12236
|
+
return null;
|
|
12237
|
+
}
|
|
12238
|
+
function isValidRulePack(pack, reservedPackName) {
|
|
12239
|
+
return validateRulePack(pack, reservedPackName) === null;
|
|
12240
|
+
}
|
|
12241
|
+
function ruleAppliesToVendor(rule, vendorId) {
|
|
12242
|
+
if (!rule.vendor) {
|
|
12243
|
+
return true;
|
|
12244
|
+
}
|
|
12245
|
+
if (Array.isArray(rule.vendor)) {
|
|
12246
|
+
return rule.vendor.includes("common") || rule.vendor.includes(vendorId);
|
|
12247
|
+
}
|
|
12248
|
+
return rule.vendor === "common" || rule.vendor === vendorId;
|
|
12249
|
+
}
|
|
12250
|
+
|
|
12078
12251
|
// index.ts
|
|
12079
12252
|
import { readFile as readFile2 } from "fs/promises";
|
|
12080
12253
|
import { statSync as statSync2 } from "fs";
|
|
12081
|
-
import { resolve as
|
|
12254
|
+
import { resolve as resolve6, dirname as dirname2, basename } from "path";
|
|
12082
12255
|
|
|
12083
12256
|
// src/sarif.ts
|
|
12084
12257
|
import { relative } from "path";
|
|
@@ -12155,7 +12328,7 @@ function generateSarif(results, filePath, rules, options = {}, ipSummary) {
|
|
|
12155
12328
|
tool: {
|
|
12156
12329
|
driver: {
|
|
12157
12330
|
name: "Sentriflow",
|
|
12158
|
-
version: "0.
|
|
12331
|
+
version: "0.3.0",
|
|
12159
12332
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
12160
12333
|
rules: sarifRules,
|
|
12161
12334
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -12315,7 +12488,7 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
|
|
|
12315
12488
|
tool: {
|
|
12316
12489
|
driver: {
|
|
12317
12490
|
name: "Sentriflow",
|
|
12318
|
-
version: "0.
|
|
12491
|
+
version: "0.3.0",
|
|
12319
12492
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
12320
12493
|
rules: sarifRules,
|
|
12321
12494
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -12529,10 +12702,10 @@ var InterfaceDescriptionRequired = {
|
|
|
12529
12702
|
loc: node.loc
|
|
12530
12703
|
};
|
|
12531
12704
|
}
|
|
12532
|
-
const
|
|
12705
|
+
const hasDescription9 = node.children.some(
|
|
12533
12706
|
(child) => startsWithIgnoreCase(child.id, "description")
|
|
12534
12707
|
);
|
|
12535
|
-
if (!
|
|
12708
|
+
if (!hasDescription9) {
|
|
12536
12709
|
return {
|
|
12537
12710
|
passed: false,
|
|
12538
12711
|
message: `Interface "${node.params.slice(1).join(" ")}" is missing a description.`,
|
|
@@ -12557,6 +12730,59 @@ var allCommonRules = [
|
|
|
12557
12730
|
InterfaceDescriptionRequired
|
|
12558
12731
|
];
|
|
12559
12732
|
|
|
12733
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/common/validation.ts
|
|
12734
|
+
var equalsIgnoreCase2 = (a, b) => a != null && b != null && a.toLowerCase() === b.toLowerCase();
|
|
12735
|
+
var includesIgnoreCase2 = (haystack, needle) => haystack != null && needle != null && haystack.toLowerCase().includes(needle.toLowerCase());
|
|
12736
|
+
var startsWithIgnoreCase2 = (str, prefix) => str != null && prefix != null && str.toLowerCase().startsWith(prefix.toLowerCase());
|
|
12737
|
+
|
|
12738
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/common/helpers.ts
|
|
12739
|
+
var hasChildCommand5 = (node, prefix) => {
|
|
12740
|
+
if (!node?.children || !prefix) return false;
|
|
12741
|
+
return node.children.some(
|
|
12742
|
+
(child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase()) ?? false
|
|
12743
|
+
);
|
|
12744
|
+
};
|
|
12745
|
+
var getChildCommand5 = (node, prefix) => {
|
|
12746
|
+
if (!node?.children || !prefix) return void 0;
|
|
12747
|
+
return node.children.find(
|
|
12748
|
+
(child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase()) ?? false
|
|
12749
|
+
);
|
|
12750
|
+
};
|
|
12751
|
+
var isShutdown6 = (node) => {
|
|
12752
|
+
if (!node?.children) return false;
|
|
12753
|
+
return node.children.some((child) => {
|
|
12754
|
+
const id = child?.id?.toLowerCase().trim();
|
|
12755
|
+
return id === "shutdown" || id === "disable";
|
|
12756
|
+
});
|
|
12757
|
+
};
|
|
12758
|
+
|
|
12759
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/cisco/helpers.ts
|
|
12760
|
+
var isShutdown7 = (node) => {
|
|
12761
|
+
if (!node?.children) return false;
|
|
12762
|
+
return node.children.some((child) => {
|
|
12763
|
+
return child?.id && equalsIgnoreCase2(child.id.trim(), "shutdown");
|
|
12764
|
+
});
|
|
12765
|
+
};
|
|
12766
|
+
var isPhysicalPort4 = (interfaceName) => {
|
|
12767
|
+
return !includesIgnoreCase2(interfaceName, "loopback") && !includesIgnoreCase2(interfaceName, "null") && !includesIgnoreCase2(interfaceName, "vlan") && !includesIgnoreCase2(interfaceName, "tunnel") && !includesIgnoreCase2(interfaceName, "port-channel") && !includesIgnoreCase2(interfaceName, "bvi") && !includesIgnoreCase2(interfaceName, "nve");
|
|
12768
|
+
};
|
|
12769
|
+
var isTrunkPort4 = (node) => {
|
|
12770
|
+
return hasChildCommand5(node, "switchport mode trunk");
|
|
12771
|
+
};
|
|
12772
|
+
var isTrunkToNonCisco2 = (node) => {
|
|
12773
|
+
const desc = getChildCommand5(node, "description");
|
|
12774
|
+
if (desc) {
|
|
12775
|
+
const descText = desc.rawText;
|
|
12776
|
+
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:")) {
|
|
12777
|
+
return true;
|
|
12778
|
+
}
|
|
12779
|
+
if (includesIgnoreCase2(descText, "uplink:") || includesIgnoreCase2(descText, "downlink:") || includesIgnoreCase2(descText, "isl:") || includesIgnoreCase2(descText, "po-member:")) {
|
|
12780
|
+
return false;
|
|
12781
|
+
}
|
|
12782
|
+
}
|
|
12783
|
+
return false;
|
|
12784
|
+
};
|
|
12785
|
+
|
|
12560
12786
|
// ../rules-default/src/cisco/ios-rules.ts
|
|
12561
12787
|
var TrunkNoDTP = {
|
|
12562
12788
|
id: "NET-TRUNK-001",
|
|
@@ -12570,16 +12796,16 @@ var TrunkNoDTP = {
|
|
|
12570
12796
|
remediation: 'Add "switchport nonegotiate" to disable DTP on trunk ports connected to non-Cisco devices.'
|
|
12571
12797
|
},
|
|
12572
12798
|
check: (node) => {
|
|
12573
|
-
if (!
|
|
12799
|
+
if (!isPhysicalPort4(node.id) || isShutdown7(node)) {
|
|
12574
12800
|
return { passed: true, message: "Not applicable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12575
12801
|
}
|
|
12576
|
-
if (!
|
|
12802
|
+
if (!isTrunkPort4(node)) {
|
|
12577
12803
|
return { passed: true, message: "Not a trunk port.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12578
12804
|
}
|
|
12579
|
-
if (!
|
|
12805
|
+
if (!isTrunkToNonCisco2(node)) {
|
|
12580
12806
|
return { passed: true, message: "Trunk to Cisco device - DTP acceptable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12581
12807
|
}
|
|
12582
|
-
if (!
|
|
12808
|
+
if (!hasChildCommand5(node, "switchport nonegotiate")) {
|
|
12583
12809
|
return {
|
|
12584
12810
|
passed: false,
|
|
12585
12811
|
message: `Trunk port "${node.params.slice(1).join(" ")}" connected to non-Cisco device needs "switchport nonegotiate".`,
|
|
@@ -12604,11 +12830,11 @@ var AccessExplicitMode = {
|
|
|
12604
12830
|
remediation: 'Add "switchport mode access" to explicitly configure access mode.'
|
|
12605
12831
|
},
|
|
12606
12832
|
check: (node) => {
|
|
12607
|
-
if (!
|
|
12833
|
+
if (!isPhysicalPort4(node.id) || isShutdown7(node)) {
|
|
12608
12834
|
return { passed: true, message: "Not applicable.", ruleId: "NET-ACCESS-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12609
12835
|
}
|
|
12610
|
-
const hasAccessVlan =
|
|
12611
|
-
const hasExplicitMode =
|
|
12836
|
+
const hasAccessVlan = hasChildCommand5(node, "switchport access vlan");
|
|
12837
|
+
const hasExplicitMode = hasChildCommand5(node, "switchport mode");
|
|
12612
12838
|
if (hasAccessVlan && !hasExplicitMode) {
|
|
12613
12839
|
return {
|
|
12614
12840
|
passed: false,
|
|
@@ -12746,6 +12972,38 @@ var allCiscoRules = [
|
|
|
12746
12972
|
EnableSecretStrong
|
|
12747
12973
|
];
|
|
12748
12974
|
|
|
12975
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/juniper/helpers.ts
|
|
12976
|
+
var findStanza8 = (node, stanzaName) => {
|
|
12977
|
+
if (!node?.children) return void 0;
|
|
12978
|
+
return node.children.find(
|
|
12979
|
+
(child) => child?.id && equalsIgnoreCase2(child.id, stanzaName)
|
|
12980
|
+
);
|
|
12981
|
+
};
|
|
12982
|
+
var findStanzas7 = (node, pattern) => {
|
|
12983
|
+
if (!node?.children) return [];
|
|
12984
|
+
return node.children.filter((child) => child?.id && pattern.test(child.id));
|
|
12985
|
+
};
|
|
12986
|
+
var isFilterTermDrop2 = (termNode) => {
|
|
12987
|
+
if (!termNode?.children) return false;
|
|
12988
|
+
for (const child of termNode.children) {
|
|
12989
|
+
const id = child?.id?.trim();
|
|
12990
|
+
if (!id) continue;
|
|
12991
|
+
if (equalsIgnoreCase2(id, "then discard") || equalsIgnoreCase2(id, "then discard;") || equalsIgnoreCase2(id, "then reject") || equalsIgnoreCase2(id, "then reject;")) {
|
|
12992
|
+
return true;
|
|
12993
|
+
}
|
|
12994
|
+
}
|
|
12995
|
+
const thenStanza = findStanza8(termNode, "then");
|
|
12996
|
+
if (!thenStanza?.children) return false;
|
|
12997
|
+
for (const child of thenStanza.children) {
|
|
12998
|
+
const id = child?.id?.trim();
|
|
12999
|
+
if (!id) continue;
|
|
13000
|
+
if (equalsIgnoreCase2(id, "discard") || equalsIgnoreCase2(id, "discard;") || equalsIgnoreCase2(id, "reject") || equalsIgnoreCase2(id, "reject;")) {
|
|
13001
|
+
return true;
|
|
13002
|
+
}
|
|
13003
|
+
}
|
|
13004
|
+
return false;
|
|
13005
|
+
};
|
|
13006
|
+
|
|
12749
13007
|
// ../rules-default/src/juniper/junos-rules.ts
|
|
12750
13008
|
var RootAuthRequired = {
|
|
12751
13009
|
id: "JUN-SYS-001",
|
|
@@ -12759,7 +13017,7 @@ var RootAuthRequired = {
|
|
|
12759
13017
|
remediation: 'Configure "root-authentication" under system stanza with encrypted-password or ssh-rsa.'
|
|
12760
13018
|
},
|
|
12761
13019
|
check: (node) => {
|
|
12762
|
-
const rootAuth =
|
|
13020
|
+
const rootAuth = findStanza8(node, "root-authentication");
|
|
12763
13021
|
if (!rootAuth) {
|
|
12764
13022
|
return {
|
|
12765
13023
|
passed: false,
|
|
@@ -12770,8 +13028,8 @@ var RootAuthRequired = {
|
|
|
12770
13028
|
loc: node.loc
|
|
12771
13029
|
};
|
|
12772
13030
|
}
|
|
12773
|
-
const hasPassword =
|
|
12774
|
-
const hasSshKey =
|
|
13031
|
+
const hasPassword = hasChildCommand5(rootAuth, "encrypted-password");
|
|
13032
|
+
const hasSshKey = hasChildCommand5(rootAuth, "ssh-rsa") || hasChildCommand5(rootAuth, "ssh-ecdsa");
|
|
12775
13033
|
if (!hasPassword && !hasSshKey) {
|
|
12776
13034
|
return {
|
|
12777
13035
|
passed: false,
|
|
@@ -12804,7 +13062,7 @@ var JunosBgpRouterId = {
|
|
|
12804
13062
|
remediation: 'Configure "router-id" under routing-options stanza.'
|
|
12805
13063
|
},
|
|
12806
13064
|
check: (node) => {
|
|
12807
|
-
const hasRouterId =
|
|
13065
|
+
const hasRouterId = hasChildCommand5(node, "router-id");
|
|
12808
13066
|
if (!hasRouterId) {
|
|
12809
13067
|
return {
|
|
12810
13068
|
passed: false,
|
|
@@ -12855,7 +13113,7 @@ var JunosFirewallDefaultDeny = {
|
|
|
12855
13113
|
}
|
|
12856
13114
|
const issues = [];
|
|
12857
13115
|
for (const filter of filters) {
|
|
12858
|
-
const terms =
|
|
13116
|
+
const terms = findStanzas7(filter, /^term/i);
|
|
12859
13117
|
if (terms.length === 0) {
|
|
12860
13118
|
continue;
|
|
12861
13119
|
}
|
|
@@ -12863,7 +13121,7 @@ var JunosFirewallDefaultDeny = {
|
|
|
12863
13121
|
if (!lastTerm) {
|
|
12864
13122
|
continue;
|
|
12865
13123
|
}
|
|
12866
|
-
if (!
|
|
13124
|
+
if (!isFilterTermDrop2(lastTerm)) {
|
|
12867
13125
|
issues.push(`Filter "${filter.id}" does not end with a deny term.`);
|
|
12868
13126
|
}
|
|
12869
13127
|
}
|
|
@@ -12896,6 +13154,69 @@ var allJuniperRules = [
|
|
|
12896
13154
|
JunosFirewallDefaultDeny
|
|
12897
13155
|
];
|
|
12898
13156
|
|
|
13157
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/aruba/helpers.ts
|
|
13158
|
+
var getInterfaceName4 = (node) => {
|
|
13159
|
+
if (!node?.id) return void 0;
|
|
13160
|
+
const match = node.id.match(/interface\s+(.+)/i);
|
|
13161
|
+
const ifName = match?.[1]?.trim();
|
|
13162
|
+
return ifName && ifName.length > 0 ? ifName : void 0;
|
|
13163
|
+
};
|
|
13164
|
+
var isAosCxPhysicalPort2 = (interfaceName) => {
|
|
13165
|
+
return /^\d+\/\d+\/\d+$/.test(interfaceName.trim());
|
|
13166
|
+
};
|
|
13167
|
+
var isAosCxLag2 = (interfaceName) => {
|
|
13168
|
+
return /^lag\s*\d+$/i.test(interfaceName.trim());
|
|
13169
|
+
};
|
|
13170
|
+
var isAosCxTrunk2 = (node) => {
|
|
13171
|
+
return hasChildCommand5(node, "vlan trunk");
|
|
13172
|
+
};
|
|
13173
|
+
var getAosCxTrunkAllowed2 = (node) => {
|
|
13174
|
+
const cmd = getChildCommand5(node, "vlan trunk allowed");
|
|
13175
|
+
if (!cmd) return [];
|
|
13176
|
+
const match = cmd.id.match(/vlan\s+trunk\s+allowed\s+([\d,]+)/i);
|
|
13177
|
+
const vlanList = match?.[1];
|
|
13178
|
+
if (!vlanList) return [];
|
|
13179
|
+
return vlanList.split(",").map((v) => parseInt(v.trim(), 10)).filter((v) => !isNaN(v));
|
|
13180
|
+
};
|
|
13181
|
+
var getAosSwitchVlanName2 = (node) => {
|
|
13182
|
+
const cmd = getChildCommand5(node, "name");
|
|
13183
|
+
if (!cmd) return void 0;
|
|
13184
|
+
const match = cmd.id.match(/name\s+["']?([^"']+)["']?/i);
|
|
13185
|
+
const name = match?.[1];
|
|
13186
|
+
return name?.trim();
|
|
13187
|
+
};
|
|
13188
|
+
var getWlanEncryption2 = (node) => {
|
|
13189
|
+
const cmd = getChildCommand5(node, "opmode");
|
|
13190
|
+
if (!cmd) return null;
|
|
13191
|
+
const match = cmd.id.match(/opmode\s+(\S+)/i);
|
|
13192
|
+
const mode = match?.[1];
|
|
13193
|
+
return mode ? mode.toLowerCase() : null;
|
|
13194
|
+
};
|
|
13195
|
+
var hasSecureEncryption2 = (node) => {
|
|
13196
|
+
const opmode = getWlanEncryption2(node);
|
|
13197
|
+
if (!opmode) return false;
|
|
13198
|
+
return opmode.includes("wpa2") || opmode.includes("wpa3") || opmode.includes("aes");
|
|
13199
|
+
};
|
|
13200
|
+
var isOpenSsid2 = (node) => {
|
|
13201
|
+
const opmode = getWlanEncryption2(node);
|
|
13202
|
+
return opmode === "opensystem" || opmode === "open";
|
|
13203
|
+
};
|
|
13204
|
+
var getRadiusHost2 = (node) => {
|
|
13205
|
+
const cmd = getChildCommand5(node, "host");
|
|
13206
|
+
if (!cmd) return void 0;
|
|
13207
|
+
const match = cmd.id.match(/host\s+(\S+)/i);
|
|
13208
|
+
const host = match?.[1];
|
|
13209
|
+
return host?.trim();
|
|
13210
|
+
};
|
|
13211
|
+
var extractProfileName2 = (nodeId) => {
|
|
13212
|
+
const match = nodeId.match(/(?:ssid-profile|virtual-ap|profile|server-group|ap-group|arm-profile)\s+["']?([^"'\n]+)["']?$/i);
|
|
13213
|
+
const profile = match?.[1];
|
|
13214
|
+
return profile ? profile.trim() : void 0;
|
|
13215
|
+
};
|
|
13216
|
+
var hasDescription5 = (node) => {
|
|
13217
|
+
return hasChildCommand5(node, "description");
|
|
13218
|
+
};
|
|
13219
|
+
|
|
12899
13220
|
// ../rules-default/src/aruba/common-rules.ts
|
|
12900
13221
|
var SshEnabled = {
|
|
12901
13222
|
id: "ARU-SEC-001",
|
|
@@ -12983,17 +13304,17 @@ var AosCxInterfaceDescription = {
|
|
|
12983
13304
|
remediation: "Add a description to physical interfaces for documentation."
|
|
12984
13305
|
},
|
|
12985
13306
|
check: (node) => {
|
|
12986
|
-
const ifName =
|
|
13307
|
+
const ifName = getInterfaceName4(node);
|
|
12987
13308
|
if (!ifName) {
|
|
12988
13309
|
return { passed: true, message: "Not an interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12989
13310
|
}
|
|
12990
|
-
if (!
|
|
13311
|
+
if (!isAosCxPhysicalPort2(ifName) && !isAosCxLag2(ifName)) {
|
|
12991
13312
|
return { passed: true, message: "Not a physical interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12992
13313
|
}
|
|
12993
|
-
if (
|
|
13314
|
+
if (isShutdown6(node)) {
|
|
12994
13315
|
return { passed: true, message: "Interface is shutdown.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
12995
13316
|
}
|
|
12996
|
-
if (!
|
|
13317
|
+
if (!hasDescription5(node)) {
|
|
12997
13318
|
return {
|
|
12998
13319
|
passed: false,
|
|
12999
13320
|
message: `Interface ${ifName} missing description.`,
|
|
@@ -13025,17 +13346,17 @@ var AosCxTrunkAllowedVlans = {
|
|
|
13025
13346
|
remediation: 'Configure "vlan trunk allowed <vlans>" on trunk interfaces.'
|
|
13026
13347
|
},
|
|
13027
13348
|
check: (node) => {
|
|
13028
|
-
const ifName =
|
|
13349
|
+
const ifName = getInterfaceName4(node);
|
|
13029
13350
|
if (!ifName) {
|
|
13030
13351
|
return { passed: true, message: "Not an interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13031
13352
|
}
|
|
13032
|
-
if (!
|
|
13353
|
+
if (!isAosCxPhysicalPort2(ifName) && !isAosCxLag2(ifName)) {
|
|
13033
13354
|
return { passed: true, message: "Not a switchport interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13034
13355
|
}
|
|
13035
|
-
if (!
|
|
13356
|
+
if (!isAosCxTrunk2(node)) {
|
|
13036
13357
|
return { passed: true, message: "Not a trunk port.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13037
13358
|
}
|
|
13038
|
-
const allowedVlans =
|
|
13359
|
+
const allowedVlans = getAosCxTrunkAllowed2(node);
|
|
13039
13360
|
if (allowedVlans.length === 0) {
|
|
13040
13361
|
return {
|
|
13041
13362
|
passed: false,
|
|
@@ -13085,7 +13406,7 @@ var AosSwitchVlanName = {
|
|
|
13085
13406
|
if (vlanId === null || isDefaultVlan(vlanId)) {
|
|
13086
13407
|
return { passed: true, message: "Default VLAN 1.", ruleId: "AOSSW-L2-001", nodeId: node.id, level: "info", loc: node.loc };
|
|
13087
13408
|
}
|
|
13088
|
-
const vlanName =
|
|
13409
|
+
const vlanName = getAosSwitchVlanName2(node);
|
|
13089
13410
|
if (!vlanName) {
|
|
13090
13411
|
return {
|
|
13091
13412
|
passed: false,
|
|
@@ -13159,8 +13480,8 @@ var WlcSsidEncryption = {
|
|
|
13159
13480
|
remediation: 'Configure "opmode wpa2-aes" or "opmode wpa3-sae-aes" for secure encryption.'
|
|
13160
13481
|
},
|
|
13161
13482
|
check: (node) => {
|
|
13162
|
-
const profileName =
|
|
13163
|
-
const opmode =
|
|
13483
|
+
const profileName = extractProfileName2(node.id);
|
|
13484
|
+
const opmode = getWlanEncryption2(node);
|
|
13164
13485
|
if (!opmode) {
|
|
13165
13486
|
return {
|
|
13166
13487
|
passed: false,
|
|
@@ -13171,7 +13492,7 @@ var WlcSsidEncryption = {
|
|
|
13171
13492
|
loc: node.loc
|
|
13172
13493
|
};
|
|
13173
13494
|
}
|
|
13174
|
-
if (
|
|
13495
|
+
if (isOpenSsid2(node)) {
|
|
13175
13496
|
return {
|
|
13176
13497
|
passed: false,
|
|
13177
13498
|
message: `SSID profile "${profileName}" is open/unencrypted. Use WPA2 or WPA3.`,
|
|
@@ -13181,7 +13502,7 @@ var WlcSsidEncryption = {
|
|
|
13181
13502
|
loc: node.loc
|
|
13182
13503
|
};
|
|
13183
13504
|
}
|
|
13184
|
-
if (!
|
|
13505
|
+
if (!hasSecureEncryption2(node)) {
|
|
13185
13506
|
return {
|
|
13186
13507
|
passed: false,
|
|
13187
13508
|
message: `SSID profile "${profileName}" uses weak encryption (${opmode}). Use WPA2 or WPA3.`,
|
|
@@ -13213,8 +13534,8 @@ var WlcRadiusHost = {
|
|
|
13213
13534
|
remediation: 'Configure "host <ip-address>" for the RADIUS server.'
|
|
13214
13535
|
},
|
|
13215
13536
|
check: (node) => {
|
|
13216
|
-
const profileName =
|
|
13217
|
-
const host =
|
|
13537
|
+
const profileName = extractProfileName2(node.id);
|
|
13538
|
+
const host = getRadiusHost2(node);
|
|
13218
13539
|
if (!host) {
|
|
13219
13540
|
return {
|
|
13220
13541
|
passed: false,
|
|
@@ -13262,6 +13583,34 @@ function getRulesByArubaVendor(vendorId) {
|
|
|
13262
13583
|
}
|
|
13263
13584
|
}
|
|
13264
13585
|
|
|
13586
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/paloalto/helpers.ts
|
|
13587
|
+
var findStanza9 = (node, stanzaName) => {
|
|
13588
|
+
if (!node?.children) return void 0;
|
|
13589
|
+
return node.children.find(
|
|
13590
|
+
(child) => child?.id?.toLowerCase() === stanzaName.toLowerCase()
|
|
13591
|
+
);
|
|
13592
|
+
};
|
|
13593
|
+
var hasLogging3 = (ruleNode) => {
|
|
13594
|
+
const logStart = hasChildCommand5(ruleNode, "log-start");
|
|
13595
|
+
const logEnd = hasChildCommand5(ruleNode, "log-end");
|
|
13596
|
+
return { logStart, logEnd };
|
|
13597
|
+
};
|
|
13598
|
+
var isRuleDisabled2 = (ruleNode) => {
|
|
13599
|
+
const disabled = getChildCommand5(ruleNode, "disabled");
|
|
13600
|
+
if (!disabled) return false;
|
|
13601
|
+
return disabled.id.toLowerCase().includes("yes") || disabled.id.toLowerCase().includes("true");
|
|
13602
|
+
};
|
|
13603
|
+
var getSecurityRules2 = (rulebaseNode) => {
|
|
13604
|
+
const security = findStanza9(rulebaseNode, "security");
|
|
13605
|
+
if (!security) return [];
|
|
13606
|
+
const rules = findStanza9(security, "rules");
|
|
13607
|
+
if (!rules?.children) return [];
|
|
13608
|
+
return rules.children;
|
|
13609
|
+
};
|
|
13610
|
+
var hasZoneProtectionProfile2 = (zoneNode) => {
|
|
13611
|
+
return hasChildCommand5(zoneNode, "zone-protection-profile");
|
|
13612
|
+
};
|
|
13613
|
+
|
|
13265
13614
|
// ../rules-default/src/paloalto/panos-rules.ts
|
|
13266
13615
|
var HostnameRequired = {
|
|
13267
13616
|
id: "PAN-SYS-001",
|
|
@@ -13275,7 +13624,7 @@ var HostnameRequired = {
|
|
|
13275
13624
|
remediation: "Configure hostname under deviceconfig > system."
|
|
13276
13625
|
},
|
|
13277
13626
|
check: (node) => {
|
|
13278
|
-
const system =
|
|
13627
|
+
const system = findStanza9(node, "system");
|
|
13279
13628
|
if (!system) {
|
|
13280
13629
|
return {
|
|
13281
13630
|
passed: false,
|
|
@@ -13286,7 +13635,7 @@ var HostnameRequired = {
|
|
|
13286
13635
|
loc: node.loc
|
|
13287
13636
|
};
|
|
13288
13637
|
}
|
|
13289
|
-
const hasHostname =
|
|
13638
|
+
const hasHostname = hasChildCommand5(system, "hostname");
|
|
13290
13639
|
if (!hasHostname) {
|
|
13291
13640
|
return {
|
|
13292
13641
|
passed: false,
|
|
@@ -13319,7 +13668,7 @@ var SecurityRuleLogging = {
|
|
|
13319
13668
|
remediation: "Enable log-end (and optionally log-start) on all security rules."
|
|
13320
13669
|
},
|
|
13321
13670
|
check: (node) => {
|
|
13322
|
-
const rules =
|
|
13671
|
+
const rules = getSecurityRules2(node);
|
|
13323
13672
|
if (rules.length === 0) {
|
|
13324
13673
|
return {
|
|
13325
13674
|
passed: true,
|
|
@@ -13332,8 +13681,8 @@ var SecurityRuleLogging = {
|
|
|
13332
13681
|
}
|
|
13333
13682
|
const issues = [];
|
|
13334
13683
|
for (const rule of rules) {
|
|
13335
|
-
if (
|
|
13336
|
-
const logging =
|
|
13684
|
+
if (isRuleDisabled2(rule)) continue;
|
|
13685
|
+
const logging = hasLogging3(rule);
|
|
13337
13686
|
if (!logging.logEnd) {
|
|
13338
13687
|
issues.push(`Rule "${rule.id}" does not have log-end enabled.`);
|
|
13339
13688
|
}
|
|
@@ -13372,7 +13721,7 @@ var ZoneProtectionRequired = {
|
|
|
13372
13721
|
check: (node) => {
|
|
13373
13722
|
const issues = [];
|
|
13374
13723
|
for (const zone of node.children) {
|
|
13375
|
-
if (!
|
|
13724
|
+
if (!hasZoneProtectionProfile2(zone)) {
|
|
13376
13725
|
issues.push(`Zone "${zone.id}" does not have a Zone Protection Profile applied.`);
|
|
13377
13726
|
}
|
|
13378
13727
|
}
|
|
@@ -13420,6 +13769,34 @@ function getRulesByPaloAltoVendor() {
|
|
|
13420
13769
|
return [...allCommonRules, ...allPaloAltoRules];
|
|
13421
13770
|
}
|
|
13422
13771
|
|
|
13772
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/arista/helpers.ts
|
|
13773
|
+
var checkMlagRequirements2 = (mlagNode) => {
|
|
13774
|
+
return {
|
|
13775
|
+
hasDomainId: hasChildCommand5(mlagNode, "domain-id"),
|
|
13776
|
+
hasPeerLink: hasChildCommand5(mlagNode, "peer-link"),
|
|
13777
|
+
hasPeerAddress: hasChildCommand5(mlagNode, "peer-address"),
|
|
13778
|
+
hasLocalInterface: hasChildCommand5(mlagNode, "local-interface")
|
|
13779
|
+
};
|
|
13780
|
+
};
|
|
13781
|
+
var isShutdown8 = (interfaceNode) => {
|
|
13782
|
+
if (!interfaceNode?.children) return false;
|
|
13783
|
+
const hasShutdown = interfaceNode.children.some(
|
|
13784
|
+
(child) => child?.id && equalsIgnoreCase2(child.id, "shutdown")
|
|
13785
|
+
);
|
|
13786
|
+
const hasNoShutdown = interfaceNode.children.some(
|
|
13787
|
+
(child) => child?.id && equalsIgnoreCase2(child.id, "no shutdown")
|
|
13788
|
+
);
|
|
13789
|
+
return hasShutdown && !hasNoShutdown;
|
|
13790
|
+
};
|
|
13791
|
+
var getInterfaceDescription2 = (interfaceNode) => {
|
|
13792
|
+
if (!interfaceNode?.children) return void 0;
|
|
13793
|
+
const descCmd = interfaceNode.children.find(
|
|
13794
|
+
(child) => child?.id && startsWithIgnoreCase2(child.id, "description ")
|
|
13795
|
+
);
|
|
13796
|
+
if (!descCmd) return void 0;
|
|
13797
|
+
return descCmd.id.replace(/^description\s+/i, "").trim();
|
|
13798
|
+
};
|
|
13799
|
+
|
|
13423
13800
|
// ../rules-default/src/arista/eos-rules.ts
|
|
13424
13801
|
var HostnameRequired2 = {
|
|
13425
13802
|
id: "ARI-SYS-001",
|
|
@@ -13468,7 +13845,7 @@ var MlagConfigComplete = {
|
|
|
13468
13845
|
remediation: "MLAG configuration requires: domain-id, peer-link, peer-address, and local-interface."
|
|
13469
13846
|
},
|
|
13470
13847
|
check: (node, context) => {
|
|
13471
|
-
const requirements =
|
|
13848
|
+
const requirements = checkMlagRequirements2(node);
|
|
13472
13849
|
const issues = [];
|
|
13473
13850
|
if (!requirements.hasDomainId) {
|
|
13474
13851
|
issues.push("domain-id");
|
|
@@ -13514,7 +13891,7 @@ var InterfaceDescription = {
|
|
|
13514
13891
|
remediation: "Add description to interface: description <text>"
|
|
13515
13892
|
},
|
|
13516
13893
|
check: (node, context) => {
|
|
13517
|
-
if (
|
|
13894
|
+
if (isShutdown8(node)) {
|
|
13518
13895
|
return {
|
|
13519
13896
|
passed: true,
|
|
13520
13897
|
message: "Interface is shutdown, description not required.",
|
|
@@ -13524,7 +13901,7 @@ var InterfaceDescription = {
|
|
|
13524
13901
|
loc: node.loc
|
|
13525
13902
|
};
|
|
13526
13903
|
}
|
|
13527
|
-
const description =
|
|
13904
|
+
const description = getInterfaceDescription2(node);
|
|
13528
13905
|
if (!description) {
|
|
13529
13906
|
return {
|
|
13530
13907
|
passed: false,
|
|
@@ -13559,6 +13936,37 @@ function getRulesByAristaVendor() {
|
|
|
13559
13936
|
return [...allCommonRules, ...allAristaRules];
|
|
13560
13937
|
}
|
|
13561
13938
|
|
|
13939
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/vyos/helpers.ts
|
|
13940
|
+
var isDisabled4 = (node) => {
|
|
13941
|
+
if (!node?.children) return false;
|
|
13942
|
+
return node.children.some((child) => child?.id?.toLowerCase().trim() === "disable");
|
|
13943
|
+
};
|
|
13944
|
+
var findStanzasByPrefix3 = (node, prefix) => {
|
|
13945
|
+
if (!node?.children) return [];
|
|
13946
|
+
return node.children.filter(
|
|
13947
|
+
(child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase())
|
|
13948
|
+
);
|
|
13949
|
+
};
|
|
13950
|
+
var getEthernetInterfaces2 = (interfacesNode) => {
|
|
13951
|
+
if (!interfacesNode?.children) return [];
|
|
13952
|
+
return interfacesNode.children.filter(
|
|
13953
|
+
(child) => child?.id?.toLowerCase().startsWith("ethernet eth")
|
|
13954
|
+
);
|
|
13955
|
+
};
|
|
13956
|
+
var getFirewallDefaultAction2 = (rulesetNode) => {
|
|
13957
|
+
if (!rulesetNode?.children) return void 0;
|
|
13958
|
+
for (const child of rulesetNode.children) {
|
|
13959
|
+
const id = child?.id?.toLowerCase().trim();
|
|
13960
|
+
if (!id) continue;
|
|
13961
|
+
if (id.startsWith("default-action")) {
|
|
13962
|
+
if (id.includes("drop")) return "drop";
|
|
13963
|
+
if (id.includes("accept")) return "accept";
|
|
13964
|
+
if (id.includes("reject")) return "reject";
|
|
13965
|
+
}
|
|
13966
|
+
}
|
|
13967
|
+
return void 0;
|
|
13968
|
+
};
|
|
13969
|
+
|
|
13562
13970
|
// ../rules-default/src/vyos/vyos-rules.ts
|
|
13563
13971
|
var VyosHostnameRequired = {
|
|
13564
13972
|
id: "VYOS-SYS-001",
|
|
@@ -13572,7 +13980,7 @@ var VyosHostnameRequired = {
|
|
|
13572
13980
|
remediation: 'Configure "host-name" under system stanza.'
|
|
13573
13981
|
},
|
|
13574
13982
|
check: (node) => {
|
|
13575
|
-
const hasHostname =
|
|
13983
|
+
const hasHostname = hasChildCommand5(node, "host-name");
|
|
13576
13984
|
if (!hasHostname) {
|
|
13577
13985
|
return {
|
|
13578
13986
|
passed: false,
|
|
@@ -13641,12 +14049,12 @@ var VyosInterfaceDescription = {
|
|
|
13641
14049
|
},
|
|
13642
14050
|
check: (node) => {
|
|
13643
14051
|
const issues = [];
|
|
13644
|
-
const ethernetInterfaces =
|
|
14052
|
+
const ethernetInterfaces = getEthernetInterfaces2(node);
|
|
13645
14053
|
for (const iface of ethernetInterfaces) {
|
|
13646
|
-
if (
|
|
14054
|
+
if (isDisabled4(iface)) {
|
|
13647
14055
|
continue;
|
|
13648
14056
|
}
|
|
13649
|
-
const hasDesc =
|
|
14057
|
+
const hasDesc = hasChildCommand5(iface, "description");
|
|
13650
14058
|
if (!hasDesc) {
|
|
13651
14059
|
const ifaceName = iface.id.split(/\s+/).pop() || iface.id;
|
|
13652
14060
|
issues.push(`Interface "${ifaceName}" missing description.`);
|
|
@@ -13684,7 +14092,7 @@ var VyosFirewallDefaultAction = {
|
|
|
13684
14092
|
remediation: 'Set "default-action drop" or "default-action reject" for each firewall ruleset.'
|
|
13685
14093
|
},
|
|
13686
14094
|
check: (node) => {
|
|
13687
|
-
const rulesets =
|
|
14095
|
+
const rulesets = findStanzasByPrefix3(node, "name");
|
|
13688
14096
|
if (rulesets.length === 0) {
|
|
13689
14097
|
return {
|
|
13690
14098
|
passed: true,
|
|
@@ -13697,7 +14105,7 @@ var VyosFirewallDefaultAction = {
|
|
|
13697
14105
|
}
|
|
13698
14106
|
const issues = [];
|
|
13699
14107
|
for (const ruleset of rulesets) {
|
|
13700
|
-
const defaultAction =
|
|
14108
|
+
const defaultAction = getFirewallDefaultAction2(ruleset);
|
|
13701
14109
|
if (!defaultAction) {
|
|
13702
14110
|
const rulesetName = ruleset.id.split(/\s+/)[1] || ruleset.id;
|
|
13703
14111
|
issues.push(`Firewall ruleset "${rulesetName}" has no default-action configured.`);
|
|
@@ -13739,20 +14147,79 @@ function getRulesByVyosVendor() {
|
|
|
13739
14147
|
return [...allCommonRules, ...allVyosRules];
|
|
13740
14148
|
}
|
|
13741
14149
|
|
|
13742
|
-
//
|
|
13743
|
-
var
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
14150
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/fortinet/helpers.ts
|
|
14151
|
+
var getEditEntries2 = (configSection) => {
|
|
14152
|
+
if (!configSection?.children) return [];
|
|
14153
|
+
return configSection.children.filter(
|
|
14154
|
+
(child) => child?.id?.toLowerCase().startsWith("edit ")
|
|
14155
|
+
);
|
|
14156
|
+
};
|
|
14157
|
+
var getEditEntryName2 = (editEntry) => {
|
|
14158
|
+
const match = editEntry.id.match(/^edit\s+["']?([^"']+)["']?$/i);
|
|
14159
|
+
const entryName = match?.[1];
|
|
14160
|
+
return entryName ?? editEntry.id;
|
|
14161
|
+
};
|
|
14162
|
+
var getSetValue2 = (node, paramName) => {
|
|
14163
|
+
if (!node?.children) return void 0;
|
|
14164
|
+
const normalizedParam = paramName.toLowerCase();
|
|
14165
|
+
for (const child of node.children) {
|
|
14166
|
+
const childId = child?.id?.toLowerCase();
|
|
14167
|
+
if (!childId) continue;
|
|
14168
|
+
const match = childId.match(new RegExp(`^set\\s+${normalizedParam}\\s+(.+)$`, "i"));
|
|
14169
|
+
const value = match?.[1];
|
|
14170
|
+
if (value) {
|
|
14171
|
+
return value.replace(/^["']|["']$/g, "").trim();
|
|
14172
|
+
}
|
|
14173
|
+
}
|
|
14174
|
+
return void 0;
|
|
14175
|
+
};
|
|
14176
|
+
var isPolicyDisabled2 = (policyNode) => {
|
|
14177
|
+
const status = getSetValue2(policyNode, "status");
|
|
14178
|
+
return status?.toLowerCase() === "disable";
|
|
14179
|
+
};
|
|
14180
|
+
var hasLogging4 = (policyNode) => {
|
|
14181
|
+
const logtraffic = getSetValue2(policyNode, "logtraffic");
|
|
14182
|
+
const logtrafficStart = getSetValue2(policyNode, "logtraffic-start");
|
|
14183
|
+
return {
|
|
14184
|
+
logtraffic,
|
|
14185
|
+
logtrafficStart: logtrafficStart?.toLowerCase() === "enable"
|
|
14186
|
+
};
|
|
14187
|
+
};
|
|
14188
|
+
var getAdminProfile2 = (adminNode) => {
|
|
14189
|
+
return getSetValue2(adminNode, "accprofile");
|
|
14190
|
+
};
|
|
14191
|
+
var isSuperAdmin2 = (adminNode) => {
|
|
14192
|
+
const profile = getAdminProfile2(adminNode);
|
|
14193
|
+
return profile?.toLowerCase() === "super_admin";
|
|
14194
|
+
};
|
|
14195
|
+
var getAdminTrustedHosts2 = (adminNode) => {
|
|
14196
|
+
const trustedHosts = [];
|
|
14197
|
+
for (let i = 1; i <= 10; i++) {
|
|
14198
|
+
const host = getSetValue2(adminNode, `trusthost${i}`);
|
|
14199
|
+
if (host && host !== "0.0.0.0 0.0.0.0") {
|
|
14200
|
+
trustedHosts.push(host);
|
|
14201
|
+
}
|
|
14202
|
+
}
|
|
14203
|
+
return trustedHosts;
|
|
14204
|
+
};
|
|
14205
|
+
var hasAdminTrustedHosts2 = (adminNode) => {
|
|
14206
|
+
return getAdminTrustedHosts2(adminNode).length > 0;
|
|
14207
|
+
};
|
|
14208
|
+
|
|
14209
|
+
// ../rules-default/src/fortinet/fortigate-rules.ts
|
|
14210
|
+
var HostnameRequired3 = {
|
|
14211
|
+
id: "FGT-SYS-001",
|
|
14212
|
+
selector: "config system global",
|
|
14213
|
+
vendor: "fortinet-fortigate",
|
|
14214
|
+
category: "Documentation",
|
|
14215
|
+
metadata: {
|
|
14216
|
+
level: "warning",
|
|
13750
14217
|
obu: "Network Engineering",
|
|
13751
14218
|
owner: "NetOps",
|
|
13752
14219
|
remediation: 'Configure hostname under "config system global" using "set hostname <name>".'
|
|
13753
14220
|
},
|
|
13754
14221
|
check: (node) => {
|
|
13755
|
-
const hostname =
|
|
14222
|
+
const hostname = getSetValue2(node, "hostname");
|
|
13756
14223
|
if (!hostname) {
|
|
13757
14224
|
return {
|
|
13758
14225
|
passed: false,
|
|
@@ -13785,7 +14252,7 @@ var AdminTrustedHostRequired = {
|
|
|
13785
14252
|
remediation: 'Configure trusted hosts for each admin user using "set trusthost1", "set trusthost2", etc.'
|
|
13786
14253
|
},
|
|
13787
14254
|
check: (node) => {
|
|
13788
|
-
const admins =
|
|
14255
|
+
const admins = getEditEntries2(node);
|
|
13789
14256
|
if (admins.length === 0) {
|
|
13790
14257
|
return {
|
|
13791
14258
|
passed: true,
|
|
@@ -13798,11 +14265,11 @@ var AdminTrustedHostRequired = {
|
|
|
13798
14265
|
}
|
|
13799
14266
|
const issues = [];
|
|
13800
14267
|
for (const admin of admins) {
|
|
13801
|
-
const adminName =
|
|
13802
|
-
if (
|
|
14268
|
+
const adminName = getEditEntryName2(admin);
|
|
14269
|
+
if (hasAdminTrustedHosts2(admin)) {
|
|
13803
14270
|
continue;
|
|
13804
14271
|
}
|
|
13805
|
-
if (
|
|
14272
|
+
if (isSuperAdmin2(admin)) {
|
|
13806
14273
|
issues.push(`Super admin "${adminName}" has no trusted host restrictions. This is a critical security issue.`);
|
|
13807
14274
|
} else {
|
|
13808
14275
|
issues.push(`Admin "${adminName}" has no trusted host restrictions.`);
|
|
@@ -13840,7 +14307,7 @@ var PolicyLoggingRequired = {
|
|
|
13840
14307
|
remediation: 'Enable logging on all firewall policies using "set logtraffic all" or "set logtraffic utm".'
|
|
13841
14308
|
},
|
|
13842
14309
|
check: (node) => {
|
|
13843
|
-
const policies =
|
|
14310
|
+
const policies = getEditEntries2(node);
|
|
13844
14311
|
if (policies.length === 0) {
|
|
13845
14312
|
return {
|
|
13846
14313
|
passed: true,
|
|
@@ -13853,10 +14320,10 @@ var PolicyLoggingRequired = {
|
|
|
13853
14320
|
}
|
|
13854
14321
|
const issues = [];
|
|
13855
14322
|
for (const policy of policies) {
|
|
13856
|
-
if (
|
|
13857
|
-
const logging =
|
|
14323
|
+
if (isPolicyDisabled2(policy)) continue;
|
|
14324
|
+
const logging = hasLogging4(policy);
|
|
13858
14325
|
if (!logging.logtraffic || isFeatureDisabled(logging.logtraffic)) {
|
|
13859
|
-
const policyId =
|
|
14326
|
+
const policyId = getEditEntryName2(policy);
|
|
13860
14327
|
issues.push(`Policy ${policyId} does not have logging enabled.`);
|
|
13861
14328
|
}
|
|
13862
14329
|
}
|
|
@@ -13894,6 +14361,44 @@ function getRulesByFortinetVendor() {
|
|
|
13894
14361
|
return [...allCommonRules, ...allFortinetRules];
|
|
13895
14362
|
}
|
|
13896
14363
|
|
|
14364
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/extreme/helpers.ts
|
|
14365
|
+
var getExosVlanName2 = (node) => {
|
|
14366
|
+
const match = node.id.match(/^(?:create|configure)\s+vlan\s+["']?(\w+)["']?/i);
|
|
14367
|
+
const vlanName = match?.[1];
|
|
14368
|
+
return vlanName?.trim();
|
|
14369
|
+
};
|
|
14370
|
+
var isVossVlanCreate2 = (node) => {
|
|
14371
|
+
return /^vlan\s+create\s+\d+/i.test(node.id);
|
|
14372
|
+
};
|
|
14373
|
+
var getVossVlanId2 = (node) => {
|
|
14374
|
+
const match = node.id.match(/^vlan\s+(?:create|members|i-sid)\s+(\d+)/i);
|
|
14375
|
+
const vlanId = match?.[1];
|
|
14376
|
+
return vlanId ? parseInt(vlanId, 10) : void 0;
|
|
14377
|
+
};
|
|
14378
|
+
var isVossGigabitEthernet2 = (node) => {
|
|
14379
|
+
return /^interface\s+GigabitEthernet\s+\d+\/\d+/i.test(node.id);
|
|
14380
|
+
};
|
|
14381
|
+
var isVossShutdown2 = (node) => {
|
|
14382
|
+
if (!node?.children) return false;
|
|
14383
|
+
const hasShutdown = node.children.some(
|
|
14384
|
+
(child) => child?.id?.toLowerCase() === "shutdown"
|
|
14385
|
+
);
|
|
14386
|
+
const hasNoShutdown = node.children.some(
|
|
14387
|
+
(child) => child?.id?.toLowerCase() === "no shutdown"
|
|
14388
|
+
);
|
|
14389
|
+
return hasShutdown && !hasNoShutdown;
|
|
14390
|
+
};
|
|
14391
|
+
var getVossDefaultVlan2 = (node) => {
|
|
14392
|
+
if (!node?.children) return void 0;
|
|
14393
|
+
const defaultVlan = node.children.find(
|
|
14394
|
+
(child) => child?.id && /^default-vlan-id\s+\d+/i.test(child.id)
|
|
14395
|
+
);
|
|
14396
|
+
if (!defaultVlan?.id) return void 0;
|
|
14397
|
+
const match = defaultVlan.id.match(/default-vlan-id\s+(\d+)/i);
|
|
14398
|
+
const vlanId = match?.[1];
|
|
14399
|
+
return vlanId ? parseInt(vlanId, 10) : void 0;
|
|
14400
|
+
};
|
|
14401
|
+
|
|
13897
14402
|
// ../rules-default/src/extreme/exos-rules.ts
|
|
13898
14403
|
var ExosSysnameRequired = {
|
|
13899
14404
|
id: "EXOS-SYS-001",
|
|
@@ -13973,7 +14478,7 @@ var ExosVlanNaming = {
|
|
|
13973
14478
|
remediation: 'Use descriptive VLAN names: create vlan "<meaningful-name>" tag <id>'
|
|
13974
14479
|
},
|
|
13975
14480
|
check: (node, context) => {
|
|
13976
|
-
const vlanName =
|
|
14481
|
+
const vlanName = getExosVlanName2(node);
|
|
13977
14482
|
if (!vlanName) {
|
|
13978
14483
|
return {
|
|
13979
14484
|
passed: false,
|
|
@@ -14064,7 +14569,7 @@ var VossVlanIsidRequired = {
|
|
|
14064
14569
|
remediation: "Configure I-SID for VLAN: vlan i-sid <vlan-id> <isid>"
|
|
14065
14570
|
},
|
|
14066
14571
|
check: (node, context) => {
|
|
14067
|
-
if (!
|
|
14572
|
+
if (!isVossVlanCreate2(node)) {
|
|
14068
14573
|
return {
|
|
14069
14574
|
passed: true,
|
|
14070
14575
|
message: "Not a VLAN create command.",
|
|
@@ -14074,7 +14579,7 @@ var VossVlanIsidRequired = {
|
|
|
14074
14579
|
loc: node.loc
|
|
14075
14580
|
};
|
|
14076
14581
|
}
|
|
14077
|
-
const vlanId =
|
|
14582
|
+
const vlanId = getVossVlanId2(node);
|
|
14078
14583
|
if (!vlanId) {
|
|
14079
14584
|
return {
|
|
14080
14585
|
passed: false,
|
|
@@ -14117,7 +14622,7 @@ var VossInterfaceDefaultVlan = {
|
|
|
14117
14622
|
remediation: "Configure default VLAN: default-vlan-id <vlan-id>"
|
|
14118
14623
|
},
|
|
14119
14624
|
check: (node, context) => {
|
|
14120
|
-
if (!
|
|
14625
|
+
if (!isVossGigabitEthernet2(node)) {
|
|
14121
14626
|
return {
|
|
14122
14627
|
passed: true,
|
|
14123
14628
|
message: "Not a GigabitEthernet interface.",
|
|
@@ -14127,7 +14632,7 @@ var VossInterfaceDefaultVlan = {
|
|
|
14127
14632
|
loc: node.loc
|
|
14128
14633
|
};
|
|
14129
14634
|
}
|
|
14130
|
-
if (
|
|
14635
|
+
if (isVossShutdown2(node)) {
|
|
14131
14636
|
return {
|
|
14132
14637
|
passed: true,
|
|
14133
14638
|
message: "Interface is shutdown.",
|
|
@@ -14137,7 +14642,7 @@ var VossInterfaceDefaultVlan = {
|
|
|
14137
14642
|
loc: node.loc
|
|
14138
14643
|
};
|
|
14139
14644
|
}
|
|
14140
|
-
const defaultVlan =
|
|
14645
|
+
const defaultVlan = getVossDefaultVlan2(node);
|
|
14141
14646
|
if (!defaultVlan) {
|
|
14142
14647
|
return {
|
|
14143
14648
|
passed: false,
|
|
@@ -14189,6 +14694,22 @@ function getRulesByExtremeVendor(vendorId) {
|
|
|
14189
14694
|
}
|
|
14190
14695
|
}
|
|
14191
14696
|
|
|
14697
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/huawei/helpers.ts
|
|
14698
|
+
var isEnabled4 = (node) => {
|
|
14699
|
+
if (!node?.children) return false;
|
|
14700
|
+
return node.children.some((child) => {
|
|
14701
|
+
const rawText = child?.rawText?.toLowerCase().trim();
|
|
14702
|
+
return rawText === "undo shutdown";
|
|
14703
|
+
});
|
|
14704
|
+
};
|
|
14705
|
+
var isPhysicalPort5 = (interfaceName) => {
|
|
14706
|
+
const name = interfaceName.toLowerCase();
|
|
14707
|
+
return !name.includes("vlanif") && !name.includes("loopback") && !name.includes("null") && !name.includes("tunnel") && !name.includes("eth-trunk") && !name.includes("nve") && !name.includes("vbdif");
|
|
14708
|
+
};
|
|
14709
|
+
var hasDescription6 = (node) => {
|
|
14710
|
+
return hasChildCommand5(node, "description");
|
|
14711
|
+
};
|
|
14712
|
+
|
|
14192
14713
|
// ../rules-default/src/huawei/vrp-rules.ts
|
|
14193
14714
|
var SysnameRequired = {
|
|
14194
14715
|
id: "HUAWEI-SYS-001",
|
|
@@ -14236,7 +14757,7 @@ var InterfaceDescriptionRequired2 = {
|
|
|
14236
14757
|
},
|
|
14237
14758
|
check: (node) => {
|
|
14238
14759
|
const interfaceName = node.id.replace(/^interface\s+/i, "").trim();
|
|
14239
|
-
if (!
|
|
14760
|
+
if (!isPhysicalPort5(interfaceName)) {
|
|
14240
14761
|
return {
|
|
14241
14762
|
passed: true,
|
|
14242
14763
|
message: "Non-physical interface, description optional.",
|
|
@@ -14246,7 +14767,7 @@ var InterfaceDescriptionRequired2 = {
|
|
|
14246
14767
|
loc: node.loc
|
|
14247
14768
|
};
|
|
14248
14769
|
}
|
|
14249
|
-
if (!
|
|
14770
|
+
if (!isEnabled4(node)) {
|
|
14250
14771
|
return {
|
|
14251
14772
|
passed: true,
|
|
14252
14773
|
message: "Interface is shutdown, description optional.",
|
|
@@ -14256,7 +14777,7 @@ var InterfaceDescriptionRequired2 = {
|
|
|
14256
14777
|
loc: node.loc
|
|
14257
14778
|
};
|
|
14258
14779
|
}
|
|
14259
|
-
if (!
|
|
14780
|
+
if (!hasDescription6(node)) {
|
|
14260
14781
|
return {
|
|
14261
14782
|
passed: false,
|
|
14262
14783
|
message: `Interface ${interfaceName} is enabled but has no description.`,
|
|
@@ -14340,6 +14861,55 @@ function getRulesByHuaweiVendor() {
|
|
|
14340
14861
|
return [...allCommonRules, ...allHuaweiRules];
|
|
14341
14862
|
}
|
|
14342
14863
|
|
|
14864
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/mikrotik/helpers.ts
|
|
14865
|
+
var parseProperty2 = (commandStr, propertyName) => {
|
|
14866
|
+
const regex = new RegExp(`\\b${propertyName}=(?:"([^"]+)"|'([^']+)'|(\\S+))`, "i");
|
|
14867
|
+
const match = commandStr.match(regex);
|
|
14868
|
+
if (match) {
|
|
14869
|
+
return match[1] || match[2] || match[3];
|
|
14870
|
+
}
|
|
14871
|
+
return void 0;
|
|
14872
|
+
};
|
|
14873
|
+
var getFirewallChain2 = (nodeOrCommand) => {
|
|
14874
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14875
|
+
return parseProperty2(str, "chain");
|
|
14876
|
+
};
|
|
14877
|
+
var getFirewallAction2 = (nodeOrCommand) => {
|
|
14878
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14879
|
+
return parseProperty2(str, "action");
|
|
14880
|
+
};
|
|
14881
|
+
var getName2 = (nodeOrCommand) => {
|
|
14882
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14883
|
+
return parseProperty2(str, "name");
|
|
14884
|
+
};
|
|
14885
|
+
var isAddCommand2 = (nodeOrCommand) => {
|
|
14886
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14887
|
+
return /^add\s+/i.test(str.trim());
|
|
14888
|
+
};
|
|
14889
|
+
var isSetCommand2 = (nodeOrCommand) => {
|
|
14890
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14891
|
+
return /^set\s+/i.test(str.trim());
|
|
14892
|
+
};
|
|
14893
|
+
var getAddCommands2 = (node) => {
|
|
14894
|
+
if (!node?.children) return [];
|
|
14895
|
+
return node.children.filter((child) => isAddCommand2(child));
|
|
14896
|
+
};
|
|
14897
|
+
var isServiceDisabled2 = (nodeOrCommand) => {
|
|
14898
|
+
const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
|
|
14899
|
+
const disabled = parseProperty2(str, "disabled");
|
|
14900
|
+
return disabled?.toLowerCase() === "yes";
|
|
14901
|
+
};
|
|
14902
|
+
var getSystemIdentity2 = (node) => {
|
|
14903
|
+
if (!node?.children) return void 0;
|
|
14904
|
+
for (const child of node.children) {
|
|
14905
|
+
if (isSetCommand2(child)) {
|
|
14906
|
+
const name = getName2(child);
|
|
14907
|
+
if (name) return name;
|
|
14908
|
+
}
|
|
14909
|
+
}
|
|
14910
|
+
return void 0;
|
|
14911
|
+
};
|
|
14912
|
+
|
|
14343
14913
|
// ../rules-default/src/mikrotik/routeros-rules.ts
|
|
14344
14914
|
var MikrotikSystemIdentity = {
|
|
14345
14915
|
id: "MIK-SYS-001",
|
|
@@ -14353,7 +14923,7 @@ var MikrotikSystemIdentity = {
|
|
|
14353
14923
|
remediation: "Configure system identity: /system identity set name=MyRouter"
|
|
14354
14924
|
},
|
|
14355
14925
|
check: (node) => {
|
|
14356
|
-
const identity =
|
|
14926
|
+
const identity = getSystemIdentity2(node);
|
|
14357
14927
|
if (!identity || equalsIgnoreCase(identity, "mikrotik") || equalsIgnoreCase(identity, "routerboard")) {
|
|
14358
14928
|
return {
|
|
14359
14929
|
passed: false,
|
|
@@ -14391,8 +14961,8 @@ var MikrotikDisableUnusedServices = {
|
|
|
14391
14961
|
for (const child of node.children) {
|
|
14392
14962
|
const childId = child.id.toLowerCase();
|
|
14393
14963
|
for (const service of dangerousServices) {
|
|
14394
|
-
if (childId.includes(service) && !
|
|
14395
|
-
const disabled =
|
|
14964
|
+
if (childId.includes(service) && !isServiceDisabled2(child)) {
|
|
14965
|
+
const disabled = parseProperty2(child.id, "disabled");
|
|
14396
14966
|
if (!disabled || !equalsIgnoreCase(disabled, "yes")) {
|
|
14397
14967
|
issues.push(`Service '${service}' is enabled. Consider disabling it.`);
|
|
14398
14968
|
}
|
|
@@ -14431,11 +15001,11 @@ var MikrotikInputChainDrop = {
|
|
|
14431
15001
|
remediation: "Add drop rule for input chain: add chain=input action=drop"
|
|
14432
15002
|
},
|
|
14433
15003
|
check: (node) => {
|
|
14434
|
-
const addCommands =
|
|
15004
|
+
const addCommands = getAddCommands2(node);
|
|
14435
15005
|
let hasInputDrop = false;
|
|
14436
15006
|
for (const cmd of addCommands) {
|
|
14437
|
-
const chain =
|
|
14438
|
-
const action =
|
|
15007
|
+
const chain = getFirewallChain2(cmd);
|
|
15008
|
+
const action = getFirewallAction2(cmd);
|
|
14439
15009
|
if (chain && equalsIgnoreCase(chain, "input") && action && equalsIgnoreCase(action, "drop")) {
|
|
14440
15010
|
hasInputDrop = true;
|
|
14441
15011
|
break;
|
|
@@ -14472,6 +15042,60 @@ function getRulesByMikroTikVendor() {
|
|
|
14472
15042
|
return [...allCommonRules, ...allMikroTikRules];
|
|
14473
15043
|
}
|
|
14474
15044
|
|
|
15045
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/nokia/helpers.ts
|
|
15046
|
+
var isAdminStateDisabled2 = (node) => {
|
|
15047
|
+
if (!node?.children) return false;
|
|
15048
|
+
const directCheck = node.children.some((child) => {
|
|
15049
|
+
const rawText = child?.rawText?.toLowerCase().trim();
|
|
15050
|
+
return rawText === "admin-state disable";
|
|
15051
|
+
});
|
|
15052
|
+
if (directCheck) return true;
|
|
15053
|
+
return node?.rawText?.toLowerCase().includes("admin-state disable") ?? false;
|
|
15054
|
+
};
|
|
15055
|
+
var isPhysicalPort6 = (portName) => {
|
|
15056
|
+
const name = portName.toLowerCase();
|
|
15057
|
+
return /^\d+\/\d+\/\d+/.test(name);
|
|
15058
|
+
};
|
|
15059
|
+
var hasDescription7 = (node) => {
|
|
15060
|
+
return hasChildCommand5(node, "description");
|
|
15061
|
+
};
|
|
15062
|
+
var getDescription4 = (node) => {
|
|
15063
|
+
const descCmd = getChildCommand5(node, "description");
|
|
15064
|
+
if (descCmd?.rawText) {
|
|
15065
|
+
const match = descCmd.rawText.match(/description\s+"([^"]+)"|description\s+(\S+)/i);
|
|
15066
|
+
if (match) {
|
|
15067
|
+
return match[1] || match[2];
|
|
15068
|
+
}
|
|
15069
|
+
}
|
|
15070
|
+
return void 0;
|
|
15071
|
+
};
|
|
15072
|
+
var getSystemName2 = (node) => {
|
|
15073
|
+
if (!node?.children) return void 0;
|
|
15074
|
+
const nameCmd = node.children.find((child) => {
|
|
15075
|
+
return child?.id?.toLowerCase().startsWith("name");
|
|
15076
|
+
});
|
|
15077
|
+
if (nameCmd?.rawText) {
|
|
15078
|
+
const match = nameCmd.rawText.match(/name\s+"([^"]+)"/i);
|
|
15079
|
+
if (match) {
|
|
15080
|
+
return match[1];
|
|
15081
|
+
}
|
|
15082
|
+
}
|
|
15083
|
+
return void 0;
|
|
15084
|
+
};
|
|
15085
|
+
var hasBgpRouterId3 = (node) => {
|
|
15086
|
+
return hasChildCommand5(node, "router-id");
|
|
15087
|
+
};
|
|
15088
|
+
var getBgpRouterId2 = (node) => {
|
|
15089
|
+
const routerIdCmd = getChildCommand5(node, "router-id");
|
|
15090
|
+
if (routerIdCmd?.rawText) {
|
|
15091
|
+
const match = routerIdCmd.rawText.match(/router-id\s+([\d.]+)/i);
|
|
15092
|
+
if (match) {
|
|
15093
|
+
return match[1];
|
|
15094
|
+
}
|
|
15095
|
+
}
|
|
15096
|
+
return void 0;
|
|
15097
|
+
};
|
|
15098
|
+
|
|
14475
15099
|
// ../rules-default/src/nokia/sros-rules.ts
|
|
14476
15100
|
var SystemNameRequired = {
|
|
14477
15101
|
id: "NOKIA-SYS-001",
|
|
@@ -14485,7 +15109,7 @@ var SystemNameRequired = {
|
|
|
14485
15109
|
remediation: 'Configure system name using: system > name "<hostname>"'
|
|
14486
15110
|
},
|
|
14487
15111
|
check: (node) => {
|
|
14488
|
-
const name =
|
|
15112
|
+
const name = getSystemName2(node);
|
|
14489
15113
|
if (!name || name.length === 0) {
|
|
14490
15114
|
return {
|
|
14491
15115
|
passed: false,
|
|
@@ -14519,7 +15143,7 @@ var PortDescriptionRequired = {
|
|
|
14519
15143
|
},
|
|
14520
15144
|
check: (node) => {
|
|
14521
15145
|
const portName = node.id.replace(/^port\s+/i, "").trim();
|
|
14522
|
-
if (!
|
|
15146
|
+
if (!isPhysicalPort6(portName)) {
|
|
14523
15147
|
return {
|
|
14524
15148
|
passed: true,
|
|
14525
15149
|
message: "Not a physical port, description optional.",
|
|
@@ -14529,7 +15153,7 @@ var PortDescriptionRequired = {
|
|
|
14529
15153
|
loc: node.loc
|
|
14530
15154
|
};
|
|
14531
15155
|
}
|
|
14532
|
-
if (
|
|
15156
|
+
if (isAdminStateDisabled2(node)) {
|
|
14533
15157
|
return {
|
|
14534
15158
|
passed: true,
|
|
14535
15159
|
message: "Port is disabled, description optional.",
|
|
@@ -14539,7 +15163,7 @@ var PortDescriptionRequired = {
|
|
|
14539
15163
|
loc: node.loc
|
|
14540
15164
|
};
|
|
14541
15165
|
}
|
|
14542
|
-
if (!
|
|
15166
|
+
if (!hasDescription7(node)) {
|
|
14543
15167
|
return {
|
|
14544
15168
|
passed: false,
|
|
14545
15169
|
message: `Port ${portName} is enabled but has no description.`,
|
|
@@ -14551,7 +15175,7 @@ var PortDescriptionRequired = {
|
|
|
14551
15175
|
}
|
|
14552
15176
|
return {
|
|
14553
15177
|
passed: true,
|
|
14554
|
-
message: `Port ${portName} has description: ${
|
|
15178
|
+
message: `Port ${portName} has description: ${getDescription4(node)}`,
|
|
14555
15179
|
ruleId: "NOKIA-PORT-001",
|
|
14556
15180
|
nodeId: node.id,
|
|
14557
15181
|
level: "info",
|
|
@@ -14571,7 +15195,7 @@ var BgpRouterIdRequired = {
|
|
|
14571
15195
|
remediation: "Configure BGP router-id: bgp > router-id <ip-address>"
|
|
14572
15196
|
},
|
|
14573
15197
|
check: (node) => {
|
|
14574
|
-
if (
|
|
15198
|
+
if (isAdminStateDisabled2(node)) {
|
|
14575
15199
|
return {
|
|
14576
15200
|
passed: true,
|
|
14577
15201
|
message: "BGP is disabled.",
|
|
@@ -14581,7 +15205,7 @@ var BgpRouterIdRequired = {
|
|
|
14581
15205
|
loc: node.loc
|
|
14582
15206
|
};
|
|
14583
15207
|
}
|
|
14584
|
-
if (!
|
|
15208
|
+
if (!hasBgpRouterId3(node)) {
|
|
14585
15209
|
return {
|
|
14586
15210
|
passed: false,
|
|
14587
15211
|
message: "BGP router-id is not configured. Configure a stable router-id.",
|
|
@@ -14593,7 +15217,7 @@ var BgpRouterIdRequired = {
|
|
|
14593
15217
|
}
|
|
14594
15218
|
return {
|
|
14595
15219
|
passed: true,
|
|
14596
|
-
message: `BGP router-id is configured: ${
|
|
15220
|
+
message: `BGP router-id is configured: ${getBgpRouterId2(node)}`,
|
|
14597
15221
|
ruleId: "NOKIA-BGP-001",
|
|
14598
15222
|
nodeId: node.id,
|
|
14599
15223
|
level: "info",
|
|
@@ -14615,6 +15239,40 @@ function getRulesByNokiaVendor() {
|
|
|
14615
15239
|
return [...allCommonRules, ...allNokiaRules];
|
|
14616
15240
|
}
|
|
14617
15241
|
|
|
15242
|
+
// ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/cumulus/helpers.ts
|
|
15243
|
+
var isSwitchPort2 = (interfaceName) => {
|
|
15244
|
+
const name = interfaceName.toLowerCase();
|
|
15245
|
+
return /swp\d+/.test(name);
|
|
15246
|
+
};
|
|
15247
|
+
var isBridgeInterface4 = (interfaceName) => {
|
|
15248
|
+
const name = interfaceName.toLowerCase();
|
|
15249
|
+
return name.includes("bridge") || name === "br_default" || /^br\d+$/.test(name);
|
|
15250
|
+
};
|
|
15251
|
+
var isVlanAwareBridge2 = (node) => {
|
|
15252
|
+
return node.children.some(
|
|
15253
|
+
(child) => child.id.toLowerCase().includes("bridge-vlan-aware") && child.id.toLowerCase().includes("yes")
|
|
15254
|
+
);
|
|
15255
|
+
};
|
|
15256
|
+
var getInterfaceName6 = (node) => {
|
|
15257
|
+
const parts = node.id.split(/\s+/);
|
|
15258
|
+
return parts[1] || node.id;
|
|
15259
|
+
};
|
|
15260
|
+
var hasDescription8 = (node) => {
|
|
15261
|
+
return node.children.some(
|
|
15262
|
+
(child) => child.id.toLowerCase().startsWith("alias ")
|
|
15263
|
+
);
|
|
15264
|
+
};
|
|
15265
|
+
var hasBridgeVids2 = (node) => {
|
|
15266
|
+
return node.children.some(
|
|
15267
|
+
(child) => child.id.toLowerCase().startsWith("bridge-vids ")
|
|
15268
|
+
);
|
|
15269
|
+
};
|
|
15270
|
+
var hasBgpRouterId4 = (node) => {
|
|
15271
|
+
return node.children.some(
|
|
15272
|
+
(child) => child.id.toLowerCase().startsWith("bgp router-id ")
|
|
15273
|
+
);
|
|
15274
|
+
};
|
|
15275
|
+
|
|
14618
15276
|
// ../rules-default/src/cumulus/cumulus-rules.ts
|
|
14619
15277
|
var CumulusInterfaceDescription = {
|
|
14620
15278
|
id: "CUM-IF-001",
|
|
@@ -14628,8 +15286,8 @@ var CumulusInterfaceDescription = {
|
|
|
14628
15286
|
remediation: 'Add "alias <description>" under the interface stanza.'
|
|
14629
15287
|
},
|
|
14630
15288
|
check: (node) => {
|
|
14631
|
-
const ifaceName =
|
|
14632
|
-
if (!
|
|
15289
|
+
const ifaceName = getInterfaceName6(node);
|
|
15290
|
+
if (!isSwitchPort2(ifaceName)) {
|
|
14633
15291
|
return {
|
|
14634
15292
|
passed: true,
|
|
14635
15293
|
message: "Not a switch port interface.",
|
|
@@ -14639,7 +15297,7 @@ var CumulusInterfaceDescription = {
|
|
|
14639
15297
|
loc: node.loc
|
|
14640
15298
|
};
|
|
14641
15299
|
}
|
|
14642
|
-
if (!
|
|
15300
|
+
if (!hasDescription8(node)) {
|
|
14643
15301
|
return {
|
|
14644
15302
|
passed: false,
|
|
14645
15303
|
message: `Switch port "${ifaceName}" missing description (alias).`,
|
|
@@ -14671,8 +15329,8 @@ var CumulusBridgeVlans = {
|
|
|
14671
15329
|
remediation: 'Add "bridge-vids <vlan-ids>" to define allowed VLANs on the bridge.'
|
|
14672
15330
|
},
|
|
14673
15331
|
check: (node) => {
|
|
14674
|
-
const ifaceName =
|
|
14675
|
-
if (!
|
|
15332
|
+
const ifaceName = getInterfaceName6(node);
|
|
15333
|
+
if (!isBridgeInterface4(ifaceName)) {
|
|
14676
15334
|
return {
|
|
14677
15335
|
passed: true,
|
|
14678
15336
|
message: "Not a bridge interface.",
|
|
@@ -14682,7 +15340,7 @@ var CumulusBridgeVlans = {
|
|
|
14682
15340
|
loc: node.loc
|
|
14683
15341
|
};
|
|
14684
15342
|
}
|
|
14685
|
-
if (!
|
|
15343
|
+
if (!isVlanAwareBridge2(node)) {
|
|
14686
15344
|
return {
|
|
14687
15345
|
passed: true,
|
|
14688
15346
|
message: "Not a VLAN-aware bridge.",
|
|
@@ -14692,7 +15350,7 @@ var CumulusBridgeVlans = {
|
|
|
14692
15350
|
loc: node.loc
|
|
14693
15351
|
};
|
|
14694
15352
|
}
|
|
14695
|
-
if (!
|
|
15353
|
+
if (!hasBridgeVids2(node)) {
|
|
14696
15354
|
return {
|
|
14697
15355
|
passed: false,
|
|
14698
15356
|
message: `VLAN-aware bridge "${ifaceName}" has no VLANs (bridge-vids) configured.`,
|
|
@@ -14724,7 +15382,7 @@ var CumulusBgpRouterId = {
|
|
|
14724
15382
|
remediation: 'Add "bgp router-id <ip>" to explicitly set router ID.'
|
|
14725
15383
|
},
|
|
14726
15384
|
check: (node) => {
|
|
14727
|
-
if (!
|
|
15385
|
+
if (!hasBgpRouterId4(node)) {
|
|
14728
15386
|
return {
|
|
14729
15387
|
passed: false,
|
|
14730
15388
|
message: "BGP missing explicit router-id configuration.",
|
|
@@ -15250,11 +15908,11 @@ function getRulesByVendor(vendorId) {
|
|
|
15250
15908
|
// src/config.ts
|
|
15251
15909
|
import { existsSync as existsSync2 } from "fs";
|
|
15252
15910
|
import { readFile as readFileAsync } from "fs/promises";
|
|
15253
|
-
import { resolve as
|
|
15911
|
+
import { resolve as resolve4, dirname } from "path";
|
|
15254
15912
|
|
|
15255
15913
|
// src/security/pathValidator.ts
|
|
15256
15914
|
import { existsSync, statSync, realpathSync } from "fs";
|
|
15257
|
-
import { extname, resolve } from "path";
|
|
15915
|
+
import { extname, resolve as resolve2 } from "path";
|
|
15258
15916
|
function normalizeSeparators(p) {
|
|
15259
15917
|
return p.replace(/\\/g, "/");
|
|
15260
15918
|
}
|
|
@@ -15272,7 +15930,7 @@ function validatePath(inputPath, options = {}) {
|
|
|
15272
15930
|
error: "Network (UNC) paths are not allowed"
|
|
15273
15931
|
};
|
|
15274
15932
|
}
|
|
15275
|
-
const absolutePath =
|
|
15933
|
+
const absolutePath = resolve2(inputPath);
|
|
15276
15934
|
const ext = extname(absolutePath).toLowerCase();
|
|
15277
15935
|
if (allowedExtensions.length > 0 && !allowedExtensions.includes(ext)) {
|
|
15278
15936
|
return {
|
|
@@ -15315,7 +15973,7 @@ function validatePath(inputPath, options = {}) {
|
|
|
15315
15973
|
const normalizedCanonical = normalizeSeparators(canonicalPath);
|
|
15316
15974
|
const isWithinBounds2 = allowedBaseDirs.some((baseDir) => {
|
|
15317
15975
|
try {
|
|
15318
|
-
const canonicalBase = realpathSync(
|
|
15976
|
+
const canonicalBase = realpathSync(resolve2(baseDir));
|
|
15319
15977
|
const normalizedBase = normalizeSeparators(canonicalBase);
|
|
15320
15978
|
return normalizedCanonical === normalizedBase || normalizedCanonical.startsWith(normalizedBase + "/");
|
|
15321
15979
|
} catch {
|
|
@@ -15345,22 +16003,6 @@ function validateConfigPath(configPath, baseDirs) {
|
|
|
15345
16003
|
mustExist: true
|
|
15346
16004
|
});
|
|
15347
16005
|
}
|
|
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
16006
|
function validateJsonRulesPath(jsonPath, baseDirs) {
|
|
15365
16007
|
return validatePath(jsonPath, {
|
|
15366
16008
|
allowedBaseDirs: baseDirs,
|
|
@@ -15378,6 +16020,40 @@ function validateInputFilePath(filePath, maxSize = 10 * 1024 * 1024, baseDirs) {
|
|
|
15378
16020
|
mustExist: true
|
|
15379
16021
|
});
|
|
15380
16022
|
}
|
|
16023
|
+
function validatePackPath(packPath, baseDirs) {
|
|
16024
|
+
return validatePath(packPath, {
|
|
16025
|
+
allowedBaseDirs: baseDirs,
|
|
16026
|
+
allowedExtensions: [],
|
|
16027
|
+
// Allow any extension - format detected via magic bytes
|
|
16028
|
+
maxFileSize: MAX_ENCRYPTED_PACK_SIZE,
|
|
16029
|
+
mustExist: true
|
|
16030
|
+
});
|
|
16031
|
+
}
|
|
16032
|
+
|
|
16033
|
+
// src/loaders/pack-detector.ts
|
|
16034
|
+
import { resolve as resolve3 } from "node:path";
|
|
16035
|
+
async function createPackDescriptor(filePath, orderIndex) {
|
|
16036
|
+
const absolutePath = resolve3(filePath);
|
|
16037
|
+
const format = await detectPackFormat(absolutePath);
|
|
16038
|
+
const basePriority = FORMAT_PRIORITIES[format];
|
|
16039
|
+
return {
|
|
16040
|
+
path: absolutePath,
|
|
16041
|
+
format,
|
|
16042
|
+
basePriority,
|
|
16043
|
+
priority: basePriority + orderIndex
|
|
16044
|
+
};
|
|
16045
|
+
}
|
|
16046
|
+
async function createPackDescriptors(packPaths) {
|
|
16047
|
+
const descriptors = [];
|
|
16048
|
+
for (let i = 0; i < packPaths.length; i++) {
|
|
16049
|
+
const packPath = packPaths[i];
|
|
16050
|
+
if (packPath !== void 0) {
|
|
16051
|
+
const descriptor = await createPackDescriptor(packPath, i);
|
|
16052
|
+
descriptors.push(descriptor);
|
|
16053
|
+
}
|
|
16054
|
+
}
|
|
16055
|
+
return descriptors;
|
|
16056
|
+
}
|
|
15381
16057
|
|
|
15382
16058
|
// src/loaders/index.ts
|
|
15383
16059
|
function validatePathOrThrow(path, pathValidator, errorContext, baseDirs) {
|
|
@@ -15424,11 +16100,11 @@ var CONFIG_FILES = [
|
|
|
15424
16100
|
".sentriflowrc.js"
|
|
15425
16101
|
];
|
|
15426
16102
|
function findConfigFile(startDir) {
|
|
15427
|
-
let currentDir =
|
|
16103
|
+
let currentDir = resolve4(startDir);
|
|
15428
16104
|
let depth = 0;
|
|
15429
16105
|
while (depth < MAX_TRAVERSAL_DEPTH) {
|
|
15430
16106
|
for (const configFile of CONFIG_FILES) {
|
|
15431
|
-
const configPath =
|
|
16107
|
+
const configPath = resolve4(currentDir, configFile);
|
|
15432
16108
|
if (existsSync2(configPath)) {
|
|
15433
16109
|
const validation = validateConfigPath(configPath);
|
|
15434
16110
|
if (validation.valid) {
|
|
@@ -15595,93 +16271,6 @@ function mergeDirectoryOptions(cliOptions, configOptions) {
|
|
|
15595
16271
|
}
|
|
15596
16272
|
return result;
|
|
15597
16273
|
}
|
|
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
16274
|
async function loadConfigFile(configPath, baseDirs) {
|
|
15686
16275
|
return loadAndValidate({
|
|
15687
16276
|
path: configPath,
|
|
@@ -15755,13 +16344,6 @@ ${errors}`);
|
|
|
15755
16344
|
return compiledRules;
|
|
15756
16345
|
}, "JSON rules");
|
|
15757
16346
|
}
|
|
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
16347
|
function isDefaultRuleDisabled(ruleId, vendorId, packs, legacyDisableIds) {
|
|
15766
16348
|
if (legacyDisableIds.has(ruleId)) return true;
|
|
15767
16349
|
for (const pack of packs) {
|
|
@@ -15800,7 +16382,7 @@ function mapPackLoadError(error) {
|
|
|
15800
16382
|
async function loadEncryptedRulePack(packPath, licenseKey, baseDirs) {
|
|
15801
16383
|
const canonicalPath = validatePathOrThrow(
|
|
15802
16384
|
packPath,
|
|
15803
|
-
|
|
16385
|
+
validatePackPath,
|
|
15804
16386
|
"encrypted pack",
|
|
15805
16387
|
baseDirs
|
|
15806
16388
|
);
|
|
@@ -15829,13 +16411,9 @@ async function resolveRules(options = {}) {
|
|
|
15829
16411
|
configPath,
|
|
15830
16412
|
noConfig = false,
|
|
15831
16413
|
rulesPath,
|
|
15832
|
-
|
|
15833
|
-
encryptedPackPaths,
|
|
16414
|
+
packPaths,
|
|
15834
16415
|
licenseKey,
|
|
15835
16416
|
strictPacks = false,
|
|
15836
|
-
// SEC-012: Default to graceful handling
|
|
15837
|
-
grx2PackPaths,
|
|
15838
|
-
strictGrx2 = false,
|
|
15839
16417
|
// Default to graceful handling
|
|
15840
16418
|
jsonRulesPaths,
|
|
15841
16419
|
disableIds = [],
|
|
@@ -15844,9 +16422,8 @@ async function resolveRules(options = {}) {
|
|
|
15844
16422
|
allowedBaseDirs
|
|
15845
16423
|
// SEC-011: Allowed base directories for file path validation
|
|
15846
16424
|
} = options;
|
|
15847
|
-
const packPathsArray =
|
|
16425
|
+
const packPathsArray = packPaths ? Array.isArray(packPaths) ? packPaths : [packPaths] : [];
|
|
15848
16426
|
const jsonPathsArray = jsonRulesPaths ? Array.isArray(jsonRulesPaths) ? jsonRulesPaths : [jsonRulesPaths] : [];
|
|
15849
|
-
const grx2PathsArray = grx2PackPaths ? Array.isArray(grx2PackPaths) ? grx2PackPaths : [grx2PackPaths] : [];
|
|
15850
16427
|
let config = { includeDefaults: true };
|
|
15851
16428
|
if (!noConfig) {
|
|
15852
16429
|
const foundConfigPath = configPath ?? findConfigFile(cwd);
|
|
@@ -15879,10 +16456,6 @@ async function resolveRules(options = {}) {
|
|
|
15879
16456
|
});
|
|
15880
16457
|
}
|
|
15881
16458
|
}
|
|
15882
|
-
if (rulePackPath) {
|
|
15883
|
-
const cliPack = await loadRulePackFile(rulePackPath, allowedBaseDirs);
|
|
15884
|
-
allPacks.push(cliPack);
|
|
15885
|
-
}
|
|
15886
16459
|
if (config.jsonRules && config.jsonRules.length > 0) {
|
|
15887
16460
|
for (const jsonPath of config.jsonRules) {
|
|
15888
16461
|
try {
|
|
@@ -15927,118 +16500,128 @@ async function resolveRules(options = {}) {
|
|
|
15927
16500
|
}
|
|
15928
16501
|
}
|
|
15929
16502
|
if (packPathsArray.length > 0) {
|
|
15930
|
-
|
|
15931
|
-
|
|
16503
|
+
let packDescriptors;
|
|
16504
|
+
try {
|
|
16505
|
+
packDescriptors = await createPackDescriptors(packPathsArray);
|
|
16506
|
+
} catch (error) {
|
|
16507
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
15932
16508
|
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
|
-
}
|
|
16509
|
+
throw new SentriflowConfigError(`Pack detection failed: ${errorMsg}`);
|
|
15959
16510
|
}
|
|
16511
|
+
console.error(`Warning: Pack detection failed: ${errorMsg}`);
|
|
16512
|
+
packDescriptors = [];
|
|
15960
16513
|
}
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
16514
|
+
const hasEncryptedPacks = packDescriptors.some(
|
|
16515
|
+
(d) => d.format === "grx2" || d.format === "grpx"
|
|
16516
|
+
);
|
|
16517
|
+
if (hasEncryptedPacks && !licenseKey) {
|
|
16518
|
+
const errorMsg = "License key required for encrypted packs (use --license-key or set SENTRIFLOW_LICENSE_KEY)";
|
|
16519
|
+
if (strictPacks) {
|
|
15966
16520
|
throw new SentriflowConfigError(errorMsg);
|
|
15967
16521
|
}
|
|
15968
16522
|
console.error(`Warning: ${errorMsg}`);
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
let machineId2;
|
|
16523
|
+
}
|
|
16524
|
+
let machineId2;
|
|
16525
|
+
const hasGrx2Packs = packDescriptors.some((d) => d.format === "grx2");
|
|
16526
|
+
if (hasGrx2Packs && licenseKey) {
|
|
15974
16527
|
try {
|
|
15975
16528
|
machineId2 = await getMachineId();
|
|
15976
16529
|
} catch (error) {
|
|
15977
16530
|
const errorMsg = "Failed to retrieve machine ID for license validation";
|
|
15978
|
-
if (
|
|
16531
|
+
if (strictPacks) {
|
|
15979
16532
|
throw new SentriflowConfigError(errorMsg);
|
|
15980
16533
|
}
|
|
15981
16534
|
console.error(`Warning: ${errorMsg}`);
|
|
15982
|
-
console.error(
|
|
15983
|
-
`Warning: Skipping ${grx2PathsArray.length} GRX2 pack(s)`
|
|
15984
|
-
);
|
|
15985
|
-
machineId2 = "";
|
|
15986
16535
|
}
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
16536
|
+
}
|
|
16537
|
+
let loadedCount = 0;
|
|
16538
|
+
let totalRules = 0;
|
|
16539
|
+
const failedPacks = [];
|
|
16540
|
+
for (const desc of packDescriptors) {
|
|
16541
|
+
try {
|
|
16542
|
+
const validation = validatePackPath(desc.path, allowedBaseDirs);
|
|
16543
|
+
if (!validation.valid) {
|
|
16544
|
+
throw new SentriflowConfigError(validation.error ?? "Invalid pack path");
|
|
16545
|
+
}
|
|
16546
|
+
let loadedPack;
|
|
16547
|
+
switch (desc.format) {
|
|
16548
|
+
case "grx2": {
|
|
16549
|
+
if (!licenseKey) {
|
|
16550
|
+
console.error(`Warning: Skipping GRX2 pack (no license key): ${desc.path}`);
|
|
16551
|
+
continue;
|
|
16552
|
+
}
|
|
16553
|
+
if (!machineId2) {
|
|
16554
|
+
console.error(`Warning: Skipping GRX2 pack (no machine ID): ${desc.path}`);
|
|
16555
|
+
continue;
|
|
15998
16556
|
}
|
|
15999
|
-
|
|
16557
|
+
loadedPack = await loadExtendedPack(
|
|
16000
16558
|
validation.canonicalPath,
|
|
16001
16559
|
licenseKey,
|
|
16002
16560
|
machineId2
|
|
16003
16561
|
);
|
|
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
|
-
);
|
|
16562
|
+
loadedPack.priority = desc.priority;
|
|
16563
|
+
break;
|
|
16564
|
+
}
|
|
16565
|
+
case "grpx": {
|
|
16566
|
+
if (!licenseKey) {
|
|
16567
|
+
console.error(`Warning: Skipping GRPX pack (no license key): ${desc.path}`);
|
|
16568
|
+
continue;
|
|
16028
16569
|
}
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16570
|
+
loadedPack = await loadEncryptedRulePack(
|
|
16571
|
+
validation.canonicalPath,
|
|
16572
|
+
licenseKey,
|
|
16573
|
+
allowedBaseDirs
|
|
16574
|
+
);
|
|
16575
|
+
loadedPack.priority = desc.priority;
|
|
16576
|
+
break;
|
|
16032
16577
|
}
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16578
|
+
case "unencrypted": {
|
|
16579
|
+
loadedPack = await loadRulePackFile(
|
|
16580
|
+
validation.canonicalPath,
|
|
16581
|
+
allowedBaseDirs
|
|
16582
|
+
);
|
|
16583
|
+
loadedPack.priority = desc.priority;
|
|
16584
|
+
break;
|
|
16040
16585
|
}
|
|
16586
|
+
default:
|
|
16587
|
+
console.error(`Warning: Unknown pack format, skipping: ${desc.path}`);
|
|
16588
|
+
continue;
|
|
16041
16589
|
}
|
|
16590
|
+
allPacks.push(loadedPack);
|
|
16591
|
+
loadedCount++;
|
|
16592
|
+
totalRules += loadedPack.rules.length;
|
|
16593
|
+
} catch (error) {
|
|
16594
|
+
let errorMsg;
|
|
16595
|
+
if (error instanceof EncryptedPackError) {
|
|
16596
|
+
const messages = {
|
|
16597
|
+
LICENSE_MISSING: "Invalid or missing license key",
|
|
16598
|
+
LICENSE_EXPIRED: "License has expired",
|
|
16599
|
+
LICENSE_INVALID: "License key is invalid for this pack",
|
|
16600
|
+
DECRYPTION_FAILED: "Failed to decrypt pack (invalid key or corrupted data)",
|
|
16601
|
+
MACHINE_MISMATCH: "License is not valid for this machine",
|
|
16602
|
+
PACK_CORRUPTED: "Pack file is corrupted or invalid",
|
|
16603
|
+
NOT_EXTENDED_FORMAT: "Pack is not in extended GRX2 format"
|
|
16604
|
+
};
|
|
16605
|
+
errorMsg = messages[error.code] ?? `Pack load error: ${error.message}`;
|
|
16606
|
+
} else {
|
|
16607
|
+
errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
16608
|
+
}
|
|
16609
|
+
if (strictPacks) {
|
|
16610
|
+
throw new SentriflowConfigError(
|
|
16611
|
+
`Failed to load pack '${desc.path}': ${errorMsg}`
|
|
16612
|
+
);
|
|
16613
|
+
}
|
|
16614
|
+
console.error(`Warning: Failed to load pack: ${errorMsg}`);
|
|
16615
|
+
console.error(`Warning: Skipping pack: ${desc.path}`);
|
|
16616
|
+
failedPacks.push(desc.path);
|
|
16617
|
+
}
|
|
16618
|
+
}
|
|
16619
|
+
if (packPathsArray.length > 0) {
|
|
16620
|
+
const successMsg = `Packs: ${loadedCount} of ${packPathsArray.length} loaded (${totalRules} rules)`;
|
|
16621
|
+
if (failedPacks.length > 0) {
|
|
16622
|
+
console.error(`${successMsg}, ${failedPacks.length} failed`);
|
|
16623
|
+
} else if (loadedCount > 0) {
|
|
16624
|
+
console.error(successMsg);
|
|
16042
16625
|
}
|
|
16043
16626
|
}
|
|
16044
16627
|
}
|
|
@@ -16072,7 +16655,7 @@ async function resolveRules(options = {}) {
|
|
|
16072
16655
|
|
|
16073
16656
|
// src/scanner/DirectoryScanner.ts
|
|
16074
16657
|
import { readdir as readdir2, stat } from "fs/promises";
|
|
16075
|
-
import { join as join2, resolve as
|
|
16658
|
+
import { join as join2, resolve as resolve5, extname as extname2 } from "path";
|
|
16076
16659
|
import { realpathSync as realpathSync2, existsSync as existsSync3 } from "fs";
|
|
16077
16660
|
var DEFAULT_CONFIG_EXTENSIONS = [
|
|
16078
16661
|
"txt",
|
|
@@ -16101,7 +16684,7 @@ function isWithinBounds(filePath, allowedBaseDirs) {
|
|
|
16101
16684
|
const normalizedPath = normalizeSeparators2(filePath);
|
|
16102
16685
|
return allowedBaseDirs.some((baseDir) => {
|
|
16103
16686
|
try {
|
|
16104
|
-
const canonicalBase = realpathSync2(
|
|
16687
|
+
const canonicalBase = realpathSync2(resolve5(baseDir));
|
|
16105
16688
|
const normalizedBase = normalizeSeparators2(canonicalBase);
|
|
16106
16689
|
return normalizedPath === normalizedBase || normalizedPath.startsWith(normalizedBase + "/");
|
|
16107
16690
|
} catch (error) {
|
|
@@ -16157,7 +16740,7 @@ async function scanDirectory(dirPath, options = {}) {
|
|
|
16157
16740
|
};
|
|
16158
16741
|
let canonicalDir;
|
|
16159
16742
|
try {
|
|
16160
|
-
canonicalDir = realpathSync2(
|
|
16743
|
+
canonicalDir = realpathSync2(resolve5(dirPath));
|
|
16161
16744
|
} catch {
|
|
16162
16745
|
result.errors.push({
|
|
16163
16746
|
path: dirPath,
|
|
@@ -16257,7 +16840,7 @@ function validateDirectoryPath(dirPath, allowedBaseDirs) {
|
|
|
16257
16840
|
error: "Network (UNC) paths are not allowed"
|
|
16258
16841
|
};
|
|
16259
16842
|
}
|
|
16260
|
-
const absolutePath =
|
|
16843
|
+
const absolutePath = resolve5(dirPath);
|
|
16261
16844
|
if (!existsSync3(absolutePath)) {
|
|
16262
16845
|
return { valid: false, error: "Directory not found" };
|
|
16263
16846
|
}
|
|
@@ -16286,14 +16869,14 @@ function validateDirectoryPath(dirPath, allowedBaseDirs) {
|
|
|
16286
16869
|
|
|
16287
16870
|
// src/loaders/stdin.ts
|
|
16288
16871
|
async function readStdin() {
|
|
16289
|
-
return new Promise((
|
|
16872
|
+
return new Promise((resolve7) => {
|
|
16290
16873
|
const stdin = process.stdin;
|
|
16291
16874
|
const chunks = [];
|
|
16292
16875
|
let totalSize = 0;
|
|
16293
16876
|
let sizeLimitExceeded = false;
|
|
16294
16877
|
stdin.setEncoding("utf8");
|
|
16295
16878
|
if (stdin.isTTY) {
|
|
16296
|
-
|
|
16879
|
+
resolve7({
|
|
16297
16880
|
success: false,
|
|
16298
16881
|
error: "No input received from stdin"
|
|
16299
16882
|
});
|
|
@@ -16305,7 +16888,7 @@ async function readStdin() {
|
|
|
16305
16888
|
totalSize += buffer.length;
|
|
16306
16889
|
if (totalSize > MAX_CONFIG_SIZE) {
|
|
16307
16890
|
sizeLimitExceeded = true;
|
|
16308
|
-
|
|
16891
|
+
resolve7({
|
|
16309
16892
|
success: false,
|
|
16310
16893
|
error: `Input exceeds maximum size (${totalSize} > ${MAX_CONFIG_SIZE} bytes)`
|
|
16311
16894
|
});
|
|
@@ -16318,19 +16901,19 @@ async function readStdin() {
|
|
|
16318
16901
|
if (sizeLimitExceeded) return;
|
|
16319
16902
|
const content = Buffer.concat(chunks).toString("utf8");
|
|
16320
16903
|
if (content.length === 0) {
|
|
16321
|
-
|
|
16904
|
+
resolve7({
|
|
16322
16905
|
success: false,
|
|
16323
16906
|
error: "No input received from stdin"
|
|
16324
16907
|
});
|
|
16325
16908
|
return;
|
|
16326
16909
|
}
|
|
16327
|
-
|
|
16910
|
+
resolve7({
|
|
16328
16911
|
success: true,
|
|
16329
16912
|
content
|
|
16330
16913
|
});
|
|
16331
16914
|
});
|
|
16332
16915
|
stdin.on("error", (err) => {
|
|
16333
|
-
|
|
16916
|
+
resolve7({
|
|
16334
16917
|
success: false,
|
|
16335
16918
|
error: `Failed to read from stdin: ${err.message}`
|
|
16336
16919
|
});
|
|
@@ -16338,7 +16921,7 @@ async function readStdin() {
|
|
|
16338
16921
|
const timeout = setTimeout(() => {
|
|
16339
16922
|
if (chunks.length === 0 && !sizeLimitExceeded) {
|
|
16340
16923
|
stdin.destroy();
|
|
16341
|
-
|
|
16924
|
+
resolve7({
|
|
16342
16925
|
success: false,
|
|
16343
16926
|
error: "No input received from stdin (timeout)"
|
|
16344
16927
|
});
|
|
@@ -16384,21 +16967,15 @@ function enrichResultsWithRuleMetadata(results, rules) {
|
|
|
16384
16967
|
});
|
|
16385
16968
|
}
|
|
16386
16969
|
var program = new Command();
|
|
16387
|
-
program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.
|
|
16388
|
-
"--
|
|
16389
|
-
"
|
|
16970
|
+
program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.3.0").argument("[files...]", "Path(s) to configuration file(s) (supports multiple files)").option("--ast", "Output the AST instead of rule results").option("-f, --format <format>", "Output format (json, sarif)", "json").option("-q, --quiet", "Only output failures (suppress passed results)").option("-c, --config <path>", "Path to config file (default: auto-detect)").option("--no-config", "Ignore config file").option("-r, --rules <path>", "Additional rules file to load (legacy)").option(
|
|
16971
|
+
"--pack <path...>",
|
|
16972
|
+
"Path(s) to rule pack(s) (auto-detects format: .grx2, .grpx, or unencrypted)"
|
|
16390
16973
|
).option(
|
|
16391
16974
|
"--license-key <key>",
|
|
16392
|
-
"
|
|
16975
|
+
"License key for encrypted rule packs (or set SENTRIFLOW_LICENSE_KEY)"
|
|
16393
16976
|
).option(
|
|
16394
16977
|
"--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"
|
|
16978
|
+
"Fail immediately if any pack cannot be loaded (default: warn and continue)"
|
|
16402
16979
|
).option(
|
|
16403
16980
|
"--show-machine-id",
|
|
16404
16981
|
"Display the current machine ID (for license binding support)"
|
|
@@ -16502,23 +17079,18 @@ Use: sentriflow --vendor <vendor> <file>`);
|
|
|
16502
17079
|
}
|
|
16503
17080
|
const licenseKey = options.licenseKey || process.env.SENTRIFLOW_LICENSE_KEY;
|
|
16504
17081
|
const firstFile = files.length > 0 ? files[0] : void 0;
|
|
16505
|
-
const configSearchDir = firstFile ? dirname2(
|
|
17082
|
+
const configSearchDir = firstFile ? dirname2(resolve6(firstFile)) : workingDir;
|
|
16506
17083
|
const rules = await resolveRules({
|
|
16507
17084
|
configPath: options.config,
|
|
16508
17085
|
noConfig: options.config === false,
|
|
16509
17086
|
// --no-config sets this to false
|
|
16510
17087
|
rulesPath: options.rules,
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
// SEC-012: Now supports array
|
|
17088
|
+
packPaths: options.pack,
|
|
17089
|
+
// Unified pack loading with auto-format detection
|
|
16514
17090
|
licenseKey,
|
|
16515
|
-
//
|
|
17091
|
+
// From CLI or SENTRIFLOW_LICENSE_KEY env var
|
|
16516
17092
|
strictPacks: options.strictPacks,
|
|
16517
|
-
//
|
|
16518
|
-
grx2PackPaths: options.grx2Pack,
|
|
16519
|
-
// Extended GRX2 packs
|
|
16520
|
-
strictGrx2: options.strictGrx2,
|
|
16521
|
-
// Fail on GRX2 pack load errors
|
|
17093
|
+
// Fail on pack load errors
|
|
16522
17094
|
jsonRulesPaths: options.jsonRules,
|
|
16523
17095
|
// JSON rules files
|
|
16524
17096
|
disableIds: options.disable ?? [],
|
|
@@ -16855,12 +17427,9 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
16855
17427
|
configPath: options.config,
|
|
16856
17428
|
noConfig: options.config === false,
|
|
16857
17429
|
rulesPath: options.rules,
|
|
16858
|
-
|
|
16859
|
-
encryptedPackPaths: options.encryptedPack,
|
|
17430
|
+
packPaths: options.pack,
|
|
16860
17431
|
licenseKey,
|
|
16861
17432
|
strictPacks: options.strictPacks,
|
|
16862
|
-
grx2PackPaths: options.grx2Pack,
|
|
16863
|
-
strictGrx2: options.strictGrx2,
|
|
16864
17433
|
jsonRulesPaths: options.jsonRules,
|
|
16865
17434
|
disableIds: options.disable ?? [],
|
|
16866
17435
|
vendorId: vendor2.id,
|
|
@@ -17050,14 +17619,10 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
17050
17619
|
configPath: options.config,
|
|
17051
17620
|
noConfig: options.config === false,
|
|
17052
17621
|
rulesPath: options.rules,
|
|
17053
|
-
|
|
17054
|
-
encryptedPackPaths: options.encryptedPack,
|
|
17622
|
+
packPaths: options.pack,
|
|
17055
17623
|
licenseKey,
|
|
17056
17624
|
strictPacks: options.strictPacks,
|
|
17057
|
-
grx2PackPaths: options.grx2Pack,
|
|
17058
|
-
strictGrx2: options.strictGrx2,
|
|
17059
17625
|
jsonRulesPaths: options.jsonRules,
|
|
17060
|
-
// JSON rules files
|
|
17061
17626
|
disableIds: options.disable ?? [],
|
|
17062
17627
|
vendorId: vendor.id,
|
|
17063
17628
|
// Now we have the actual detected vendor
|