korext 0.9.10 → 0.9.12
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/bin/korext.js +82 -16
- package/package.json +1 -1
package/bin/korext.js
CHANGED
|
@@ -296,13 +296,22 @@ async function fetchAndCacheRules() {
|
|
|
296
296
|
* Mirrors korext-core/src/engine/localEngine.ts analyzeLocally().
|
|
297
297
|
*/
|
|
298
298
|
function analyzeLocally(code, packId, definitions) {
|
|
299
|
-
|
|
300
|
-
|
|
299
|
+
// Normalize packId to array to support multi-pack (e.g. ["web", "pci-dss-v1"])
|
|
300
|
+
const packIdList = Array.isArray(packId) ? packId : [packId];
|
|
301
|
+
// Merge rules from all requested packs via Set
|
|
302
|
+
const mergedRuleIds = new Set();
|
|
303
|
+
for (const pid of packIdList) {
|
|
304
|
+
const pack = definitions.packs[pid];
|
|
305
|
+
if (pack && pack.rules) {
|
|
306
|
+
for (const rid of pack.rules) mergedRuleIds.add(rid);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (mergedRuleIds.size === 0) return [];
|
|
301
310
|
|
|
302
311
|
const violations = [];
|
|
303
312
|
const lines = code.split('\n');
|
|
304
313
|
|
|
305
|
-
for (const ruleId of
|
|
314
|
+
for (const ruleId of mergedRuleIds) {
|
|
306
315
|
const rule = definitions.rules[ruleId];
|
|
307
316
|
if (!rule) continue;
|
|
308
317
|
if (rule.checkMode === 'server-only') continue;
|
|
@@ -630,18 +639,65 @@ program
|
|
|
630
639
|
});
|
|
631
640
|
|
|
632
641
|
console.log();
|
|
633
|
-
const industryInput = await ask(chalk.bold(' Select industries (
|
|
642
|
+
const industryInput = await ask(chalk.bold(' Select industries or packs (numbers, IDs, or names): '));
|
|
643
|
+
|
|
644
|
+
// Parse user input: accept numbers, industry IDs, industry names (fuzzy), or pack IDs
|
|
645
|
+
const rawTokens = industryInput.split(',').map(s => s.trim()).filter(Boolean);
|
|
646
|
+
const selectedIndustries = [];
|
|
647
|
+
const directPacks = [];
|
|
634
648
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
649
|
+
for (const token of rawTokens) {
|
|
650
|
+
const lower = token.toLowerCase();
|
|
651
|
+
|
|
652
|
+
// Strategy 1: Number (e.g., "8" for Finance)
|
|
653
|
+
const num = parseInt(token, 10);
|
|
638
654
|
if (!isNaN(num) && num >= 1 && num <= sortedIndustries.length) {
|
|
639
|
-
|
|
655
|
+
selectedIndustries.push(sortedIndustries[num - 1][0]);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Strategy 2: Industry ID exact match (e.g., "finance")
|
|
660
|
+
if (taxonomy.industries[lower]) {
|
|
661
|
+
selectedIndustries.push(lower);
|
|
662
|
+
continue;
|
|
640
663
|
}
|
|
641
|
-
return s.toLowerCase();
|
|
642
|
-
}).filter(id => taxonomy.industries[id]);
|
|
643
664
|
|
|
644
|
-
|
|
665
|
+
// Strategy 3: Industry label fuzzy match (e.g., "Finance", "health", "aero")
|
|
666
|
+
const labelMatch = sortedIndustries.find(([id, group]) =>
|
|
667
|
+
group.label.toLowerCase().startsWith(lower)
|
|
668
|
+
);
|
|
669
|
+
if (labelMatch) {
|
|
670
|
+
selectedIndustries.push(labelMatch[0]);
|
|
671
|
+
console.log(chalk.dim(` "${token}" matched industry: ${labelMatch[1].label}`));
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Strategy 4: Direct pack ID (e.g., "web", "pci-dss-v1", "security")
|
|
676
|
+
const allPackIds = Object.keys(defs.packs);
|
|
677
|
+
if (allPackIds.includes(lower)) {
|
|
678
|
+
directPacks.push(lower);
|
|
679
|
+
console.log(chalk.dim(` "${token}" is a pack ID, adding directly.`));
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Nothing matched
|
|
684
|
+
console.log(chalk.yellow(` Unknown input: "${token}" (not an industry, pack ID, or name)`));
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// If user typed only pack IDs (e.g., "web" or "web, security"), skip region prompt
|
|
688
|
+
if (selectedIndustries.length === 0 && directPacks.length > 0) {
|
|
689
|
+
const config = {
|
|
690
|
+
targetPacks: directPacks,
|
|
691
|
+
exclude: ['node_modules', 'dist', 'build', '.next']
|
|
692
|
+
};
|
|
693
|
+
fs.writeFileSync(outputPath, JSON.stringify(config, null, 2));
|
|
694
|
+
console.log(chalk.green(`\n Created ${outputPath} with packs: ${directPacks.join(', ')}\n`));
|
|
695
|
+
console.log(chalk.dim(` Run: ${chalk.green('korext enforce .')} to enforce these packs.\n`));
|
|
696
|
+
rl.close();
|
|
697
|
+
process.exit(0);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (selectedIndustries.length === 0 && directPacks.length === 0) {
|
|
645
701
|
console.log(chalk.yellow('\n No valid industries selected. Using default: web\n'));
|
|
646
702
|
const config = {
|
|
647
703
|
targetPacks: ['web'],
|
|
@@ -676,22 +732,25 @@ program
|
|
|
676
732
|
}
|
|
677
733
|
}
|
|
678
734
|
|
|
679
|
-
// Resolve packs
|
|
735
|
+
// Resolve packs from industries
|
|
680
736
|
const resolvedPacks = resolvePacksForIndustryRegion(
|
|
681
737
|
selectedIndustries.join(','),
|
|
682
738
|
selectedRegion,
|
|
683
739
|
taxonomy
|
|
684
740
|
);
|
|
685
741
|
|
|
686
|
-
|
|
687
|
-
|
|
742
|
+
// Merge any direct pack IDs the user typed alongside industries
|
|
743
|
+
const allPacks = [...new Set([...resolvedPacks, ...directPacks])];
|
|
744
|
+
|
|
745
|
+
console.log(chalk.green(`\n Resolved ${allPacks.length} packs for ${selectedIndustries.map(id => INDUSTRY_LABELS[id]).join(', ')}${selectedRegion ? ` (${REGION_LABELS[selectedRegion] || selectedRegion})` : ''}${directPacks.length > 0 ? ` + ${directPacks.join(', ')}` : ''}:`));
|
|
746
|
+
for (const pid of allPacks) {
|
|
688
747
|
console.log(` ${chalk.cyan(pid)}`);
|
|
689
748
|
}
|
|
690
749
|
|
|
691
750
|
const config = {
|
|
692
751
|
industry: selectedIndustries.join(','),
|
|
693
752
|
...(selectedRegion && { region: selectedRegion }),
|
|
694
|
-
targetPacks:
|
|
753
|
+
targetPacks: allPacks,
|
|
695
754
|
exclude: ['node_modules', 'dist', 'build', '.next']
|
|
696
755
|
};
|
|
697
756
|
|
|
@@ -980,7 +1039,14 @@ program
|
|
|
980
1039
|
console.log(chalk.yellow('\n⚡ Offline mode: using local rule engine (regex-based analysis)'));
|
|
981
1040
|
console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules · ${localDefinitions.packCount} packs`));
|
|
982
1041
|
// Show rule coverage breakdown
|
|
983
|
-
|
|
1042
|
+
// Merge rules from all packs for coverage display
|
|
1043
|
+
const coveragePackList = Array.isArray(pack) ? pack : [pack];
|
|
1044
|
+
const allPackRuleIds = new Set();
|
|
1045
|
+
for (const pid of coveragePackList) {
|
|
1046
|
+
const pr = localDefinitions.packs?.[pid]?.rules || [];
|
|
1047
|
+
for (const rid of pr) allPackRuleIds.add(rid);
|
|
1048
|
+
}
|
|
1049
|
+
const packRules = [...allPackRuleIds];
|
|
984
1050
|
const availableCount = packRules.filter(r => localDefinitions.rules?.[r]).length;
|
|
985
1051
|
const serverOnlyCount = packRules.length - availableCount;
|
|
986
1052
|
console.log(chalk.dim(` Offline mode: ${availableCount} of ${packRules.length} rules available. ${serverOnlyCount} rules require server analysis.`));
|