korext 0.9.4 → 0.9.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 +11 -1
- package/bin/korext.js +49 -5
- 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
|
@@ -254,19 +254,30 @@ program
|
|
|
254
254
|
program
|
|
255
255
|
.command('enforce [dir]')
|
|
256
256
|
.description('Statically analyze files in a directory against Korext policies')
|
|
257
|
-
.option('-p, --pack <
|
|
257
|
+
.option('-p, --pack <packIds>', 'Policy Pack ID(s) to enforce (comma-separated, e.g. web,pci-dss-v1)', 'web')
|
|
258
258
|
.option('-f, --format <format>', 'Output format (text, json, sarif)', 'text')
|
|
259
259
|
.option('--offline', 'Force local-only analysis using cached rule definitions (no server calls)', false)
|
|
260
260
|
.option('--sync-rules', 'Fetch and cache latest rule definitions before running analysis', false)
|
|
261
261
|
.action(async (dirArg, options) => {
|
|
262
262
|
const dir = dirArg || '.';
|
|
263
|
-
const
|
|
263
|
+
const packInput = options.pack;
|
|
264
|
+
// Multi-pack: split on comma, trim whitespace
|
|
265
|
+
const packIds = packInput.split(',').map(p => p.trim()).filter(Boolean);
|
|
266
|
+
const pack = packIds.length === 1 ? packIds[0] : packIds;
|
|
264
267
|
const format = options.format.toLowerCase();
|
|
265
268
|
const isText = format === 'text';
|
|
266
269
|
|
|
267
270
|
let forceOffline = options.offline;
|
|
268
271
|
let localDefinitions = null;
|
|
269
272
|
|
|
273
|
+
// Pre-flight: verify directory exists
|
|
274
|
+
const resolvedInputDir = path.resolve(dir);
|
|
275
|
+
if (!fs.existsSync(resolvedInputDir)) {
|
|
276
|
+
if (isText) console.error(chalk.red(`\n✖ Directory does not exist: ${resolvedInputDir}`));
|
|
277
|
+
else console.error(JSON.stringify({ error: `Directory does not exist: ${resolvedInputDir}` }));
|
|
278
|
+
process.exit(2);
|
|
279
|
+
}
|
|
280
|
+
|
|
270
281
|
if (isText) {
|
|
271
282
|
console.log(`\n${chalk.bold.hex('#F27D26')('▲ KOREXT CLI ENFORCEMENT ENGINE')} v${version}`);
|
|
272
283
|
console.log(chalk.dim('======================================='));
|
|
@@ -295,6 +306,11 @@ program
|
|
|
295
306
|
if (isText) {
|
|
296
307
|
console.log(chalk.yellow('\n⚡ Offline mode: using local rule engine (regex-based analysis)'));
|
|
297
308
|
console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules · ${localDefinitions.packCount} packs`));
|
|
309
|
+
// Show rule coverage breakdown
|
|
310
|
+
const packRules = localDefinitions.packs?.[pack]?.rules || [];
|
|
311
|
+
const availableCount = packRules.filter(r => localDefinitions.rules?.[r]).length;
|
|
312
|
+
const serverOnlyCount = packRules.length - availableCount;
|
|
313
|
+
console.log(chalk.dim(` Offline mode: ${availableCount} of ${packRules.length} rules available. ${serverOnlyCount} rules require server analysis.`));
|
|
298
314
|
console.log(chalk.dim(' Note: AI-powered deep analysis is unavailable in offline mode.\n'));
|
|
299
315
|
}
|
|
300
316
|
}
|
|
@@ -308,7 +324,8 @@ program
|
|
|
308
324
|
|
|
309
325
|
const report = {
|
|
310
326
|
version,
|
|
311
|
-
packId: pack,
|
|
327
|
+
packId: Array.isArray(pack) ? pack[0] : pack,
|
|
328
|
+
packIds,
|
|
312
329
|
directory: dir,
|
|
313
330
|
summary: { totalFiles: 0, scannedFiles: 0, skippedFiles: 0, errorFiles: 0, critical: 0, high: 0, medium: 0, low: 0, totalViolations: 0 },
|
|
314
331
|
results: []
|
|
@@ -330,7 +347,7 @@ program
|
|
|
330
347
|
process.exit(0);
|
|
331
348
|
}
|
|
332
349
|
|
|
333
|
-
if (isText) console.log(`Found ${files.length} files. Starting analysis with pack: ${chalk.cyan(
|
|
350
|
+
if (isText) console.log(`Found ${files.length} files. Starting analysis with pack${packIds.length > 1 ? 's' : ''}: ${chalk.cyan(packIds.join(', '))}...\n`);
|
|
334
351
|
|
|
335
352
|
let usedLocalEngine = false;
|
|
336
353
|
|
|
@@ -364,6 +381,8 @@ program
|
|
|
364
381
|
} else {
|
|
365
382
|
// Online mode: try server, fall back to local on failure
|
|
366
383
|
try {
|
|
384
|
+
const controller = new AbortController();
|
|
385
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
|
367
386
|
const res = await fetch(`${API_URL}/api/ide/analyze`, {
|
|
368
387
|
method: 'POST',
|
|
369
388
|
headers: {
|
|
@@ -377,8 +396,10 @@ program
|
|
|
377
396
|
packId: pack,
|
|
378
397
|
requestSignature: false,
|
|
379
398
|
asyncExplanations: false
|
|
380
|
-
})
|
|
399
|
+
}),
|
|
400
|
+
signal: controller.signal
|
|
381
401
|
});
|
|
402
|
+
clearTimeout(timeoutId);
|
|
382
403
|
|
|
383
404
|
if (!res.ok) {
|
|
384
405
|
throw new Error(`HTTP ${res.status}`);
|
|
@@ -1309,6 +1330,29 @@ program
|
|
|
1309
1330
|
if (fs.existsSync(fullPath)) analyzeWatchedFile(fullPath);
|
|
1310
1331
|
}, DEBOUNCE_MS));
|
|
1311
1332
|
});
|
|
1333
|
+
|
|
1334
|
+
// Also do an initial scan of all existing files on startup
|
|
1335
|
+
const initialFiles = [];
|
|
1336
|
+
function walkDir(d) {
|
|
1337
|
+
let entries;
|
|
1338
|
+
try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
|
|
1339
|
+
for (const entry of entries) {
|
|
1340
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
1341
|
+
const fullPath = path.join(d, entry.name);
|
|
1342
|
+
if (entry.isDirectory()) {
|
|
1343
|
+
walkDir(fullPath);
|
|
1344
|
+
} else if (SUPPORTED_EXTS.has(path.extname(entry.name))) {
|
|
1345
|
+
initialFiles.push(fullPath);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
walkDir(dir);
|
|
1350
|
+
if (initialFiles.length > 0) {
|
|
1351
|
+
console.log(chalk.dim(` Initial scan: ${initialFiles.length} file(s)...\n`));
|
|
1352
|
+
for (const f of initialFiles) {
|
|
1353
|
+
await analyzeWatchedFile(f);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1312
1356
|
} catch (e) {
|
|
1313
1357
|
console.error(chalk.red(`Failed to watch ${dir}: ${e.message}`));
|
|
1314
1358
|
process.exit(1);
|