@sentriflow/cli 0.1.7 → 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 +422 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10334,6 +10334,350 @@ function validateRegex(pattern, flags, path, ctx) {
10334
10334
  }
10335
10335
  }
10336
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
+
10337
10681
  // index.ts
10338
10682
  import { readFile } from "fs/promises";
10339
10683
  import { statSync as statSync2 } from "fs";
@@ -10341,7 +10685,7 @@ import { resolve as resolve4, dirname as dirname2, basename } from "path";
10341
10685
 
10342
10686
  // src/sarif.ts
10343
10687
  import { relative } from "path";
10344
- function generateSarif(results, filePath, rules, options = {}) {
10688
+ function generateSarif(results, filePath, rules, options = {}, ipSummary) {
10345
10689
  const fileUri = options.relativePaths ? relative(options.baseDir ?? process.cwd(), filePath) : filePath;
10346
10690
  const sarifResults = results.map((result) => {
10347
10691
  return {
@@ -10408,7 +10752,7 @@ function generateSarif(results, filePath, rules, options = {}) {
10408
10752
  tool: {
10409
10753
  driver: {
10410
10754
  name: "Sentriflow",
10411
- version: "0.1.7",
10755
+ version: "0.1.8",
10412
10756
  informationUri: "https://github.com/sentriflow/sentriflow",
10413
10757
  rules: sarifRules,
10414
10758
  // SEC-007: Include CWE taxonomy when rules reference it
@@ -10435,13 +10779,61 @@ function generateSarif(results, filePath, rules, options = {}) {
10435
10779
  }
10436
10780
  ]
10437
10781
  },
10438
- results: sarifResults
10782
+ results: sarifResults,
10783
+ // Include IP summary in properties if available
10784
+ ...ipSummary && {
10785
+ properties: {
10786
+ ipSummary
10787
+ }
10788
+ }
10439
10789
  }
10440
10790
  ]
10441
10791
  };
10442
10792
  return JSON.stringify(report, null, 2);
10443
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
+ }
10444
10833
  function generateMultiFileSarif(fileResults, rules, options = {}) {
10834
+ const aggregatedIpSummary = aggregateIPSummaries(
10835
+ fileResults.map((fr) => fr.ipSummary).filter((s) => !!s)
10836
+ );
10445
10837
  const allSarifResults = fileResults.flatMap(({ filePath, results }) => {
10446
10838
  const fileUri = options.relativePaths ? relative(options.baseDir ?? process.cwd(), filePath) : filePath;
10447
10839
  return results.map((result) => ({
@@ -10514,7 +10906,7 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
10514
10906
  tool: {
10515
10907
  driver: {
10516
10908
  name: "Sentriflow",
10517
- version: "0.1.7",
10909
+ version: "0.1.8",
10518
10910
  informationUri: "https://github.com/sentriflow/sentriflow",
10519
10911
  rules: sarifRules,
10520
10912
  // SEC-007: Include CWE taxonomy when rules reference it
@@ -10545,7 +10937,13 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
10545
10937
  }
10546
10938
  ]
10547
10939
  },
10548
- results: allSarifResults
10940
+ results: allSarifResults,
10941
+ // Include aggregated IP summary in properties if available
10942
+ ...aggregatedIpSummary && {
10943
+ properties: {
10944
+ ipSummary: aggregatedIpSummary
10945
+ }
10946
+ }
10549
10947
  }
10550
10948
  ]
10551
10949
  };
@@ -14459,7 +14857,7 @@ function isStdinRequested(files) {
14459
14857
 
14460
14858
  // index.ts
14461
14859
  var program = new Command();
14462
- program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.1.7").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(
14463
14861
  "--encrypted-pack <path...>",
14464
14862
  "SEC-012: Path(s) to encrypted rule pack(s) (.grpx), can specify multiple"
14465
14863
  ).option(
@@ -14752,10 +15150,12 @@ Parsing complete: ${allAsts.length} files`);
14752
15150
  const passed = results2.filter((r) => r.passed).length;
14753
15151
  totalFailures += failures;
14754
15152
  totalPassed += passed;
15153
+ const fileIpSummary = extractIPSummary(content2);
14755
15154
  allFileResults.push({
14756
15155
  filePath: filePath2,
14757
15156
  results: results2,
14758
- vendor: { id: vendor2.id, name: vendor2.name }
15157
+ vendor: { id: vendor2.id, name: vendor2.name },
15158
+ ipSummary: fileIpSummary
14759
15159
  });
14760
15160
  } catch (err) {
14761
15161
  const errMsg = err instanceof Error ? err.message : "Unknown error";
@@ -14787,7 +15187,8 @@ Parsing complete: ${allAsts.length} files`);
14787
15187
  files: allFileResults.map((fr) => ({
14788
15188
  file: fr.filePath,
14789
15189
  vendor: fr.vendor,
14790
- results: fr.results
15190
+ results: fr.results,
15191
+ ipSummary: fr.ipSummary
14791
15192
  }))
14792
15193
  };
14793
15194
  console.log(JSON.stringify(output, null, 2));
@@ -14863,17 +15264,19 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
14863
15264
  if (options.quiet) {
14864
15265
  results2 = results2.filter((r) => !r.passed);
14865
15266
  }
15267
+ const stdinIpSummary = extractIPSummary(content2);
14866
15268
  if (options.format === "sarif") {
14867
15269
  const sarifOptions = {
14868
15270
  relativePaths: options.relativePaths,
14869
15271
  baseDir: process.cwd()
14870
15272
  };
14871
- console.log(generateSarif(results2, "<stdin>", stdinRules, sarifOptions));
15273
+ console.log(generateSarif(results2, "<stdin>", stdinRules, sarifOptions, stdinIpSummary));
14872
15274
  } else {
14873
15275
  const output = {
14874
15276
  file: "<stdin>",
14875
15277
  vendor: { id: vendor2.id, name: vendor2.name },
14876
- results: results2
15278
+ results: results2,
15279
+ ipSummary: stdinIpSummary
14877
15280
  };
14878
15281
  console.log(JSON.stringify(output, null, 2));
14879
15282
  }
@@ -14932,10 +15335,12 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
14932
15335
  const passed = results2.filter((r) => r.passed).length;
14933
15336
  totalFailures += failures;
14934
15337
  totalPassed += passed;
15338
+ const fileIpSummary = extractIPSummary(content2);
14935
15339
  allFileResults.push({
14936
15340
  filePath: filePath2,
14937
15341
  results: results2,
14938
- vendor: { id: vendor2.id, name: vendor2.name }
15342
+ vendor: { id: vendor2.id, name: vendor2.name },
15343
+ ipSummary: fileIpSummary
14939
15344
  });
14940
15345
  } catch (err) {
14941
15346
  const errMsg = err instanceof Error ? err.message : "Unknown error";
@@ -14962,7 +15367,8 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
14962
15367
  files: allFileResults.map((fr) => ({
14963
15368
  file: fr.filePath,
14964
15369
  vendor: fr.vendor,
14965
- results: fr.results
15370
+ results: fr.results,
15371
+ ipSummary: fr.ipSummary
14966
15372
  }))
14967
15373
  };
14968
15374
  console.log(JSON.stringify(output, null, 2));
@@ -15047,13 +15453,14 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
15047
15453
  if (options.quiet) {
15048
15454
  results = results.filter((r) => !r.passed);
15049
15455
  }
15456
+ const ipSummary = extractIPSummary(content);
15050
15457
  if (options.format === "sarif") {
15051
15458
  const sarifOptions = {
15052
15459
  relativePaths: options.relativePaths,
15053
15460
  baseDir: process.cwd()
15054
15461
  };
15055
15462
  console.log(
15056
- generateSarif(results, filePath, singleFileRules, sarifOptions)
15463
+ generateSarif(results, filePath, singleFileRules, sarifOptions, ipSummary)
15057
15464
  );
15058
15465
  } else {
15059
15466
  const output = {
@@ -15061,7 +15468,8 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
15061
15468
  id: vendor.id,
15062
15469
  name: vendor.name
15063
15470
  },
15064
- results
15471
+ results,
15472
+ ipSummary
15065
15473
  };
15066
15474
  console.log(JSON.stringify(output, null, 2));
15067
15475
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentriflow/cli",
3
- "version": "0.1.7",
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",