@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.
Files changed (2) hide show
  1. package/dist/index.js +926 -361
  2. 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 isEnabled5 = (stanza) => {
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: isEnabled5(tcpSyn),
10022
- hasUdp: isEnabled5(udp),
10023
- hasIcmp: isEnabled5(icmp),
10024
- hasOtherIp: isEnabled5(otherIp)
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 resolve4, dirname as dirname2, basename } from "path";
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.2.1",
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.2.1",
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 hasDescription5 = node.children.some(
12705
+ const hasDescription9 = node.children.some(
12533
12706
  (child) => startsWithIgnoreCase(child.id, "description")
12534
12707
  );
12535
- if (!hasDescription5) {
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 (!isPhysicalPort(node.id) || isShutdown3(node)) {
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 (!isTrunkPort2(node)) {
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 (!isTrunkToNonCisco(node)) {
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 (!hasChildCommand(node, "switchport nonegotiate")) {
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 (!isPhysicalPort(node.id) || isShutdown3(node)) {
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 = hasChildCommand(node, "switchport access vlan");
12611
- const hasExplicitMode = hasChildCommand(node, "switchport mode");
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 = findStanza4(node, "root-authentication");
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 = hasChildCommand(rootAuth, "encrypted-password");
12774
- const hasSshKey = hasChildCommand(rootAuth, "ssh-rsa") || hasChildCommand(rootAuth, "ssh-ecdsa");
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 = hasChildCommand(node, "router-id");
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 = findStanzas3(filter, /^term/i);
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 (!isFilterTermDrop(lastTerm)) {
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 = getInterfaceName(node);
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 (!isAosCxPhysicalPort(ifName) && !isAosCxLag(ifName)) {
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 (isShutdown(node)) {
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 (!hasDescription(node)) {
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 = getInterfaceName(node);
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 (!isAosCxPhysicalPort(ifName) && !isAosCxLag(ifName)) {
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 (!isAosCxTrunk(node)) {
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 = getAosCxTrunkAllowed(node);
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 = getAosSwitchVlanName(node);
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 = extractProfileName(node.id);
13163
- const opmode = getWlanEncryption(node);
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 (isOpenSsid(node)) {
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 (!hasSecureEncryption(node)) {
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 = extractProfileName(node.id);
13217
- const host = getRadiusHost(node);
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 = findStanza6(node, "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 = hasChildCommand(system, "hostname");
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 = getSecurityRules(node);
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 (isRuleDisabled(rule)) continue;
13336
- const logging = hasLogging2(rule);
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 (!hasZoneProtectionProfile(zone)) {
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 = checkMlagRequirements(node);
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 (isShutdown2(node)) {
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 = getInterfaceDescription(node);
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 = hasChildCommand(node, "host-name");
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 = getEthernetInterfaces(node);
14052
+ const ethernetInterfaces = getEthernetInterfaces2(node);
13645
14053
  for (const iface of ethernetInterfaces) {
13646
- if (isDisabled2(iface)) {
14054
+ if (isDisabled4(iface)) {
13647
14055
  continue;
13648
14056
  }
13649
- const hasDesc = hasChildCommand(iface, "description");
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 = findStanzasByPrefix2(node, "name");
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 = getFirewallDefaultAction(ruleset);
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
- // ../rules-default/src/fortinet/fortigate-rules.ts
13743
- var HostnameRequired3 = {
13744
- id: "FGT-SYS-001",
13745
- selector: "config system global",
13746
- vendor: "fortinet-fortigate",
13747
- category: "Documentation",
13748
- metadata: {
13749
- level: "warning",
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 = getSetValue(node, "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 = getEditEntries(node);
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 = getEditEntryName(admin);
13802
- if (hasAdminTrustedHosts(admin)) {
14268
+ const adminName = getEditEntryName2(admin);
14269
+ if (hasAdminTrustedHosts2(admin)) {
13803
14270
  continue;
13804
14271
  }
13805
- if (isSuperAdmin(admin)) {
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 = getEditEntries(node);
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 (isPolicyDisabled(policy)) continue;
13857
- const logging = hasLogging(policy);
14323
+ if (isPolicyDisabled2(policy)) continue;
14324
+ const logging = hasLogging4(policy);
13858
14325
  if (!logging.logtraffic || isFeatureDisabled(logging.logtraffic)) {
13859
- const policyId = getEditEntryName(policy);
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 = getExosVlanName(node);
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 (!isVossVlanCreate(node)) {
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 = getVossVlanId(node);
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 (!isVossGigabitEthernet(node)) {
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 (isVossShutdown(node)) {
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 = getVossDefaultVlan(node);
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 (!isPhysicalPort2(interfaceName)) {
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 (!isEnabled(node)) {
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 (!hasDescription3(node)) {
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 = getSystemIdentity(node);
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) && !isServiceDisabled(child)) {
14395
- const disabled = parseProperty(child.id, "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 = getAddCommands(node);
15004
+ const addCommands = getAddCommands2(node);
14435
15005
  let hasInputDrop = false;
14436
15006
  for (const cmd of addCommands) {
14437
- const chain = getFirewallChain(cmd);
14438
- const action = getFirewallAction(cmd);
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 = getSystemName(node);
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 (!isPhysicalPort3(portName)) {
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 (isAdminStateDisabled(node)) {
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 (!hasDescription4(node)) {
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: ${getDescription3(node)}`,
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 (isAdminStateDisabled(node)) {
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 (!hasBgpRouterId2(node)) {
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: ${getBgpRouterId(node)}`,
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 = getInterfaceName2(node);
14632
- if (!isSwitchPort(ifaceName)) {
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 (!hasDescription2(node)) {
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 = getInterfaceName2(node);
14675
- if (!isBridgeInterface(ifaceName)) {
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 (!isVlanAwareBridge(node)) {
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 (!hasBridgeVids(node)) {
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 (!hasBgpRouterId(node)) {
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 resolve2, dirname } from "path";
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 = resolve(inputPath);
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(resolve(baseDir));
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 = resolve2(startDir);
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 = resolve2(currentDir, configFile);
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
- validateEncryptedPackPath,
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
- rulePackPath,
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 = encryptedPackPaths ? Array.isArray(encryptedPackPaths) ? encryptedPackPaths : [encryptedPackPaths] : [];
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
- if (!licenseKey) {
15931
- const errorMsg = "License key required for encrypted packs (use --license-key or set SENTRIFLOW_LICENSE_KEY)";
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
- if (grx2PathsArray.length > 0) {
15963
- if (!licenseKey) {
15964
- const errorMsg = "License key required for GRX2 packs (use --license-key or set SENTRIFLOW_LICENSE_KEY)";
15965
- if (strictGrx2) {
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
- console.error(
15970
- `Warning: Skipping ${grx2PathsArray.length} GRX2 pack(s)`
15971
- );
15972
- } else {
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 (strictGrx2) {
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
- if (machineId2) {
15988
- let loadedCount = 0;
15989
- let totalRules = 0;
15990
- const failedPacks = [];
15991
- for (let i = 0; i < grx2PathsArray.length; i++) {
15992
- const packPath = grx2PathsArray[i];
15993
- if (!packPath) continue;
15994
- try {
15995
- const validation = validateGrx2PackPath(packPath, allowedBaseDirs);
15996
- if (!validation.valid) {
15997
- throw new SentriflowConfigError(validation.error ?? "Invalid pack path");
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
- const grx2Pack = await loadExtendedPack(
16557
+ loadedPack = await loadExtendedPack(
16000
16558
  validation.canonicalPath,
16001
16559
  licenseKey,
16002
16560
  machineId2
16003
16561
  );
16004
- grx2Pack.priority = 300 + i;
16005
- allPacks.push(grx2Pack);
16006
- loadedCount++;
16007
- totalRules += grx2Pack.rules.length;
16008
- } catch (error) {
16009
- let errorMsg;
16010
- if (error instanceof EncryptedPackError) {
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
- console.error(`Warning: Failed to load GRX2 pack: ${errorMsg}`);
16030
- console.error(`Warning: Skipping GRX2 pack: ${packPath}`);
16031
- failedPacks.push(packPath);
16570
+ loadedPack = await loadEncryptedRulePack(
16571
+ validation.canonicalPath,
16572
+ licenseKey,
16573
+ allowedBaseDirs
16574
+ );
16575
+ loadedPack.priority = desc.priority;
16576
+ break;
16032
16577
  }
16033
- }
16034
- if (grx2PathsArray.length > 0) {
16035
- const successMsg = `GRX2 packs: ${loadedCount} of ${grx2PathsArray.length} loaded (${totalRules} rules)`;
16036
- if (failedPacks.length > 0) {
16037
- console.error(`${successMsg}, ${failedPacks.length} failed`);
16038
- } else {
16039
- console.error(successMsg);
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 resolve3, extname as extname2 } from "path";
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(resolve3(baseDir));
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(resolve3(dirPath));
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 = resolve3(dirPath);
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((resolve5) => {
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
- resolve5({
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
- resolve5({
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
- resolve5({
16904
+ resolve7({
16322
16905
  success: false,
16323
16906
  error: "No input received from stdin"
16324
16907
  });
16325
16908
  return;
16326
16909
  }
16327
- resolve5({
16910
+ resolve7({
16328
16911
  success: true,
16329
16912
  content
16330
16913
  });
16331
16914
  });
16332
16915
  stdin.on("error", (err) => {
16333
- resolve5({
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
- resolve5({
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.2.1").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("-p, --rule-pack <path>", "Rule pack file to load").option(
16388
- "--encrypted-pack <path...>",
16389
- "SEC-012: Path(s) to encrypted rule pack(s) (.grpx), can specify multiple"
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
- "SEC-012: License key for encrypted rule packs (or set SENTRIFLOW_LICENSE_KEY)"
16975
+ "License key for encrypted rule packs (or set SENTRIFLOW_LICENSE_KEY)"
16393
16976
  ).option(
16394
16977
  "--strict-packs",
16395
- "Fail if encrypted pack cannot be loaded (default: warn and continue)"
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(resolve4(firstFile)) : workingDir;
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
- rulePackPath: options.rulePack,
16512
- encryptedPackPaths: options.encryptedPack,
16513
- // SEC-012: Now supports array
17088
+ packPaths: options.pack,
17089
+ // Unified pack loading with auto-format detection
16514
17090
  licenseKey,
16515
- // SEC-012: From CLI or SENTRIFLOW_LICENSE_KEY env var
17091
+ // From CLI or SENTRIFLOW_LICENSE_KEY env var
16516
17092
  strictPacks: options.strictPacks,
16517
- // SEC-012: Fail on pack load errors
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
- rulePackPath: options.rulePack,
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
- rulePackPath: options.rulePack,
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