@sentriflow/cli 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +484 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9975,6 +9975,7 @@ var JsonRuleCompiler = class {
9975
9975
  id: jsonRule.id,
9976
9976
  selector: jsonRule.selector,
9977
9977
  vendor: jsonRule.vendor,
9978
+ category: jsonRule.category,
9978
9979
  metadata: jsonRule.metadata,
9979
9980
  check: (node, _ctx) => {
9980
9981
  const passed = !this.evaluateCheck(jsonRule.check, node);
@@ -10333,6 +10334,350 @@ function validateRegex(pattern, flags, path, ctx) {
10333
10334
  }
10334
10335
  }
10335
10336
 
10337
+ // ../core/src/ip/extractor.ts
10338
+ function isValidIPv4(ip) {
10339
+ if (!ip || typeof ip !== "string") return false;
10340
+ const octets = ip.split(".");
10341
+ if (octets.length !== 4) return false;
10342
+ for (const octet of octets) {
10343
+ if (!/^\d+$/.test(octet)) return false;
10344
+ if (octet.length > 1 && octet.startsWith("0")) return false;
10345
+ const num = parseInt(octet, 10);
10346
+ if (isNaN(num) || num < 0 || num > 255) return false;
10347
+ }
10348
+ return true;
10349
+ }
10350
+ function isValidIPv6(ip) {
10351
+ if (!ip || typeof ip !== "string") return false;
10352
+ let addr = ip;
10353
+ const zoneIndex = ip.indexOf("%");
10354
+ if (zoneIndex !== -1) {
10355
+ addr = ip.substring(0, zoneIndex);
10356
+ }
10357
+ if (!addr.includes(":")) return false;
10358
+ if (addr.includes(":::")) return false;
10359
+ const doubleColonCount = (addr.match(/::/g) || []).length;
10360
+ if (doubleColonCount > 1) return false;
10361
+ const parts = addr.split(":");
10362
+ if (doubleColonCount === 1) {
10363
+ const nonEmptyParts = parts.filter((p) => p !== "");
10364
+ for (const part of nonEmptyParts) {
10365
+ if (!/^[0-9a-fA-F]{1,4}$/.test(part)) return false;
10366
+ }
10367
+ if (nonEmptyParts.length > 7) return false;
10368
+ } else {
10369
+ if (parts.length !== 8) return false;
10370
+ for (const part of parts) {
10371
+ if (!/^[0-9a-fA-F]{1,4}$/.test(part)) return false;
10372
+ }
10373
+ }
10374
+ return true;
10375
+ }
10376
+ function isValidSubnet(subnet) {
10377
+ if (!subnet || typeof subnet !== "string") return false;
10378
+ const slashIndex = subnet.lastIndexOf("/");
10379
+ if (slashIndex === -1) return false;
10380
+ const ip = subnet.substring(0, slashIndex);
10381
+ const prefixStr = subnet.substring(slashIndex + 1);
10382
+ if (!/^\d+$/.test(prefixStr)) return false;
10383
+ const prefix = parseInt(prefixStr, 10);
10384
+ if (isValidIPv4(ip)) {
10385
+ return prefix >= 0 && prefix <= 32;
10386
+ } else if (isValidIPv6(ip)) {
10387
+ return prefix >= 0 && prefix <= 128;
10388
+ }
10389
+ return false;
10390
+ }
10391
+ function normalizeIPv4(ip) {
10392
+ return ip.split(".").map((octet) => parseInt(octet, 10).toString()).join(".");
10393
+ }
10394
+ function normalizeIPv6(ip) {
10395
+ let addr = ip;
10396
+ const zoneIndex = ip.indexOf("%");
10397
+ if (zoneIndex !== -1) {
10398
+ addr = ip.substring(0, zoneIndex);
10399
+ }
10400
+ addr = addr.toLowerCase();
10401
+ if (addr.includes("::")) {
10402
+ const sides = addr.split("::");
10403
+ const left = sides[0] ? sides[0].split(":").filter((p) => p !== "") : [];
10404
+ const right = sides[1] ? sides[1].split(":").filter((p) => p !== "") : [];
10405
+ const zerosNeeded = 8 - left.length - right.length;
10406
+ const expanded = [];
10407
+ for (const part of left) {
10408
+ expanded.push(parseInt(part, 16).toString(16));
10409
+ }
10410
+ for (let i = 0; i < zerosNeeded; i++) {
10411
+ expanded.push("0");
10412
+ }
10413
+ for (const part of right) {
10414
+ expanded.push(parseInt(part, 16).toString(16));
10415
+ }
10416
+ return expanded.join(":");
10417
+ }
10418
+ const parts = addr.split(":");
10419
+ const result = [];
10420
+ for (const part of parts) {
10421
+ if (part !== "") {
10422
+ result.push(parseInt(part, 16).toString(16));
10423
+ }
10424
+ }
10425
+ return result.join(":");
10426
+ }
10427
+ function ipv4ToNumber(ip) {
10428
+ const octets = ip.split(".").map(Number);
10429
+ const o0 = octets[0] ?? 0;
10430
+ const o1 = octets[1] ?? 0;
10431
+ const o2 = octets[2] ?? 0;
10432
+ const o3 = octets[3] ?? 0;
10433
+ return (o0 << 24 >>> 0) + (o1 << 16) + (o2 << 8) + o3;
10434
+ }
10435
+ function compareIPv4(a, b) {
10436
+ const numA = ipv4ToNumber(a);
10437
+ const numB = ipv4ToNumber(b);
10438
+ return numA < numB ? -1 : numA > numB ? 1 : 0;
10439
+ }
10440
+ function expandIPv6(ip) {
10441
+ let addr = ip;
10442
+ const zoneIndex = ip.indexOf("%");
10443
+ if (zoneIndex !== -1) {
10444
+ addr = ip.substring(0, zoneIndex);
10445
+ }
10446
+ const parts = addr.split(":");
10447
+ const result = [];
10448
+ for (let i = 0; i < parts.length; i++) {
10449
+ if (parts[i] === "" && i > 0 && i < parts.length - 1) {
10450
+ const nonEmpty = parts.filter((p) => p !== "").length;
10451
+ const zeros = 8 - nonEmpty;
10452
+ for (let j = 0; j < zeros; j++) {
10453
+ result.push("0");
10454
+ }
10455
+ } else if (parts[i] !== "") {
10456
+ result.push(parts[i] ?? "0");
10457
+ } else if (i === 0 && parts[1] === "") {
10458
+ const nonEmpty = parts.filter((p) => p !== "").length;
10459
+ const zeros = 8 - nonEmpty;
10460
+ for (let j = 0; j < zeros; j++) {
10461
+ result.push("0");
10462
+ }
10463
+ } else if (i === parts.length - 1 && parts[i - 1] === "") {
10464
+ }
10465
+ }
10466
+ while (result.length < 8) {
10467
+ result.push("0");
10468
+ }
10469
+ return result.slice(0, 8);
10470
+ }
10471
+ function ipv6ToBigInt(ip) {
10472
+ const parts = expandIPv6(ip);
10473
+ let result = 0n;
10474
+ for (const part of parts) {
10475
+ result = (result << 16n) + BigInt(parseInt(part, 16) || 0);
10476
+ }
10477
+ return result;
10478
+ }
10479
+ function compareIPv6(a, b) {
10480
+ const bigA = ipv6ToBigInt(a);
10481
+ const bigB = ipv6ToBigInt(b);
10482
+ return bigA < bigB ? -1 : bigA > bigB ? 1 : 0;
10483
+ }
10484
+ function sortIPv4Addresses(ips) {
10485
+ return [...ips].sort(compareIPv4);
10486
+ }
10487
+ function sortIPv6Addresses(ips) {
10488
+ return [...ips].sort(compareIPv6);
10489
+ }
10490
+ function parseSubnet(subnet) {
10491
+ const slashIndex = subnet.lastIndexOf("/");
10492
+ return {
10493
+ network: subnet.substring(0, slashIndex),
10494
+ prefix: parseInt(subnet.substring(slashIndex + 1), 10)
10495
+ };
10496
+ }
10497
+ function sortSubnets(subnets, type) {
10498
+ const compare = type === "ipv4" ? compareIPv4 : compareIPv6;
10499
+ return [...subnets].sort((a, b) => {
10500
+ const subA = parseSubnet(a);
10501
+ const subB = parseSubnet(b);
10502
+ const netCompare = compare(subA.network, subB.network);
10503
+ if (netCompare !== 0) return netCompare;
10504
+ return subA.prefix - subB.prefix;
10505
+ });
10506
+ }
10507
+ function isSubnetMask(ip) {
10508
+ if (!ip.startsWith("255.")) return false;
10509
+ const octets = ip.split(".").map(Number);
10510
+ const num = ((octets[0] ?? 0) << 24) + ((octets[1] ?? 0) << 16) + ((octets[2] ?? 0) << 8) + (octets[3] ?? 0);
10511
+ const inverted = ~num >>> 0;
10512
+ return inverted === 0 || (inverted & inverted + 1) === 0;
10513
+ }
10514
+ function isWildcardMask(ip) {
10515
+ if (!ip.startsWith("0.")) return false;
10516
+ const octets = ip.split(".").map(Number);
10517
+ const num = ((octets[0] ?? 0) << 24) + ((octets[1] ?? 0) << 16) + ((octets[2] ?? 0) << 8) + (octets[3] ?? 0);
10518
+ return num === 0 || (num + 1 & num) === 0;
10519
+ }
10520
+ function maskToCidr(mask) {
10521
+ if (!isSubnetMask(mask)) return -1;
10522
+ const octets = mask.split(".").map(Number);
10523
+ const num = ((octets[0] ?? 0) << 24) + ((octets[1] ?? 0) << 16) + ((octets[2] ?? 0) << 8) + (octets[3] ?? 0);
10524
+ let prefix = 0;
10525
+ let n = num >>> 0;
10526
+ while (n & 2147483648) {
10527
+ prefix++;
10528
+ n = n << 1 >>> 0;
10529
+ }
10530
+ return prefix;
10531
+ }
10532
+ function wildcardToCidr(wildcard) {
10533
+ if (!isWildcardMask(wildcard)) return -1;
10534
+ const octets = wildcard.split(".").map(Number);
10535
+ const num = ((octets[0] ?? 0) << 24) + ((octets[1] ?? 0) << 16) + ((octets[2] ?? 0) << 8) + (octets[3] ?? 0);
10536
+ let prefix = 0;
10537
+ let n = num >>> 0;
10538
+ while (prefix < 32 && !(n & 2147483648)) {
10539
+ prefix++;
10540
+ n = n << 1 >>> 0;
10541
+ }
10542
+ return prefix;
10543
+ }
10544
+ var IPV4_PATTERN = /\b(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b/g;
10545
+ var IPV4_CIDR_PATTERN = /\b(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\/(?:3[0-2]|[12]?[0-9])\b/g;
10546
+ var IPV4_WITH_MASK_PATTERN = /\b((?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))\s+(255\.(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){2}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))\b/g;
10547
+ var IPV4_WITH_MASK_KEYWORD_PATTERN = /\b((?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))\s+mask\s+(255\.(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){2}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))\b/gi;
10548
+ var IPV4_WITH_WILDCARD_PATTERN = /\b((?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))\s+(0\.(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){2}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))\b/g;
10549
+ var IPV6_PATTERN = /(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|:(?::[0-9a-fA-F]{1,4}){1,7}|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|(?:[0-9a-fA-F]{1,4}:){1,7}:|::/g;
10550
+ var IPV6_CIDR_PATTERN = /(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|:(?::[0-9a-fA-F]{1,4}){1,7}|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|(?:[0-9a-fA-F]{1,4}:){1,7}:|::)\/(?:12[0-8]|1[01][0-9]|[1-9]?[0-9])/g;
10551
+ function createEmptyIPSummary() {
10552
+ return {
10553
+ ipv4Addresses: [],
10554
+ ipv6Addresses: [],
10555
+ ipv4Subnets: [],
10556
+ ipv6Subnets: [],
10557
+ counts: {
10558
+ ipv4: 0,
10559
+ ipv6: 0,
10560
+ ipv4Subnets: 0,
10561
+ ipv6Subnets: 0,
10562
+ total: 0
10563
+ }
10564
+ };
10565
+ }
10566
+ function extractIPSummary(content, options = {}) {
10567
+ if (!content || typeof content !== "string") {
10568
+ return createEmptyIPSummary();
10569
+ }
10570
+ const ipv4Set = /* @__PURE__ */ new Set();
10571
+ const ipv6Set = /* @__PURE__ */ new Set();
10572
+ const ipv4SubnetSet = /* @__PURE__ */ new Set();
10573
+ const ipv6SubnetSet = /* @__PURE__ */ new Set();
10574
+ const subnetNetworks = /* @__PURE__ */ new Set();
10575
+ const ipsWithMasks = /* @__PURE__ */ new Set();
10576
+ if (!options.skipSubnets) {
10577
+ const ipv4CidrMatches = content.matchAll(IPV4_CIDR_PATTERN);
10578
+ for (const match of ipv4CidrMatches) {
10579
+ const subnet = match[0];
10580
+ if (isValidSubnet(subnet)) {
10581
+ const { network } = parseSubnet(subnet);
10582
+ const normalizedNetwork = normalizeIPv4(network);
10583
+ ipv4SubnetSet.add(`${normalizedNetwork}/${parseSubnet(subnet).prefix}`);
10584
+ subnetNetworks.add(normalizedNetwork);
10585
+ }
10586
+ }
10587
+ const ipMaskMatches = content.matchAll(IPV4_WITH_MASK_PATTERN);
10588
+ for (const match of ipMaskMatches) {
10589
+ const ip = match[1];
10590
+ const mask = match[2];
10591
+ if (ip && mask && isValidIPv4(ip) && isSubnetMask(mask)) {
10592
+ const normalizedIP = normalizeIPv4(ip);
10593
+ const prefix = maskToCidr(mask);
10594
+ if (prefix >= 0) {
10595
+ ipv4SubnetSet.add(`${normalizedIP}/${prefix}`);
10596
+ ipsWithMasks.add(normalizedIP);
10597
+ }
10598
+ }
10599
+ }
10600
+ const ipMaskKeywordMatches = content.matchAll(IPV4_WITH_MASK_KEYWORD_PATTERN);
10601
+ for (const match of ipMaskKeywordMatches) {
10602
+ const ip = match[1];
10603
+ const mask = match[2];
10604
+ if (ip && mask && isValidIPv4(ip) && isSubnetMask(mask)) {
10605
+ const normalizedIP = normalizeIPv4(ip);
10606
+ const prefix = maskToCidr(mask);
10607
+ if (prefix >= 0) {
10608
+ ipv4SubnetSet.add(`${normalizedIP}/${prefix}`);
10609
+ ipsWithMasks.add(normalizedIP);
10610
+ }
10611
+ }
10612
+ }
10613
+ const ipWildcardMatches = content.matchAll(IPV4_WITH_WILDCARD_PATTERN);
10614
+ for (const match of ipWildcardMatches) {
10615
+ const ip = match[1];
10616
+ const wildcard = match[2];
10617
+ if (ip && wildcard && isValidIPv4(ip) && isWildcardMask(wildcard)) {
10618
+ const normalizedIP = normalizeIPv4(ip);
10619
+ const prefix = wildcardToCidr(wildcard);
10620
+ if (prefix >= 0) {
10621
+ ipv4SubnetSet.add(`${normalizedIP}/${prefix}`);
10622
+ ipsWithMasks.add(normalizedIP);
10623
+ }
10624
+ }
10625
+ }
10626
+ }
10627
+ const ipv4Matches = content.matchAll(IPV4_PATTERN);
10628
+ for (const match of ipv4Matches) {
10629
+ const ip = match[0];
10630
+ if (isValidIPv4(ip)) {
10631
+ const normalized = normalizeIPv4(ip);
10632
+ if (!subnetNetworks.has(normalized) && !ipsWithMasks.has(normalized) && !isSubnetMask(normalized) && !isWildcardMask(normalized)) {
10633
+ ipv4Set.add(normalized);
10634
+ }
10635
+ }
10636
+ }
10637
+ if (!options.skipIPv6) {
10638
+ if (!options.skipSubnets) {
10639
+ const ipv6CidrMatches = content.matchAll(IPV6_CIDR_PATTERN);
10640
+ for (const match of ipv6CidrMatches) {
10641
+ const subnet = match[0];
10642
+ if (isValidSubnet(subnet)) {
10643
+ const { network, prefix } = parseSubnet(subnet);
10644
+ const normalizedNetwork = normalizeIPv6(network);
10645
+ ipv6SubnetSet.add(`${normalizedNetwork}/${prefix}`);
10646
+ subnetNetworks.add(normalizedNetwork);
10647
+ }
10648
+ }
10649
+ }
10650
+ const ipv6Matches = content.matchAll(IPV6_PATTERN);
10651
+ for (const match of ipv6Matches) {
10652
+ const ip = match[0];
10653
+ if (isValidIPv6(ip)) {
10654
+ const normalized = normalizeIPv6(ip);
10655
+ if (!subnetNetworks.has(normalized)) {
10656
+ ipv6Set.add(normalized);
10657
+ }
10658
+ }
10659
+ }
10660
+ }
10661
+ const ipv4Addresses = sortIPv4Addresses([...ipv4Set]);
10662
+ const ipv6Addresses = sortIPv6Addresses([...ipv6Set]);
10663
+ const ipv4Subnets = sortSubnets([...ipv4SubnetSet], "ipv4");
10664
+ const ipv6Subnets = sortSubnets([...ipv6SubnetSet], "ipv6");
10665
+ const counts = {
10666
+ ipv4: ipv4Addresses.length,
10667
+ ipv6: ipv6Addresses.length,
10668
+ ipv4Subnets: ipv4Subnets.length,
10669
+ ipv6Subnets: ipv6Subnets.length,
10670
+ total: ipv4Addresses.length + ipv6Addresses.length + ipv4Subnets.length + ipv6Subnets.length
10671
+ };
10672
+ return {
10673
+ ipv4Addresses,
10674
+ ipv6Addresses,
10675
+ ipv4Subnets,
10676
+ ipv6Subnets,
10677
+ counts
10678
+ };
10679
+ }
10680
+
10336
10681
  // index.ts
10337
10682
  import { readFile } from "fs/promises";
10338
10683
  import { statSync as statSync2 } from "fs";
@@ -10340,7 +10685,7 @@ import { resolve as resolve4, dirname as dirname2, basename } from "path";
10340
10685
 
10341
10686
  // src/sarif.ts
10342
10687
  import { relative } from "path";
10343
- function generateSarif(results, filePath, rules, options = {}) {
10688
+ function generateSarif(results, filePath, rules, options = {}, ipSummary) {
10344
10689
  const fileUri = options.relativePaths ? relative(options.baseDir ?? process.cwd(), filePath) : filePath;
10345
10690
  const sarifResults = results.map((result) => {
10346
10691
  return {
@@ -10407,7 +10752,7 @@ function generateSarif(results, filePath, rules, options = {}) {
10407
10752
  tool: {
10408
10753
  driver: {
10409
10754
  name: "Sentriflow",
10410
- version: "0.1.6",
10755
+ version: "0.1.8",
10411
10756
  informationUri: "https://github.com/sentriflow/sentriflow",
10412
10757
  rules: sarifRules,
10413
10758
  // SEC-007: Include CWE taxonomy when rules reference it
@@ -10434,13 +10779,61 @@ function generateSarif(results, filePath, rules, options = {}) {
10434
10779
  }
10435
10780
  ]
10436
10781
  },
10437
- results: sarifResults
10782
+ results: sarifResults,
10783
+ // Include IP summary in properties if available
10784
+ ...ipSummary && {
10785
+ properties: {
10786
+ ipSummary
10787
+ }
10788
+ }
10438
10789
  }
10439
10790
  ]
10440
10791
  };
10441
10792
  return JSON.stringify(report, null, 2);
10442
10793
  }
10794
+ function aggregateIPSummaries(summaries) {
10795
+ if (summaries.length === 0) return void 0;
10796
+ const ipv4Set = /* @__PURE__ */ new Set();
10797
+ const ipv6Set = /* @__PURE__ */ new Set();
10798
+ const ipv4SubnetSet = /* @__PURE__ */ new Set();
10799
+ const ipv6SubnetSet = /* @__PURE__ */ new Set();
10800
+ for (const summary of summaries) {
10801
+ for (const ip of summary.ipv4Addresses) ipv4Set.add(ip);
10802
+ for (const ip of summary.ipv6Addresses) ipv6Set.add(ip);
10803
+ for (const subnet of summary.ipv4Subnets) ipv4SubnetSet.add(subnet);
10804
+ for (const subnet of summary.ipv6Subnets) ipv6SubnetSet.add(subnet);
10805
+ }
10806
+ const ipv4Addresses = [...ipv4Set].sort((a, b) => {
10807
+ const aParts = a.split(".").map(Number);
10808
+ const bParts = b.split(".").map(Number);
10809
+ for (let i = 0; i < 4; i++) {
10810
+ if ((aParts[i] ?? 0) !== (bParts[i] ?? 0)) {
10811
+ return (aParts[i] ?? 0) - (bParts[i] ?? 0);
10812
+ }
10813
+ }
10814
+ return 0;
10815
+ });
10816
+ const ipv6Addresses = [...ipv6Set].sort();
10817
+ const ipv4Subnets = [...ipv4SubnetSet].sort();
10818
+ const ipv6Subnets = [...ipv6SubnetSet].sort();
10819
+ return {
10820
+ ipv4Addresses,
10821
+ ipv6Addresses,
10822
+ ipv4Subnets,
10823
+ ipv6Subnets,
10824
+ counts: {
10825
+ ipv4: ipv4Addresses.length,
10826
+ ipv6: ipv6Addresses.length,
10827
+ ipv4Subnets: ipv4Subnets.length,
10828
+ ipv6Subnets: ipv6Subnets.length,
10829
+ total: ipv4Addresses.length + ipv6Addresses.length + ipv4Subnets.length + ipv6Subnets.length
10830
+ }
10831
+ };
10832
+ }
10443
10833
  function generateMultiFileSarif(fileResults, rules, options = {}) {
10834
+ const aggregatedIpSummary = aggregateIPSummaries(
10835
+ fileResults.map((fr) => fr.ipSummary).filter((s) => !!s)
10836
+ );
10444
10837
  const allSarifResults = fileResults.flatMap(({ filePath, results }) => {
10445
10838
  const fileUri = options.relativePaths ? relative(options.baseDir ?? process.cwd(), filePath) : filePath;
10446
10839
  return results.map((result) => ({
@@ -10513,7 +10906,7 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
10513
10906
  tool: {
10514
10907
  driver: {
10515
10908
  name: "Sentriflow",
10516
- version: "0.1.6",
10909
+ version: "0.1.8",
10517
10910
  informationUri: "https://github.com/sentriflow/sentriflow",
10518
10911
  rules: sarifRules,
10519
10912
  // SEC-007: Include CWE taxonomy when rules reference it
@@ -10544,7 +10937,13 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
10544
10937
  }
10545
10938
  ]
10546
10939
  },
10547
- results: allSarifResults
10940
+ results: allSarifResults,
10941
+ // Include aggregated IP summary in properties if available
10942
+ ...aggregatedIpSummary && {
10943
+ properties: {
10944
+ ipSummary: aggregatedIpSummary
10945
+ }
10946
+ }
10548
10947
  }
10549
10948
  ]
10550
10949
  };
@@ -10556,10 +10955,12 @@ var NoMulticastBroadcastIp = {
10556
10955
  id: "NET-IP-001",
10557
10956
  selector: "ip address",
10558
10957
  vendor: "common",
10958
+ category: "IP-Addressing",
10559
10959
  metadata: {
10560
10960
  level: "error",
10561
10961
  obu: "Network Engineering",
10562
10962
  owner: "NetOps",
10963
+ description: "Ensure IP addresses are not Multicast, Broadcast, or Network ID addresses.",
10563
10964
  remediation: "Configure a valid unicast IP address. Do not use Multicast, Broadcast, or Network ID addresses."
10564
10965
  },
10565
10966
  check: (node) => {
@@ -10669,10 +11070,12 @@ var InterfaceDescriptionRequired = {
10669
11070
  id: "NET-DOC-001",
10670
11071
  selector: "interface",
10671
11072
  vendor: "common",
11073
+ category: "Documentation",
10672
11074
  metadata: {
10673
11075
  level: "warning",
10674
11076
  obu: "Network Engineering",
10675
11077
  owner: "NetOps",
11078
+ description: "Ensure interfaces have descriptive labels for operational clarity.",
10676
11079
  remediation: 'Add a description to the interface using the "description" command.'
10677
11080
  },
10678
11081
  check: (node) => {
@@ -10750,6 +11153,7 @@ var TrunkNoDTP = {
10750
11153
  id: "NET-TRUNK-001",
10751
11154
  selector: "interface",
10752
11155
  vendor: "cisco-ios",
11156
+ category: "Network-Segmentation",
10753
11157
  metadata: {
10754
11158
  level: "warning",
10755
11159
  obu: "Network Engineering",
@@ -10783,6 +11187,7 @@ var AccessExplicitMode = {
10783
11187
  id: "NET-ACCESS-001",
10784
11188
  selector: "interface",
10785
11189
  vendor: "cisco-ios",
11190
+ category: "Network-Segmentation",
10786
11191
  metadata: {
10787
11192
  level: "warning",
10788
11193
  obu: "Network Engineering",
@@ -10812,6 +11217,7 @@ var EnableSecretStrong = {
10812
11217
  id: "NET-AAA-003",
10813
11218
  selector: "enable",
10814
11219
  vendor: "cisco-ios",
11220
+ category: "Authentication",
10815
11221
  metadata: {
10816
11222
  level: "error",
10817
11223
  obu: "Security",
@@ -10848,6 +11254,7 @@ var CiscoNoPlaintextPasswords = {
10848
11254
  id: "NET-SEC-001",
10849
11255
  selector: "password",
10850
11256
  vendor: ["cisco-ios", "cisco-nxos"],
11257
+ category: "Authentication",
10851
11258
  metadata: {
10852
11259
  level: "error",
10853
11260
  obu: "Security",
@@ -10935,6 +11342,7 @@ var RootAuthRequired = {
10935
11342
  id: "JUN-SYS-001",
10936
11343
  selector: "system",
10937
11344
  vendor: "juniper-junos",
11345
+ category: "Authentication",
10938
11346
  metadata: {
10939
11347
  level: "error",
10940
11348
  obu: "Security",
@@ -10979,6 +11387,7 @@ var JunosBgpRouterId = {
10979
11387
  id: "JUN-BGP-001",
10980
11388
  selector: "routing-options",
10981
11389
  vendor: "juniper-junos",
11390
+ category: "Routing",
10982
11391
  metadata: {
10983
11392
  level: "warning",
10984
11393
  obu: "Network Engineering",
@@ -11011,6 +11420,7 @@ var JunosFirewallDefaultDeny = {
11011
11420
  id: "JUN-FW-001",
11012
11421
  selector: "firewall",
11013
11422
  vendor: "juniper-junos",
11423
+ category: "Network-Segmentation",
11014
11424
  metadata: {
11015
11425
  level: "warning",
11016
11426
  obu: "Security",
@@ -11082,6 +11492,7 @@ var SshEnabled = {
11082
11492
  id: "ARU-SEC-001",
11083
11493
  selector: "ssh",
11084
11494
  vendor: ["aruba-aoscx", "aruba-aosswitch", "aruba-wlc"],
11495
+ category: "Session-Management",
11085
11496
  metadata: {
11086
11497
  level: "error",
11087
11498
  obu: "Security",
@@ -11114,6 +11525,7 @@ var NtpConfigured = {
11114
11525
  id: "ARU-NTP-001",
11115
11526
  selector: "ntp",
11116
11527
  vendor: ["aruba-aoscx", "aruba-aosswitch", "aruba-wlc"],
11528
+ category: "Time-Synchronization",
11117
11529
  metadata: {
11118
11530
  level: "warning",
11119
11531
  obu: "Network Engineering",
@@ -11154,6 +11566,7 @@ var AosCxInterfaceDescription = {
11154
11566
  id: "AOSCX-IF-001",
11155
11567
  selector: "interface",
11156
11568
  vendor: "aruba-aoscx",
11569
+ category: "Documentation",
11157
11570
  metadata: {
11158
11571
  level: "warning",
11159
11572
  obu: "Network Engineering",
@@ -11195,6 +11608,7 @@ var AosCxTrunkAllowedVlans = {
11195
11608
  id: "AOSCX-L2-001",
11196
11609
  selector: "interface",
11197
11610
  vendor: "aruba-aoscx",
11611
+ category: "Network-Segmentation",
11198
11612
  metadata: {
11199
11613
  level: "warning",
11200
11614
  obu: "Network Engineering",
@@ -11245,6 +11659,7 @@ var AosSwitchVlanName = {
11245
11659
  id: "AOSSW-L2-001",
11246
11660
  selector: "vlan",
11247
11661
  vendor: "aruba-aosswitch",
11662
+ category: "Documentation",
11248
11663
  metadata: {
11249
11664
  level: "warning",
11250
11665
  obu: "Network Engineering",
@@ -11286,6 +11701,7 @@ var AosSwitchManagerPassword = {
11286
11701
  id: "AOSSW-SEC-001",
11287
11702
  selector: "password",
11288
11703
  vendor: "aruba-aosswitch",
11704
+ category: "Authentication",
11289
11705
  metadata: {
11290
11706
  level: "error",
11291
11707
  obu: "Security",
@@ -11326,6 +11742,7 @@ var WlcSsidEncryption = {
11326
11742
  id: "ARUWLC-WLAN-001",
11327
11743
  selector: "wlan ssid-profile",
11328
11744
  vendor: "aruba-wlc",
11745
+ category: "Wireless",
11329
11746
  metadata: {
11330
11747
  level: "error",
11331
11748
  obu: "Security",
@@ -11379,6 +11796,7 @@ var WlcRadiusHost = {
11379
11796
  id: "ARUWLC-AAA-001",
11380
11797
  selector: "aaa authentication-server radius",
11381
11798
  vendor: "aruba-wlc",
11799
+ category: "Authentication",
11382
11800
  metadata: {
11383
11801
  level: "error",
11384
11802
  obu: "Security",
@@ -11440,6 +11858,7 @@ var HostnameRequired = {
11440
11858
  id: "PAN-SYS-001",
11441
11859
  selector: "deviceconfig",
11442
11860
  vendor: "paloalto-panos",
11861
+ category: "Documentation",
11443
11862
  metadata: {
11444
11863
  level: "warning",
11445
11864
  obu: "Network Engineering",
@@ -11483,6 +11902,7 @@ var SecurityRuleLogging = {
11483
11902
  id: "PAN-SEC-001",
11484
11903
  selector: "rulebase",
11485
11904
  vendor: "paloalto-panos",
11905
+ category: "Logging",
11486
11906
  metadata: {
11487
11907
  level: "warning",
11488
11908
  obu: "Security",
@@ -11533,6 +11953,7 @@ var ZoneProtectionRequired = {
11533
11953
  id: "PAN-ZONE-001",
11534
11954
  selector: "zone",
11535
11955
  vendor: "paloalto-panos",
11956
+ category: "Network-Segmentation",
11536
11957
  metadata: {
11537
11958
  level: "error",
11538
11959
  obu: "Security",
@@ -11595,6 +12016,7 @@ var HostnameRequired2 = {
11595
12016
  id: "ARI-SYS-001",
11596
12017
  selector: "hostname",
11597
12018
  vendor: "arista-eos",
12019
+ category: "Documentation",
11598
12020
  metadata: {
11599
12021
  level: "warning",
11600
12022
  obu: "Network Engineering",
@@ -11629,6 +12051,7 @@ var MlagConfigComplete = {
11629
12051
  id: "ARI-MLAG-001",
11630
12052
  selector: "mlag configuration",
11631
12053
  vendor: "arista-eos",
12054
+ category: "High-Availability",
11632
12055
  metadata: {
11633
12056
  level: "error",
11634
12057
  obu: "Network Engineering",
@@ -11674,6 +12097,7 @@ var InterfaceDescription = {
11674
12097
  id: "ARI-INT-001",
11675
12098
  selector: "interface Ethernet",
11676
12099
  vendor: "arista-eos",
12100
+ category: "Documentation",
11677
12101
  metadata: {
11678
12102
  level: "info",
11679
12103
  obu: "Network Engineering",
@@ -11731,6 +12155,7 @@ var VyosHostnameRequired = {
11731
12155
  id: "VYOS-SYS-001",
11732
12156
  selector: "system",
11733
12157
  vendor: "vyos",
12158
+ category: "Documentation",
11734
12159
  metadata: {
11735
12160
  level: "warning",
11736
12161
  obu: "Network Engineering",
@@ -11763,6 +12188,7 @@ var VyosNoPlaintextPassword = {
11763
12188
  id: "VYOS-SEC-001",
11764
12189
  selector: "authentication",
11765
12190
  vendor: "vyos",
12191
+ category: "Authentication",
11766
12192
  metadata: {
11767
12193
  level: "error",
11768
12194
  obu: "Security",
@@ -11797,6 +12223,7 @@ var VyosInterfaceDescription = {
11797
12223
  id: "VYOS-IF-001",
11798
12224
  selector: "interfaces",
11799
12225
  vendor: "vyos",
12226
+ category: "Documentation",
11800
12227
  metadata: {
11801
12228
  level: "warning",
11802
12229
  obu: "Network Engineering",
@@ -11840,6 +12267,7 @@ var VyosFirewallDefaultAction = {
11840
12267
  id: "VYOS-FW-001",
11841
12268
  selector: "firewall",
11842
12269
  vendor: "vyos",
12270
+ category: "Network-Segmentation",
11843
12271
  metadata: {
11844
12272
  level: "warning",
11845
12273
  obu: "Security",
@@ -11907,6 +12335,7 @@ var HostnameRequired3 = {
11907
12335
  id: "FGT-SYS-001",
11908
12336
  selector: "config system global",
11909
12337
  vendor: "fortinet-fortigate",
12338
+ category: "Documentation",
11910
12339
  metadata: {
11911
12340
  level: "warning",
11912
12341
  obu: "Network Engineering",
@@ -11939,6 +12368,7 @@ var AdminTrustedHostRequired = {
11939
12368
  id: "FGT-ADMIN-001",
11940
12369
  selector: "config system admin",
11941
12370
  vendor: "fortinet-fortigate",
12371
+ category: "Authentication",
11942
12372
  metadata: {
11943
12373
  level: "error",
11944
12374
  obu: "Security",
@@ -11993,6 +12423,7 @@ var PolicyLoggingRequired = {
11993
12423
  id: "FGT-POL-001",
11994
12424
  selector: "config firewall policy",
11995
12425
  vendor: "fortinet-fortigate",
12426
+ category: "Logging",
11996
12427
  metadata: {
11997
12428
  level: "warning",
11998
12429
  obu: "Security",
@@ -12059,6 +12490,7 @@ var ExosSysnameRequired = {
12059
12490
  id: "EXOS-SYS-001",
12060
12491
  selector: "configure snmp sysname",
12061
12492
  vendor: "extreme-exos",
12493
+ category: "Documentation",
12062
12494
  metadata: {
12063
12495
  level: "warning",
12064
12496
  obu: "Network Engineering",
@@ -12092,6 +12524,7 @@ var ExosSsh2Enabled = {
12092
12524
  id: "EXOS-SEC-001",
12093
12525
  selector: "enable ssh2",
12094
12526
  vendor: "extreme-exos",
12527
+ category: "Session-Management",
12095
12528
  metadata: {
12096
12529
  level: "warning",
12097
12530
  obu: "Security",
@@ -12123,6 +12556,7 @@ var ExosVlanNaming = {
12123
12556
  id: "EXOS-VLAN-001",
12124
12557
  selector: "create vlan",
12125
12558
  vendor: "extreme-exos",
12559
+ category: "Documentation",
12126
12560
  metadata: {
12127
12561
  level: "info",
12128
12562
  obu: "Network Engineering",
@@ -12179,6 +12613,7 @@ var VossSysNameRequired = {
12179
12613
  id: "VOSS-SYS-001",
12180
12614
  selector: "snmp-server name",
12181
12615
  vendor: "extreme-voss",
12616
+ category: "Documentation",
12182
12617
  metadata: {
12183
12618
  level: "warning",
12184
12619
  obu: "Network Engineering",
@@ -12212,6 +12647,7 @@ var VossVlanIsidRequired = {
12212
12647
  id: "VOSS-VLAN-001",
12213
12648
  selector: "vlan create",
12214
12649
  vendor: "extreme-voss",
12650
+ category: "Network-Segmentation",
12215
12651
  metadata: {
12216
12652
  level: "info",
12217
12653
  obu: "Network Engineering",
@@ -12264,6 +12700,7 @@ var VossInterfaceDefaultVlan = {
12264
12700
  id: "VOSS-INT-001",
12265
12701
  selector: "interface GigabitEthernet",
12266
12702
  vendor: "extreme-voss",
12703
+ category: "Network-Segmentation",
12267
12704
  metadata: {
12268
12705
  level: "info",
12269
12706
  obu: "Network Engineering",
@@ -12348,6 +12785,7 @@ var SysnameRequired = {
12348
12785
  id: "HUAWEI-SYS-001",
12349
12786
  selector: "sysname",
12350
12787
  vendor: "huawei-vrp",
12788
+ category: "Documentation",
12351
12789
  metadata: {
12352
12790
  level: "warning",
12353
12791
  obu: "Network Engineering",
@@ -12380,6 +12818,7 @@ var InterfaceDescriptionRequired2 = {
12380
12818
  id: "HUAWEI-IF-001",
12381
12819
  selector: "interface",
12382
12820
  vendor: "huawei-vrp",
12821
+ category: "Documentation",
12383
12822
  metadata: {
12384
12823
  level: "warning",
12385
12824
  obu: "Network Engineering",
@@ -12432,6 +12871,7 @@ var VtySshRequired = {
12432
12871
  id: "HUAWEI-VTY-002",
12433
12872
  selector: "user-interface vty",
12434
12873
  vendor: "huawei-vrp",
12874
+ category: "Session-Management",
12435
12875
  metadata: {
12436
12876
  level: "error",
12437
12877
  obu: "Security",
@@ -12496,6 +12936,7 @@ var MikrotikSystemIdentity = {
12496
12936
  id: "MIK-SYS-001",
12497
12937
  selector: "/system identity",
12498
12938
  vendor: "mikrotik-routeros",
12939
+ category: "Documentation",
12499
12940
  metadata: {
12500
12941
  level: "warning",
12501
12942
  obu: "Network Engineering",
@@ -12528,6 +12969,7 @@ var MikrotikDisableUnusedServices = {
12528
12969
  id: "MIK-SEC-001",
12529
12970
  selector: "/ip service",
12530
12971
  vendor: "mikrotik-routeros",
12972
+ category: "Service-Hardening",
12531
12973
  metadata: {
12532
12974
  level: "warning",
12533
12975
  obu: "Security",
@@ -12572,6 +13014,7 @@ var MikrotikInputChainDrop = {
12572
13014
  id: "MIK-FW-001",
12573
13015
  selector: "/ip firewall filter",
12574
13016
  vendor: "mikrotik-routeros",
13017
+ category: "Network-Segmentation",
12575
13018
  metadata: {
12576
13019
  level: "error",
12577
13020
  obu: "Security",
@@ -12625,6 +13068,7 @@ var SystemNameRequired = {
12625
13068
  id: "NOKIA-SYS-001",
12626
13069
  selector: "system",
12627
13070
  vendor: "nokia-sros",
13071
+ category: "Documentation",
12628
13072
  metadata: {
12629
13073
  level: "warning",
12630
13074
  obu: "Network Engineering",
@@ -12657,6 +13101,7 @@ var PortDescriptionRequired = {
12657
13101
  id: "NOKIA-PORT-001",
12658
13102
  selector: "port",
12659
13103
  vendor: "nokia-sros",
13104
+ category: "Documentation",
12660
13105
  metadata: {
12661
13106
  level: "warning",
12662
13107
  obu: "Network Engineering",
@@ -12709,6 +13154,7 @@ var BgpRouterIdRequired = {
12709
13154
  id: "NOKIA-BGP-001",
12710
13155
  selector: "bgp",
12711
13156
  vendor: "nokia-sros",
13157
+ category: "Routing",
12712
13158
  metadata: {
12713
13159
  level: "warning",
12714
13160
  obu: "Network Engineering",
@@ -12765,6 +13211,7 @@ var CumulusInterfaceDescription = {
12765
13211
  id: "CUM-IF-001",
12766
13212
  selector: "iface",
12767
13213
  vendor: "cumulus-linux",
13214
+ category: "Documentation",
12768
13215
  metadata: {
12769
13216
  level: "warning",
12770
13217
  obu: "Network Engineering",
@@ -12807,6 +13254,7 @@ var CumulusBridgeVlans = {
12807
13254
  id: "CUM-BR-001",
12808
13255
  selector: "iface",
12809
13256
  vendor: "cumulus-linux",
13257
+ category: "Network-Segmentation",
12810
13258
  metadata: {
12811
13259
  level: "warning",
12812
13260
  obu: "Network Engineering",
@@ -12859,6 +13307,7 @@ var CumulusBgpRouterId = {
12859
13307
  id: "CUM-BGP-001",
12860
13308
  selector: "router bgp",
12861
13309
  vendor: "cumulus-linux",
13310
+ category: "Routing",
12862
13311
  metadata: {
12863
13312
  level: "warning",
12864
13313
  obu: "Network Engineering",
@@ -12915,6 +13364,7 @@ var cisco_json_rules_default = {
12915
13364
  id: "JSON-CISCO-001",
12916
13365
  selector: "interface",
12917
13366
  vendor: "cisco-ios",
13367
+ category: "Network-Segmentation",
12918
13368
  metadata: {
12919
13369
  level: "warning",
12920
13370
  obu: "Network Engineering",
@@ -12953,6 +13403,7 @@ var cisco_json_rules_default = {
12953
13403
  id: "JSON-CISCO-002",
12954
13404
  selector: "interface",
12955
13405
  vendor: "cisco-ios",
13406
+ category: "Network-Segmentation",
12956
13407
  metadata: {
12957
13408
  level: "info",
12958
13409
  obu: "Network Engineering",
@@ -12991,6 +13442,7 @@ var cisco_json_rules_default = {
12991
13442
  id: "JSON-CISCO-003",
12992
13443
  selector: "interface",
12993
13444
  vendor: "cisco-ios",
13445
+ category: "Documentation",
12994
13446
  metadata: {
12995
13447
  level: "warning",
12996
13448
  obu: "Network Engineering",
@@ -13024,6 +13476,7 @@ var cisco_json_rules_default = {
13024
13476
  id: "JSON-CISCO-004",
13025
13477
  selector: "interface",
13026
13478
  vendor: "cisco-ios",
13479
+ category: "Protocol-Security",
13027
13480
  metadata: {
13028
13481
  level: "error",
13029
13482
  obu: "Security",
@@ -13066,6 +13519,7 @@ var cisco_json_rules_default = {
13066
13519
  id: "JSON-CISCO-005",
13067
13520
  selector: "line vty",
13068
13521
  vendor: "cisco-ios",
13522
+ category: "Session-Management",
13069
13523
  metadata: {
13070
13524
  level: "error",
13071
13525
  obu: "Security",
@@ -13101,6 +13555,7 @@ var common_json_rules_default = {
13101
13555
  id: "JSON-COMMON-001",
13102
13556
  selector: "interface",
13103
13557
  vendor: "common",
13558
+ category: "Documentation",
13104
13559
  metadata: {
13105
13560
  level: "info",
13106
13561
  obu: "Network Engineering",
@@ -13148,6 +13603,7 @@ var juniper_json_rules_default = {
13148
13603
  id: "JSON-JUNOS-001",
13149
13604
  selector: "system services",
13150
13605
  vendor: "juniper-junos",
13606
+ category: "Session-Management",
13151
13607
  metadata: {
13152
13608
  level: "error",
13153
13609
  obu: "Security",
@@ -13180,6 +13636,7 @@ var juniper_json_rules_default = {
13180
13636
  id: "JSON-JUNOS-002",
13181
13637
  selector: "system services",
13182
13638
  vendor: "juniper-junos",
13639
+ category: "Service-Hardening",
13183
13640
  metadata: {
13184
13641
  level: "error",
13185
13642
  obu: "Security",
@@ -13202,6 +13659,7 @@ var juniper_json_rules_default = {
13202
13659
  id: "JSON-JUNOS-003",
13203
13660
  selector: "system",
13204
13661
  vendor: "juniper-junos",
13662
+ category: "Session-Management",
13205
13663
  metadata: {
13206
13664
  level: "warning",
13207
13665
  obu: "Security",
@@ -13221,6 +13679,7 @@ var juniper_json_rules_default = {
13221
13679
  id: "JSON-JUNOS-004",
13222
13680
  selector: "protocols ospf area",
13223
13681
  vendor: "juniper-junos",
13682
+ category: "Routing",
13224
13683
  metadata: {
13225
13684
  level: "warning",
13226
13685
  obu: "Network Engineering",
@@ -13240,6 +13699,7 @@ var juniper_json_rules_default = {
13240
13699
  id: "JSON-JUNOS-005",
13241
13700
  selector: "system syslog",
13242
13701
  vendor: "juniper-junos",
13702
+ category: "Logging",
13243
13703
  metadata: {
13244
13704
  level: "warning",
13245
13705
  obu: "Operations",
@@ -14397,7 +14857,7 @@ function isStdinRequested(files) {
14397
14857
 
14398
14858
  // index.ts
14399
14859
  var program = new Command();
14400
- program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.1.6").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(
14860
+ program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.1.8").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(
14401
14861
  "--encrypted-pack <path...>",
14402
14862
  "SEC-012: Path(s) to encrypted rule pack(s) (.grpx), can specify multiple"
14403
14863
  ).option(
@@ -14690,10 +15150,12 @@ Parsing complete: ${allAsts.length} files`);
14690
15150
  const passed = results2.filter((r) => r.passed).length;
14691
15151
  totalFailures += failures;
14692
15152
  totalPassed += passed;
15153
+ const fileIpSummary = extractIPSummary(content2);
14693
15154
  allFileResults.push({
14694
15155
  filePath: filePath2,
14695
15156
  results: results2,
14696
- vendor: { id: vendor2.id, name: vendor2.name }
15157
+ vendor: { id: vendor2.id, name: vendor2.name },
15158
+ ipSummary: fileIpSummary
14697
15159
  });
14698
15160
  } catch (err) {
14699
15161
  const errMsg = err instanceof Error ? err.message : "Unknown error";
@@ -14725,7 +15187,8 @@ Parsing complete: ${allAsts.length} files`);
14725
15187
  files: allFileResults.map((fr) => ({
14726
15188
  file: fr.filePath,
14727
15189
  vendor: fr.vendor,
14728
- results: fr.results
15190
+ results: fr.results,
15191
+ ipSummary: fr.ipSummary
14729
15192
  }))
14730
15193
  };
14731
15194
  console.log(JSON.stringify(output, null, 2));
@@ -14801,17 +15264,19 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
14801
15264
  if (options.quiet) {
14802
15265
  results2 = results2.filter((r) => !r.passed);
14803
15266
  }
15267
+ const stdinIpSummary = extractIPSummary(content2);
14804
15268
  if (options.format === "sarif") {
14805
15269
  const sarifOptions = {
14806
15270
  relativePaths: options.relativePaths,
14807
15271
  baseDir: process.cwd()
14808
15272
  };
14809
- console.log(generateSarif(results2, "<stdin>", stdinRules, sarifOptions));
15273
+ console.log(generateSarif(results2, "<stdin>", stdinRules, sarifOptions, stdinIpSummary));
14810
15274
  } else {
14811
15275
  const output = {
14812
15276
  file: "<stdin>",
14813
15277
  vendor: { id: vendor2.id, name: vendor2.name },
14814
- results: results2
15278
+ results: results2,
15279
+ ipSummary: stdinIpSummary
14815
15280
  };
14816
15281
  console.log(JSON.stringify(output, null, 2));
14817
15282
  }
@@ -14870,10 +15335,12 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
14870
15335
  const passed = results2.filter((r) => r.passed).length;
14871
15336
  totalFailures += failures;
14872
15337
  totalPassed += passed;
15338
+ const fileIpSummary = extractIPSummary(content2);
14873
15339
  allFileResults.push({
14874
15340
  filePath: filePath2,
14875
15341
  results: results2,
14876
- vendor: { id: vendor2.id, name: vendor2.name }
15342
+ vendor: { id: vendor2.id, name: vendor2.name },
15343
+ ipSummary: fileIpSummary
14877
15344
  });
14878
15345
  } catch (err) {
14879
15346
  const errMsg = err instanceof Error ? err.message : "Unknown error";
@@ -14900,7 +15367,8 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
14900
15367
  files: allFileResults.map((fr) => ({
14901
15368
  file: fr.filePath,
14902
15369
  vendor: fr.vendor,
14903
- results: fr.results
15370
+ results: fr.results,
15371
+ ipSummary: fr.ipSummary
14904
15372
  }))
14905
15373
  };
14906
15374
  console.log(JSON.stringify(output, null, 2));
@@ -14985,13 +15453,14 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
14985
15453
  if (options.quiet) {
14986
15454
  results = results.filter((r) => !r.passed);
14987
15455
  }
15456
+ const ipSummary = extractIPSummary(content);
14988
15457
  if (options.format === "sarif") {
14989
15458
  const sarifOptions = {
14990
15459
  relativePaths: options.relativePaths,
14991
15460
  baseDir: process.cwd()
14992
15461
  };
14993
15462
  console.log(
14994
- generateSarif(results, filePath, singleFileRules, sarifOptions)
15463
+ generateSarif(results, filePath, singleFileRules, sarifOptions, ipSummary)
14995
15464
  );
14996
15465
  } else {
14997
15466
  const output = {
@@ -14999,7 +15468,8 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
14999
15468
  id: vendor.id,
15000
15469
  name: vendor.name
15001
15470
  },
15002
- results
15471
+ results,
15472
+ ipSummary
15003
15473
  };
15004
15474
  console.log(JSON.stringify(output, null, 2));
15005
15475
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentriflow/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "SentriFlow CLI - Network configuration linter and validator",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",