@sentriflow/cli 0.1.5 → 0.1.6
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.
- package/dist/index.js +431 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10407,7 +10407,7 @@ function generateSarif(results, filePath, rules, options = {}) {
|
|
|
10407
10407
|
tool: {
|
|
10408
10408
|
driver: {
|
|
10409
10409
|
name: "Sentriflow",
|
|
10410
|
-
version: "0.1.
|
|
10410
|
+
version: "0.1.6",
|
|
10411
10411
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
10412
10412
|
rules: sarifRules,
|
|
10413
10413
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -10513,7 +10513,7 @@ function generateMultiFileSarif(fileResults, rules, options = {}) {
|
|
|
10513
10513
|
tool: {
|
|
10514
10514
|
driver: {
|
|
10515
10515
|
name: "Sentriflow",
|
|
10516
|
-
version: "0.1.
|
|
10516
|
+
version: "0.1.6",
|
|
10517
10517
|
informationUri: "https://github.com/sentriflow/sentriflow",
|
|
10518
10518
|
rules: sarifRules,
|
|
10519
10519
|
// SEC-007: Include CWE taxonomy when rules reference it
|
|
@@ -13602,8 +13602,110 @@ function isValidSentriflowConfig(config) {
|
|
|
13602
13602
|
}
|
|
13603
13603
|
}
|
|
13604
13604
|
}
|
|
13605
|
+
if (obj.directory !== void 0) {
|
|
13606
|
+
if (!isValidDirectoryConfig(obj.directory)) {
|
|
13607
|
+
return false;
|
|
13608
|
+
}
|
|
13609
|
+
}
|
|
13610
|
+
return true;
|
|
13611
|
+
}
|
|
13612
|
+
function isValidDirectoryConfig(config) {
|
|
13613
|
+
if (config === null || config === void 0) {
|
|
13614
|
+
return false;
|
|
13615
|
+
}
|
|
13616
|
+
if (typeof config !== "object") {
|
|
13617
|
+
return false;
|
|
13618
|
+
}
|
|
13619
|
+
const obj = config;
|
|
13620
|
+
if (obj.excludePatterns !== void 0) {
|
|
13621
|
+
if (!Array.isArray(obj.excludePatterns)) {
|
|
13622
|
+
return false;
|
|
13623
|
+
}
|
|
13624
|
+
for (const pattern of obj.excludePatterns) {
|
|
13625
|
+
if (typeof pattern !== "string") {
|
|
13626
|
+
return false;
|
|
13627
|
+
}
|
|
13628
|
+
try {
|
|
13629
|
+
new RegExp(pattern);
|
|
13630
|
+
} catch {
|
|
13631
|
+
return false;
|
|
13632
|
+
}
|
|
13633
|
+
}
|
|
13634
|
+
}
|
|
13635
|
+
if (obj.extensions !== void 0) {
|
|
13636
|
+
if (!Array.isArray(obj.extensions)) {
|
|
13637
|
+
return false;
|
|
13638
|
+
}
|
|
13639
|
+
for (const ext of obj.extensions) {
|
|
13640
|
+
if (typeof ext !== "string") {
|
|
13641
|
+
return false;
|
|
13642
|
+
}
|
|
13643
|
+
}
|
|
13644
|
+
}
|
|
13645
|
+
if (obj.recursive !== void 0 && typeof obj.recursive !== "boolean") {
|
|
13646
|
+
return false;
|
|
13647
|
+
}
|
|
13648
|
+
if (obj.maxDepth !== void 0) {
|
|
13649
|
+
if (typeof obj.maxDepth !== "number") {
|
|
13650
|
+
return false;
|
|
13651
|
+
}
|
|
13652
|
+
if (obj.maxDepth < 0 || obj.maxDepth > 1e3) {
|
|
13653
|
+
return false;
|
|
13654
|
+
}
|
|
13655
|
+
}
|
|
13656
|
+
if (obj.exclude !== void 0) {
|
|
13657
|
+
if (!Array.isArray(obj.exclude)) {
|
|
13658
|
+
return false;
|
|
13659
|
+
}
|
|
13660
|
+
for (const pattern of obj.exclude) {
|
|
13661
|
+
if (typeof pattern !== "string") {
|
|
13662
|
+
return false;
|
|
13663
|
+
}
|
|
13664
|
+
}
|
|
13665
|
+
}
|
|
13605
13666
|
return true;
|
|
13606
13667
|
}
|
|
13668
|
+
function mergeDirectoryOptions(cliOptions, configOptions) {
|
|
13669
|
+
const result = {};
|
|
13670
|
+
if (cliOptions.excludePatterns) {
|
|
13671
|
+
result.excludePatterns = [...cliOptions.excludePatterns];
|
|
13672
|
+
} else {
|
|
13673
|
+
result.excludePatterns = [];
|
|
13674
|
+
}
|
|
13675
|
+
if (configOptions?.excludePatterns) {
|
|
13676
|
+
for (const pattern of configOptions.excludePatterns) {
|
|
13677
|
+
try {
|
|
13678
|
+
const regex = new RegExp(pattern);
|
|
13679
|
+
result.excludePatterns.push(regex);
|
|
13680
|
+
} catch {
|
|
13681
|
+
}
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
const excludeSet = /* @__PURE__ */ new Set();
|
|
13685
|
+
if (cliOptions.exclude) {
|
|
13686
|
+
for (const p of cliOptions.exclude) excludeSet.add(p);
|
|
13687
|
+
}
|
|
13688
|
+
if (configOptions?.exclude) {
|
|
13689
|
+
for (const p of configOptions.exclude) excludeSet.add(p);
|
|
13690
|
+
}
|
|
13691
|
+
result.exclude = [...excludeSet];
|
|
13692
|
+
if (cliOptions.recursive !== void 0) {
|
|
13693
|
+
result.recursive = cliOptions.recursive;
|
|
13694
|
+
} else if (configOptions?.recursive !== void 0) {
|
|
13695
|
+
result.recursive = configOptions.recursive;
|
|
13696
|
+
}
|
|
13697
|
+
if (cliOptions.maxDepth !== void 0) {
|
|
13698
|
+
result.maxDepth = cliOptions.maxDepth;
|
|
13699
|
+
} else if (configOptions?.maxDepth !== void 0) {
|
|
13700
|
+
result.maxDepth = configOptions.maxDepth;
|
|
13701
|
+
}
|
|
13702
|
+
if (cliOptions.extensions !== void 0) {
|
|
13703
|
+
result.extensions = cliOptions.extensions;
|
|
13704
|
+
} else if (configOptions?.extensions !== void 0) {
|
|
13705
|
+
result.extensions = configOptions.extensions;
|
|
13706
|
+
}
|
|
13707
|
+
return result;
|
|
13708
|
+
}
|
|
13607
13709
|
function isValidRule(rule) {
|
|
13608
13710
|
if (typeof rule !== "object" || rule === null) {
|
|
13609
13711
|
return false;
|
|
@@ -14048,6 +14150,19 @@ function isExcluded(relativePath, excludePatterns) {
|
|
|
14048
14150
|
if (excludePatterns.length === 0) return false;
|
|
14049
14151
|
return excludePatterns.some((pattern) => matchesPattern(relativePath, pattern));
|
|
14050
14152
|
}
|
|
14153
|
+
function validateRegexPattern(pattern) {
|
|
14154
|
+
try {
|
|
14155
|
+
const regex = new RegExp(pattern);
|
|
14156
|
+
return { valid: true, regex };
|
|
14157
|
+
} catch (error) {
|
|
14158
|
+
const message = error instanceof Error ? error.message : "Invalid regex pattern";
|
|
14159
|
+
return { valid: false, error: message };
|
|
14160
|
+
}
|
|
14161
|
+
}
|
|
14162
|
+
function isExcludedByRegex(relativePath, patterns) {
|
|
14163
|
+
if (patterns.length === 0 || relativePath === "") return false;
|
|
14164
|
+
return patterns.some((pattern) => pattern.test(relativePath));
|
|
14165
|
+
}
|
|
14051
14166
|
async function scanDirectory(dirPath, options = {}) {
|
|
14052
14167
|
const {
|
|
14053
14168
|
recursive = false,
|
|
@@ -14056,7 +14171,8 @@ async function scanDirectory(dirPath, options = {}) {
|
|
|
14056
14171
|
maxFileSize = MAX_CONFIG_SIZE,
|
|
14057
14172
|
maxDepth = 100,
|
|
14058
14173
|
allowedBaseDirs,
|
|
14059
|
-
exclude = []
|
|
14174
|
+
exclude = [],
|
|
14175
|
+
excludePatterns = []
|
|
14060
14176
|
} = options;
|
|
14061
14177
|
const result = {
|
|
14062
14178
|
files: [],
|
|
@@ -14102,7 +14218,11 @@ async function scanDirectory(dirPath, options = {}) {
|
|
|
14102
14218
|
for (const entry of entries) {
|
|
14103
14219
|
const fullPath = join(currentDir, entry);
|
|
14104
14220
|
const relativePath = join(basePath, entry);
|
|
14105
|
-
|
|
14221
|
+
const normalizedRelativePath = normalizeSeparators2(relativePath);
|
|
14222
|
+
if (isExcluded(normalizedRelativePath, exclude)) {
|
|
14223
|
+
continue;
|
|
14224
|
+
}
|
|
14225
|
+
if (isExcludedByRegex(normalizedRelativePath, excludePatterns)) {
|
|
14106
14226
|
continue;
|
|
14107
14227
|
}
|
|
14108
14228
|
let stats;
|
|
@@ -14188,9 +14308,96 @@ function validateDirectoryPath(dirPath, allowedBaseDirs) {
|
|
|
14188
14308
|
}
|
|
14189
14309
|
}
|
|
14190
14310
|
|
|
14311
|
+
// src/loaders/stdin.ts
|
|
14312
|
+
async function readStdin() {
|
|
14313
|
+
return new Promise((resolve5) => {
|
|
14314
|
+
const stdin = process.stdin;
|
|
14315
|
+
const chunks = [];
|
|
14316
|
+
let totalSize = 0;
|
|
14317
|
+
let sizeLimitExceeded = false;
|
|
14318
|
+
stdin.setEncoding("utf8");
|
|
14319
|
+
if (stdin.isTTY) {
|
|
14320
|
+
resolve5({
|
|
14321
|
+
success: false,
|
|
14322
|
+
error: "No input received from stdin"
|
|
14323
|
+
});
|
|
14324
|
+
return;
|
|
14325
|
+
}
|
|
14326
|
+
stdin.on("data", (chunk) => {
|
|
14327
|
+
if (sizeLimitExceeded) return;
|
|
14328
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf8");
|
|
14329
|
+
totalSize += buffer.length;
|
|
14330
|
+
if (totalSize > MAX_CONFIG_SIZE) {
|
|
14331
|
+
sizeLimitExceeded = true;
|
|
14332
|
+
resolve5({
|
|
14333
|
+
success: false,
|
|
14334
|
+
error: `Input exceeds maximum size (${totalSize} > ${MAX_CONFIG_SIZE} bytes)`
|
|
14335
|
+
});
|
|
14336
|
+
stdin.destroy();
|
|
14337
|
+
return;
|
|
14338
|
+
}
|
|
14339
|
+
chunks.push(buffer);
|
|
14340
|
+
});
|
|
14341
|
+
stdin.on("end", () => {
|
|
14342
|
+
if (sizeLimitExceeded) return;
|
|
14343
|
+
const content = Buffer.concat(chunks).toString("utf8");
|
|
14344
|
+
if (content.length === 0) {
|
|
14345
|
+
resolve5({
|
|
14346
|
+
success: false,
|
|
14347
|
+
error: "No input received from stdin"
|
|
14348
|
+
});
|
|
14349
|
+
return;
|
|
14350
|
+
}
|
|
14351
|
+
resolve5({
|
|
14352
|
+
success: true,
|
|
14353
|
+
content
|
|
14354
|
+
});
|
|
14355
|
+
});
|
|
14356
|
+
stdin.on("error", (err) => {
|
|
14357
|
+
resolve5({
|
|
14358
|
+
success: false,
|
|
14359
|
+
error: `Failed to read from stdin: ${err.message}`
|
|
14360
|
+
});
|
|
14361
|
+
});
|
|
14362
|
+
const timeout = setTimeout(() => {
|
|
14363
|
+
if (chunks.length === 0 && !sizeLimitExceeded) {
|
|
14364
|
+
stdin.destroy();
|
|
14365
|
+
resolve5({
|
|
14366
|
+
success: false,
|
|
14367
|
+
error: "No input received from stdin (timeout)"
|
|
14368
|
+
});
|
|
14369
|
+
}
|
|
14370
|
+
}, 100);
|
|
14371
|
+
stdin.on("end", () => clearTimeout(timeout));
|
|
14372
|
+
stdin.on("error", () => clearTimeout(timeout));
|
|
14373
|
+
});
|
|
14374
|
+
}
|
|
14375
|
+
function validateStdinArgument(files, hasDirectory) {
|
|
14376
|
+
const hasStdin = files.includes("-");
|
|
14377
|
+
if (!hasStdin) {
|
|
14378
|
+
return { valid: true };
|
|
14379
|
+
}
|
|
14380
|
+
if (files.length > 1) {
|
|
14381
|
+
return {
|
|
14382
|
+
valid: false,
|
|
14383
|
+
error: "Cannot combine stdin (-) with other file arguments"
|
|
14384
|
+
};
|
|
14385
|
+
}
|
|
14386
|
+
if (hasDirectory) {
|
|
14387
|
+
return {
|
|
14388
|
+
valid: false,
|
|
14389
|
+
error: "Cannot combine stdin (-) with directory mode (-D)"
|
|
14390
|
+
};
|
|
14391
|
+
}
|
|
14392
|
+
return { valid: true };
|
|
14393
|
+
}
|
|
14394
|
+
function isStdinRequested(files) {
|
|
14395
|
+
return files.length === 1 && files[0] === "-";
|
|
14396
|
+
}
|
|
14397
|
+
|
|
14191
14398
|
// index.ts
|
|
14192
14399
|
var program = new Command();
|
|
14193
|
-
program.name("sentriflow").description("SentriFlow Network Configuration Validator").version("0.1.
|
|
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(
|
|
14194
14401
|
"--encrypted-pack <path...>",
|
|
14195
14402
|
"SEC-012: Path(s) to encrypted rule pack(s) (.grpx), can specify multiple"
|
|
14196
14403
|
).option(
|
|
@@ -14231,7 +14438,14 @@ program.name("sentriflow").description("SentriFlow Network Configuration Validat
|
|
|
14231
14438
|
"--exclude <patterns>",
|
|
14232
14439
|
"Exclude patterns (comma-separated glob patterns)",
|
|
14233
14440
|
(val) => val.split(",")
|
|
14234
|
-
).option(
|
|
14441
|
+
).option(
|
|
14442
|
+
"--exclude-pattern <pattern...>",
|
|
14443
|
+
"Regex pattern(s) to exclude files (JavaScript regex syntax, can specify multiple)"
|
|
14444
|
+
).option(
|
|
14445
|
+
"--max-depth <number>",
|
|
14446
|
+
"Maximum recursion depth for directory scanning (use with -R)",
|
|
14447
|
+
(val) => parseInt(val, 10)
|
|
14448
|
+
).option("--progress", "Show progress during directory scanning").action(async (files, options) => {
|
|
14235
14449
|
try {
|
|
14236
14450
|
if (options.listVendors) {
|
|
14237
14451
|
console.log("Supported vendors:\n");
|
|
@@ -14258,8 +14472,21 @@ Use: sentriflow --vendor <vendor> <file>`);
|
|
|
14258
14472
|
}
|
|
14259
14473
|
const workingDir = process.cwd();
|
|
14260
14474
|
const allowedBaseDirs = options.allowExternal ? void 0 : [workingDir];
|
|
14475
|
+
const excludePatterns = [];
|
|
14476
|
+
if (options.excludePattern) {
|
|
14477
|
+
for (const pattern of options.excludePattern) {
|
|
14478
|
+
const result = validateRegexPattern(pattern);
|
|
14479
|
+
if (!result.valid) {
|
|
14480
|
+
console.error(`Error: Invalid regex pattern '${pattern}'`);
|
|
14481
|
+
console.error(` ${result.error}`);
|
|
14482
|
+
process.exit(2);
|
|
14483
|
+
}
|
|
14484
|
+
excludePatterns.push(result.regex);
|
|
14485
|
+
}
|
|
14486
|
+
}
|
|
14261
14487
|
const licenseKey = options.licenseKey || process.env.SENTRIFLOW_LICENSE_KEY;
|
|
14262
|
-
const
|
|
14488
|
+
const firstFile = files.length > 0 ? files[0] : void 0;
|
|
14489
|
+
const configSearchDir = firstFile ? dirname2(resolve4(firstFile)) : workingDir;
|
|
14263
14490
|
const rules = await resolveRules({
|
|
14264
14491
|
configPath: options.config,
|
|
14265
14492
|
noConfig: options.config === false,
|
|
@@ -14313,18 +14540,44 @@ Config file: ${configFile}`);
|
|
|
14313
14540
|
process.exit(2);
|
|
14314
14541
|
}
|
|
14315
14542
|
const canonicalDir = dirValidation.canonicalPath;
|
|
14543
|
+
let directoryConfig;
|
|
14544
|
+
if (options.config !== false) {
|
|
14545
|
+
const configPath = options.config ?? findConfigFile(canonicalDir);
|
|
14546
|
+
if (configPath) {
|
|
14547
|
+
try {
|
|
14548
|
+
const config = await loadConfigFile(configPath, allowedBaseDirs);
|
|
14549
|
+
directoryConfig = config.directory;
|
|
14550
|
+
} catch (err) {
|
|
14551
|
+
if (options.progress) {
|
|
14552
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
14553
|
+
console.error(`Warning: Failed to load config: ${msg}`);
|
|
14554
|
+
}
|
|
14555
|
+
}
|
|
14556
|
+
}
|
|
14557
|
+
}
|
|
14558
|
+
const cliDirOptions = {
|
|
14559
|
+
recursive: options.recursive,
|
|
14560
|
+
extensions: options.extensions,
|
|
14561
|
+
exclude: options.exclude,
|
|
14562
|
+
excludePatterns: excludePatterns.length > 0 ? excludePatterns : void 0,
|
|
14563
|
+
maxDepth: options.maxDepth
|
|
14564
|
+
};
|
|
14565
|
+
const mergedOptions = mergeDirectoryOptions(cliDirOptions, directoryConfig);
|
|
14316
14566
|
if (options.progress) {
|
|
14567
|
+
const recursive = mergedOptions.recursive ?? false;
|
|
14317
14568
|
console.error(
|
|
14318
|
-
`Scanning directory: ${canonicalDir}${
|
|
14569
|
+
`Scanning directory: ${canonicalDir}${recursive ? " (recursive)" : ""}`
|
|
14319
14570
|
);
|
|
14320
14571
|
}
|
|
14321
14572
|
const scanResult = await scanDirectory(canonicalDir, {
|
|
14322
|
-
recursive:
|
|
14573
|
+
recursive: mergedOptions.recursive ?? false,
|
|
14323
14574
|
patterns: options.glob ? [options.glob] : [],
|
|
14324
|
-
extensions:
|
|
14575
|
+
extensions: mergedOptions.extensions ?? DEFAULT_CONFIG_EXTENSIONS,
|
|
14325
14576
|
maxFileSize: MAX_CONFIG_SIZE,
|
|
14577
|
+
maxDepth: mergedOptions.maxDepth,
|
|
14326
14578
|
allowedBaseDirs,
|
|
14327
|
-
exclude:
|
|
14579
|
+
exclude: mergedOptions.exclude ?? [],
|
|
14580
|
+
excludePatterns: mergedOptions.excludePatterns ?? []
|
|
14328
14581
|
});
|
|
14329
14582
|
if (scanResult.errors.length > 0 && options.progress) {
|
|
14330
14583
|
console.error(`
|
|
@@ -14488,10 +14741,176 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
14488
14741
|
}
|
|
14489
14742
|
return;
|
|
14490
14743
|
}
|
|
14491
|
-
if (
|
|
14744
|
+
if (files.length === 0) {
|
|
14492
14745
|
program.help();
|
|
14493
14746
|
return;
|
|
14494
14747
|
}
|
|
14748
|
+
const stdinValidation = validateStdinArgument(files, !!options.directory);
|
|
14749
|
+
if (!stdinValidation.valid) {
|
|
14750
|
+
console.error(`Error: ${stdinValidation.error}`);
|
|
14751
|
+
process.exit(2);
|
|
14752
|
+
}
|
|
14753
|
+
if (isStdinRequested(files)) {
|
|
14754
|
+
const stdinResult = await readStdin();
|
|
14755
|
+
if (!stdinResult.success) {
|
|
14756
|
+
console.error(`Error: ${stdinResult.error}`);
|
|
14757
|
+
process.exit(2);
|
|
14758
|
+
}
|
|
14759
|
+
const content2 = stdinResult.content;
|
|
14760
|
+
let vendor2;
|
|
14761
|
+
if (options.vendor === "auto") {
|
|
14762
|
+
vendor2 = detectVendor(content2);
|
|
14763
|
+
if (!options.quiet && !options.ast) {
|
|
14764
|
+
console.error(`Detected vendor: ${vendor2.name} (${vendor2.id})`);
|
|
14765
|
+
}
|
|
14766
|
+
} else {
|
|
14767
|
+
try {
|
|
14768
|
+
vendor2 = getVendor(options.vendor);
|
|
14769
|
+
} catch {
|
|
14770
|
+
console.error(`Error: Unknown vendor '${options.vendor}'`);
|
|
14771
|
+
console.error(`Available vendors: ${getAvailableVendors().join(", ")}, auto`);
|
|
14772
|
+
process.exit(2);
|
|
14773
|
+
}
|
|
14774
|
+
}
|
|
14775
|
+
const stdinRules = await resolveRules({
|
|
14776
|
+
configPath: options.config,
|
|
14777
|
+
noConfig: options.config === false,
|
|
14778
|
+
rulesPath: options.rules,
|
|
14779
|
+
rulePackPath: options.rulePack,
|
|
14780
|
+
encryptedPackPaths: options.encryptedPack,
|
|
14781
|
+
licenseKey,
|
|
14782
|
+
strictPacks: options.strictPacks,
|
|
14783
|
+
jsonRulesPaths: options.jsonRules,
|
|
14784
|
+
disableIds: options.disable ?? [],
|
|
14785
|
+
vendorId: vendor2.id,
|
|
14786
|
+
cwd: workingDir,
|
|
14787
|
+
allowedBaseDirs
|
|
14788
|
+
});
|
|
14789
|
+
const parser2 = new SchemaAwareParser({ vendor: vendor2 });
|
|
14790
|
+
const nodes2 = parser2.parse(content2);
|
|
14791
|
+
if (options.ast) {
|
|
14792
|
+
const output = {
|
|
14793
|
+
vendor: { id: vendor2.id, name: vendor2.name },
|
|
14794
|
+
ast: nodes2
|
|
14795
|
+
};
|
|
14796
|
+
console.log(JSON.stringify(output, null, 2));
|
|
14797
|
+
return;
|
|
14798
|
+
}
|
|
14799
|
+
const engine2 = new RuleEngine();
|
|
14800
|
+
let results2 = engine2.run(nodes2, stdinRules);
|
|
14801
|
+
if (options.quiet) {
|
|
14802
|
+
results2 = results2.filter((r) => !r.passed);
|
|
14803
|
+
}
|
|
14804
|
+
if (options.format === "sarif") {
|
|
14805
|
+
const sarifOptions = {
|
|
14806
|
+
relativePaths: options.relativePaths,
|
|
14807
|
+
baseDir: process.cwd()
|
|
14808
|
+
};
|
|
14809
|
+
console.log(generateSarif(results2, "<stdin>", stdinRules, sarifOptions));
|
|
14810
|
+
} else {
|
|
14811
|
+
const output = {
|
|
14812
|
+
file: "<stdin>",
|
|
14813
|
+
vendor: { id: vendor2.id, name: vendor2.name },
|
|
14814
|
+
results: results2
|
|
14815
|
+
};
|
|
14816
|
+
console.log(JSON.stringify(output, null, 2));
|
|
14817
|
+
}
|
|
14818
|
+
const hasFailures2 = results2.some((r) => !r.passed);
|
|
14819
|
+
if (hasFailures2) {
|
|
14820
|
+
process.exit(1);
|
|
14821
|
+
}
|
|
14822
|
+
return;
|
|
14823
|
+
}
|
|
14824
|
+
if (files.length > 1) {
|
|
14825
|
+
const allFileResults = [];
|
|
14826
|
+
let totalFailures = 0;
|
|
14827
|
+
let totalPassed = 0;
|
|
14828
|
+
const engine2 = new RuleEngine();
|
|
14829
|
+
for (let i = 0; i < files.length; i++) {
|
|
14830
|
+
const file2 = files[i];
|
|
14831
|
+
if (!file2) continue;
|
|
14832
|
+
const fileValidation2 = validateInputFilePath(
|
|
14833
|
+
file2,
|
|
14834
|
+
MAX_CONFIG_SIZE,
|
|
14835
|
+
allowedBaseDirs
|
|
14836
|
+
);
|
|
14837
|
+
if (!fileValidation2.valid) {
|
|
14838
|
+
console.error(`Error processing ${file2}: ${fileValidation2.error}`);
|
|
14839
|
+
allFileResults.push({
|
|
14840
|
+
filePath: file2,
|
|
14841
|
+
results: []
|
|
14842
|
+
});
|
|
14843
|
+
continue;
|
|
14844
|
+
}
|
|
14845
|
+
const filePath2 = fileValidation2.canonicalPath;
|
|
14846
|
+
try {
|
|
14847
|
+
const stats2 = statSync2(filePath2);
|
|
14848
|
+
if (stats2.size > MAX_CONFIG_SIZE) {
|
|
14849
|
+
console.error(`Error: ${file2} exceeds maximum size`);
|
|
14850
|
+
allFileResults.push({ filePath: file2, results: [] });
|
|
14851
|
+
continue;
|
|
14852
|
+
}
|
|
14853
|
+
const content2 = await readFile(filePath2, "utf-8");
|
|
14854
|
+
let vendor2;
|
|
14855
|
+
if (options.vendor === "auto") {
|
|
14856
|
+
vendor2 = detectVendor(content2);
|
|
14857
|
+
} else {
|
|
14858
|
+
vendor2 = getVendor(options.vendor);
|
|
14859
|
+
}
|
|
14860
|
+
const fileRules = rules.filter(
|
|
14861
|
+
(rule) => ruleAppliesToVendor(rule, vendor2.id)
|
|
14862
|
+
);
|
|
14863
|
+
const parser2 = new SchemaAwareParser({ vendor: vendor2 });
|
|
14864
|
+
const nodes2 = parser2.parse(content2);
|
|
14865
|
+
let results2 = engine2.run(nodes2, fileRules);
|
|
14866
|
+
if (options.quiet) {
|
|
14867
|
+
results2 = results2.filter((r) => !r.passed);
|
|
14868
|
+
}
|
|
14869
|
+
const failures = results2.filter((r) => !r.passed).length;
|
|
14870
|
+
const passed = results2.filter((r) => r.passed).length;
|
|
14871
|
+
totalFailures += failures;
|
|
14872
|
+
totalPassed += passed;
|
|
14873
|
+
allFileResults.push({
|
|
14874
|
+
filePath: filePath2,
|
|
14875
|
+
results: results2,
|
|
14876
|
+
vendor: { id: vendor2.id, name: vendor2.name }
|
|
14877
|
+
});
|
|
14878
|
+
} catch (err) {
|
|
14879
|
+
const errMsg = err instanceof Error ? err.message : "Unknown error";
|
|
14880
|
+
console.error(`Error processing ${basename(file2)}: ${errMsg}`);
|
|
14881
|
+
allFileResults.push({ filePath: file2, results: [] });
|
|
14882
|
+
}
|
|
14883
|
+
}
|
|
14884
|
+
if (options.format === "sarif") {
|
|
14885
|
+
const sarifOptions = {
|
|
14886
|
+
relativePaths: options.relativePaths,
|
|
14887
|
+
baseDir: process.cwd()
|
|
14888
|
+
};
|
|
14889
|
+
console.log(
|
|
14890
|
+
generateMultiFileSarif(allFileResults, rules, sarifOptions)
|
|
14891
|
+
);
|
|
14892
|
+
} else {
|
|
14893
|
+
const output = {
|
|
14894
|
+
summary: {
|
|
14895
|
+
filesScanned: allFileResults.length,
|
|
14896
|
+
totalResults: totalFailures + totalPassed,
|
|
14897
|
+
failures: totalFailures,
|
|
14898
|
+
passed: totalPassed
|
|
14899
|
+
},
|
|
14900
|
+
files: allFileResults.map((fr) => ({
|
|
14901
|
+
file: fr.filePath,
|
|
14902
|
+
vendor: fr.vendor,
|
|
14903
|
+
results: fr.results
|
|
14904
|
+
}))
|
|
14905
|
+
};
|
|
14906
|
+
console.log(JSON.stringify(output, null, 2));
|
|
14907
|
+
}
|
|
14908
|
+
if (totalFailures > 0) {
|
|
14909
|
+
process.exit(1);
|
|
14910
|
+
}
|
|
14911
|
+
return;
|
|
14912
|
+
}
|
|
14913
|
+
const file = files[0];
|
|
14495
14914
|
const fileValidation = validateInputFilePath(
|
|
14496
14915
|
file,
|
|
14497
14916
|
MAX_CONFIG_SIZE,
|