@sentriflow/cli 0.3.0 → 0.4.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 (3) hide show
  1. package/README.md +35 -0
  2. package/dist/index.js +412 -608
  3. package/package.json +54 -54
package/dist/index.js CHANGED
@@ -4738,6 +4738,7 @@ __export(helpers_exports, {
4738
4738
  cumulus: () => cumulus_exports,
4739
4739
  equalsIgnoreCase: () => equalsIgnoreCase,
4740
4740
  extreme: () => extreme_exports,
4741
+ findParentSection: () => findParentSection,
4741
4742
  fortinet: () => fortinet_exports,
4742
4743
  getAllVendorModules: () => getAllVendorModules,
4743
4744
  getChildCommand: () => getChildCommand,
@@ -4953,6 +4954,20 @@ var isShutdown = (node) => {
4953
4954
  return id === "shutdown" || id === "disable";
4954
4955
  });
4955
4956
  };
4957
+ var findParentSection = (ast, targetNode) => {
4958
+ for (const node of ast) {
4959
+ if (node.type === "section") {
4960
+ if (node.children.some(
4961
+ (child) => child.loc.startLine === targetNode.loc.startLine
4962
+ )) {
4963
+ return node;
4964
+ }
4965
+ const found = findParentSection(node.children, targetNode);
4966
+ if (found) return found;
4967
+ }
4968
+ }
4969
+ return void 0;
4970
+ };
4956
4971
  var isInterfaceDefinition = (node) => {
4957
4972
  if (!node?.id) return false;
4958
4973
  const id = node.id.toLowerCase();
@@ -6109,6 +6124,7 @@ var getDescription = (node) => {
6109
6124
  // ../core/src/helpers/cisco/index.ts
6110
6125
  var cisco_exports = {};
6111
6126
  __export(cisco_exports, {
6127
+ findParentSection: () => findParentSection,
6112
6128
  getBgpNeighbors: () => getBgpNeighbors,
6113
6129
  getChildCommand: () => getChildCommand,
6114
6130
  getChildCommands: () => getChildCommands,
@@ -6143,6 +6159,7 @@ __export(cisco_exports, {
6143
6159
  isEndpointPort: () => isEndpointPort,
6144
6160
  isExternalFacing: () => isExternalFacing,
6145
6161
  isLikelyTrunk: () => isLikelyTrunk,
6162
+ isLineConfigPassword: () => isLineConfigPassword,
6146
6163
  isLoopbackInterface: () => isLoopbackInterface,
6147
6164
  isPhoneOrAP: () => isPhoneOrAP,
6148
6165
  isPhysicalPort: () => isPhysicalPort,
@@ -6240,6 +6257,12 @@ var hasWeakUsernamePassword = (node) => {
6240
6257
  }
6241
6258
  return false;
6242
6259
  };
6260
+ var isLineConfigPassword = (ast, node) => {
6261
+ const parent = findParentSection(ast, node);
6262
+ if (!parent) return false;
6263
+ const parentId = parent.id.toLowerCase();
6264
+ return parentId.startsWith("line vty") || parentId.startsWith("line console") || parentId.startsWith("line aux");
6265
+ };
6243
6266
  var getSshVersion = (node) => {
6244
6267
  if (includesIgnoreCase(node.rawText, "ip ssh version")) {
6245
6268
  const match = node.params.find((p) => p === "1" || p === "2");
@@ -10010,16 +10033,16 @@ var hasFloodProtection = (zppNode) => {
10010
10033
  const udp = findStanza6(flood, "udp");
10011
10034
  const icmp = findStanza6(flood, "icmp");
10012
10035
  const otherIp = findStanza6(flood, "other-ip");
10013
- const isEnabled6 = (stanza) => {
10036
+ const isEnabled5 = (stanza) => {
10014
10037
  if (!stanza) return false;
10015
10038
  const enableCmd = getChildCommand(stanza, "enable");
10016
10039
  return enableCmd?.id.toLowerCase().includes("yes") ?? false;
10017
10040
  };
10018
10041
  return {
10019
- hasSyn: isEnabled6(tcpSyn),
10020
- hasUdp: isEnabled6(udp),
10021
- hasIcmp: isEnabled6(icmp),
10022
- hasOtherIp: isEnabled6(otherIp)
10042
+ hasSyn: isEnabled5(tcpSyn),
10043
+ hasUdp: isEnabled5(udp),
10044
+ hasIcmp: isEnabled5(icmp),
10045
+ hasOtherIp: isEnabled5(otherIp)
10023
10046
  };
10024
10047
  };
10025
10048
  var hasReconProtection = (zppNode) => {
@@ -10702,8 +10725,10 @@ var GRX2_EXTENDED_FLAG = 1;
10702
10725
  var GRX2_PORTABLE_FLAG = 2;
10703
10726
  var GRX2_ALGORITHM_AES_256_GCM = 1;
10704
10727
  var GRX2_KDF_PBKDF2 = 1;
10705
- var DEFAULT_PACKS_DIRECTORY = join(homedir(), ".sentriflow", "packs");
10706
- var CACHE_DIRECTORY = join(homedir(), ".sentriflow", "cache");
10728
+ var SENTRIFLOW_HOME = join(homedir(), ".sentriflow");
10729
+ var DEFAULT_PACKS_DIRECTORY = join(SENTRIFLOW_HOME, "packs");
10730
+ var DEFAULT_RULES_DIRECTORY = join(SENTRIFLOW_HOME, "rules");
10731
+ var CACHE_DIRECTORY = join(SENTRIFLOW_HOME, "cache");
10707
10732
 
10708
10733
  // ../core/src/grx2-loader/MachineId.ts
10709
10734
  var import_node_machine_id = __toESM(require_dist(), 1);
@@ -10865,7 +10890,6 @@ function parseExtendedHeader(data) {
10865
10890
  }
10866
10891
  async function loadExtendedPack(filePath, licenseKey, machineId2, debug) {
10867
10892
  debug?.(`[GRX2Loader] Loading pack: ${filePath}`);
10868
- debug?.(`[GRX2Loader] License key length: ${licenseKey.length}, first 20 chars: ${licenseKey.substring(0, 20)}...`);
10869
10893
  debug?.(`[GRX2Loader] Machine ID: "${machineId2}" (length: ${machineId2.length})`);
10870
10894
  const data = await readFile(filePath);
10871
10895
  debug?.(`[GRX2Loader] Pack file size: ${data.length} bytes`);
@@ -12129,6 +12153,178 @@ function extractIPSummary(content, options = {}) {
12129
12153
  };
12130
12154
  }
12131
12155
 
12156
+ // ../core/src/ip/classifier.ts
12157
+ var DEFAULT_FILTER_OPTIONS = {
12158
+ keepPublic: true,
12159
+ keepPrivate: true,
12160
+ keepLoopback: false,
12161
+ keepLinkLocal: false,
12162
+ keepMulticast: false,
12163
+ keepReserved: false,
12164
+ keepUnspecified: false,
12165
+ keepBroadcast: false,
12166
+ keepDocumentation: false,
12167
+ keepCgnat: true
12168
+ };
12169
+ function ipv4ToNumber2(ip) {
12170
+ const parts = ip.split(".");
12171
+ if (parts.length !== 4) return 0;
12172
+ let result = 0;
12173
+ for (let i = 0; i < 4; i++) {
12174
+ const octet = parseInt(parts[i] ?? "0", 10);
12175
+ if (isNaN(octet) || octet < 0 || octet > 255) return 0;
12176
+ result = (result << 8) + octet;
12177
+ }
12178
+ return result >>> 0;
12179
+ }
12180
+ function isInIPv4Range(ip, network, prefix) {
12181
+ const ipNum = ipv4ToNumber2(ip);
12182
+ const netNum = ipv4ToNumber2(network);
12183
+ const mask = prefix === 0 ? 0 : ~0 << 32 - prefix >>> 0;
12184
+ return (ipNum & mask) === (netNum & mask);
12185
+ }
12186
+ function classifyIPv4(ip) {
12187
+ if (ip === "0.0.0.0") return "unspecified";
12188
+ if (ip === "255.255.255.255") return "broadcast";
12189
+ if (isInIPv4Range(ip, "0.0.0.0", 8)) return "unspecified";
12190
+ if (isInIPv4Range(ip, "127.0.0.0", 8)) return "loopback";
12191
+ if (isInIPv4Range(ip, "169.254.0.0", 16)) return "link-local";
12192
+ if (isInIPv4Range(ip, "10.0.0.0", 8)) return "private";
12193
+ if (isInIPv4Range(ip, "172.16.0.0", 12)) return "private";
12194
+ if (isInIPv4Range(ip, "192.168.0.0", 16)) return "private";
12195
+ if (isInIPv4Range(ip, "100.64.0.0", 10)) return "cgnat";
12196
+ if (isInIPv4Range(ip, "192.0.2.0", 24)) return "documentation";
12197
+ if (isInIPv4Range(ip, "198.51.100.0", 24)) return "documentation";
12198
+ if (isInIPv4Range(ip, "203.0.113.0", 24)) return "documentation";
12199
+ if (isInIPv4Range(ip, "224.0.0.0", 4)) return "multicast";
12200
+ if (isInIPv4Range(ip, "240.0.0.0", 4)) return "reserved";
12201
+ return "public";
12202
+ }
12203
+ function classifyIPv4Subnet(subnet) {
12204
+ const slashIndex = subnet.lastIndexOf("/");
12205
+ if (slashIndex === -1) return classifyIPv4(subnet);
12206
+ const network = subnet.substring(0, slashIndex);
12207
+ return classifyIPv4(network);
12208
+ }
12209
+ function expandIPv62(ip) {
12210
+ const zoneIndex = ip.indexOf("%");
12211
+ const addr = zoneIndex !== -1 ? ip.substring(0, zoneIndex) : ip;
12212
+ const parts = addr.split(":");
12213
+ const result = [];
12214
+ for (let i = 0; i < parts.length; i++) {
12215
+ const part = parts[i] ?? "";
12216
+ if (part === "" && i > 0 && i < parts.length - 1) {
12217
+ const nonEmpty = parts.filter((p) => p !== "").length;
12218
+ const zeros = 8 - nonEmpty;
12219
+ for (let j = 0; j < zeros; j++) {
12220
+ result.push(0);
12221
+ }
12222
+ } else if (part !== "") {
12223
+ result.push(parseInt(part, 16) || 0);
12224
+ } else if (i === 0 && (parts[1] ?? "") === "") {
12225
+ const nonEmpty = parts.filter((p) => p !== "").length;
12226
+ const zeros = 8 - nonEmpty;
12227
+ for (let j = 0; j < zeros; j++) {
12228
+ result.push(0);
12229
+ }
12230
+ }
12231
+ }
12232
+ while (result.length < 8) {
12233
+ result.push(0);
12234
+ }
12235
+ return result.slice(0, 8);
12236
+ }
12237
+ function classifyIPv6(ip) {
12238
+ const parts = expandIPv62(ip);
12239
+ if (parts.every((p) => p === 0)) return "unspecified";
12240
+ if (parts.slice(0, 7).every((p) => p === 0) && parts[7] === 1) return "loopback";
12241
+ if ((parts[0] ?? 0) >= 65152 && (parts[0] ?? 0) <= 65215) return "link-local";
12242
+ if (((parts[0] ?? 0) & 65280) === 65280) return "multicast";
12243
+ if (parts[0] === 8193 && parts[1] === 3512) return "documentation";
12244
+ if (((parts[0] ?? 0) & 65024) === 64512) return "private";
12245
+ return "public";
12246
+ }
12247
+ function classifyIPv6Subnet(subnet) {
12248
+ const slashIndex = subnet.lastIndexOf("/");
12249
+ if (slashIndex === -1) return classifyIPv6(subnet);
12250
+ const network = subnet.substring(0, slashIndex);
12251
+ return classifyIPv6(network);
12252
+ }
12253
+ function shouldKeepClassification(classification, options) {
12254
+ switch (classification) {
12255
+ case "public":
12256
+ return options.keepPublic;
12257
+ case "private":
12258
+ return options.keepPrivate;
12259
+ case "loopback":
12260
+ return options.keepLoopback;
12261
+ case "link-local":
12262
+ return options.keepLinkLocal;
12263
+ case "multicast":
12264
+ return options.keepMulticast;
12265
+ case "reserved":
12266
+ return options.keepReserved;
12267
+ case "unspecified":
12268
+ return options.keepUnspecified;
12269
+ case "broadcast":
12270
+ return options.keepBroadcast;
12271
+ case "documentation":
12272
+ return options.keepDocumentation;
12273
+ case "cgnat":
12274
+ return options.keepCgnat;
12275
+ default:
12276
+ return true;
12277
+ }
12278
+ }
12279
+ function filterIPv4Addresses(addresses, options = {}) {
12280
+ const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
12281
+ return addresses.filter((ip) => {
12282
+ const classification = classifyIPv4(ip);
12283
+ return shouldKeepClassification(classification, opts);
12284
+ });
12285
+ }
12286
+ function filterIPv6Addresses(addresses, options = {}) {
12287
+ const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
12288
+ return addresses.filter((ip) => {
12289
+ const classification = classifyIPv6(ip);
12290
+ return shouldKeepClassification(classification, opts);
12291
+ });
12292
+ }
12293
+ function filterIPv4Subnets(subnets, options = {}) {
12294
+ const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
12295
+ return subnets.filter((subnet) => {
12296
+ const classification = classifyIPv4Subnet(subnet);
12297
+ return shouldKeepClassification(classification, opts);
12298
+ });
12299
+ }
12300
+ function filterIPv6Subnets(subnets, options = {}) {
12301
+ const opts = { ...DEFAULT_FILTER_OPTIONS, ...options };
12302
+ return subnets.filter((subnet) => {
12303
+ const classification = classifyIPv6Subnet(subnet);
12304
+ return shouldKeepClassification(classification, opts);
12305
+ });
12306
+ }
12307
+ function filterIPSummary(summary, options = {}) {
12308
+ const ipv4Addresses = filterIPv4Addresses(summary.ipv4Addresses, options);
12309
+ const ipv6Addresses = filterIPv6Addresses(summary.ipv6Addresses, options);
12310
+ const ipv4Subnets = filterIPv4Subnets(summary.ipv4Subnets, options);
12311
+ const ipv6Subnets = filterIPv6Subnets(summary.ipv6Subnets, options);
12312
+ const counts = {
12313
+ ipv4: ipv4Addresses.length,
12314
+ ipv6: ipv6Addresses.length,
12315
+ ipv4Subnets: ipv4Subnets.length,
12316
+ ipv6Subnets: ipv6Subnets.length,
12317
+ total: ipv4Addresses.length + ipv6Addresses.length + ipv4Subnets.length + ipv6Subnets.length
12318
+ };
12319
+ return {
12320
+ ipv4Addresses,
12321
+ ipv6Addresses,
12322
+ ipv4Subnets,
12323
+ ipv6Subnets,
12324
+ counts
12325
+ };
12326
+ }
12327
+
12132
12328
  // ../core/src/validation/rule-validation.ts
12133
12329
  function validateRule2(rule) {
12134
12330
  if (typeof rule !== "object" || rule === null) {
@@ -12328,7 +12524,7 @@ function generateSarif(results, filePath, rules, options = {}, ipSummary) {
12328
12524
  tool: {
12329
12525
  driver: {
12330
12526
  name: "Sentriflow",
12331
- version: "0.3.0",
12527
+ version: "0.4.0",
12332
12528
  informationUri: "https://github.com/sentriflow/sentriflow",
12333
12529
  rules: sarifRules,
12334
12530
  // SEC-007: Include CWE taxonomy when rules reference it
@@ -12488,7 +12684,7 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
12488
12684
  tool: {
12489
12685
  driver: {
12490
12686
  name: "Sentriflow",
12491
- version: "0.3.0",
12687
+ version: "0.4.0",
12492
12688
  informationUri: "https://github.com/sentriflow/sentriflow",
12493
12689
  rules: sarifRules,
12494
12690
  // SEC-007: Include CWE taxonomy when rules reference it
@@ -12702,10 +12898,10 @@ var InterfaceDescriptionRequired = {
12702
12898
  loc: node.loc
12703
12899
  };
12704
12900
  }
12705
- const hasDescription9 = node.children.some(
12901
+ const hasDescription5 = node.children.some(
12706
12902
  (child) => startsWithIgnoreCase(child.id, "description")
12707
12903
  );
12708
- if (!hasDescription9) {
12904
+ if (!hasDescription5) {
12709
12905
  return {
12710
12906
  passed: false,
12711
12907
  message: `Interface "${node.params.slice(1).join(" ")}" is missing a description.`,
@@ -12730,59 +12926,6 @@ var allCommonRules = [
12730
12926
  InterfaceDescriptionRequired
12731
12927
  ];
12732
12928
 
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
-
12786
12929
  // ../rules-default/src/cisco/ios-rules.ts
12787
12930
  var TrunkNoDTP = {
12788
12931
  id: "NET-TRUNK-001",
@@ -12796,16 +12939,16 @@ var TrunkNoDTP = {
12796
12939
  remediation: 'Add "switchport nonegotiate" to disable DTP on trunk ports connected to non-Cisco devices.'
12797
12940
  },
12798
12941
  check: (node) => {
12799
- if (!isPhysicalPort4(node.id) || isShutdown7(node)) {
12942
+ if (!isPhysicalPort(node.id) || isShutdown3(node)) {
12800
12943
  return { passed: true, message: "Not applicable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
12801
12944
  }
12802
- if (!isTrunkPort4(node)) {
12945
+ if (!isTrunkPort2(node)) {
12803
12946
  return { passed: true, message: "Not a trunk port.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
12804
12947
  }
12805
- if (!isTrunkToNonCisco2(node)) {
12948
+ if (!isTrunkToNonCisco(node)) {
12806
12949
  return { passed: true, message: "Trunk to Cisco device - DTP acceptable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
12807
12950
  }
12808
- if (!hasChildCommand5(node, "switchport nonegotiate")) {
12951
+ if (!hasChildCommand(node, "switchport nonegotiate")) {
12809
12952
  return {
12810
12953
  passed: false,
12811
12954
  message: `Trunk port "${node.params.slice(1).join(" ")}" connected to non-Cisco device needs "switchport nonegotiate".`,
@@ -12830,11 +12973,11 @@ var AccessExplicitMode = {
12830
12973
  remediation: 'Add "switchport mode access" to explicitly configure access mode.'
12831
12974
  },
12832
12975
  check: (node) => {
12833
- if (!isPhysicalPort4(node.id) || isShutdown7(node)) {
12976
+ if (!isPhysicalPort(node.id) || isShutdown3(node)) {
12834
12977
  return { passed: true, message: "Not applicable.", ruleId: "NET-ACCESS-001", nodeId: node.id, level: "info", loc: node.loc };
12835
12978
  }
12836
- const hasAccessVlan = hasChildCommand5(node, "switchport access vlan");
12837
- const hasExplicitMode = hasChildCommand5(node, "switchport mode");
12979
+ const hasAccessVlan = hasChildCommand(node, "switchport access vlan");
12980
+ const hasExplicitMode = hasChildCommand(node, "switchport mode");
12838
12981
  if (hasAccessVlan && !hasExplicitMode) {
12839
12982
  return {
12840
12983
  passed: false,
@@ -12894,15 +13037,26 @@ var CiscoNoPlaintextPasswords = {
12894
13037
  level: "error",
12895
13038
  obu: "Security",
12896
13039
  owner: "SecOps",
12897
- remediation: 'Use "secret" instead of "password", or ensure password is encrypted (type 7 or higher).'
13040
+ remediation: 'Use "secret" instead of "password", or encrypt with type 7/8/9.'
12898
13041
  },
12899
- check: (node) => {
13042
+ check: (node, context) => {
12900
13043
  const params = node.params;
12901
- const nodeId = node.id;
12902
- if (includesIgnoreCase(nodeId, "encryption") || includesIgnoreCase(nodeId, "service")) {
13044
+ const nodeId = node.id.toLowerCase();
13045
+ if (nodeId.includes("encryption") || nodeId.includes("service")) {
12903
13046
  return {
12904
13047
  passed: true,
12905
- message: "Global password configuration command.",
13048
+ message: "Global password configuration.",
13049
+ ruleId: "NET-SEC-001",
13050
+ nodeId: node.id,
13051
+ level: "info",
13052
+ loc: node.loc
13053
+ };
13054
+ }
13055
+ const ast = context.getAst?.();
13056
+ if (ast && isLineConfigPassword(ast, node)) {
13057
+ return {
13058
+ passed: true,
13059
+ message: "Line password - use AAA authentication for security (line passwords cannot be encrypted).",
12906
13060
  ruleId: "NET-SEC-001",
12907
13061
  nodeId: node.id,
12908
13062
  level: "info",
@@ -12911,20 +13065,10 @@ var CiscoNoPlaintextPasswords = {
12911
13065
  }
12912
13066
  if (params.length >= 2) {
12913
13067
  const typeOrValue = params[1];
12914
- if (!typeOrValue) {
12915
- return {
12916
- passed: false,
12917
- message: 'Possible plaintext password detected. Use encryption type 7 or "secret" command.',
12918
- ruleId: "NET-SEC-001",
12919
- nodeId: node.id,
12920
- level: "error",
12921
- loc: node.loc
12922
- };
12923
- }
12924
- if (typeOrValue === "7" || typeOrValue === "5" || typeOrValue === "8" || typeOrValue === "9") {
13068
+ if (typeOrValue && ["5", "7", "8", "9"].includes(typeOrValue)) {
12925
13069
  return {
12926
13070
  passed: true,
12927
- message: "Password is encrypted.",
13071
+ message: `Password is encrypted (type ${typeOrValue}).`,
12928
13072
  ruleId: "NET-SEC-001",
12929
13073
  nodeId: node.id,
12930
13074
  level: "info",
@@ -12934,17 +13078,17 @@ var CiscoNoPlaintextPasswords = {
12934
13078
  if (typeOrValue === "0") {
12935
13079
  return {
12936
13080
  passed: false,
12937
- message: "Plaintext password detected (type 0).",
13081
+ message: 'Plaintext password detected (type 0). Use "secret" or encrypt with type 7.',
12938
13082
  ruleId: "NET-SEC-001",
12939
13083
  nodeId: node.id,
12940
13084
  level: "error",
12941
13085
  loc: node.loc
12942
13086
  };
12943
13087
  }
12944
- if (!/^\d+$/.test(typeOrValue)) {
13088
+ if (typeOrValue && !/^\d+$/.test(typeOrValue)) {
12945
13089
  return {
12946
13090
  passed: false,
12947
- message: 'Possible plaintext password detected. Use encryption type 7 or "secret" command.',
13091
+ message: 'Plaintext password detected. Use "secret" or encrypt with type 7.',
12948
13092
  ruleId: "NET-SEC-001",
12949
13093
  nodeId: node.id,
12950
13094
  level: "error",
@@ -12972,38 +13116,6 @@ var allCiscoRules = [
12972
13116
  EnableSecretStrong
12973
13117
  ];
12974
13118
 
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
-
13007
13119
  // ../rules-default/src/juniper/junos-rules.ts
13008
13120
  var RootAuthRequired = {
13009
13121
  id: "JUN-SYS-001",
@@ -13017,7 +13129,7 @@ var RootAuthRequired = {
13017
13129
  remediation: 'Configure "root-authentication" under system stanza with encrypted-password or ssh-rsa.'
13018
13130
  },
13019
13131
  check: (node) => {
13020
- const rootAuth = findStanza8(node, "root-authentication");
13132
+ const rootAuth = findStanza4(node, "root-authentication");
13021
13133
  if (!rootAuth) {
13022
13134
  return {
13023
13135
  passed: false,
@@ -13028,8 +13140,8 @@ var RootAuthRequired = {
13028
13140
  loc: node.loc
13029
13141
  };
13030
13142
  }
13031
- const hasPassword = hasChildCommand5(rootAuth, "encrypted-password");
13032
- const hasSshKey = hasChildCommand5(rootAuth, "ssh-rsa") || hasChildCommand5(rootAuth, "ssh-ecdsa");
13143
+ const hasPassword = hasChildCommand(rootAuth, "encrypted-password");
13144
+ const hasSshKey = hasChildCommand(rootAuth, "ssh-rsa") || hasChildCommand(rootAuth, "ssh-ecdsa");
13033
13145
  if (!hasPassword && !hasSshKey) {
13034
13146
  return {
13035
13147
  passed: false,
@@ -13062,7 +13174,7 @@ var JunosBgpRouterId = {
13062
13174
  remediation: 'Configure "router-id" under routing-options stanza.'
13063
13175
  },
13064
13176
  check: (node) => {
13065
- const hasRouterId = hasChildCommand5(node, "router-id");
13177
+ const hasRouterId = hasChildCommand(node, "router-id");
13066
13178
  if (!hasRouterId) {
13067
13179
  return {
13068
13180
  passed: false,
@@ -13113,7 +13225,7 @@ var JunosFirewallDefaultDeny = {
13113
13225
  }
13114
13226
  const issues = [];
13115
13227
  for (const filter of filters) {
13116
- const terms = findStanzas7(filter, /^term/i);
13228
+ const terms = findStanzas3(filter, /^term/i);
13117
13229
  if (terms.length === 0) {
13118
13230
  continue;
13119
13231
  }
@@ -13121,7 +13233,7 @@ var JunosFirewallDefaultDeny = {
13121
13233
  if (!lastTerm) {
13122
13234
  continue;
13123
13235
  }
13124
- if (!isFilterTermDrop2(lastTerm)) {
13236
+ if (!isFilterTermDrop(lastTerm)) {
13125
13237
  issues.push(`Filter "${filter.id}" does not end with a deny term.`);
13126
13238
  }
13127
13239
  }
@@ -13154,69 +13266,6 @@ var allJuniperRules = [
13154
13266
  JunosFirewallDefaultDeny
13155
13267
  ];
13156
13268
 
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
-
13220
13269
  // ../rules-default/src/aruba/common-rules.ts
13221
13270
  var SshEnabled = {
13222
13271
  id: "ARU-SEC-001",
@@ -13304,17 +13353,17 @@ var AosCxInterfaceDescription = {
13304
13353
  remediation: "Add a description to physical interfaces for documentation."
13305
13354
  },
13306
13355
  check: (node) => {
13307
- const ifName = getInterfaceName4(node);
13356
+ const ifName = getInterfaceName(node);
13308
13357
  if (!ifName) {
13309
13358
  return { passed: true, message: "Not an interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
13310
13359
  }
13311
- if (!isAosCxPhysicalPort2(ifName) && !isAosCxLag2(ifName)) {
13360
+ if (!isAosCxPhysicalPort(ifName) && !isAosCxLag(ifName)) {
13312
13361
  return { passed: true, message: "Not a physical interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
13313
13362
  }
13314
- if (isShutdown6(node)) {
13363
+ if (isShutdown(node)) {
13315
13364
  return { passed: true, message: "Interface is shutdown.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
13316
13365
  }
13317
- if (!hasDescription5(node)) {
13366
+ if (!hasDescription(node)) {
13318
13367
  return {
13319
13368
  passed: false,
13320
13369
  message: `Interface ${ifName} missing description.`,
@@ -13346,17 +13395,17 @@ var AosCxTrunkAllowedVlans = {
13346
13395
  remediation: 'Configure "vlan trunk allowed <vlans>" on trunk interfaces.'
13347
13396
  },
13348
13397
  check: (node) => {
13349
- const ifName = getInterfaceName4(node);
13398
+ const ifName = getInterfaceName(node);
13350
13399
  if (!ifName) {
13351
13400
  return { passed: true, message: "Not an interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
13352
13401
  }
13353
- if (!isAosCxPhysicalPort2(ifName) && !isAosCxLag2(ifName)) {
13402
+ if (!isAosCxPhysicalPort(ifName) && !isAosCxLag(ifName)) {
13354
13403
  return { passed: true, message: "Not a switchport interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
13355
13404
  }
13356
- if (!isAosCxTrunk2(node)) {
13405
+ if (!isAosCxTrunk(node)) {
13357
13406
  return { passed: true, message: "Not a trunk port.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
13358
13407
  }
13359
- const allowedVlans = getAosCxTrunkAllowed2(node);
13408
+ const allowedVlans = getAosCxTrunkAllowed(node);
13360
13409
  if (allowedVlans.length === 0) {
13361
13410
  return {
13362
13411
  passed: false,
@@ -13406,7 +13455,7 @@ var AosSwitchVlanName = {
13406
13455
  if (vlanId === null || isDefaultVlan(vlanId)) {
13407
13456
  return { passed: true, message: "Default VLAN 1.", ruleId: "AOSSW-L2-001", nodeId: node.id, level: "info", loc: node.loc };
13408
13457
  }
13409
- const vlanName = getAosSwitchVlanName2(node);
13458
+ const vlanName = getAosSwitchVlanName(node);
13410
13459
  if (!vlanName) {
13411
13460
  return {
13412
13461
  passed: false,
@@ -13480,8 +13529,8 @@ var WlcSsidEncryption = {
13480
13529
  remediation: 'Configure "opmode wpa2-aes" or "opmode wpa3-sae-aes" for secure encryption.'
13481
13530
  },
13482
13531
  check: (node) => {
13483
- const profileName = extractProfileName2(node.id);
13484
- const opmode = getWlanEncryption2(node);
13532
+ const profileName = extractProfileName(node.id);
13533
+ const opmode = getWlanEncryption(node);
13485
13534
  if (!opmode) {
13486
13535
  return {
13487
13536
  passed: false,
@@ -13492,7 +13541,7 @@ var WlcSsidEncryption = {
13492
13541
  loc: node.loc
13493
13542
  };
13494
13543
  }
13495
- if (isOpenSsid2(node)) {
13544
+ if (isOpenSsid(node)) {
13496
13545
  return {
13497
13546
  passed: false,
13498
13547
  message: `SSID profile "${profileName}" is open/unencrypted. Use WPA2 or WPA3.`,
@@ -13502,7 +13551,7 @@ var WlcSsidEncryption = {
13502
13551
  loc: node.loc
13503
13552
  };
13504
13553
  }
13505
- if (!hasSecureEncryption2(node)) {
13554
+ if (!hasSecureEncryption(node)) {
13506
13555
  return {
13507
13556
  passed: false,
13508
13557
  message: `SSID profile "${profileName}" uses weak encryption (${opmode}). Use WPA2 or WPA3.`,
@@ -13534,8 +13583,8 @@ var WlcRadiusHost = {
13534
13583
  remediation: 'Configure "host <ip-address>" for the RADIUS server.'
13535
13584
  },
13536
13585
  check: (node) => {
13537
- const profileName = extractProfileName2(node.id);
13538
- const host = getRadiusHost2(node);
13586
+ const profileName = extractProfileName(node.id);
13587
+ const host = getRadiusHost(node);
13539
13588
  if (!host) {
13540
13589
  return {
13541
13590
  passed: false,
@@ -13583,34 +13632,6 @@ function getRulesByArubaVendor(vendorId) {
13583
13632
  }
13584
13633
  }
13585
13634
 
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
-
13614
13635
  // ../rules-default/src/paloalto/panos-rules.ts
13615
13636
  var HostnameRequired = {
13616
13637
  id: "PAN-SYS-001",
@@ -13624,7 +13645,7 @@ var HostnameRequired = {
13624
13645
  remediation: "Configure hostname under deviceconfig > system."
13625
13646
  },
13626
13647
  check: (node) => {
13627
- const system = findStanza9(node, "system");
13648
+ const system = findStanza6(node, "system");
13628
13649
  if (!system) {
13629
13650
  return {
13630
13651
  passed: false,
@@ -13635,7 +13656,7 @@ var HostnameRequired = {
13635
13656
  loc: node.loc
13636
13657
  };
13637
13658
  }
13638
- const hasHostname = hasChildCommand5(system, "hostname");
13659
+ const hasHostname = hasChildCommand(system, "hostname");
13639
13660
  if (!hasHostname) {
13640
13661
  return {
13641
13662
  passed: false,
@@ -13668,7 +13689,7 @@ var SecurityRuleLogging = {
13668
13689
  remediation: "Enable log-end (and optionally log-start) on all security rules."
13669
13690
  },
13670
13691
  check: (node) => {
13671
- const rules = getSecurityRules2(node);
13692
+ const rules = getSecurityRules(node);
13672
13693
  if (rules.length === 0) {
13673
13694
  return {
13674
13695
  passed: true,
@@ -13681,8 +13702,8 @@ var SecurityRuleLogging = {
13681
13702
  }
13682
13703
  const issues = [];
13683
13704
  for (const rule of rules) {
13684
- if (isRuleDisabled2(rule)) continue;
13685
- const logging = hasLogging3(rule);
13705
+ if (isRuleDisabled(rule)) continue;
13706
+ const logging = hasLogging2(rule);
13686
13707
  if (!logging.logEnd) {
13687
13708
  issues.push(`Rule "${rule.id}" does not have log-end enabled.`);
13688
13709
  }
@@ -13721,7 +13742,7 @@ var ZoneProtectionRequired = {
13721
13742
  check: (node) => {
13722
13743
  const issues = [];
13723
13744
  for (const zone of node.children) {
13724
- if (!hasZoneProtectionProfile2(zone)) {
13745
+ if (!hasZoneProtectionProfile(zone)) {
13725
13746
  issues.push(`Zone "${zone.id}" does not have a Zone Protection Profile applied.`);
13726
13747
  }
13727
13748
  }
@@ -13769,34 +13790,6 @@ function getRulesByPaloAltoVendor() {
13769
13790
  return [...allCommonRules, ...allPaloAltoRules];
13770
13791
  }
13771
13792
 
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
-
13800
13793
  // ../rules-default/src/arista/eos-rules.ts
13801
13794
  var HostnameRequired2 = {
13802
13795
  id: "ARI-SYS-001",
@@ -13845,7 +13838,7 @@ var MlagConfigComplete = {
13845
13838
  remediation: "MLAG configuration requires: domain-id, peer-link, peer-address, and local-interface."
13846
13839
  },
13847
13840
  check: (node, context) => {
13848
- const requirements = checkMlagRequirements2(node);
13841
+ const requirements = checkMlagRequirements(node);
13849
13842
  const issues = [];
13850
13843
  if (!requirements.hasDomainId) {
13851
13844
  issues.push("domain-id");
@@ -13891,7 +13884,7 @@ var InterfaceDescription = {
13891
13884
  remediation: "Add description to interface: description <text>"
13892
13885
  },
13893
13886
  check: (node, context) => {
13894
- if (isShutdown8(node)) {
13887
+ if (isShutdown2(node)) {
13895
13888
  return {
13896
13889
  passed: true,
13897
13890
  message: "Interface is shutdown, description not required.",
@@ -13901,7 +13894,7 @@ var InterfaceDescription = {
13901
13894
  loc: node.loc
13902
13895
  };
13903
13896
  }
13904
- const description = getInterfaceDescription2(node);
13897
+ const description = getInterfaceDescription(node);
13905
13898
  if (!description) {
13906
13899
  return {
13907
13900
  passed: false,
@@ -13936,37 +13929,6 @@ function getRulesByAristaVendor() {
13936
13929
  return [...allCommonRules, ...allAristaRules];
13937
13930
  }
13938
13931
 
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
-
13970
13932
  // ../rules-default/src/vyos/vyos-rules.ts
13971
13933
  var VyosHostnameRequired = {
13972
13934
  id: "VYOS-SYS-001",
@@ -13980,7 +13942,7 @@ var VyosHostnameRequired = {
13980
13942
  remediation: 'Configure "host-name" under system stanza.'
13981
13943
  },
13982
13944
  check: (node) => {
13983
- const hasHostname = hasChildCommand5(node, "host-name");
13945
+ const hasHostname = hasChildCommand(node, "host-name");
13984
13946
  if (!hasHostname) {
13985
13947
  return {
13986
13948
  passed: false,
@@ -14049,12 +14011,12 @@ var VyosInterfaceDescription = {
14049
14011
  },
14050
14012
  check: (node) => {
14051
14013
  const issues = [];
14052
- const ethernetInterfaces = getEthernetInterfaces2(node);
14014
+ const ethernetInterfaces = getEthernetInterfaces(node);
14053
14015
  for (const iface of ethernetInterfaces) {
14054
- if (isDisabled4(iface)) {
14016
+ if (isDisabled2(iface)) {
14055
14017
  continue;
14056
14018
  }
14057
- const hasDesc = hasChildCommand5(iface, "description");
14019
+ const hasDesc = hasChildCommand(iface, "description");
14058
14020
  if (!hasDesc) {
14059
14021
  const ifaceName = iface.id.split(/\s+/).pop() || iface.id;
14060
14022
  issues.push(`Interface "${ifaceName}" missing description.`);
@@ -14092,7 +14054,7 @@ var VyosFirewallDefaultAction = {
14092
14054
  remediation: 'Set "default-action drop" or "default-action reject" for each firewall ruleset.'
14093
14055
  },
14094
14056
  check: (node) => {
14095
- const rulesets = findStanzasByPrefix3(node, "name");
14057
+ const rulesets = findStanzasByPrefix2(node, "name");
14096
14058
  if (rulesets.length === 0) {
14097
14059
  return {
14098
14060
  passed: true,
@@ -14105,7 +14067,7 @@ var VyosFirewallDefaultAction = {
14105
14067
  }
14106
14068
  const issues = [];
14107
14069
  for (const ruleset of rulesets) {
14108
- const defaultAction = getFirewallDefaultAction2(ruleset);
14070
+ const defaultAction = getFirewallDefaultAction(ruleset);
14109
14071
  if (!defaultAction) {
14110
14072
  const rulesetName = ruleset.id.split(/\s+/)[1] || ruleset.id;
14111
14073
  issues.push(`Firewall ruleset "${rulesetName}" has no default-action configured.`);
@@ -14147,65 +14109,6 @@ function getRulesByVyosVendor() {
14147
14109
  return [...allCommonRules, ...allVyosRules];
14148
14110
  }
14149
14111
 
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
14112
  // ../rules-default/src/fortinet/fortigate-rules.ts
14210
14113
  var HostnameRequired3 = {
14211
14114
  id: "FGT-SYS-001",
@@ -14219,7 +14122,7 @@ var HostnameRequired3 = {
14219
14122
  remediation: 'Configure hostname under "config system global" using "set hostname <name>".'
14220
14123
  },
14221
14124
  check: (node) => {
14222
- const hostname = getSetValue2(node, "hostname");
14125
+ const hostname = getSetValue(node, "hostname");
14223
14126
  if (!hostname) {
14224
14127
  return {
14225
14128
  passed: false,
@@ -14252,7 +14155,7 @@ var AdminTrustedHostRequired = {
14252
14155
  remediation: 'Configure trusted hosts for each admin user using "set trusthost1", "set trusthost2", etc.'
14253
14156
  },
14254
14157
  check: (node) => {
14255
- const admins = getEditEntries2(node);
14158
+ const admins = getEditEntries(node);
14256
14159
  if (admins.length === 0) {
14257
14160
  return {
14258
14161
  passed: true,
@@ -14265,11 +14168,11 @@ var AdminTrustedHostRequired = {
14265
14168
  }
14266
14169
  const issues = [];
14267
14170
  for (const admin of admins) {
14268
- const adminName = getEditEntryName2(admin);
14269
- if (hasAdminTrustedHosts2(admin)) {
14171
+ const adminName = getEditEntryName(admin);
14172
+ if (hasAdminTrustedHosts(admin)) {
14270
14173
  continue;
14271
14174
  }
14272
- if (isSuperAdmin2(admin)) {
14175
+ if (isSuperAdmin(admin)) {
14273
14176
  issues.push(`Super admin "${adminName}" has no trusted host restrictions. This is a critical security issue.`);
14274
14177
  } else {
14275
14178
  issues.push(`Admin "${adminName}" has no trusted host restrictions.`);
@@ -14307,7 +14210,7 @@ var PolicyLoggingRequired = {
14307
14210
  remediation: 'Enable logging on all firewall policies using "set logtraffic all" or "set logtraffic utm".'
14308
14211
  },
14309
14212
  check: (node) => {
14310
- const policies = getEditEntries2(node);
14213
+ const policies = getEditEntries(node);
14311
14214
  if (policies.length === 0) {
14312
14215
  return {
14313
14216
  passed: true,
@@ -14320,10 +14223,10 @@ var PolicyLoggingRequired = {
14320
14223
  }
14321
14224
  const issues = [];
14322
14225
  for (const policy of policies) {
14323
- if (isPolicyDisabled2(policy)) continue;
14324
- const logging = hasLogging4(policy);
14226
+ if (isPolicyDisabled(policy)) continue;
14227
+ const logging = hasLogging(policy);
14325
14228
  if (!logging.logtraffic || isFeatureDisabled(logging.logtraffic)) {
14326
- const policyId = getEditEntryName2(policy);
14229
+ const policyId = getEditEntryName(policy);
14327
14230
  issues.push(`Policy ${policyId} does not have logging enabled.`);
14328
14231
  }
14329
14232
  }
@@ -14361,44 +14264,6 @@ function getRulesByFortinetVendor() {
14361
14264
  return [...allCommonRules, ...allFortinetRules];
14362
14265
  }
14363
14266
 
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
-
14402
14267
  // ../rules-default/src/extreme/exos-rules.ts
14403
14268
  var ExosSysnameRequired = {
14404
14269
  id: "EXOS-SYS-001",
@@ -14478,7 +14343,7 @@ var ExosVlanNaming = {
14478
14343
  remediation: 'Use descriptive VLAN names: create vlan "<meaningful-name>" tag <id>'
14479
14344
  },
14480
14345
  check: (node, context) => {
14481
- const vlanName = getExosVlanName2(node);
14346
+ const vlanName = getExosVlanName(node);
14482
14347
  if (!vlanName) {
14483
14348
  return {
14484
14349
  passed: false,
@@ -14569,7 +14434,7 @@ var VossVlanIsidRequired = {
14569
14434
  remediation: "Configure I-SID for VLAN: vlan i-sid <vlan-id> <isid>"
14570
14435
  },
14571
14436
  check: (node, context) => {
14572
- if (!isVossVlanCreate2(node)) {
14437
+ if (!isVossVlanCreate(node)) {
14573
14438
  return {
14574
14439
  passed: true,
14575
14440
  message: "Not a VLAN create command.",
@@ -14579,7 +14444,7 @@ var VossVlanIsidRequired = {
14579
14444
  loc: node.loc
14580
14445
  };
14581
14446
  }
14582
- const vlanId = getVossVlanId2(node);
14447
+ const vlanId = getVossVlanId(node);
14583
14448
  if (!vlanId) {
14584
14449
  return {
14585
14450
  passed: false,
@@ -14622,7 +14487,7 @@ var VossInterfaceDefaultVlan = {
14622
14487
  remediation: "Configure default VLAN: default-vlan-id <vlan-id>"
14623
14488
  },
14624
14489
  check: (node, context) => {
14625
- if (!isVossGigabitEthernet2(node)) {
14490
+ if (!isVossGigabitEthernet(node)) {
14626
14491
  return {
14627
14492
  passed: true,
14628
14493
  message: "Not a GigabitEthernet interface.",
@@ -14632,7 +14497,7 @@ var VossInterfaceDefaultVlan = {
14632
14497
  loc: node.loc
14633
14498
  };
14634
14499
  }
14635
- if (isVossShutdown2(node)) {
14500
+ if (isVossShutdown(node)) {
14636
14501
  return {
14637
14502
  passed: true,
14638
14503
  message: "Interface is shutdown.",
@@ -14642,7 +14507,7 @@ var VossInterfaceDefaultVlan = {
14642
14507
  loc: node.loc
14643
14508
  };
14644
14509
  }
14645
- const defaultVlan = getVossDefaultVlan2(node);
14510
+ const defaultVlan = getVossDefaultVlan(node);
14646
14511
  if (!defaultVlan) {
14647
14512
  return {
14648
14513
  passed: false,
@@ -14694,22 +14559,6 @@ function getRulesByExtremeVendor(vendorId) {
14694
14559
  }
14695
14560
  }
14696
14561
 
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
-
14713
14562
  // ../rules-default/src/huawei/vrp-rules.ts
14714
14563
  var SysnameRequired = {
14715
14564
  id: "HUAWEI-SYS-001",
@@ -14757,7 +14606,7 @@ var InterfaceDescriptionRequired2 = {
14757
14606
  },
14758
14607
  check: (node) => {
14759
14608
  const interfaceName = node.id.replace(/^interface\s+/i, "").trim();
14760
- if (!isPhysicalPort5(interfaceName)) {
14609
+ if (!isPhysicalPort2(interfaceName)) {
14761
14610
  return {
14762
14611
  passed: true,
14763
14612
  message: "Non-physical interface, description optional.",
@@ -14767,7 +14616,7 @@ var InterfaceDescriptionRequired2 = {
14767
14616
  loc: node.loc
14768
14617
  };
14769
14618
  }
14770
- if (!isEnabled4(node)) {
14619
+ if (!isEnabled(node)) {
14771
14620
  return {
14772
14621
  passed: true,
14773
14622
  message: "Interface is shutdown, description optional.",
@@ -14777,7 +14626,7 @@ var InterfaceDescriptionRequired2 = {
14777
14626
  loc: node.loc
14778
14627
  };
14779
14628
  }
14780
- if (!hasDescription6(node)) {
14629
+ if (!hasDescription3(node)) {
14781
14630
  return {
14782
14631
  passed: false,
14783
14632
  message: `Interface ${interfaceName} is enabled but has no description.`,
@@ -14861,55 +14710,6 @@ function getRulesByHuaweiVendor() {
14861
14710
  return [...allCommonRules, ...allHuaweiRules];
14862
14711
  }
14863
14712
 
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
-
14913
14713
  // ../rules-default/src/mikrotik/routeros-rules.ts
14914
14714
  var MikrotikSystemIdentity = {
14915
14715
  id: "MIK-SYS-001",
@@ -14923,7 +14723,7 @@ var MikrotikSystemIdentity = {
14923
14723
  remediation: "Configure system identity: /system identity set name=MyRouter"
14924
14724
  },
14925
14725
  check: (node) => {
14926
- const identity = getSystemIdentity2(node);
14726
+ const identity = getSystemIdentity(node);
14927
14727
  if (!identity || equalsIgnoreCase(identity, "mikrotik") || equalsIgnoreCase(identity, "routerboard")) {
14928
14728
  return {
14929
14729
  passed: false,
@@ -14961,8 +14761,8 @@ var MikrotikDisableUnusedServices = {
14961
14761
  for (const child of node.children) {
14962
14762
  const childId = child.id.toLowerCase();
14963
14763
  for (const service of dangerousServices) {
14964
- if (childId.includes(service) && !isServiceDisabled2(child)) {
14965
- const disabled = parseProperty2(child.id, "disabled");
14764
+ if (childId.includes(service) && !isServiceDisabled(child)) {
14765
+ const disabled = parseProperty(child.id, "disabled");
14966
14766
  if (!disabled || !equalsIgnoreCase(disabled, "yes")) {
14967
14767
  issues.push(`Service '${service}' is enabled. Consider disabling it.`);
14968
14768
  }
@@ -15001,11 +14801,11 @@ var MikrotikInputChainDrop = {
15001
14801
  remediation: "Add drop rule for input chain: add chain=input action=drop"
15002
14802
  },
15003
14803
  check: (node) => {
15004
- const addCommands = getAddCommands2(node);
14804
+ const addCommands = getAddCommands(node);
15005
14805
  let hasInputDrop = false;
15006
14806
  for (const cmd of addCommands) {
15007
- const chain = getFirewallChain2(cmd);
15008
- const action = getFirewallAction2(cmd);
14807
+ const chain = getFirewallChain(cmd);
14808
+ const action = getFirewallAction(cmd);
15009
14809
  if (chain && equalsIgnoreCase(chain, "input") && action && equalsIgnoreCase(action, "drop")) {
15010
14810
  hasInputDrop = true;
15011
14811
  break;
@@ -15042,60 +14842,6 @@ function getRulesByMikroTikVendor() {
15042
14842
  return [...allCommonRules, ...allMikroTikRules];
15043
14843
  }
15044
14844
 
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
-
15099
14845
  // ../rules-default/src/nokia/sros-rules.ts
15100
14846
  var SystemNameRequired = {
15101
14847
  id: "NOKIA-SYS-001",
@@ -15109,7 +14855,7 @@ var SystemNameRequired = {
15109
14855
  remediation: 'Configure system name using: system > name "<hostname>"'
15110
14856
  },
15111
14857
  check: (node) => {
15112
- const name = getSystemName2(node);
14858
+ const name = getSystemName(node);
15113
14859
  if (!name || name.length === 0) {
15114
14860
  return {
15115
14861
  passed: false,
@@ -15143,7 +14889,7 @@ var PortDescriptionRequired = {
15143
14889
  },
15144
14890
  check: (node) => {
15145
14891
  const portName = node.id.replace(/^port\s+/i, "").trim();
15146
- if (!isPhysicalPort6(portName)) {
14892
+ if (!isPhysicalPort3(portName)) {
15147
14893
  return {
15148
14894
  passed: true,
15149
14895
  message: "Not a physical port, description optional.",
@@ -15153,7 +14899,7 @@ var PortDescriptionRequired = {
15153
14899
  loc: node.loc
15154
14900
  };
15155
14901
  }
15156
- if (isAdminStateDisabled2(node)) {
14902
+ if (isAdminStateDisabled(node)) {
15157
14903
  return {
15158
14904
  passed: true,
15159
14905
  message: "Port is disabled, description optional.",
@@ -15163,7 +14909,7 @@ var PortDescriptionRequired = {
15163
14909
  loc: node.loc
15164
14910
  };
15165
14911
  }
15166
- if (!hasDescription7(node)) {
14912
+ if (!hasDescription4(node)) {
15167
14913
  return {
15168
14914
  passed: false,
15169
14915
  message: `Port ${portName} is enabled but has no description.`,
@@ -15175,7 +14921,7 @@ var PortDescriptionRequired = {
15175
14921
  }
15176
14922
  return {
15177
14923
  passed: true,
15178
- message: `Port ${portName} has description: ${getDescription4(node)}`,
14924
+ message: `Port ${portName} has description: ${getDescription3(node)}`,
15179
14925
  ruleId: "NOKIA-PORT-001",
15180
14926
  nodeId: node.id,
15181
14927
  level: "info",
@@ -15195,7 +14941,7 @@ var BgpRouterIdRequired = {
15195
14941
  remediation: "Configure BGP router-id: bgp > router-id <ip-address>"
15196
14942
  },
15197
14943
  check: (node) => {
15198
- if (isAdminStateDisabled2(node)) {
14944
+ if (isAdminStateDisabled(node)) {
15199
14945
  return {
15200
14946
  passed: true,
15201
14947
  message: "BGP is disabled.",
@@ -15205,7 +14951,7 @@ var BgpRouterIdRequired = {
15205
14951
  loc: node.loc
15206
14952
  };
15207
14953
  }
15208
- if (!hasBgpRouterId3(node)) {
14954
+ if (!hasBgpRouterId2(node)) {
15209
14955
  return {
15210
14956
  passed: false,
15211
14957
  message: "BGP router-id is not configured. Configure a stable router-id.",
@@ -15217,7 +14963,7 @@ var BgpRouterIdRequired = {
15217
14963
  }
15218
14964
  return {
15219
14965
  passed: true,
15220
- message: `BGP router-id is configured: ${getBgpRouterId2(node)}`,
14966
+ message: `BGP router-id is configured: ${getBgpRouterId(node)}`,
15221
14967
  ruleId: "NOKIA-BGP-001",
15222
14968
  nodeId: node.id,
15223
14969
  level: "info",
@@ -15239,40 +14985,6 @@ function getRulesByNokiaVendor() {
15239
14985
  return [...allCommonRules, ...allNokiaRules];
15240
14986
  }
15241
14987
 
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
-
15276
14988
  // ../rules-default/src/cumulus/cumulus-rules.ts
15277
14989
  var CumulusInterfaceDescription = {
15278
14990
  id: "CUM-IF-001",
@@ -15286,8 +14998,8 @@ var CumulusInterfaceDescription = {
15286
14998
  remediation: 'Add "alias <description>" under the interface stanza.'
15287
14999
  },
15288
15000
  check: (node) => {
15289
- const ifaceName = getInterfaceName6(node);
15290
- if (!isSwitchPort2(ifaceName)) {
15001
+ const ifaceName = getInterfaceName2(node);
15002
+ if (!isSwitchPort(ifaceName)) {
15291
15003
  return {
15292
15004
  passed: true,
15293
15005
  message: "Not a switch port interface.",
@@ -15297,7 +15009,7 @@ var CumulusInterfaceDescription = {
15297
15009
  loc: node.loc
15298
15010
  };
15299
15011
  }
15300
- if (!hasDescription8(node)) {
15012
+ if (!hasDescription2(node)) {
15301
15013
  return {
15302
15014
  passed: false,
15303
15015
  message: `Switch port "${ifaceName}" missing description (alias).`,
@@ -15329,8 +15041,8 @@ var CumulusBridgeVlans = {
15329
15041
  remediation: 'Add "bridge-vids <vlan-ids>" to define allowed VLANs on the bridge.'
15330
15042
  },
15331
15043
  check: (node) => {
15332
- const ifaceName = getInterfaceName6(node);
15333
- if (!isBridgeInterface4(ifaceName)) {
15044
+ const ifaceName = getInterfaceName2(node);
15045
+ if (!isBridgeInterface(ifaceName)) {
15334
15046
  return {
15335
15047
  passed: true,
15336
15048
  message: "Not a bridge interface.",
@@ -15340,7 +15052,7 @@ var CumulusBridgeVlans = {
15340
15052
  loc: node.loc
15341
15053
  };
15342
15054
  }
15343
- if (!isVlanAwareBridge2(node)) {
15055
+ if (!isVlanAwareBridge(node)) {
15344
15056
  return {
15345
15057
  passed: true,
15346
15058
  message: "Not a VLAN-aware bridge.",
@@ -15350,7 +15062,7 @@ var CumulusBridgeVlans = {
15350
15062
  loc: node.loc
15351
15063
  };
15352
15064
  }
15353
- if (!hasBridgeVids2(node)) {
15065
+ if (!hasBridgeVids(node)) {
15354
15066
  return {
15355
15067
  passed: false,
15356
15068
  message: `VLAN-aware bridge "${ifaceName}" has no VLANs (bridge-vids) configured.`,
@@ -15382,7 +15094,7 @@ var CumulusBgpRouterId = {
15382
15094
  remediation: 'Add "bgp router-id <ip>" to explicitly set router ID.'
15383
15095
  },
15384
15096
  check: (node) => {
15385
- if (!hasBgpRouterId4(node)) {
15097
+ if (!hasBgpRouterId(node)) {
15386
15098
  return {
15387
15099
  passed: false,
15388
15100
  message: "BGP missing explicit router-id configuration.",
@@ -15418,7 +15130,7 @@ function getRulesByCumulusVendor() {
15418
15130
 
15419
15131
  // ../rules-default/src/json/cisco-json-rules.json
15420
15132
  var cisco_json_rules_default = {
15421
- $schema: "https://sentriflow.io/schemas/json-rules/v1.0.json",
15133
+ $schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
15422
15134
  version: "1.0",
15423
15135
  meta: {
15424
15136
  name: "Cisco IOS JSON Rules",
@@ -15615,7 +15327,7 @@ var cisco_json_rules_default = {
15615
15327
 
15616
15328
  // ../rules-default/src/json/common-json-rules.json
15617
15329
  var common_json_rules_default = {
15618
- $schema: "https://sentriflow.io/schemas/json-rules/v1.0.json",
15330
+ $schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
15619
15331
  version: "1.0",
15620
15332
  meta: {
15621
15333
  name: "Common JSON Rules",
@@ -15663,7 +15375,7 @@ var common_json_rules_default = {
15663
15375
 
15664
15376
  // ../rules-default/src/json/juniper-json-rules.json
15665
15377
  var juniper_json_rules_default = {
15666
- $schema: "https://sentriflow.io/schemas/json-rules/v1.0.json",
15378
+ $schema: "https://sentriflow.com.au/schemas/json-rules/v1.0.json",
15667
15379
  version: "1.0",
15668
15380
  meta: {
15669
15381
  name: "Juniper JunOS JSON Rules",
@@ -16172,6 +15884,9 @@ function isValidSentriflowConfig(config) {
16172
15884
  return false;
16173
15885
  }
16174
15886
  }
15887
+ if (obj.filterSpecialIps !== void 0 && typeof obj.filterSpecialIps !== "boolean") {
15888
+ return false;
15889
+ }
16175
15890
  return true;
16176
15891
  }
16177
15892
  function isValidDirectoryConfig(config) {
@@ -16967,7 +16682,7 @@ function enrichResultsWithRuleMetadata(results, rules) {
16967
16682
  });
16968
16683
  }
16969
16684
  var program = new Command();
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(
16685
+ program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.4.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
16686
  "--pack <path...>",
16972
16687
  "Path(s) to rule pack(s) (auto-detects format: .grx2, .grpx, or unencrypted)"
16973
16688
  ).option(
@@ -17025,7 +16740,10 @@ program.name("sentriflow").description("SentriFlow Network Configuration Validat
17025
16740
  "--max-depth <number>",
17026
16741
  "Maximum recursion depth for directory scanning (use with -R)",
17027
16742
  (val) => parseInt(val, 10)
17028
- ).option("--progress", "Show progress during directory scanning").action(async (files, options) => {
16743
+ ).option("--progress", "Show progress during directory scanning").option(
16744
+ "--filter-special-ips",
16745
+ "Filter out special IP ranges (loopback, multicast, reserved, broadcast) from IP summary"
16746
+ ).action(async (files, options) => {
17029
16747
  try {
17030
16748
  if (options.showMachineId) {
17031
16749
  try {
@@ -17099,6 +16817,19 @@ Use: sentriflow --vendor <vendor> <file>`);
17099
16817
  allowedBaseDirs
17100
16818
  // SEC-011: Pass allowed base dirs for rule file validation
17101
16819
  });
16820
+ let filterSpecialIps = options.filterSpecialIps ?? false;
16821
+ if (!options.filterSpecialIps && options.config !== false) {
16822
+ const configPath = options.config ?? findConfigFile(configSearchDir);
16823
+ if (configPath) {
16824
+ try {
16825
+ const config = await loadConfigFile(configPath, allowedBaseDirs);
16826
+ if (config.filterSpecialIps) {
16827
+ filterSpecialIps = true;
16828
+ }
16829
+ } catch {
16830
+ }
16831
+ }
16832
+ }
17102
16833
  if (options.listCategories) {
17103
16834
  const counts = /* @__PURE__ */ new Map();
17104
16835
  for (const rule of rules) {
@@ -17338,7 +17069,21 @@ Parsing complete: ${allAsts.length} files`);
17338
17069
  const passed = results2.filter((r) => r.passed).length;
17339
17070
  totalFailures += failures;
17340
17071
  totalPassed += passed;
17341
- const fileIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
17072
+ let fileIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
17073
+ if (filterSpecialIps) {
17074
+ fileIpSummary = filterIPSummary(fileIpSummary, {
17075
+ keepPublic: true,
17076
+ keepPrivate: true,
17077
+ keepCgnat: true,
17078
+ keepLoopback: false,
17079
+ keepLinkLocal: false,
17080
+ keepMulticast: false,
17081
+ keepReserved: false,
17082
+ keepUnspecified: false,
17083
+ keepBroadcast: false,
17084
+ keepDocumentation: false
17085
+ });
17086
+ }
17342
17087
  allFileResults.push({
17343
17088
  filePath: filePath2,
17344
17089
  results: results2,
@@ -17454,6 +17199,20 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
17454
17199
  let stdinIpSummary;
17455
17200
  try {
17456
17201
  stdinIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
17202
+ if (filterSpecialIps) {
17203
+ stdinIpSummary = filterIPSummary(stdinIpSummary, {
17204
+ keepPublic: true,
17205
+ keepPrivate: true,
17206
+ keepCgnat: true,
17207
+ keepLoopback: false,
17208
+ keepLinkLocal: false,
17209
+ keepMulticast: false,
17210
+ keepReserved: false,
17211
+ keepUnspecified: false,
17212
+ keepBroadcast: false,
17213
+ keepDocumentation: false
17214
+ });
17215
+ }
17457
17216
  } catch (error) {
17458
17217
  if (error instanceof InputValidationError) {
17459
17218
  console.error(`Input validation error: ${error.message}`);
@@ -17531,7 +17290,21 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
17531
17290
  const passed = results2.filter((r) => r.passed).length;
17532
17291
  totalFailures += failures;
17533
17292
  totalPassed += passed;
17534
- const fileIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
17293
+ let fileIpSummary = extractIPSummary(content2, { includeSubnetNetworks: true });
17294
+ if (filterSpecialIps) {
17295
+ fileIpSummary = filterIPSummary(fileIpSummary, {
17296
+ keepPublic: true,
17297
+ keepPrivate: true,
17298
+ keepCgnat: true,
17299
+ keepLoopback: false,
17300
+ keepLinkLocal: false,
17301
+ keepMulticast: false,
17302
+ keepReserved: false,
17303
+ keepUnspecified: false,
17304
+ keepBroadcast: false,
17305
+ keepDocumentation: false
17306
+ });
17307
+ }
17535
17308
  allFileResults.push({
17536
17309
  filePath: filePath2,
17537
17310
  results: results2,
@@ -17647,7 +17420,21 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
17647
17420
  if (options.quiet) {
17648
17421
  results = results.filter((r) => !r.passed);
17649
17422
  }
17650
- const ipSummary = extractIPSummary(content, { includeSubnetNetworks: true });
17423
+ let ipSummary = extractIPSummary(content, { includeSubnetNetworks: true });
17424
+ if (filterSpecialIps) {
17425
+ ipSummary = filterIPSummary(ipSummary, {
17426
+ keepPublic: true,
17427
+ keepPrivate: true,
17428
+ keepCgnat: true,
17429
+ keepLoopback: false,
17430
+ keepLinkLocal: false,
17431
+ keepMulticast: false,
17432
+ keepReserved: false,
17433
+ keepUnspecified: false,
17434
+ keepBroadcast: false,
17435
+ keepDocumentation: false
17436
+ });
17437
+ }
17651
17438
  if (options.format === "sarif") {
17652
17439
  const sarifOptions = {
17653
17440
  relativePaths: options.relativePaths,
@@ -17693,6 +17480,23 @@ async function loadLicensingExtension() {
17693
17480
  licensing.registerCommands(program);
17694
17481
  }
17695
17482
  } catch {
17483
+ const licensingMessage = `
17484
+ Cloud licensing features require the @sentriflow/licensing package.
17485
+
17486
+ This package is provided to customers after purchasing a license.
17487
+ Visit https://sentriflow.com.au/pricing for more information.
17488
+
17489
+ Once you have a license, you'll receive access to the private package
17490
+ and can enable cloud features like pack downloads and license activation.
17491
+ `.trim();
17492
+ const fallbackAction = () => {
17493
+ console.log(licensingMessage);
17494
+ process.exit(0);
17495
+ };
17496
+ program.command("activate").description("Activate your SentriFlow license (requires @sentriflow/licensing)").action(fallbackAction);
17497
+ program.command("update").description("Check and download pack updates (requires @sentriflow/licensing)").action(fallbackAction);
17498
+ program.command("offline").description("Manage offline bundles (requires @sentriflow/licensing)").action(fallbackAction);
17499
+ program.command("license").description("Show license status (requires @sentriflow/licensing)").action(fallbackAction);
17696
17500
  }
17697
17501
  }
17698
17502
  loadLicensingExtension().finally(() => {