korext 0.9.3 → 0.9.5
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 +11 -1
- package/bin/korext.js +70 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,6 +99,16 @@ OWASP Top 10 | PCI-DSS | HIPAA | GDPR | SOC 2 | NIST SP 800-53 | NIST SP 800-171
|
|
|
99
99
|
| `KOREXT_API_TOKEN` | API authentication token (recommended) |
|
|
100
100
|
| `KOREXT_TOKEN` | Deprecated alias (shows warning) |
|
|
101
101
|
|
|
102
|
+
## Changelog
|
|
103
|
+
|
|
104
|
+
### v0.9.5
|
|
105
|
+
|
|
106
|
+
Fixed
|
|
107
|
+
- Watch mode now detects file changes correctly and scans on startup
|
|
108
|
+
- Enforcing a nonexistent directory now prints an error and exits with code 2 instead of silently passing
|
|
109
|
+
- Offline enforcement prints how many rules are available versus how many require server analysis
|
|
110
|
+
- Policy commands now default to the production API instead of localhost
|
|
111
|
+
|
|
102
112
|
## Links
|
|
103
113
|
|
|
104
114
|
- Website: [korext.com](https://www.korext.com)
|
|
@@ -112,4 +122,4 @@ OWASP Top 10 | PCI-DSS | HIPAA | GDPR | SOC 2 | NIST SP 800-53 | NIST SP 800-171
|
|
|
112
122
|
|
|
113
123
|
**Publisher**: Korext
|
|
114
124
|
**License**: Proprietary
|
|
115
|
-
**Version**: 0.
|
|
125
|
+
**Version**: 0.9.5
|
package/bin/korext.js
CHANGED
|
@@ -172,8 +172,11 @@ program
|
|
|
172
172
|
.action((tokenArg) => {
|
|
173
173
|
let token = tokenArg;
|
|
174
174
|
if (!token) {
|
|
175
|
-
console.log(`\
|
|
176
|
-
console.log(`
|
|
175
|
+
console.log(`\nTo authenticate, sign in at ${chalk.cyan('https://app.korext.com')} and use your session token.`);
|
|
176
|
+
console.log(`Long-lived API tokens for CI/CD are coming soon.`);
|
|
177
|
+
console.log(`\nFor CI/CD, Korext works in anonymous mode (20 requests per hour).`);
|
|
178
|
+
console.log(`Set ${chalk.green('KOREXT_API_TOKEN')} for higher limits.`);
|
|
179
|
+
console.log(`\nThen run: ${chalk.green('korext login <your-token>')}\n`);
|
|
177
180
|
process.exit(1);
|
|
178
181
|
}
|
|
179
182
|
const config = getConfig();
|
|
@@ -264,6 +267,14 @@ program
|
|
|
264
267
|
let forceOffline = options.offline;
|
|
265
268
|
let localDefinitions = null;
|
|
266
269
|
|
|
270
|
+
// Pre-flight: verify directory exists
|
|
271
|
+
const resolvedInputDir = path.resolve(dir);
|
|
272
|
+
if (!fs.existsSync(resolvedInputDir)) {
|
|
273
|
+
if (isText) console.error(chalk.red(`\n✖ Directory does not exist: ${resolvedInputDir}`));
|
|
274
|
+
else console.error(JSON.stringify({ error: `Directory does not exist: ${resolvedInputDir}` }));
|
|
275
|
+
process.exit(2);
|
|
276
|
+
}
|
|
277
|
+
|
|
267
278
|
if (isText) {
|
|
268
279
|
console.log(`\n${chalk.bold.hex('#F27D26')('▲ KOREXT CLI ENFORCEMENT ENGINE')} v${version}`);
|
|
269
280
|
console.log(chalk.dim('======================================='));
|
|
@@ -292,6 +303,11 @@ program
|
|
|
292
303
|
if (isText) {
|
|
293
304
|
console.log(chalk.yellow('\n⚡ Offline mode: using local rule engine (regex-based analysis)'));
|
|
294
305
|
console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules · ${localDefinitions.packCount} packs`));
|
|
306
|
+
// Show rule coverage breakdown
|
|
307
|
+
const packRules = localDefinitions.packs?.[pack]?.rules || [];
|
|
308
|
+
const availableCount = packRules.filter(r => localDefinitions.rules?.[r]).length;
|
|
309
|
+
const serverOnlyCount = packRules.length - availableCount;
|
|
310
|
+
console.log(chalk.dim(` Offline mode: ${availableCount} of ${packRules.length} rules available. ${serverOnlyCount} rules require server analysis.`));
|
|
295
311
|
console.log(chalk.dim(' Note: AI-powered deep analysis is unavailable in offline mode.\n'));
|
|
296
312
|
}
|
|
297
313
|
}
|
|
@@ -331,9 +347,12 @@ program
|
|
|
331
347
|
|
|
332
348
|
let usedLocalEngine = false;
|
|
333
349
|
|
|
350
|
+
const resolvedDir = path.resolve(dir);
|
|
351
|
+
|
|
334
352
|
for (let i = 0; i < files.length; i++) {
|
|
335
353
|
const file = files[i];
|
|
336
354
|
const displayPath = path.relative(process.cwd(), file);
|
|
355
|
+
const sarifPath = path.relative(resolvedDir, file);
|
|
337
356
|
|
|
338
357
|
let fileSpinner = null;
|
|
339
358
|
if (isText) fileSpinner = ora(`Analyzing ${displayPath} (${i + 1}/${files.length})...`).start();
|
|
@@ -433,39 +452,51 @@ program
|
|
|
433
452
|
}
|
|
434
453
|
storedV.push(v);
|
|
435
454
|
}
|
|
436
|
-
report.results.push({ file: displayPath, violations: storedV });
|
|
455
|
+
report.results.push({ file: displayPath, sarifFile: sarifPath, violations: storedV });
|
|
437
456
|
} else {
|
|
438
457
|
if (fileSpinner) fileSpinner.stop();
|
|
439
|
-
report.results.push({ file: displayPath, violations: [] });
|
|
458
|
+
report.results.push({ file: displayPath, sarifFile: sarifPath, violations: [] });
|
|
440
459
|
}
|
|
441
460
|
}
|
|
442
461
|
|
|
443
462
|
if (format === 'json') {
|
|
444
463
|
console.log(JSON.stringify(report, null, 2));
|
|
445
464
|
} else if (format === 'sarif') {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
$schema: "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
|
|
449
|
-
runs: [{
|
|
450
|
-
tool: { driver: { name: "Korext", version } },
|
|
451
|
-
results: []
|
|
452
|
-
}]
|
|
453
|
-
};
|
|
465
|
+
// Build SARIF results
|
|
466
|
+
const sarifResults = [];
|
|
454
467
|
for (const res of report.results) {
|
|
455
468
|
for (const v of res.violations) {
|
|
456
|
-
|
|
457
|
-
|
|
469
|
+
const messageText = v.explanation
|
|
470
|
+
|| (v.ruleName ? v.ruleName + ': ' + (v.message || 'violation detected') : (v.ruleId || 'unknown') + ' violation detected');
|
|
471
|
+
sarifResults.push({
|
|
472
|
+
ruleId: v.ruleName || v.ruleId || 'unknown',
|
|
458
473
|
level: v.severity === 'critical' || v.severity === 'high' ? "error" : v.severity === 'medium' ? "warning" : "note",
|
|
459
|
-
message: { text:
|
|
474
|
+
message: { text: messageText },
|
|
460
475
|
locations: [{
|
|
461
476
|
physicalLocation: {
|
|
462
|
-
artifactLocation: { uri: res.file },
|
|
477
|
+
artifactLocation: { uri: res.sarifFile || res.file },
|
|
463
478
|
region: { startLine: v.line, startColumn: v.column || 1 }
|
|
464
479
|
}
|
|
465
480
|
}]
|
|
466
481
|
});
|
|
467
482
|
}
|
|
468
483
|
}
|
|
484
|
+
|
|
485
|
+
// Build driver.rules from unique ruleIds
|
|
486
|
+
const ruleIds = [...new Set(sarifResults.map(r => r.ruleId))];
|
|
487
|
+
const rules = ruleIds.map(id => ({
|
|
488
|
+
id,
|
|
489
|
+
shortDescription: { text: id.replace(/-/g, ' ') }
|
|
490
|
+
}));
|
|
491
|
+
|
|
492
|
+
const sarif = {
|
|
493
|
+
version: "2.1.0",
|
|
494
|
+
$schema: "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
|
|
495
|
+
runs: [{
|
|
496
|
+
tool: { driver: { name: "Korext", version, rules } },
|
|
497
|
+
results: sarifResults
|
|
498
|
+
}]
|
|
499
|
+
};
|
|
469
500
|
console.log(JSON.stringify(sarif, null, 2));
|
|
470
501
|
} else {
|
|
471
502
|
console.log('\n' + chalk.dim('======================================='));
|
|
@@ -1291,6 +1322,29 @@ program
|
|
|
1291
1322
|
if (fs.existsSync(fullPath)) analyzeWatchedFile(fullPath);
|
|
1292
1323
|
}, DEBOUNCE_MS));
|
|
1293
1324
|
});
|
|
1325
|
+
|
|
1326
|
+
// Also do an initial scan of all existing files on startup
|
|
1327
|
+
const initialFiles = [];
|
|
1328
|
+
function walkDir(d) {
|
|
1329
|
+
let entries;
|
|
1330
|
+
try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
|
|
1331
|
+
for (const entry of entries) {
|
|
1332
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
1333
|
+
const fullPath = path.join(d, entry.name);
|
|
1334
|
+
if (entry.isDirectory()) {
|
|
1335
|
+
walkDir(fullPath);
|
|
1336
|
+
} else if (SUPPORTED_EXTS.has(path.extname(entry.name))) {
|
|
1337
|
+
initialFiles.push(fullPath);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
walkDir(dir);
|
|
1342
|
+
if (initialFiles.length > 0) {
|
|
1343
|
+
console.log(chalk.dim(` Initial scan: ${initialFiles.length} file(s)...\n`));
|
|
1344
|
+
for (const f of initialFiles) {
|
|
1345
|
+
await analyzeWatchedFile(f);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1294
1348
|
} catch (e) {
|
|
1295
1349
|
console.error(chalk.red(`Failed to watch ${dir}: ${e.message}`));
|
|
1296
1350
|
process.exit(1);
|