@sentriflow/cli 0.2.1 → 0.3.2

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