@sentriflow/cli 0.3.2 → 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 (2) hide show
  1. package/dist/index.js +141 -601
  2. 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`);
@@ -12500,7 +12524,7 @@ function generateSarif(results, filePath, rules, options = {}, ipSummary) {
12500
12524
  tool: {
12501
12525
  driver: {
12502
12526
  name: "Sentriflow",
12503
- version: "0.3.2",
12527
+ version: "0.4.0",
12504
12528
  informationUri: "https://github.com/sentriflow/sentriflow",
12505
12529
  rules: sarifRules,
12506
12530
  // SEC-007: Include CWE taxonomy when rules reference it
@@ -12660,7 +12684,7 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
12660
12684
  tool: {
12661
12685
  driver: {
12662
12686
  name: "Sentriflow",
12663
- version: "0.3.2",
12687
+ version: "0.4.0",
12664
12688
  informationUri: "https://github.com/sentriflow/sentriflow",
12665
12689
  rules: sarifRules,
12666
12690
  // SEC-007: Include CWE taxonomy when rules reference it
@@ -12874,10 +12898,10 @@ var InterfaceDescriptionRequired = {
12874
12898
  loc: node.loc
12875
12899
  };
12876
12900
  }
12877
- const hasDescription9 = node.children.some(
12901
+ const hasDescription5 = node.children.some(
12878
12902
  (child) => startsWithIgnoreCase(child.id, "description")
12879
12903
  );
12880
- if (!hasDescription9) {
12904
+ if (!hasDescription5) {
12881
12905
  return {
12882
12906
  passed: false,
12883
12907
  message: `Interface "${node.params.slice(1).join(" ")}" is missing a description.`,
@@ -12902,59 +12926,6 @@ var allCommonRules = [
12902
12926
  InterfaceDescriptionRequired
12903
12927
  ];
12904
12928
 
12905
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/common/validation.ts
12906
- var equalsIgnoreCase2 = (a, b) => a != null && b != null && a.toLowerCase() === b.toLowerCase();
12907
- var includesIgnoreCase2 = (haystack, needle) => haystack != null && needle != null && haystack.toLowerCase().includes(needle.toLowerCase());
12908
- var startsWithIgnoreCase2 = (str, prefix) => str != null && prefix != null && str.toLowerCase().startsWith(prefix.toLowerCase());
12909
-
12910
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/common/helpers.ts
12911
- var hasChildCommand5 = (node, prefix) => {
12912
- if (!node?.children || !prefix) return false;
12913
- return node.children.some(
12914
- (child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase()) ?? false
12915
- );
12916
- };
12917
- var getChildCommand5 = (node, prefix) => {
12918
- if (!node?.children || !prefix) return void 0;
12919
- return node.children.find(
12920
- (child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase()) ?? false
12921
- );
12922
- };
12923
- var isShutdown6 = (node) => {
12924
- if (!node?.children) return false;
12925
- return node.children.some((child) => {
12926
- const id = child?.id?.toLowerCase().trim();
12927
- return id === "shutdown" || id === "disable";
12928
- });
12929
- };
12930
-
12931
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/cisco/helpers.ts
12932
- var isShutdown7 = (node) => {
12933
- if (!node?.children) return false;
12934
- return node.children.some((child) => {
12935
- return child?.id && equalsIgnoreCase2(child.id.trim(), "shutdown");
12936
- });
12937
- };
12938
- var isPhysicalPort4 = (interfaceName) => {
12939
- return !includesIgnoreCase2(interfaceName, "loopback") && !includesIgnoreCase2(interfaceName, "null") && !includesIgnoreCase2(interfaceName, "vlan") && !includesIgnoreCase2(interfaceName, "tunnel") && !includesIgnoreCase2(interfaceName, "port-channel") && !includesIgnoreCase2(interfaceName, "bvi") && !includesIgnoreCase2(interfaceName, "nve");
12940
- };
12941
- var isTrunkPort4 = (node) => {
12942
- return hasChildCommand5(node, "switchport mode trunk");
12943
- };
12944
- var isTrunkToNonCisco2 = (node) => {
12945
- const desc = getChildCommand5(node, "description");
12946
- if (desc) {
12947
- const descText = desc.rawText;
12948
- if (includesIgnoreCase2(descText, "server:") || includesIgnoreCase2(descText, "storage:") || includesIgnoreCase2(descText, "esx") || includesIgnoreCase2(descText, "vmware") || includesIgnoreCase2(descText, "hyperv") || includesIgnoreCase2(descText, "hyper-v") || includesIgnoreCase2(descText, "linux") || includesIgnoreCase2(descText, "appliance") || includesIgnoreCase2(descText, "firewall") || includesIgnoreCase2(descText, "loadbalancer") || includesIgnoreCase2(descText, "lb:") || includesIgnoreCase2(descText, "nas:") || includesIgnoreCase2(descText, "san:")) {
12949
- return true;
12950
- }
12951
- if (includesIgnoreCase2(descText, "uplink:") || includesIgnoreCase2(descText, "downlink:") || includesIgnoreCase2(descText, "isl:") || includesIgnoreCase2(descText, "po-member:")) {
12952
- return false;
12953
- }
12954
- }
12955
- return false;
12956
- };
12957
-
12958
12929
  // ../rules-default/src/cisco/ios-rules.ts
12959
12930
  var TrunkNoDTP = {
12960
12931
  id: "NET-TRUNK-001",
@@ -12968,16 +12939,16 @@ var TrunkNoDTP = {
12968
12939
  remediation: 'Add "switchport nonegotiate" to disable DTP on trunk ports connected to non-Cisco devices.'
12969
12940
  },
12970
12941
  check: (node) => {
12971
- if (!isPhysicalPort4(node.id) || isShutdown7(node)) {
12942
+ if (!isPhysicalPort(node.id) || isShutdown3(node)) {
12972
12943
  return { passed: true, message: "Not applicable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
12973
12944
  }
12974
- if (!isTrunkPort4(node)) {
12945
+ if (!isTrunkPort2(node)) {
12975
12946
  return { passed: true, message: "Not a trunk port.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
12976
12947
  }
12977
- if (!isTrunkToNonCisco2(node)) {
12948
+ if (!isTrunkToNonCisco(node)) {
12978
12949
  return { passed: true, message: "Trunk to Cisco device - DTP acceptable.", ruleId: "NET-TRUNK-001", nodeId: node.id, level: "info", loc: node.loc };
12979
12950
  }
12980
- if (!hasChildCommand5(node, "switchport nonegotiate")) {
12951
+ if (!hasChildCommand(node, "switchport nonegotiate")) {
12981
12952
  return {
12982
12953
  passed: false,
12983
12954
  message: `Trunk port "${node.params.slice(1).join(" ")}" connected to non-Cisco device needs "switchport nonegotiate".`,
@@ -13002,11 +12973,11 @@ var AccessExplicitMode = {
13002
12973
  remediation: 'Add "switchport mode access" to explicitly configure access mode.'
13003
12974
  },
13004
12975
  check: (node) => {
13005
- if (!isPhysicalPort4(node.id) || isShutdown7(node)) {
12976
+ if (!isPhysicalPort(node.id) || isShutdown3(node)) {
13006
12977
  return { passed: true, message: "Not applicable.", ruleId: "NET-ACCESS-001", nodeId: node.id, level: "info", loc: node.loc };
13007
12978
  }
13008
- const hasAccessVlan = hasChildCommand5(node, "switchport access vlan");
13009
- const hasExplicitMode = hasChildCommand5(node, "switchport mode");
12979
+ const hasAccessVlan = hasChildCommand(node, "switchport access vlan");
12980
+ const hasExplicitMode = hasChildCommand(node, "switchport mode");
13010
12981
  if (hasAccessVlan && !hasExplicitMode) {
13011
12982
  return {
13012
12983
  passed: false,
@@ -13066,15 +13037,26 @@ var CiscoNoPlaintextPasswords = {
13066
13037
  level: "error",
13067
13038
  obu: "Security",
13068
13039
  owner: "SecOps",
13069
- 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.'
13070
13041
  },
13071
- check: (node) => {
13042
+ check: (node, context) => {
13072
13043
  const params = node.params;
13073
- const nodeId = node.id;
13074
- if (includesIgnoreCase(nodeId, "encryption") || includesIgnoreCase(nodeId, "service")) {
13044
+ const nodeId = node.id.toLowerCase();
13045
+ if (nodeId.includes("encryption") || nodeId.includes("service")) {
13075
13046
  return {
13076
13047
  passed: true,
13077
- 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).",
13078
13060
  ruleId: "NET-SEC-001",
13079
13061
  nodeId: node.id,
13080
13062
  level: "info",
@@ -13083,20 +13065,10 @@ var CiscoNoPlaintextPasswords = {
13083
13065
  }
13084
13066
  if (params.length >= 2) {
13085
13067
  const typeOrValue = params[1];
13086
- if (!typeOrValue) {
13087
- return {
13088
- passed: false,
13089
- message: 'Possible plaintext password detected. Use encryption type 7 or "secret" command.',
13090
- ruleId: "NET-SEC-001",
13091
- nodeId: node.id,
13092
- level: "error",
13093
- loc: node.loc
13094
- };
13095
- }
13096
- if (typeOrValue === "7" || typeOrValue === "5" || typeOrValue === "8" || typeOrValue === "9") {
13068
+ if (typeOrValue && ["5", "7", "8", "9"].includes(typeOrValue)) {
13097
13069
  return {
13098
13070
  passed: true,
13099
- message: "Password is encrypted.",
13071
+ message: `Password is encrypted (type ${typeOrValue}).`,
13100
13072
  ruleId: "NET-SEC-001",
13101
13073
  nodeId: node.id,
13102
13074
  level: "info",
@@ -13106,17 +13078,17 @@ var CiscoNoPlaintextPasswords = {
13106
13078
  if (typeOrValue === "0") {
13107
13079
  return {
13108
13080
  passed: false,
13109
- message: "Plaintext password detected (type 0).",
13081
+ message: 'Plaintext password detected (type 0). Use "secret" or encrypt with type 7.',
13110
13082
  ruleId: "NET-SEC-001",
13111
13083
  nodeId: node.id,
13112
13084
  level: "error",
13113
13085
  loc: node.loc
13114
13086
  };
13115
13087
  }
13116
- if (!/^\d+$/.test(typeOrValue)) {
13088
+ if (typeOrValue && !/^\d+$/.test(typeOrValue)) {
13117
13089
  return {
13118
13090
  passed: false,
13119
- message: 'Possible plaintext password detected. Use encryption type 7 or "secret" command.',
13091
+ message: 'Plaintext password detected. Use "secret" or encrypt with type 7.',
13120
13092
  ruleId: "NET-SEC-001",
13121
13093
  nodeId: node.id,
13122
13094
  level: "error",
@@ -13144,38 +13116,6 @@ var allCiscoRules = [
13144
13116
  EnableSecretStrong
13145
13117
  ];
13146
13118
 
13147
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/juniper/helpers.ts
13148
- var findStanza8 = (node, stanzaName) => {
13149
- if (!node?.children) return void 0;
13150
- return node.children.find(
13151
- (child) => child?.id && equalsIgnoreCase2(child.id, stanzaName)
13152
- );
13153
- };
13154
- var findStanzas7 = (node, pattern) => {
13155
- if (!node?.children) return [];
13156
- return node.children.filter((child) => child?.id && pattern.test(child.id));
13157
- };
13158
- var isFilterTermDrop2 = (termNode) => {
13159
- if (!termNode?.children) return false;
13160
- for (const child of termNode.children) {
13161
- const id = child?.id?.trim();
13162
- if (!id) continue;
13163
- if (equalsIgnoreCase2(id, "then discard") || equalsIgnoreCase2(id, "then discard;") || equalsIgnoreCase2(id, "then reject") || equalsIgnoreCase2(id, "then reject;")) {
13164
- return true;
13165
- }
13166
- }
13167
- const thenStanza = findStanza8(termNode, "then");
13168
- if (!thenStanza?.children) return false;
13169
- for (const child of thenStanza.children) {
13170
- const id = child?.id?.trim();
13171
- if (!id) continue;
13172
- if (equalsIgnoreCase2(id, "discard") || equalsIgnoreCase2(id, "discard;") || equalsIgnoreCase2(id, "reject") || equalsIgnoreCase2(id, "reject;")) {
13173
- return true;
13174
- }
13175
- }
13176
- return false;
13177
- };
13178
-
13179
13119
  // ../rules-default/src/juniper/junos-rules.ts
13180
13120
  var RootAuthRequired = {
13181
13121
  id: "JUN-SYS-001",
@@ -13189,7 +13129,7 @@ var RootAuthRequired = {
13189
13129
  remediation: 'Configure "root-authentication" under system stanza with encrypted-password or ssh-rsa.'
13190
13130
  },
13191
13131
  check: (node) => {
13192
- const rootAuth = findStanza8(node, "root-authentication");
13132
+ const rootAuth = findStanza4(node, "root-authentication");
13193
13133
  if (!rootAuth) {
13194
13134
  return {
13195
13135
  passed: false,
@@ -13200,8 +13140,8 @@ var RootAuthRequired = {
13200
13140
  loc: node.loc
13201
13141
  };
13202
13142
  }
13203
- const hasPassword = hasChildCommand5(rootAuth, "encrypted-password");
13204
- 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");
13205
13145
  if (!hasPassword && !hasSshKey) {
13206
13146
  return {
13207
13147
  passed: false,
@@ -13234,7 +13174,7 @@ var JunosBgpRouterId = {
13234
13174
  remediation: 'Configure "router-id" under routing-options stanza.'
13235
13175
  },
13236
13176
  check: (node) => {
13237
- const hasRouterId = hasChildCommand5(node, "router-id");
13177
+ const hasRouterId = hasChildCommand(node, "router-id");
13238
13178
  if (!hasRouterId) {
13239
13179
  return {
13240
13180
  passed: false,
@@ -13285,7 +13225,7 @@ var JunosFirewallDefaultDeny = {
13285
13225
  }
13286
13226
  const issues = [];
13287
13227
  for (const filter of filters) {
13288
- const terms = findStanzas7(filter, /^term/i);
13228
+ const terms = findStanzas3(filter, /^term/i);
13289
13229
  if (terms.length === 0) {
13290
13230
  continue;
13291
13231
  }
@@ -13293,7 +13233,7 @@ var JunosFirewallDefaultDeny = {
13293
13233
  if (!lastTerm) {
13294
13234
  continue;
13295
13235
  }
13296
- if (!isFilterTermDrop2(lastTerm)) {
13236
+ if (!isFilterTermDrop(lastTerm)) {
13297
13237
  issues.push(`Filter "${filter.id}" does not end with a deny term.`);
13298
13238
  }
13299
13239
  }
@@ -13326,69 +13266,6 @@ var allJuniperRules = [
13326
13266
  JunosFirewallDefaultDeny
13327
13267
  ];
13328
13268
 
13329
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/aruba/helpers.ts
13330
- var getInterfaceName4 = (node) => {
13331
- if (!node?.id) return void 0;
13332
- const match = node.id.match(/interface\s+(.+)/i);
13333
- const ifName = match?.[1]?.trim();
13334
- return ifName && ifName.length > 0 ? ifName : void 0;
13335
- };
13336
- var isAosCxPhysicalPort2 = (interfaceName) => {
13337
- return /^\d+\/\d+\/\d+$/.test(interfaceName.trim());
13338
- };
13339
- var isAosCxLag2 = (interfaceName) => {
13340
- return /^lag\s*\d+$/i.test(interfaceName.trim());
13341
- };
13342
- var isAosCxTrunk2 = (node) => {
13343
- return hasChildCommand5(node, "vlan trunk");
13344
- };
13345
- var getAosCxTrunkAllowed2 = (node) => {
13346
- const cmd = getChildCommand5(node, "vlan trunk allowed");
13347
- if (!cmd) return [];
13348
- const match = cmd.id.match(/vlan\s+trunk\s+allowed\s+([\d,]+)/i);
13349
- const vlanList = match?.[1];
13350
- if (!vlanList) return [];
13351
- return vlanList.split(",").map((v) => parseInt(v.trim(), 10)).filter((v) => !isNaN(v));
13352
- };
13353
- var getAosSwitchVlanName2 = (node) => {
13354
- const cmd = getChildCommand5(node, "name");
13355
- if (!cmd) return void 0;
13356
- const match = cmd.id.match(/name\s+["']?([^"']+)["']?/i);
13357
- const name = match?.[1];
13358
- return name?.trim();
13359
- };
13360
- var getWlanEncryption2 = (node) => {
13361
- const cmd = getChildCommand5(node, "opmode");
13362
- if (!cmd) return null;
13363
- const match = cmd.id.match(/opmode\s+(\S+)/i);
13364
- const mode = match?.[1];
13365
- return mode ? mode.toLowerCase() : null;
13366
- };
13367
- var hasSecureEncryption2 = (node) => {
13368
- const opmode = getWlanEncryption2(node);
13369
- if (!opmode) return false;
13370
- return opmode.includes("wpa2") || opmode.includes("wpa3") || opmode.includes("aes");
13371
- };
13372
- var isOpenSsid2 = (node) => {
13373
- const opmode = getWlanEncryption2(node);
13374
- return opmode === "opensystem" || opmode === "open";
13375
- };
13376
- var getRadiusHost2 = (node) => {
13377
- const cmd = getChildCommand5(node, "host");
13378
- if (!cmd) return void 0;
13379
- const match = cmd.id.match(/host\s+(\S+)/i);
13380
- const host = match?.[1];
13381
- return host?.trim();
13382
- };
13383
- var extractProfileName2 = (nodeId) => {
13384
- const match = nodeId.match(/(?:ssid-profile|virtual-ap|profile|server-group|ap-group|arm-profile)\s+["']?([^"'\n]+)["']?$/i);
13385
- const profile = match?.[1];
13386
- return profile ? profile.trim() : void 0;
13387
- };
13388
- var hasDescription5 = (node) => {
13389
- return hasChildCommand5(node, "description");
13390
- };
13391
-
13392
13269
  // ../rules-default/src/aruba/common-rules.ts
13393
13270
  var SshEnabled = {
13394
13271
  id: "ARU-SEC-001",
@@ -13476,17 +13353,17 @@ var AosCxInterfaceDescription = {
13476
13353
  remediation: "Add a description to physical interfaces for documentation."
13477
13354
  },
13478
13355
  check: (node) => {
13479
- const ifName = getInterfaceName4(node);
13356
+ const ifName = getInterfaceName(node);
13480
13357
  if (!ifName) {
13481
13358
  return { passed: true, message: "Not an interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
13482
13359
  }
13483
- if (!isAosCxPhysicalPort2(ifName) && !isAosCxLag2(ifName)) {
13360
+ if (!isAosCxPhysicalPort(ifName) && !isAosCxLag(ifName)) {
13484
13361
  return { passed: true, message: "Not a physical interface.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
13485
13362
  }
13486
- if (isShutdown6(node)) {
13363
+ if (isShutdown(node)) {
13487
13364
  return { passed: true, message: "Interface is shutdown.", ruleId: "AOSCX-IF-001", nodeId: node.id, level: "info", loc: node.loc };
13488
13365
  }
13489
- if (!hasDescription5(node)) {
13366
+ if (!hasDescription(node)) {
13490
13367
  return {
13491
13368
  passed: false,
13492
13369
  message: `Interface ${ifName} missing description.`,
@@ -13518,17 +13395,17 @@ var AosCxTrunkAllowedVlans = {
13518
13395
  remediation: 'Configure "vlan trunk allowed <vlans>" on trunk interfaces.'
13519
13396
  },
13520
13397
  check: (node) => {
13521
- const ifName = getInterfaceName4(node);
13398
+ const ifName = getInterfaceName(node);
13522
13399
  if (!ifName) {
13523
13400
  return { passed: true, message: "Not an interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
13524
13401
  }
13525
- if (!isAosCxPhysicalPort2(ifName) && !isAosCxLag2(ifName)) {
13402
+ if (!isAosCxPhysicalPort(ifName) && !isAosCxLag(ifName)) {
13526
13403
  return { passed: true, message: "Not a switchport interface.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
13527
13404
  }
13528
- if (!isAosCxTrunk2(node)) {
13405
+ if (!isAosCxTrunk(node)) {
13529
13406
  return { passed: true, message: "Not a trunk port.", ruleId: "AOSCX-L2-001", nodeId: node.id, level: "info", loc: node.loc };
13530
13407
  }
13531
- const allowedVlans = getAosCxTrunkAllowed2(node);
13408
+ const allowedVlans = getAosCxTrunkAllowed(node);
13532
13409
  if (allowedVlans.length === 0) {
13533
13410
  return {
13534
13411
  passed: false,
@@ -13578,7 +13455,7 @@ var AosSwitchVlanName = {
13578
13455
  if (vlanId === null || isDefaultVlan(vlanId)) {
13579
13456
  return { passed: true, message: "Default VLAN 1.", ruleId: "AOSSW-L2-001", nodeId: node.id, level: "info", loc: node.loc };
13580
13457
  }
13581
- const vlanName = getAosSwitchVlanName2(node);
13458
+ const vlanName = getAosSwitchVlanName(node);
13582
13459
  if (!vlanName) {
13583
13460
  return {
13584
13461
  passed: false,
@@ -13652,8 +13529,8 @@ var WlcSsidEncryption = {
13652
13529
  remediation: 'Configure "opmode wpa2-aes" or "opmode wpa3-sae-aes" for secure encryption.'
13653
13530
  },
13654
13531
  check: (node) => {
13655
- const profileName = extractProfileName2(node.id);
13656
- const opmode = getWlanEncryption2(node);
13532
+ const profileName = extractProfileName(node.id);
13533
+ const opmode = getWlanEncryption(node);
13657
13534
  if (!opmode) {
13658
13535
  return {
13659
13536
  passed: false,
@@ -13664,7 +13541,7 @@ var WlcSsidEncryption = {
13664
13541
  loc: node.loc
13665
13542
  };
13666
13543
  }
13667
- if (isOpenSsid2(node)) {
13544
+ if (isOpenSsid(node)) {
13668
13545
  return {
13669
13546
  passed: false,
13670
13547
  message: `SSID profile "${profileName}" is open/unencrypted. Use WPA2 or WPA3.`,
@@ -13674,7 +13551,7 @@ var WlcSsidEncryption = {
13674
13551
  loc: node.loc
13675
13552
  };
13676
13553
  }
13677
- if (!hasSecureEncryption2(node)) {
13554
+ if (!hasSecureEncryption(node)) {
13678
13555
  return {
13679
13556
  passed: false,
13680
13557
  message: `SSID profile "${profileName}" uses weak encryption (${opmode}). Use WPA2 or WPA3.`,
@@ -13706,8 +13583,8 @@ var WlcRadiusHost = {
13706
13583
  remediation: 'Configure "host <ip-address>" for the RADIUS server.'
13707
13584
  },
13708
13585
  check: (node) => {
13709
- const profileName = extractProfileName2(node.id);
13710
- const host = getRadiusHost2(node);
13586
+ const profileName = extractProfileName(node.id);
13587
+ const host = getRadiusHost(node);
13711
13588
  if (!host) {
13712
13589
  return {
13713
13590
  passed: false,
@@ -13755,34 +13632,6 @@ function getRulesByArubaVendor(vendorId) {
13755
13632
  }
13756
13633
  }
13757
13634
 
13758
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/paloalto/helpers.ts
13759
- var findStanza9 = (node, stanzaName) => {
13760
- if (!node?.children) return void 0;
13761
- return node.children.find(
13762
- (child) => child?.id?.toLowerCase() === stanzaName.toLowerCase()
13763
- );
13764
- };
13765
- var hasLogging3 = (ruleNode) => {
13766
- const logStart = hasChildCommand5(ruleNode, "log-start");
13767
- const logEnd = hasChildCommand5(ruleNode, "log-end");
13768
- return { logStart, logEnd };
13769
- };
13770
- var isRuleDisabled2 = (ruleNode) => {
13771
- const disabled = getChildCommand5(ruleNode, "disabled");
13772
- if (!disabled) return false;
13773
- return disabled.id.toLowerCase().includes("yes") || disabled.id.toLowerCase().includes("true");
13774
- };
13775
- var getSecurityRules2 = (rulebaseNode) => {
13776
- const security = findStanza9(rulebaseNode, "security");
13777
- if (!security) return [];
13778
- const rules = findStanza9(security, "rules");
13779
- if (!rules?.children) return [];
13780
- return rules.children;
13781
- };
13782
- var hasZoneProtectionProfile2 = (zoneNode) => {
13783
- return hasChildCommand5(zoneNode, "zone-protection-profile");
13784
- };
13785
-
13786
13635
  // ../rules-default/src/paloalto/panos-rules.ts
13787
13636
  var HostnameRequired = {
13788
13637
  id: "PAN-SYS-001",
@@ -13796,7 +13645,7 @@ var HostnameRequired = {
13796
13645
  remediation: "Configure hostname under deviceconfig > system."
13797
13646
  },
13798
13647
  check: (node) => {
13799
- const system = findStanza9(node, "system");
13648
+ const system = findStanza6(node, "system");
13800
13649
  if (!system) {
13801
13650
  return {
13802
13651
  passed: false,
@@ -13807,7 +13656,7 @@ var HostnameRequired = {
13807
13656
  loc: node.loc
13808
13657
  };
13809
13658
  }
13810
- const hasHostname = hasChildCommand5(system, "hostname");
13659
+ const hasHostname = hasChildCommand(system, "hostname");
13811
13660
  if (!hasHostname) {
13812
13661
  return {
13813
13662
  passed: false,
@@ -13840,7 +13689,7 @@ var SecurityRuleLogging = {
13840
13689
  remediation: "Enable log-end (and optionally log-start) on all security rules."
13841
13690
  },
13842
13691
  check: (node) => {
13843
- const rules = getSecurityRules2(node);
13692
+ const rules = getSecurityRules(node);
13844
13693
  if (rules.length === 0) {
13845
13694
  return {
13846
13695
  passed: true,
@@ -13853,8 +13702,8 @@ var SecurityRuleLogging = {
13853
13702
  }
13854
13703
  const issues = [];
13855
13704
  for (const rule of rules) {
13856
- if (isRuleDisabled2(rule)) continue;
13857
- const logging = hasLogging3(rule);
13705
+ if (isRuleDisabled(rule)) continue;
13706
+ const logging = hasLogging2(rule);
13858
13707
  if (!logging.logEnd) {
13859
13708
  issues.push(`Rule "${rule.id}" does not have log-end enabled.`);
13860
13709
  }
@@ -13893,7 +13742,7 @@ var ZoneProtectionRequired = {
13893
13742
  check: (node) => {
13894
13743
  const issues = [];
13895
13744
  for (const zone of node.children) {
13896
- if (!hasZoneProtectionProfile2(zone)) {
13745
+ if (!hasZoneProtectionProfile(zone)) {
13897
13746
  issues.push(`Zone "${zone.id}" does not have a Zone Protection Profile applied.`);
13898
13747
  }
13899
13748
  }
@@ -13941,34 +13790,6 @@ function getRulesByPaloAltoVendor() {
13941
13790
  return [...allCommonRules, ...allPaloAltoRules];
13942
13791
  }
13943
13792
 
13944
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/arista/helpers.ts
13945
- var checkMlagRequirements2 = (mlagNode) => {
13946
- return {
13947
- hasDomainId: hasChildCommand5(mlagNode, "domain-id"),
13948
- hasPeerLink: hasChildCommand5(mlagNode, "peer-link"),
13949
- hasPeerAddress: hasChildCommand5(mlagNode, "peer-address"),
13950
- hasLocalInterface: hasChildCommand5(mlagNode, "local-interface")
13951
- };
13952
- };
13953
- var isShutdown8 = (interfaceNode) => {
13954
- if (!interfaceNode?.children) return false;
13955
- const hasShutdown = interfaceNode.children.some(
13956
- (child) => child?.id && equalsIgnoreCase2(child.id, "shutdown")
13957
- );
13958
- const hasNoShutdown = interfaceNode.children.some(
13959
- (child) => child?.id && equalsIgnoreCase2(child.id, "no shutdown")
13960
- );
13961
- return hasShutdown && !hasNoShutdown;
13962
- };
13963
- var getInterfaceDescription2 = (interfaceNode) => {
13964
- if (!interfaceNode?.children) return void 0;
13965
- const descCmd = interfaceNode.children.find(
13966
- (child) => child?.id && startsWithIgnoreCase2(child.id, "description ")
13967
- );
13968
- if (!descCmd) return void 0;
13969
- return descCmd.id.replace(/^description\s+/i, "").trim();
13970
- };
13971
-
13972
13793
  // ../rules-default/src/arista/eos-rules.ts
13973
13794
  var HostnameRequired2 = {
13974
13795
  id: "ARI-SYS-001",
@@ -14017,7 +13838,7 @@ var MlagConfigComplete = {
14017
13838
  remediation: "MLAG configuration requires: domain-id, peer-link, peer-address, and local-interface."
14018
13839
  },
14019
13840
  check: (node, context) => {
14020
- const requirements = checkMlagRequirements2(node);
13841
+ const requirements = checkMlagRequirements(node);
14021
13842
  const issues = [];
14022
13843
  if (!requirements.hasDomainId) {
14023
13844
  issues.push("domain-id");
@@ -14063,7 +13884,7 @@ var InterfaceDescription = {
14063
13884
  remediation: "Add description to interface: description <text>"
14064
13885
  },
14065
13886
  check: (node, context) => {
14066
- if (isShutdown8(node)) {
13887
+ if (isShutdown2(node)) {
14067
13888
  return {
14068
13889
  passed: true,
14069
13890
  message: "Interface is shutdown, description not required.",
@@ -14073,7 +13894,7 @@ var InterfaceDescription = {
14073
13894
  loc: node.loc
14074
13895
  };
14075
13896
  }
14076
- const description = getInterfaceDescription2(node);
13897
+ const description = getInterfaceDescription(node);
14077
13898
  if (!description) {
14078
13899
  return {
14079
13900
  passed: false,
@@ -14108,37 +13929,6 @@ function getRulesByAristaVendor() {
14108
13929
  return [...allCommonRules, ...allAristaRules];
14109
13930
  }
14110
13931
 
14111
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/vyos/helpers.ts
14112
- var isDisabled4 = (node) => {
14113
- if (!node?.children) return false;
14114
- return node.children.some((child) => child?.id?.toLowerCase().trim() === "disable");
14115
- };
14116
- var findStanzasByPrefix3 = (node, prefix) => {
14117
- if (!node?.children) return [];
14118
- return node.children.filter(
14119
- (child) => child?.id?.toLowerCase().startsWith(prefix.toLowerCase())
14120
- );
14121
- };
14122
- var getEthernetInterfaces2 = (interfacesNode) => {
14123
- if (!interfacesNode?.children) return [];
14124
- return interfacesNode.children.filter(
14125
- (child) => child?.id?.toLowerCase().startsWith("ethernet eth")
14126
- );
14127
- };
14128
- var getFirewallDefaultAction2 = (rulesetNode) => {
14129
- if (!rulesetNode?.children) return void 0;
14130
- for (const child of rulesetNode.children) {
14131
- const id = child?.id?.toLowerCase().trim();
14132
- if (!id) continue;
14133
- if (id.startsWith("default-action")) {
14134
- if (id.includes("drop")) return "drop";
14135
- if (id.includes("accept")) return "accept";
14136
- if (id.includes("reject")) return "reject";
14137
- }
14138
- }
14139
- return void 0;
14140
- };
14141
-
14142
13932
  // ../rules-default/src/vyos/vyos-rules.ts
14143
13933
  var VyosHostnameRequired = {
14144
13934
  id: "VYOS-SYS-001",
@@ -14152,7 +13942,7 @@ var VyosHostnameRequired = {
14152
13942
  remediation: 'Configure "host-name" under system stanza.'
14153
13943
  },
14154
13944
  check: (node) => {
14155
- const hasHostname = hasChildCommand5(node, "host-name");
13945
+ const hasHostname = hasChildCommand(node, "host-name");
14156
13946
  if (!hasHostname) {
14157
13947
  return {
14158
13948
  passed: false,
@@ -14221,12 +14011,12 @@ var VyosInterfaceDescription = {
14221
14011
  },
14222
14012
  check: (node) => {
14223
14013
  const issues = [];
14224
- const ethernetInterfaces = getEthernetInterfaces2(node);
14014
+ const ethernetInterfaces = getEthernetInterfaces(node);
14225
14015
  for (const iface of ethernetInterfaces) {
14226
- if (isDisabled4(iface)) {
14016
+ if (isDisabled2(iface)) {
14227
14017
  continue;
14228
14018
  }
14229
- const hasDesc = hasChildCommand5(iface, "description");
14019
+ const hasDesc = hasChildCommand(iface, "description");
14230
14020
  if (!hasDesc) {
14231
14021
  const ifaceName = iface.id.split(/\s+/).pop() || iface.id;
14232
14022
  issues.push(`Interface "${ifaceName}" missing description.`);
@@ -14264,7 +14054,7 @@ var VyosFirewallDefaultAction = {
14264
14054
  remediation: 'Set "default-action drop" or "default-action reject" for each firewall ruleset.'
14265
14055
  },
14266
14056
  check: (node) => {
14267
- const rulesets = findStanzasByPrefix3(node, "name");
14057
+ const rulesets = findStanzasByPrefix2(node, "name");
14268
14058
  if (rulesets.length === 0) {
14269
14059
  return {
14270
14060
  passed: true,
@@ -14277,7 +14067,7 @@ var VyosFirewallDefaultAction = {
14277
14067
  }
14278
14068
  const issues = [];
14279
14069
  for (const ruleset of rulesets) {
14280
- const defaultAction = getFirewallDefaultAction2(ruleset);
14070
+ const defaultAction = getFirewallDefaultAction(ruleset);
14281
14071
  if (!defaultAction) {
14282
14072
  const rulesetName = ruleset.id.split(/\s+/)[1] || ruleset.id;
14283
14073
  issues.push(`Firewall ruleset "${rulesetName}" has no default-action configured.`);
@@ -14319,65 +14109,6 @@ function getRulesByVyosVendor() {
14319
14109
  return [...allCommonRules, ...allVyosRules];
14320
14110
  }
14321
14111
 
14322
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/fortinet/helpers.ts
14323
- var getEditEntries2 = (configSection) => {
14324
- if (!configSection?.children) return [];
14325
- return configSection.children.filter(
14326
- (child) => child?.id?.toLowerCase().startsWith("edit ")
14327
- );
14328
- };
14329
- var getEditEntryName2 = (editEntry) => {
14330
- const match = editEntry.id.match(/^edit\s+["']?([^"']+)["']?$/i);
14331
- const entryName = match?.[1];
14332
- return entryName ?? editEntry.id;
14333
- };
14334
- var getSetValue2 = (node, paramName) => {
14335
- if (!node?.children) return void 0;
14336
- const normalizedParam = paramName.toLowerCase();
14337
- for (const child of node.children) {
14338
- const childId = child?.id?.toLowerCase();
14339
- if (!childId) continue;
14340
- const match = childId.match(new RegExp(`^set\\s+${normalizedParam}\\s+(.+)$`, "i"));
14341
- const value = match?.[1];
14342
- if (value) {
14343
- return value.replace(/^["']|["']$/g, "").trim();
14344
- }
14345
- }
14346
- return void 0;
14347
- };
14348
- var isPolicyDisabled2 = (policyNode) => {
14349
- const status = getSetValue2(policyNode, "status");
14350
- return status?.toLowerCase() === "disable";
14351
- };
14352
- var hasLogging4 = (policyNode) => {
14353
- const logtraffic = getSetValue2(policyNode, "logtraffic");
14354
- const logtrafficStart = getSetValue2(policyNode, "logtraffic-start");
14355
- return {
14356
- logtraffic,
14357
- logtrafficStart: logtrafficStart?.toLowerCase() === "enable"
14358
- };
14359
- };
14360
- var getAdminProfile2 = (adminNode) => {
14361
- return getSetValue2(adminNode, "accprofile");
14362
- };
14363
- var isSuperAdmin2 = (adminNode) => {
14364
- const profile = getAdminProfile2(adminNode);
14365
- return profile?.toLowerCase() === "super_admin";
14366
- };
14367
- var getAdminTrustedHosts2 = (adminNode) => {
14368
- const trustedHosts = [];
14369
- for (let i = 1; i <= 10; i++) {
14370
- const host = getSetValue2(adminNode, `trusthost${i}`);
14371
- if (host && host !== "0.0.0.0 0.0.0.0") {
14372
- trustedHosts.push(host);
14373
- }
14374
- }
14375
- return trustedHosts;
14376
- };
14377
- var hasAdminTrustedHosts2 = (adminNode) => {
14378
- return getAdminTrustedHosts2(adminNode).length > 0;
14379
- };
14380
-
14381
14112
  // ../rules-default/src/fortinet/fortigate-rules.ts
14382
14113
  var HostnameRequired3 = {
14383
14114
  id: "FGT-SYS-001",
@@ -14391,7 +14122,7 @@ var HostnameRequired3 = {
14391
14122
  remediation: 'Configure hostname under "config system global" using "set hostname <name>".'
14392
14123
  },
14393
14124
  check: (node) => {
14394
- const hostname = getSetValue2(node, "hostname");
14125
+ const hostname = getSetValue(node, "hostname");
14395
14126
  if (!hostname) {
14396
14127
  return {
14397
14128
  passed: false,
@@ -14424,7 +14155,7 @@ var AdminTrustedHostRequired = {
14424
14155
  remediation: 'Configure trusted hosts for each admin user using "set trusthost1", "set trusthost2", etc.'
14425
14156
  },
14426
14157
  check: (node) => {
14427
- const admins = getEditEntries2(node);
14158
+ const admins = getEditEntries(node);
14428
14159
  if (admins.length === 0) {
14429
14160
  return {
14430
14161
  passed: true,
@@ -14437,11 +14168,11 @@ var AdminTrustedHostRequired = {
14437
14168
  }
14438
14169
  const issues = [];
14439
14170
  for (const admin of admins) {
14440
- const adminName = getEditEntryName2(admin);
14441
- if (hasAdminTrustedHosts2(admin)) {
14171
+ const adminName = getEditEntryName(admin);
14172
+ if (hasAdminTrustedHosts(admin)) {
14442
14173
  continue;
14443
14174
  }
14444
- if (isSuperAdmin2(admin)) {
14175
+ if (isSuperAdmin(admin)) {
14445
14176
  issues.push(`Super admin "${adminName}" has no trusted host restrictions. This is a critical security issue.`);
14446
14177
  } else {
14447
14178
  issues.push(`Admin "${adminName}" has no trusted host restrictions.`);
@@ -14479,7 +14210,7 @@ var PolicyLoggingRequired = {
14479
14210
  remediation: 'Enable logging on all firewall policies using "set logtraffic all" or "set logtraffic utm".'
14480
14211
  },
14481
14212
  check: (node) => {
14482
- const policies = getEditEntries2(node);
14213
+ const policies = getEditEntries(node);
14483
14214
  if (policies.length === 0) {
14484
14215
  return {
14485
14216
  passed: true,
@@ -14492,10 +14223,10 @@ var PolicyLoggingRequired = {
14492
14223
  }
14493
14224
  const issues = [];
14494
14225
  for (const policy of policies) {
14495
- if (isPolicyDisabled2(policy)) continue;
14496
- const logging = hasLogging4(policy);
14226
+ if (isPolicyDisabled(policy)) continue;
14227
+ const logging = hasLogging(policy);
14497
14228
  if (!logging.logtraffic || isFeatureDisabled(logging.logtraffic)) {
14498
- const policyId = getEditEntryName2(policy);
14229
+ const policyId = getEditEntryName(policy);
14499
14230
  issues.push(`Policy ${policyId} does not have logging enabled.`);
14500
14231
  }
14501
14232
  }
@@ -14533,44 +14264,6 @@ function getRulesByFortinetVendor() {
14533
14264
  return [...allCommonRules, ...allFortinetRules];
14534
14265
  }
14535
14266
 
14536
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/extreme/helpers.ts
14537
- var getExosVlanName2 = (node) => {
14538
- const match = node.id.match(/^(?:create|configure)\s+vlan\s+["']?(\w+)["']?/i);
14539
- const vlanName = match?.[1];
14540
- return vlanName?.trim();
14541
- };
14542
- var isVossVlanCreate2 = (node) => {
14543
- return /^vlan\s+create\s+\d+/i.test(node.id);
14544
- };
14545
- var getVossVlanId2 = (node) => {
14546
- const match = node.id.match(/^vlan\s+(?:create|members|i-sid)\s+(\d+)/i);
14547
- const vlanId = match?.[1];
14548
- return vlanId ? parseInt(vlanId, 10) : void 0;
14549
- };
14550
- var isVossGigabitEthernet2 = (node) => {
14551
- return /^interface\s+GigabitEthernet\s+\d+\/\d+/i.test(node.id);
14552
- };
14553
- var isVossShutdown2 = (node) => {
14554
- if (!node?.children) return false;
14555
- const hasShutdown = node.children.some(
14556
- (child) => child?.id?.toLowerCase() === "shutdown"
14557
- );
14558
- const hasNoShutdown = node.children.some(
14559
- (child) => child?.id?.toLowerCase() === "no shutdown"
14560
- );
14561
- return hasShutdown && !hasNoShutdown;
14562
- };
14563
- var getVossDefaultVlan2 = (node) => {
14564
- if (!node?.children) return void 0;
14565
- const defaultVlan = node.children.find(
14566
- (child) => child?.id && /^default-vlan-id\s+\d+/i.test(child.id)
14567
- );
14568
- if (!defaultVlan?.id) return void 0;
14569
- const match = defaultVlan.id.match(/default-vlan-id\s+(\d+)/i);
14570
- const vlanId = match?.[1];
14571
- return vlanId ? parseInt(vlanId, 10) : void 0;
14572
- };
14573
-
14574
14267
  // ../rules-default/src/extreme/exos-rules.ts
14575
14268
  var ExosSysnameRequired = {
14576
14269
  id: "EXOS-SYS-001",
@@ -14650,7 +14343,7 @@ var ExosVlanNaming = {
14650
14343
  remediation: 'Use descriptive VLAN names: create vlan "<meaningful-name>" tag <id>'
14651
14344
  },
14652
14345
  check: (node, context) => {
14653
- const vlanName = getExosVlanName2(node);
14346
+ const vlanName = getExosVlanName(node);
14654
14347
  if (!vlanName) {
14655
14348
  return {
14656
14349
  passed: false,
@@ -14741,7 +14434,7 @@ var VossVlanIsidRequired = {
14741
14434
  remediation: "Configure I-SID for VLAN: vlan i-sid <vlan-id> <isid>"
14742
14435
  },
14743
14436
  check: (node, context) => {
14744
- if (!isVossVlanCreate2(node)) {
14437
+ if (!isVossVlanCreate(node)) {
14745
14438
  return {
14746
14439
  passed: true,
14747
14440
  message: "Not a VLAN create command.",
@@ -14751,7 +14444,7 @@ var VossVlanIsidRequired = {
14751
14444
  loc: node.loc
14752
14445
  };
14753
14446
  }
14754
- const vlanId = getVossVlanId2(node);
14447
+ const vlanId = getVossVlanId(node);
14755
14448
  if (!vlanId) {
14756
14449
  return {
14757
14450
  passed: false,
@@ -14794,7 +14487,7 @@ var VossInterfaceDefaultVlan = {
14794
14487
  remediation: "Configure default VLAN: default-vlan-id <vlan-id>"
14795
14488
  },
14796
14489
  check: (node, context) => {
14797
- if (!isVossGigabitEthernet2(node)) {
14490
+ if (!isVossGigabitEthernet(node)) {
14798
14491
  return {
14799
14492
  passed: true,
14800
14493
  message: "Not a GigabitEthernet interface.",
@@ -14804,7 +14497,7 @@ var VossInterfaceDefaultVlan = {
14804
14497
  loc: node.loc
14805
14498
  };
14806
14499
  }
14807
- if (isVossShutdown2(node)) {
14500
+ if (isVossShutdown(node)) {
14808
14501
  return {
14809
14502
  passed: true,
14810
14503
  message: "Interface is shutdown.",
@@ -14814,7 +14507,7 @@ var VossInterfaceDefaultVlan = {
14814
14507
  loc: node.loc
14815
14508
  };
14816
14509
  }
14817
- const defaultVlan = getVossDefaultVlan2(node);
14510
+ const defaultVlan = getVossDefaultVlan(node);
14818
14511
  if (!defaultVlan) {
14819
14512
  return {
14820
14513
  passed: false,
@@ -14866,22 +14559,6 @@ function getRulesByExtremeVendor(vendorId) {
14866
14559
  }
14867
14560
  }
14868
14561
 
14869
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/huawei/helpers.ts
14870
- var isEnabled4 = (node) => {
14871
- if (!node?.children) return false;
14872
- return node.children.some((child) => {
14873
- const rawText = child?.rawText?.toLowerCase().trim();
14874
- return rawText === "undo shutdown";
14875
- });
14876
- };
14877
- var isPhysicalPort5 = (interfaceName) => {
14878
- const name = interfaceName.toLowerCase();
14879
- return !name.includes("vlanif") && !name.includes("loopback") && !name.includes("null") && !name.includes("tunnel") && !name.includes("eth-trunk") && !name.includes("nve") && !name.includes("vbdif");
14880
- };
14881
- var hasDescription6 = (node) => {
14882
- return hasChildCommand5(node, "description");
14883
- };
14884
-
14885
14562
  // ../rules-default/src/huawei/vrp-rules.ts
14886
14563
  var SysnameRequired = {
14887
14564
  id: "HUAWEI-SYS-001",
@@ -14929,7 +14606,7 @@ var InterfaceDescriptionRequired2 = {
14929
14606
  },
14930
14607
  check: (node) => {
14931
14608
  const interfaceName = node.id.replace(/^interface\s+/i, "").trim();
14932
- if (!isPhysicalPort5(interfaceName)) {
14609
+ if (!isPhysicalPort2(interfaceName)) {
14933
14610
  return {
14934
14611
  passed: true,
14935
14612
  message: "Non-physical interface, description optional.",
@@ -14939,7 +14616,7 @@ var InterfaceDescriptionRequired2 = {
14939
14616
  loc: node.loc
14940
14617
  };
14941
14618
  }
14942
- if (!isEnabled4(node)) {
14619
+ if (!isEnabled(node)) {
14943
14620
  return {
14944
14621
  passed: true,
14945
14622
  message: "Interface is shutdown, description optional.",
@@ -14949,7 +14626,7 @@ var InterfaceDescriptionRequired2 = {
14949
14626
  loc: node.loc
14950
14627
  };
14951
14628
  }
14952
- if (!hasDescription6(node)) {
14629
+ if (!hasDescription3(node)) {
14953
14630
  return {
14954
14631
  passed: false,
14955
14632
  message: `Interface ${interfaceName} is enabled but has no description.`,
@@ -15033,55 +14710,6 @@ function getRulesByHuaweiVendor() {
15033
14710
  return [...allCommonRules, ...allHuaweiRules];
15034
14711
  }
15035
14712
 
15036
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/mikrotik/helpers.ts
15037
- var parseProperty2 = (commandStr, propertyName) => {
15038
- const regex = new RegExp(`\\b${propertyName}=(?:"([^"]+)"|'([^']+)'|(\\S+))`, "i");
15039
- const match = commandStr.match(regex);
15040
- if (match) {
15041
- return match[1] || match[2] || match[3];
15042
- }
15043
- return void 0;
15044
- };
15045
- var getFirewallChain2 = (nodeOrCommand) => {
15046
- const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
15047
- return parseProperty2(str, "chain");
15048
- };
15049
- var getFirewallAction2 = (nodeOrCommand) => {
15050
- const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
15051
- return parseProperty2(str, "action");
15052
- };
15053
- var getName2 = (nodeOrCommand) => {
15054
- const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
15055
- return parseProperty2(str, "name");
15056
- };
15057
- var isAddCommand2 = (nodeOrCommand) => {
15058
- const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
15059
- return /^add\s+/i.test(str.trim());
15060
- };
15061
- var isSetCommand2 = (nodeOrCommand) => {
15062
- const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
15063
- return /^set\s+/i.test(str.trim());
15064
- };
15065
- var getAddCommands2 = (node) => {
15066
- if (!node?.children) return [];
15067
- return node.children.filter((child) => isAddCommand2(child));
15068
- };
15069
- var isServiceDisabled2 = (nodeOrCommand) => {
15070
- const str = typeof nodeOrCommand === "string" ? nodeOrCommand : nodeOrCommand.id;
15071
- const disabled = parseProperty2(str, "disabled");
15072
- return disabled?.toLowerCase() === "yes";
15073
- };
15074
- var getSystemIdentity2 = (node) => {
15075
- if (!node?.children) return void 0;
15076
- for (const child of node.children) {
15077
- if (isSetCommand2(child)) {
15078
- const name = getName2(child);
15079
- if (name) return name;
15080
- }
15081
- }
15082
- return void 0;
15083
- };
15084
-
15085
14713
  // ../rules-default/src/mikrotik/routeros-rules.ts
15086
14714
  var MikrotikSystemIdentity = {
15087
14715
  id: "MIK-SYS-001",
@@ -15095,7 +14723,7 @@ var MikrotikSystemIdentity = {
15095
14723
  remediation: "Configure system identity: /system identity set name=MyRouter"
15096
14724
  },
15097
14725
  check: (node) => {
15098
- const identity = getSystemIdentity2(node);
14726
+ const identity = getSystemIdentity(node);
15099
14727
  if (!identity || equalsIgnoreCase(identity, "mikrotik") || equalsIgnoreCase(identity, "routerboard")) {
15100
14728
  return {
15101
14729
  passed: false,
@@ -15133,8 +14761,8 @@ var MikrotikDisableUnusedServices = {
15133
14761
  for (const child of node.children) {
15134
14762
  const childId = child.id.toLowerCase();
15135
14763
  for (const service of dangerousServices) {
15136
- if (childId.includes(service) && !isServiceDisabled2(child)) {
15137
- const disabled = parseProperty2(child.id, "disabled");
14764
+ if (childId.includes(service) && !isServiceDisabled(child)) {
14765
+ const disabled = parseProperty(child.id, "disabled");
15138
14766
  if (!disabled || !equalsIgnoreCase(disabled, "yes")) {
15139
14767
  issues.push(`Service '${service}' is enabled. Consider disabling it.`);
15140
14768
  }
@@ -15173,11 +14801,11 @@ var MikrotikInputChainDrop = {
15173
14801
  remediation: "Add drop rule for input chain: add chain=input action=drop"
15174
14802
  },
15175
14803
  check: (node) => {
15176
- const addCommands = getAddCommands2(node);
14804
+ const addCommands = getAddCommands(node);
15177
14805
  let hasInputDrop = false;
15178
14806
  for (const cmd of addCommands) {
15179
- const chain = getFirewallChain2(cmd);
15180
- const action = getFirewallAction2(cmd);
14807
+ const chain = getFirewallChain(cmd);
14808
+ const action = getFirewallAction(cmd);
15181
14809
  if (chain && equalsIgnoreCase(chain, "input") && action && equalsIgnoreCase(action, "drop")) {
15182
14810
  hasInputDrop = true;
15183
14811
  break;
@@ -15214,60 +14842,6 @@ function getRulesByMikroTikVendor() {
15214
14842
  return [...allCommonRules, ...allMikroTikRules];
15215
14843
  }
15216
14844
 
15217
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/nokia/helpers.ts
15218
- var isAdminStateDisabled2 = (node) => {
15219
- if (!node?.children) return false;
15220
- const directCheck = node.children.some((child) => {
15221
- const rawText = child?.rawText?.toLowerCase().trim();
15222
- return rawText === "admin-state disable";
15223
- });
15224
- if (directCheck) return true;
15225
- return node?.rawText?.toLowerCase().includes("admin-state disable") ?? false;
15226
- };
15227
- var isPhysicalPort6 = (portName) => {
15228
- const name = portName.toLowerCase();
15229
- return /^\d+\/\d+\/\d+/.test(name);
15230
- };
15231
- var hasDescription7 = (node) => {
15232
- return hasChildCommand5(node, "description");
15233
- };
15234
- var getDescription4 = (node) => {
15235
- const descCmd = getChildCommand5(node, "description");
15236
- if (descCmd?.rawText) {
15237
- const match = descCmd.rawText.match(/description\s+"([^"]+)"|description\s+(\S+)/i);
15238
- if (match) {
15239
- return match[1] || match[2];
15240
- }
15241
- }
15242
- return void 0;
15243
- };
15244
- var getSystemName2 = (node) => {
15245
- if (!node?.children) return void 0;
15246
- const nameCmd = node.children.find((child) => {
15247
- return child?.id?.toLowerCase().startsWith("name");
15248
- });
15249
- if (nameCmd?.rawText) {
15250
- const match = nameCmd.rawText.match(/name\s+"([^"]+)"/i);
15251
- if (match) {
15252
- return match[1];
15253
- }
15254
- }
15255
- return void 0;
15256
- };
15257
- var hasBgpRouterId3 = (node) => {
15258
- return hasChildCommand5(node, "router-id");
15259
- };
15260
- var getBgpRouterId2 = (node) => {
15261
- const routerIdCmd = getChildCommand5(node, "router-id");
15262
- if (routerIdCmd?.rawText) {
15263
- const match = routerIdCmd.rawText.match(/router-id\s+([\d.]+)/i);
15264
- if (match) {
15265
- return match[1];
15266
- }
15267
- }
15268
- return void 0;
15269
- };
15270
-
15271
14845
  // ../rules-default/src/nokia/sros-rules.ts
15272
14846
  var SystemNameRequired = {
15273
14847
  id: "NOKIA-SYS-001",
@@ -15281,7 +14855,7 @@ var SystemNameRequired = {
15281
14855
  remediation: 'Configure system name using: system > name "<hostname>"'
15282
14856
  },
15283
14857
  check: (node) => {
15284
- const name = getSystemName2(node);
14858
+ const name = getSystemName(node);
15285
14859
  if (!name || name.length === 0) {
15286
14860
  return {
15287
14861
  passed: false,
@@ -15315,7 +14889,7 @@ var PortDescriptionRequired = {
15315
14889
  },
15316
14890
  check: (node) => {
15317
14891
  const portName = node.id.replace(/^port\s+/i, "").trim();
15318
- if (!isPhysicalPort6(portName)) {
14892
+ if (!isPhysicalPort3(portName)) {
15319
14893
  return {
15320
14894
  passed: true,
15321
14895
  message: "Not a physical port, description optional.",
@@ -15325,7 +14899,7 @@ var PortDescriptionRequired = {
15325
14899
  loc: node.loc
15326
14900
  };
15327
14901
  }
15328
- if (isAdminStateDisabled2(node)) {
14902
+ if (isAdminStateDisabled(node)) {
15329
14903
  return {
15330
14904
  passed: true,
15331
14905
  message: "Port is disabled, description optional.",
@@ -15335,7 +14909,7 @@ var PortDescriptionRequired = {
15335
14909
  loc: node.loc
15336
14910
  };
15337
14911
  }
15338
- if (!hasDescription7(node)) {
14912
+ if (!hasDescription4(node)) {
15339
14913
  return {
15340
14914
  passed: false,
15341
14915
  message: `Port ${portName} is enabled but has no description.`,
@@ -15347,7 +14921,7 @@ var PortDescriptionRequired = {
15347
14921
  }
15348
14922
  return {
15349
14923
  passed: true,
15350
- message: `Port ${portName} has description: ${getDescription4(node)}`,
14924
+ message: `Port ${portName} has description: ${getDescription3(node)}`,
15351
14925
  ruleId: "NOKIA-PORT-001",
15352
14926
  nodeId: node.id,
15353
14927
  level: "info",
@@ -15367,7 +14941,7 @@ var BgpRouterIdRequired = {
15367
14941
  remediation: "Configure BGP router-id: bgp > router-id <ip-address>"
15368
14942
  },
15369
14943
  check: (node) => {
15370
- if (isAdminStateDisabled2(node)) {
14944
+ if (isAdminStateDisabled(node)) {
15371
14945
  return {
15372
14946
  passed: true,
15373
14947
  message: "BGP is disabled.",
@@ -15377,7 +14951,7 @@ var BgpRouterIdRequired = {
15377
14951
  loc: node.loc
15378
14952
  };
15379
14953
  }
15380
- if (!hasBgpRouterId3(node)) {
14954
+ if (!hasBgpRouterId2(node)) {
15381
14955
  return {
15382
14956
  passed: false,
15383
14957
  message: "BGP router-id is not configured. Configure a stable router-id.",
@@ -15389,7 +14963,7 @@ var BgpRouterIdRequired = {
15389
14963
  }
15390
14964
  return {
15391
14965
  passed: true,
15392
- message: `BGP router-id is configured: ${getBgpRouterId2(node)}`,
14966
+ message: `BGP router-id is configured: ${getBgpRouterId(node)}`,
15393
14967
  ruleId: "NOKIA-BGP-001",
15394
14968
  nodeId: node.id,
15395
14969
  level: "info",
@@ -15411,40 +14985,6 @@ function getRulesByNokiaVendor() {
15411
14985
  return [...allCommonRules, ...allNokiaRules];
15412
14986
  }
15413
14987
 
15414
- // ../../node_modules/.bun/@sentriflow+core@0.2.1+1fb4c65d43e298b9/node_modules/@sentriflow/core/src/helpers/cumulus/helpers.ts
15415
- var isSwitchPort2 = (interfaceName) => {
15416
- const name = interfaceName.toLowerCase();
15417
- return /swp\d+/.test(name);
15418
- };
15419
- var isBridgeInterface4 = (interfaceName) => {
15420
- const name = interfaceName.toLowerCase();
15421
- return name.includes("bridge") || name === "br_default" || /^br\d+$/.test(name);
15422
- };
15423
- var isVlanAwareBridge2 = (node) => {
15424
- return node.children.some(
15425
- (child) => child.id.toLowerCase().includes("bridge-vlan-aware") && child.id.toLowerCase().includes("yes")
15426
- );
15427
- };
15428
- var getInterfaceName6 = (node) => {
15429
- const parts = node.id.split(/\s+/);
15430
- return parts[1] || node.id;
15431
- };
15432
- var hasDescription8 = (node) => {
15433
- return node.children.some(
15434
- (child) => child.id.toLowerCase().startsWith("alias ")
15435
- );
15436
- };
15437
- var hasBridgeVids2 = (node) => {
15438
- return node.children.some(
15439
- (child) => child.id.toLowerCase().startsWith("bridge-vids ")
15440
- );
15441
- };
15442
- var hasBgpRouterId4 = (node) => {
15443
- return node.children.some(
15444
- (child) => child.id.toLowerCase().startsWith("bgp router-id ")
15445
- );
15446
- };
15447
-
15448
14988
  // ../rules-default/src/cumulus/cumulus-rules.ts
15449
14989
  var CumulusInterfaceDescription = {
15450
14990
  id: "CUM-IF-001",
@@ -15458,8 +14998,8 @@ var CumulusInterfaceDescription = {
15458
14998
  remediation: 'Add "alias <description>" under the interface stanza.'
15459
14999
  },
15460
15000
  check: (node) => {
15461
- const ifaceName = getInterfaceName6(node);
15462
- if (!isSwitchPort2(ifaceName)) {
15001
+ const ifaceName = getInterfaceName2(node);
15002
+ if (!isSwitchPort(ifaceName)) {
15463
15003
  return {
15464
15004
  passed: true,
15465
15005
  message: "Not a switch port interface.",
@@ -15469,7 +15009,7 @@ var CumulusInterfaceDescription = {
15469
15009
  loc: node.loc
15470
15010
  };
15471
15011
  }
15472
- if (!hasDescription8(node)) {
15012
+ if (!hasDescription2(node)) {
15473
15013
  return {
15474
15014
  passed: false,
15475
15015
  message: `Switch port "${ifaceName}" missing description (alias).`,
@@ -15501,8 +15041,8 @@ var CumulusBridgeVlans = {
15501
15041
  remediation: 'Add "bridge-vids <vlan-ids>" to define allowed VLANs on the bridge.'
15502
15042
  },
15503
15043
  check: (node) => {
15504
- const ifaceName = getInterfaceName6(node);
15505
- if (!isBridgeInterface4(ifaceName)) {
15044
+ const ifaceName = getInterfaceName2(node);
15045
+ if (!isBridgeInterface(ifaceName)) {
15506
15046
  return {
15507
15047
  passed: true,
15508
15048
  message: "Not a bridge interface.",
@@ -15512,7 +15052,7 @@ var CumulusBridgeVlans = {
15512
15052
  loc: node.loc
15513
15053
  };
15514
15054
  }
15515
- if (!isVlanAwareBridge2(node)) {
15055
+ if (!isVlanAwareBridge(node)) {
15516
15056
  return {
15517
15057
  passed: true,
15518
15058
  message: "Not a VLAN-aware bridge.",
@@ -15522,7 +15062,7 @@ var CumulusBridgeVlans = {
15522
15062
  loc: node.loc
15523
15063
  };
15524
15064
  }
15525
- if (!hasBridgeVids2(node)) {
15065
+ if (!hasBridgeVids(node)) {
15526
15066
  return {
15527
15067
  passed: false,
15528
15068
  message: `VLAN-aware bridge "${ifaceName}" has no VLANs (bridge-vids) configured.`,
@@ -15554,7 +15094,7 @@ var CumulusBgpRouterId = {
15554
15094
  remediation: 'Add "bgp router-id <ip>" to explicitly set router ID.'
15555
15095
  },
15556
15096
  check: (node) => {
15557
- if (!hasBgpRouterId4(node)) {
15097
+ if (!hasBgpRouterId(node)) {
15558
15098
  return {
15559
15099
  passed: false,
15560
15100
  message: "BGP missing explicit router-id configuration.",
@@ -17142,7 +16682,7 @@ function enrichResultsWithRuleMetadata(results, rules) {
17142
16682
  });
17143
16683
  }
17144
16684
  var program = new Command();
17145
- program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.3.2").argument("[files...]", "Path(s) to configuration file(s) (supports multiple files)").option("--ast", "Output the AST instead of rule results").option("-f, --format <format>", "Output format (json, sarif)", "json").option("-q, --quiet", "Only output failures (suppress passed results)").option("-c, --config <path>", "Path to config file (default: auto-detect)").option("--no-config", "Ignore config file").option("-r, --rules <path>", "Additional rules file to load (legacy)").option(
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(
17146
16686
  "--pack <path...>",
17147
16687
  "Path(s) to rule pack(s) (auto-detects format: .grx2, .grpx, or unencrypted)"
17148
16688
  ).option(