@sentriflow/cli 0.1.4 → 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/README.md +7 -7
- package/dist/index.js +534 -116
- package/package.json +54 -54
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @sentriflow/cli
|
|
2
2
|
|
|
3
|
-
Command-line interface for SentriFlow -
|
|
3
|
+
Command-line interface for SentriFlow - check network configurations for compliance against best practices or organization-specific policies.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -17,10 +17,10 @@ bun add -g @sentriflow/cli
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
#
|
|
20
|
+
# Check a single configuration file
|
|
21
21
|
sentriflow router.conf
|
|
22
22
|
|
|
23
|
-
#
|
|
23
|
+
# Check with specific vendor
|
|
24
24
|
sentriflow -v cisco-ios router.conf
|
|
25
25
|
|
|
26
26
|
# Scan a directory of configs
|
|
@@ -44,7 +44,7 @@ sentriflow --list-rules
|
|
|
44
44
|
```
|
|
45
45
|
Usage: sentriflow [options] [file]
|
|
46
46
|
|
|
47
|
-
SentriFlow Network Configuration
|
|
47
|
+
SentriFlow Network Configuration Compliance Checker
|
|
48
48
|
|
|
49
49
|
Arguments:
|
|
50
50
|
file Path to the configuration file
|
|
@@ -164,7 +164,7 @@ sentriflow router.conf -f sarif > results.sarif
|
|
|
164
164
|
### GitHub Actions
|
|
165
165
|
|
|
166
166
|
```yaml
|
|
167
|
-
- name:
|
|
167
|
+
- name: Check network config compliance
|
|
168
168
|
run: |
|
|
169
169
|
npx @sentriflow/cli -D configs/ -R -f sarif > results.sarif
|
|
170
170
|
|
|
@@ -190,8 +190,8 @@ SentriFlow automatically looks for `.sentriflowrc` or `.sentriflowrc.json` in th
|
|
|
190
190
|
|
|
191
191
|
## Related Packages
|
|
192
192
|
|
|
193
|
-
- [`@sentriflow/core`](https://github.com/sentriflow/sentriflow/tree/main/packages/core) - Core parsing engine
|
|
194
|
-
- [`@sentriflow/rules-default`](https://github.com/sentriflow/sentriflow/tree/main/packages/rules-default) - Default
|
|
193
|
+
- [`@sentriflow/core`](https://github.com/sentriflow/sentriflow/tree/main/packages/core) - Core parsing and compliance engine
|
|
194
|
+
- [`@sentriflow/rules-default`](https://github.com/sentriflow/sentriflow/tree/main/packages/rules-default) - Default compliance rules
|
|
195
195
|
|
|
196
196
|
## License
|
|
197
197
|
|
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
|
|
@@ -13490,6 +13490,43 @@ function validateInputFilePath(filePath, maxSize = 10 * 1024 * 1024, baseDirs) {
|
|
|
13490
13490
|
});
|
|
13491
13491
|
}
|
|
13492
13492
|
|
|
13493
|
+
// src/loaders/index.ts
|
|
13494
|
+
function validatePathOrThrow(path, pathValidator, errorContext, baseDirs) {
|
|
13495
|
+
const validation = pathValidator(path, baseDirs);
|
|
13496
|
+
if (!validation.valid) {
|
|
13497
|
+
throw new SentriflowConfigError(
|
|
13498
|
+
`Invalid ${errorContext} path: ${validation.error}`
|
|
13499
|
+
);
|
|
13500
|
+
}
|
|
13501
|
+
return validation.canonicalPath;
|
|
13502
|
+
}
|
|
13503
|
+
async function wrapLoadError(operation, errorContext) {
|
|
13504
|
+
try {
|
|
13505
|
+
return await operation();
|
|
13506
|
+
} catch (error) {
|
|
13507
|
+
if (error instanceof SentriflowConfigError) {
|
|
13508
|
+
throw error;
|
|
13509
|
+
}
|
|
13510
|
+
throw new SentriflowConfigError(`Failed to load ${errorContext} file`);
|
|
13511
|
+
}
|
|
13512
|
+
}
|
|
13513
|
+
async function loadAndValidate(options) {
|
|
13514
|
+
const { path, baseDirs, pathValidator, loader, validator, errorContext } = options;
|
|
13515
|
+
const canonicalPath = validatePathOrThrow(
|
|
13516
|
+
path,
|
|
13517
|
+
pathValidator,
|
|
13518
|
+
errorContext,
|
|
13519
|
+
baseDirs
|
|
13520
|
+
);
|
|
13521
|
+
return wrapLoadError(async () => {
|
|
13522
|
+
const data = await loader(canonicalPath);
|
|
13523
|
+
if (!validator(data)) {
|
|
13524
|
+
throw new SentriflowConfigError(`Invalid ${errorContext} structure`);
|
|
13525
|
+
}
|
|
13526
|
+
return data;
|
|
13527
|
+
}, errorContext);
|
|
13528
|
+
}
|
|
13529
|
+
|
|
13493
13530
|
// src/config.ts
|
|
13494
13531
|
var CONFIG_FILES = [
|
|
13495
13532
|
"sentriflow.config.ts",
|
|
@@ -13565,8 +13602,110 @@ function isValidSentriflowConfig(config) {
|
|
|
13565
13602
|
}
|
|
13566
13603
|
}
|
|
13567
13604
|
}
|
|
13605
|
+
if (obj.directory !== void 0) {
|
|
13606
|
+
if (!isValidDirectoryConfig(obj.directory)) {
|
|
13607
|
+
return false;
|
|
13608
|
+
}
|
|
13609
|
+
}
|
|
13568
13610
|
return true;
|
|
13569
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
|
+
}
|
|
13666
|
+
return true;
|
|
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
|
+
}
|
|
13570
13709
|
function isValidRule(rule) {
|
|
13571
13710
|
if (typeof rule !== "object" || rule === null) {
|
|
13572
13711
|
return false;
|
|
@@ -13655,36 +13794,30 @@ function isValidRulePack(pack) {
|
|
|
13655
13794
|
return true;
|
|
13656
13795
|
}
|
|
13657
13796
|
async function loadConfigFile(configPath, baseDirs) {
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13668
|
-
|
|
13669
|
-
} catch (error) {
|
|
13670
|
-
if (error instanceof SentriflowConfigError) {
|
|
13671
|
-
throw error;
|
|
13672
|
-
}
|
|
13673
|
-
throw new SentriflowConfigError("Failed to load configuration file");
|
|
13674
|
-
}
|
|
13797
|
+
return loadAndValidate({
|
|
13798
|
+
path: configPath,
|
|
13799
|
+
baseDirs,
|
|
13800
|
+
pathValidator: validateConfigPath,
|
|
13801
|
+
loader: async (p) => {
|
|
13802
|
+
const m = await import(p);
|
|
13803
|
+
return m.default ?? m;
|
|
13804
|
+
},
|
|
13805
|
+
validator: isValidSentriflowConfig,
|
|
13806
|
+
errorContext: "config"
|
|
13807
|
+
});
|
|
13675
13808
|
}
|
|
13676
13809
|
async function loadExternalRules(rulesPath, baseDirs) {
|
|
13677
|
-
const
|
|
13678
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13810
|
+
const canonicalPath = validatePathOrThrow(
|
|
13811
|
+
rulesPath,
|
|
13812
|
+
validateConfigPath,
|
|
13813
|
+
"rules",
|
|
13814
|
+
baseDirs
|
|
13815
|
+
);
|
|
13816
|
+
return wrapLoadError(async () => {
|
|
13817
|
+
const module = await import(canonicalPath);
|
|
13683
13818
|
const rules = module.default ?? module.rules ?? module;
|
|
13684
13819
|
if (!Array.isArray(rules)) {
|
|
13685
|
-
throw new SentriflowConfigError(
|
|
13686
|
-
"Rules file must export an array of rules"
|
|
13687
|
-
);
|
|
13820
|
+
throw new SentriflowConfigError("Rules file must export an array of rules");
|
|
13688
13821
|
}
|
|
13689
13822
|
const validRules = [];
|
|
13690
13823
|
for (const rule of rules) {
|
|
@@ -13692,29 +13825,24 @@ async function loadExternalRules(rulesPath, baseDirs) {
|
|
|
13692
13825
|
validRules.push(rule);
|
|
13693
13826
|
} else {
|
|
13694
13827
|
const safeRuleId = typeof rule === "object" && rule !== null && "id" in rule ? String(rule.id).slice(0, 50) : "unknown";
|
|
13695
|
-
console.warn(
|
|
13696
|
-
`Skipping invalid rule: ${safeRuleId} (validation failed)`
|
|
13697
|
-
);
|
|
13828
|
+
console.warn(`Skipping invalid rule: ${safeRuleId} (validation failed)`);
|
|
13698
13829
|
}
|
|
13699
13830
|
}
|
|
13700
13831
|
if (validRules.length === 0 && rules.length > 0) {
|
|
13701
13832
|
throw new SentriflowConfigError("No valid rules found in rules file");
|
|
13702
13833
|
}
|
|
13703
13834
|
return validRules;
|
|
13704
|
-
}
|
|
13705
|
-
if (error instanceof SentriflowConfigError) {
|
|
13706
|
-
throw error;
|
|
13707
|
-
}
|
|
13708
|
-
throw new SentriflowConfigError("Failed to load rules file");
|
|
13709
|
-
}
|
|
13835
|
+
}, "rules");
|
|
13710
13836
|
}
|
|
13711
13837
|
async function loadJsonRules(jsonPath, baseDirs) {
|
|
13712
|
-
const
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
13716
|
-
|
|
13717
|
-
|
|
13838
|
+
const canonicalPath = validatePathOrThrow(
|
|
13839
|
+
jsonPath,
|
|
13840
|
+
validateJsonRulesPath,
|
|
13841
|
+
"JSON rules",
|
|
13842
|
+
baseDirs
|
|
13843
|
+
);
|
|
13844
|
+
return wrapLoadError(async () => {
|
|
13845
|
+
const content = await readFileAsync(canonicalPath, "utf-8");
|
|
13718
13846
|
let jsonData;
|
|
13719
13847
|
try {
|
|
13720
13848
|
jsonData = JSON.parse(content);
|
|
@@ -13723,16 +13851,12 @@ async function loadJsonRules(jsonPath, baseDirs) {
|
|
|
13723
13851
|
}
|
|
13724
13852
|
const validationResult = validateJsonRuleFile(jsonData);
|
|
13725
13853
|
if (!validationResult.valid) {
|
|
13726
|
-
const
|
|
13727
|
-
throw new SentriflowConfigError(
|
|
13728
|
-
|
|
13729
|
-
${errorMessages}`
|
|
13730
|
-
);
|
|
13854
|
+
const errors = validationResult.errors.map((e) => ` ${e.path}: ${e.message}`).join("\n");
|
|
13855
|
+
throw new SentriflowConfigError(`Invalid JSON rules file:
|
|
13856
|
+
${errors}`);
|
|
13731
13857
|
}
|
|
13732
|
-
|
|
13733
|
-
|
|
13734
|
-
console.warn(`[JSON Rules] Warning: ${warning.path}: ${warning.message}`);
|
|
13735
|
-
}
|
|
13858
|
+
for (const warning of validationResult.warnings) {
|
|
13859
|
+
console.warn(`[JSON Rules] Warning: ${warning.path}: ${warning.message}`);
|
|
13736
13860
|
}
|
|
13737
13861
|
const ruleFile = jsonData;
|
|
13738
13862
|
const compiledRules = compileJsonRules(ruleFile.rules);
|
|
@@ -13740,12 +13864,7 @@ ${errorMessages}`
|
|
|
13740
13864
|
throw new SentriflowConfigError("No valid rules compiled from JSON file");
|
|
13741
13865
|
}
|
|
13742
13866
|
return compiledRules;
|
|
13743
|
-
}
|
|
13744
|
-
if (error instanceof SentriflowConfigError) {
|
|
13745
|
-
throw error;
|
|
13746
|
-
}
|
|
13747
|
-
throw new SentriflowConfigError("Failed to load JSON rules file");
|
|
13748
|
-
}
|
|
13867
|
+
}, "JSON rules");
|
|
13749
13868
|
}
|
|
13750
13869
|
function ruleAppliesToVendor(rule, vendorId) {
|
|
13751
13870
|
if (!rule.vendor) return true;
|
|
@@ -13766,70 +13885,52 @@ function isDefaultRuleDisabled(ruleId, vendorId, packs, legacyDisableIds) {
|
|
|
13766
13885
|
return false;
|
|
13767
13886
|
}
|
|
13768
13887
|
async function loadRulePackFile(packPath, baseDirs) {
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
)
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
|
|
13782
|
-
|
|
13783
|
-
|
|
13784
|
-
|
|
13785
|
-
|
|
13888
|
+
return loadAndValidate({
|
|
13889
|
+
path: packPath,
|
|
13890
|
+
baseDirs,
|
|
13891
|
+
pathValidator: validateConfigPath,
|
|
13892
|
+
loader: async (p) => {
|
|
13893
|
+
const m = await import(p);
|
|
13894
|
+
return m.default ?? m;
|
|
13895
|
+
},
|
|
13896
|
+
validator: isValidRulePack,
|
|
13897
|
+
errorContext: "rule pack"
|
|
13898
|
+
});
|
|
13899
|
+
}
|
|
13900
|
+
function mapPackLoadError(error) {
|
|
13901
|
+
const messages = {
|
|
13902
|
+
DECRYPTION_FAILED: "Invalid license key for encrypted pack",
|
|
13903
|
+
EXPIRED: "Encrypted pack has expired",
|
|
13904
|
+
MACHINE_MISMATCH: "License is not valid for this machine",
|
|
13905
|
+
ACTIVATION_LIMIT: "Maximum activations exceeded for this license"
|
|
13906
|
+
};
|
|
13907
|
+
throw new SentriflowConfigError(
|
|
13908
|
+
messages[error.code] ?? `Failed to load encrypted pack: ${error.message}`
|
|
13909
|
+
);
|
|
13786
13910
|
}
|
|
13787
13911
|
async function loadEncryptedRulePack(packPath, licenseKey, baseDirs) {
|
|
13788
|
-
const
|
|
13789
|
-
|
|
13790
|
-
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13912
|
+
const canonicalPath = validatePathOrThrow(
|
|
13913
|
+
packPath,
|
|
13914
|
+
validateEncryptedPackPath,
|
|
13915
|
+
"encrypted pack",
|
|
13916
|
+
baseDirs
|
|
13917
|
+
);
|
|
13794
13918
|
try {
|
|
13795
|
-
const packData = await readFileAsync(
|
|
13919
|
+
const packData = await readFileAsync(canonicalPath);
|
|
13796
13920
|
if (!validatePackFormat(packData)) {
|
|
13797
13921
|
throw new SentriflowConfigError("Invalid encrypted pack format");
|
|
13798
13922
|
}
|
|
13799
13923
|
const loadedPack = await loadEncryptedPack(packData, {
|
|
13800
13924
|
licenseKey,
|
|
13801
13925
|
timeout: 1e4
|
|
13802
|
-
// 10 seconds for pack validation
|
|
13803
13926
|
});
|
|
13804
13927
|
return {
|
|
13805
13928
|
...loadedPack.metadata,
|
|
13806
13929
|
priority: 200,
|
|
13807
|
-
// High priority for licensed packs
|
|
13808
13930
|
rules: loadedPack.rules
|
|
13809
13931
|
};
|
|
13810
13932
|
} catch (error) {
|
|
13811
|
-
if (error instanceof PackLoadError)
|
|
13812
|
-
switch (error.code) {
|
|
13813
|
-
case "DECRYPTION_FAILED":
|
|
13814
|
-
throw new SentriflowConfigError(
|
|
13815
|
-
"Invalid license key for encrypted pack"
|
|
13816
|
-
);
|
|
13817
|
-
case "EXPIRED":
|
|
13818
|
-
throw new SentriflowConfigError("Encrypted pack has expired");
|
|
13819
|
-
case "MACHINE_MISMATCH":
|
|
13820
|
-
throw new SentriflowConfigError(
|
|
13821
|
-
"License is not valid for this machine"
|
|
13822
|
-
);
|
|
13823
|
-
case "ACTIVATION_LIMIT":
|
|
13824
|
-
throw new SentriflowConfigError(
|
|
13825
|
-
"Maximum activations exceeded for this license"
|
|
13826
|
-
);
|
|
13827
|
-
default:
|
|
13828
|
-
throw new SentriflowConfigError(
|
|
13829
|
-
`Failed to load encrypted pack: ${error.message}`
|
|
13830
|
-
);
|
|
13831
|
-
}
|
|
13832
|
-
}
|
|
13933
|
+
if (error instanceof PackLoadError) mapPackLoadError(error);
|
|
13833
13934
|
if (error instanceof SentriflowConfigError) throw error;
|
|
13834
13935
|
throw new SentriflowConfigError("Failed to load encrypted rule pack");
|
|
13835
13936
|
}
|
|
@@ -14049,6 +14150,19 @@ function isExcluded(relativePath, excludePatterns) {
|
|
|
14049
14150
|
if (excludePatterns.length === 0) return false;
|
|
14050
14151
|
return excludePatterns.some((pattern) => matchesPattern(relativePath, pattern));
|
|
14051
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
|
+
}
|
|
14052
14166
|
async function scanDirectory(dirPath, options = {}) {
|
|
14053
14167
|
const {
|
|
14054
14168
|
recursive = false,
|
|
@@ -14057,7 +14171,8 @@ async function scanDirectory(dirPath, options = {}) {
|
|
|
14057
14171
|
maxFileSize = MAX_CONFIG_SIZE,
|
|
14058
14172
|
maxDepth = 100,
|
|
14059
14173
|
allowedBaseDirs,
|
|
14060
|
-
exclude = []
|
|
14174
|
+
exclude = [],
|
|
14175
|
+
excludePatterns = []
|
|
14061
14176
|
} = options;
|
|
14062
14177
|
const result = {
|
|
14063
14178
|
files: [],
|
|
@@ -14103,7 +14218,11 @@ async function scanDirectory(dirPath, options = {}) {
|
|
|
14103
14218
|
for (const entry of entries) {
|
|
14104
14219
|
const fullPath = join(currentDir, entry);
|
|
14105
14220
|
const relativePath = join(basePath, entry);
|
|
14106
|
-
|
|
14221
|
+
const normalizedRelativePath = normalizeSeparators2(relativePath);
|
|
14222
|
+
if (isExcluded(normalizedRelativePath, exclude)) {
|
|
14223
|
+
continue;
|
|
14224
|
+
}
|
|
14225
|
+
if (isExcludedByRegex(normalizedRelativePath, excludePatterns)) {
|
|
14107
14226
|
continue;
|
|
14108
14227
|
}
|
|
14109
14228
|
let stats;
|
|
@@ -14189,9 +14308,96 @@ function validateDirectoryPath(dirPath, allowedBaseDirs) {
|
|
|
14189
14308
|
}
|
|
14190
14309
|
}
|
|
14191
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
|
+
|
|
14192
14398
|
// index.ts
|
|
14193
14399
|
var program = new Command();
|
|
14194
|
-
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(
|
|
14195
14401
|
"--encrypted-pack <path...>",
|
|
14196
14402
|
"SEC-012: Path(s) to encrypted rule pack(s) (.grpx), can specify multiple"
|
|
14197
14403
|
).option(
|
|
@@ -14232,7 +14438,14 @@ program.name("sentriflow").description("SentriFlow Network Configuration Validat
|
|
|
14232
14438
|
"--exclude <patterns>",
|
|
14233
14439
|
"Exclude patterns (comma-separated glob patterns)",
|
|
14234
14440
|
(val) => val.split(",")
|
|
14235
|
-
).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) => {
|
|
14236
14449
|
try {
|
|
14237
14450
|
if (options.listVendors) {
|
|
14238
14451
|
console.log("Supported vendors:\n");
|
|
@@ -14259,8 +14472,21 @@ Use: sentriflow --vendor <vendor> <file>`);
|
|
|
14259
14472
|
}
|
|
14260
14473
|
const workingDir = process.cwd();
|
|
14261
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
|
+
}
|
|
14262
14487
|
const licenseKey = options.licenseKey || process.env.SENTRIFLOW_LICENSE_KEY;
|
|
14263
|
-
const
|
|
14488
|
+
const firstFile = files.length > 0 ? files[0] : void 0;
|
|
14489
|
+
const configSearchDir = firstFile ? dirname2(resolve4(firstFile)) : workingDir;
|
|
14264
14490
|
const rules = await resolveRules({
|
|
14265
14491
|
configPath: options.config,
|
|
14266
14492
|
noConfig: options.config === false,
|
|
@@ -14314,18 +14540,44 @@ Config file: ${configFile}`);
|
|
|
14314
14540
|
process.exit(2);
|
|
14315
14541
|
}
|
|
14316
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);
|
|
14317
14566
|
if (options.progress) {
|
|
14567
|
+
const recursive = mergedOptions.recursive ?? false;
|
|
14318
14568
|
console.error(
|
|
14319
|
-
`Scanning directory: ${canonicalDir}${
|
|
14569
|
+
`Scanning directory: ${canonicalDir}${recursive ? " (recursive)" : ""}`
|
|
14320
14570
|
);
|
|
14321
14571
|
}
|
|
14322
14572
|
const scanResult = await scanDirectory(canonicalDir, {
|
|
14323
|
-
recursive:
|
|
14573
|
+
recursive: mergedOptions.recursive ?? false,
|
|
14324
14574
|
patterns: options.glob ? [options.glob] : [],
|
|
14325
|
-
extensions:
|
|
14575
|
+
extensions: mergedOptions.extensions ?? DEFAULT_CONFIG_EXTENSIONS,
|
|
14326
14576
|
maxFileSize: MAX_CONFIG_SIZE,
|
|
14577
|
+
maxDepth: mergedOptions.maxDepth,
|
|
14327
14578
|
allowedBaseDirs,
|
|
14328
|
-
exclude:
|
|
14579
|
+
exclude: mergedOptions.exclude ?? [],
|
|
14580
|
+
excludePatterns: mergedOptions.excludePatterns ?? []
|
|
14329
14581
|
});
|
|
14330
14582
|
if (scanResult.errors.length > 0 && options.progress) {
|
|
14331
14583
|
console.error(`
|
|
@@ -14489,10 +14741,176 @@ Scan complete: ${allFileResults.length} files, ${totalFailures} failures, ${tota
|
|
|
14489
14741
|
}
|
|
14490
14742
|
return;
|
|
14491
14743
|
}
|
|
14492
|
-
if (
|
|
14744
|
+
if (files.length === 0) {
|
|
14493
14745
|
program.help();
|
|
14494
14746
|
return;
|
|
14495
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];
|
|
14496
14914
|
const fileValidation = validateInputFilePath(
|
|
14497
14915
|
file,
|
|
14498
14916
|
MAX_CONFIG_SIZE,
|
package/package.json
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@sentriflow/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "SentriFlow CLI - Network configuration linter and validator",
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"module": "dist/index.js",
|
|
8
|
-
"type": "module",
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/sentriflow/sentriflow.git",
|
|
12
|
-
"directory": "packages/cli"
|
|
13
|
-
},
|
|
14
|
-
"homepage": "https://github.com/sentriflow/sentriflow#readme",
|
|
15
|
-
"bugs": {
|
|
16
|
-
"url": "https://github.com/sentriflow/sentriflow/issues"
|
|
17
|
-
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"cli",
|
|
20
|
-
"network",
|
|
21
|
-
"configuration",
|
|
22
|
-
"linter",
|
|
23
|
-
"security",
|
|
24
|
-
"compliance",
|
|
25
|
-
"sarif"
|
|
26
|
-
],
|
|
27
|
-
"files": [
|
|
28
|
-
"dist",
|
|
29
|
-
"LICENSE",
|
|
30
|
-
"README.md"
|
|
31
|
-
],
|
|
32
|
-
"publishConfig": {
|
|
33
|
-
"access": "public"
|
|
34
|
-
},
|
|
35
|
-
"scripts": {
|
|
36
|
-
"build": "bun build.mjs",
|
|
37
|
-
"prepublishOnly": "bun run build"
|
|
38
|
-
},
|
|
39
|
-
"dependencies": {
|
|
40
|
-
"commander": "^14.0.2"
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"@sentriflow/core": "workspace:*",
|
|
44
|
-
"@sentriflow/rules-default": "workspace:*",
|
|
45
|
-
"bun-types": "latest",
|
|
46
|
-
"esbuild": "^0.27.0"
|
|
47
|
-
},
|
|
48
|
-
"bin": {
|
|
49
|
-
"sentriflow": "dist/index.js"
|
|
50
|
-
},
|
|
51
|
-
"engines": {
|
|
52
|
-
"node": ">=18.0.0"
|
|
53
|
-
}
|
|
54
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@sentriflow/cli",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "SentriFlow CLI - Network configuration linter and validator",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/sentriflow/sentriflow.git",
|
|
12
|
+
"directory": "packages/cli"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/sentriflow/sentriflow#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/sentriflow/sentriflow/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"network",
|
|
21
|
+
"configuration",
|
|
22
|
+
"linter",
|
|
23
|
+
"security",
|
|
24
|
+
"compliance",
|
|
25
|
+
"sarif"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "bun build.mjs",
|
|
37
|
+
"prepublishOnly": "bun run build"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"commander": "^14.0.2"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@sentriflow/core": "workspace:*",
|
|
44
|
+
"@sentriflow/rules-default": "workspace:*",
|
|
45
|
+
"bun-types": "latest",
|
|
46
|
+
"esbuild": "^0.27.0"
|
|
47
|
+
},
|
|
48
|
+
"bin": {
|
|
49
|
+
"sentriflow": "dist/index.js"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|