korext 1.0.0 → 1.0.1
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 +76 -17
- package/package.json +1 -1
package/bin/korext.js
CHANGED
|
@@ -295,6 +295,15 @@ async function fetchAndCacheRules() {
|
|
|
295
295
|
* Run rules locally against code using cached regex definitions.
|
|
296
296
|
* Mirrors korext-core/src/engine/localEngine.ts analyzeLocally().
|
|
297
297
|
*/
|
|
298
|
+
// M1: Check if a line is a comment (skip from local engine scanning)
|
|
299
|
+
function isCommentLine(line) {
|
|
300
|
+
const trimmed = line.trim();
|
|
301
|
+
return trimmed.startsWith('//')
|
|
302
|
+
|| trimmed.startsWith('*')
|
|
303
|
+
|| trimmed.startsWith('/*')
|
|
304
|
+
|| trimmed.startsWith('#');
|
|
305
|
+
}
|
|
306
|
+
|
|
298
307
|
function analyzeLocally(code, packId, definitions) {
|
|
299
308
|
// Normalize packId to array to support multi-pack (e.g. ["web", "pci-dss-v1"])
|
|
300
309
|
const packIdList = Array.isArray(packId) ? packId : [packId];
|
|
@@ -321,6 +330,8 @@ function analyzeLocally(code, packId, definitions) {
|
|
|
321
330
|
try {
|
|
322
331
|
const re = new RegExp(pattern.regex, pattern.flags || 'gi');
|
|
323
332
|
lines.forEach((line, i) => {
|
|
333
|
+
// M1: Skip comment lines to avoid false positives
|
|
334
|
+
if (isCommentLine(line)) return;
|
|
324
335
|
re.lastIndex = 0;
|
|
325
336
|
if (re.test(line)) {
|
|
326
337
|
violations.push({
|
|
@@ -371,7 +382,12 @@ const program = new Command();
|
|
|
371
382
|
program
|
|
372
383
|
.name('korext')
|
|
373
384
|
.description('Korext Command Line Interface - Enterprise Policy Enforcement')
|
|
374
|
-
.version(version)
|
|
385
|
+
.version(version)
|
|
386
|
+
.action(() => {
|
|
387
|
+
// H3: Show help and exit 0 when no subcommand is provided
|
|
388
|
+
program.outputHelp();
|
|
389
|
+
process.exit(0);
|
|
390
|
+
});
|
|
375
391
|
|
|
376
392
|
program
|
|
377
393
|
.command('login [token]')
|
|
@@ -859,6 +875,13 @@ program
|
|
|
859
875
|
const format = options.format.toLowerCase();
|
|
860
876
|
const isText = format === 'text';
|
|
861
877
|
|
|
878
|
+
// H1: Validate output format before proceeding
|
|
879
|
+
const SUPPORTED_FORMATS = ['text', 'json', 'sarif'];
|
|
880
|
+
if (!SUPPORTED_FORMATS.includes(format)) {
|
|
881
|
+
console.error(chalk.red(`\n\u2716 Unsupported format: "${format}". Supported formats: ${SUPPORTED_FORMATS.join(', ')}`));
|
|
882
|
+
process.exit(2);
|
|
883
|
+
}
|
|
884
|
+
|
|
862
885
|
// ── Workspace config reader ──────────────────────────────────────────────
|
|
863
886
|
function loadWorkspaceConfig(targetDir) {
|
|
864
887
|
const configPaths = [
|
|
@@ -919,7 +942,7 @@ program
|
|
|
919
942
|
} else {
|
|
920
943
|
console.error(JSON.stringify({ error: `Unknown pack ID(s): ${unknownPacks.join(', ')}` }));
|
|
921
944
|
}
|
|
922
|
-
process.exit(
|
|
945
|
+
process.exit(2); // C2: Error exit for invalid pack ID
|
|
923
946
|
}
|
|
924
947
|
}
|
|
925
948
|
} else if (options.industry) {
|
|
@@ -928,14 +951,14 @@ program
|
|
|
928
951
|
if (!taxonomy) {
|
|
929
952
|
if (isText) console.error(chalk.red('\n\u2716 --industry requires cached rule definitions.'));
|
|
930
953
|
if (isText) console.error(chalk.dim(` Run ${chalk.green('korext rules sync')} first to cache pack tags.`));
|
|
931
|
-
process.exit(
|
|
954
|
+
process.exit(2); // C2: Error exit for missing cache
|
|
932
955
|
}
|
|
933
956
|
packIds = resolvePacksForIndustryRegion(options.industry, options.region || null, taxonomy);
|
|
934
957
|
packSource = 'industry-flag';
|
|
935
958
|
if (packIds.length === 0) {
|
|
936
959
|
if (isText) console.error(chalk.red(`\n\u2716 No packs found for industry '${options.industry}'${options.region ? ` in region '${options.region}'` : ''}.`));
|
|
937
960
|
if (isText) console.error(chalk.dim(` Run ${chalk.green('korext industries')} to see valid industry names.`));
|
|
938
|
-
process.exit(
|
|
961
|
+
process.exit(2); // C2: Error exit for no matching packs
|
|
939
962
|
}
|
|
940
963
|
if (isText) console.log(`[KOREXT] Resolved ${packIds.length} packs for industry: ${options.industry}${options.region ? ` (region: ${options.region})` : ''}`);
|
|
941
964
|
} else {
|
|
@@ -1005,11 +1028,30 @@ program
|
|
|
1005
1028
|
let forceOffline = options.offline;
|
|
1006
1029
|
let localDefinitions = null;
|
|
1007
1030
|
|
|
1008
|
-
// Pre-flight: verify
|
|
1031
|
+
// C1: Pre-flight: verify path exists and determine file vs directory
|
|
1009
1032
|
const resolvedInputDir = path.resolve(dir);
|
|
1010
1033
|
if (!fs.existsSync(resolvedInputDir)) {
|
|
1011
|
-
if (isText) console.error(chalk.red(`\n
|
|
1012
|
-
else console.error(JSON.stringify({ error: `
|
|
1034
|
+
if (isText) console.error(chalk.red(`\n\u2716 Path does not exist: ${resolvedInputDir}`));
|
|
1035
|
+
else console.error(JSON.stringify({ error: `Path does not exist: ${resolvedInputDir}` }));
|
|
1036
|
+
process.exit(2);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// C1: Detect single file vs directory
|
|
1040
|
+
let isSingleFile = false;
|
|
1041
|
+
const inputStats = fs.statSync(resolvedInputDir);
|
|
1042
|
+
if (inputStats.isFile()) {
|
|
1043
|
+
isSingleFile = true;
|
|
1044
|
+
const ext = path.extname(resolvedInputDir);
|
|
1045
|
+
const SUPPORTED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.java', '.rs'];
|
|
1046
|
+
if (!SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
1047
|
+
if (isText) console.error(chalk.red(`\n\u2716 Unsupported file type: ${ext}`));
|
|
1048
|
+
else console.error(JSON.stringify({ error: `Unsupported file type: ${ext}` }));
|
|
1049
|
+
if (isText) console.error(chalk.dim(` Supported: ${SUPPORTED_EXTENSIONS.join(', ')}\n`));
|
|
1050
|
+
process.exit(2);
|
|
1051
|
+
}
|
|
1052
|
+
} else if (!inputStats.isDirectory()) {
|
|
1053
|
+
if (isText) console.error(chalk.red(`\n\u2716 Invalid path (not a file or directory): ${resolvedInputDir}`));
|
|
1054
|
+
else console.error(JSON.stringify({ error: `Invalid path: ${resolvedInputDir}` }));
|
|
1013
1055
|
process.exit(2);
|
|
1014
1056
|
}
|
|
1015
1057
|
|
|
@@ -1036,7 +1078,7 @@ program
|
|
|
1036
1078
|
if (!localDefinitions) {
|
|
1037
1079
|
if (isText) console.error(chalk.red('\n✖ Offline mode requires cached rule definitions.'));
|
|
1038
1080
|
if (isText) console.error(chalk.dim(` Run ${chalk.green('korext rules sync')} or ${chalk.green('korext enforce --sync-rules')} first while online.`));
|
|
1039
|
-
process.exit(
|
|
1081
|
+
process.exit(2); // C2: Error exit for missing offline cache
|
|
1040
1082
|
}
|
|
1041
1083
|
if (isText) {
|
|
1042
1084
|
console.log(chalk.yellow('\n⚡ Offline mode: using local rule engine (regex-based analysis)'));
|
|
@@ -1082,12 +1124,17 @@ program
|
|
|
1082
1124
|
bundles: []
|
|
1083
1125
|
};
|
|
1084
1126
|
|
|
1127
|
+
// C1: Single file support
|
|
1085
1128
|
let files;
|
|
1086
|
-
|
|
1087
|
-
files =
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1129
|
+
if (isSingleFile) {
|
|
1130
|
+
files = [resolvedInputDir];
|
|
1131
|
+
} else {
|
|
1132
|
+
try {
|
|
1133
|
+
files = findFiles(dir);
|
|
1134
|
+
} catch (e) {
|
|
1135
|
+
if (isText) console.error(chalk.red(`Error reading directory: ${e.message}`));
|
|
1136
|
+
process.exit(2); // C2: Error exit for filesystem errors
|
|
1137
|
+
}
|
|
1091
1138
|
}
|
|
1092
1139
|
|
|
1093
1140
|
report.summary.totalFiles = files.length;
|
|
@@ -1153,6 +1200,18 @@ program
|
|
|
1153
1200
|
});
|
|
1154
1201
|
clearTimeout(timeoutId);
|
|
1155
1202
|
|
|
1203
|
+
// C3 + H2: Differentiate auth errors from server/network errors
|
|
1204
|
+
if (res.status === 401 || res.status === 403) {
|
|
1205
|
+
if (fileSpinner) fileSpinner.stop();
|
|
1206
|
+
if (isText) {
|
|
1207
|
+
console.error(chalk.red('\n\u2716 Authentication failed. Your token may be expired.'));
|
|
1208
|
+
console.error(chalk.dim(` Run: ${chalk.green('korext login')} to re-authenticate.\n`));
|
|
1209
|
+
} else {
|
|
1210
|
+
console.error(JSON.stringify({ error: 'Authentication failed. Token may be expired. Run: korext login' }));
|
|
1211
|
+
}
|
|
1212
|
+
process.exit(2);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1156
1215
|
if (!res.ok) {
|
|
1157
1216
|
throw new Error(`HTTP ${res.status}`);
|
|
1158
1217
|
}
|
|
@@ -1173,7 +1232,7 @@ program
|
|
|
1173
1232
|
file: displayPath,
|
|
1174
1233
|
bundleId: result.proofBundle.bundleId,
|
|
1175
1234
|
decision: result.proofBundle.decision,
|
|
1176
|
-
signed: !!result.proofBundle.hmacSignature,
|
|
1235
|
+
signed: !!result.proofBundle.hmacSignature || !!result.proofBundle.signature,
|
|
1177
1236
|
verifyUrl: `https://app.korext.com/verify/${result.proofBundle.bundleId}`,
|
|
1178
1237
|
});
|
|
1179
1238
|
}
|
|
@@ -1183,12 +1242,12 @@ program
|
|
|
1183
1242
|
fetchAndCacheRules().catch(() => {});
|
|
1184
1243
|
}
|
|
1185
1244
|
} catch (e) {
|
|
1186
|
-
//
|
|
1245
|
+
// Network error or server 5xx: fall back to local engine
|
|
1187
1246
|
if (localDefinitions) {
|
|
1188
1247
|
if (!usedLocalEngine && isText && i === 0) {
|
|
1189
1248
|
// Show fallback banner once
|
|
1190
|
-
console.log(chalk.yellow(`\n
|
|
1191
|
-
console.log(chalk.dim(` Cached rules: v${localDefinitions.version}
|
|
1249
|
+
console.log(chalk.yellow(`\n\u26a1 Server unreachable. Falling back to local engine (regex-based analysis).`));
|
|
1250
|
+
console.log(chalk.dim(` Cached rules: v${localDefinitions.version} \u00b7 ${localDefinitions.ruleCount} rules\n`));
|
|
1192
1251
|
}
|
|
1193
1252
|
fileViolations = analyzeLocally(fileContent, pack, localDefinitions);
|
|
1194
1253
|
usedLocalEngine = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "korext",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"mcpName": "io.github.Korext/governance",
|
|
5
5
|
"description": "KOREXT CLI. AI Code Governance. Enforce compliance policies on human written and AI generated code. 72 policy packs. 532 rules. 13 languages. Signed proof bundles.",
|
|
6
6
|
"type": "module",
|