korext 0.7.0 → 0.8.0
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/CHANGELOG.md +21 -0
- package/README.md +102 -28
- package/bin/korext.js +251 -56
- package/package.json +20 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## 0.8.0 - 2026-03-27
|
|
2
|
+
|
|
3
|
+
### New
|
|
4
|
+
- Scoped AI fixes: fixes are constrained to the violation region. Surrounding code is never modified.
|
|
5
|
+
- Four deterministic verification guards: every fix is checked for scope boundary, security function preservation, dangerous function introduction, and middleware chain integrity before it is applied.
|
|
6
|
+
- Silent token refresh: sessions no longer expire after one hour. Tokens refresh automatically in the background.
|
|
7
|
+
- Tiered AI concurrency: Enterprise (15), Team (10), Free (5) concurrent AI calls for faster explanations on large files.
|
|
8
|
+
- Offline mode improvements: status bar shows "Offline (local rules only)" with helpful messaging instead of error states.
|
|
9
|
+
- 478 policy rules across 44 packs with real detection logic. Zero stubs.
|
|
10
|
+
- Full 3-layer governance mappings on every rule: regulatory citations, technical standards (CWE, OWASP), and security intelligence (MITRE ATT&CK).
|
|
11
|
+
- 9 jurisdiction coverage: US, EU, UK, Canada, Australia, New Zealand, Japan, Taiwan, Singapore.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- IDE and dashboard fix paths unified. Both use the same scoped extraction and deterministic guards.
|
|
15
|
+
- HMAC signature verification now returns signatureValid: true for correctly signed bundles.
|
|
16
|
+
- Auto-subscription for first-time users. No more access denied on sign-in.
|
|
17
|
+
|
|
18
|
+
### Improved
|
|
19
|
+
- Consistent KOREXT_API_TOKEN environment variable across all clients. KOREXT_TOKEN is deprecated with a warning.
|
|
20
|
+
- Rate limiting adjusted: IDE status polling (200/min), explain stream (30/min).
|
|
21
|
+
|
|
1
22
|
## 0.7.0 - 2026-03-19
|
|
2
23
|
|
|
3
24
|
### Added
|
package/README.md
CHANGED
|
@@ -1,41 +1,115 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Korext CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Enforce compliance on AI-generated code from the command line and CI/CD pipelines. 478 rules across 44 policy packs with real detection logic. Every violation mapped to specific regulatory clauses. SARIF output for CI scanner integration.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g korext
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Quick Start
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- `korext policy init`: Initialize a new custom policy workflow
|
|
16
|
-
- `korext policy extract`: Extract rules from a PDF/Markdown policy document
|
|
17
|
-
- `korext policy review`: Interactively review extracted rules
|
|
18
|
-
- `korext policy publish`: Deploy and activate custom policy packs
|
|
13
|
+
```bash
|
|
14
|
+
korext login
|
|
15
|
+
korext enforce ./src --pack web-platform-v2
|
|
16
|
+
```
|
|
19
17
|
|
|
20
|
-
##
|
|
18
|
+
## Commands
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
| Command | Description |
|
|
21
|
+
|---------|-------------|
|
|
22
|
+
| `korext login [token]` | Save API token |
|
|
23
|
+
| `korext status` | Check connection and subscription |
|
|
24
|
+
| `korext enforce [dir]` | Scan files for violations |
|
|
25
|
+
| `korext policy init` | Initialize a policy document |
|
|
26
|
+
| `korext policy extract` | AI rule extraction from documents |
|
|
27
|
+
| `korext policy review` | Review extracted rules |
|
|
28
|
+
| `korext rules sync` | Cache rules for offline use |
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
### Enforce Options
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
| Flag | Description |
|
|
33
|
+
|------|-------------|
|
|
34
|
+
| `--pack <id>` | Select a policy pack |
|
|
35
|
+
| `--format text\|json\|sarif` | Output format |
|
|
36
|
+
| `--offline` | Use cached rules only |
|
|
37
|
+
| `--sync-rules` | Download rule cache before scan |
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
## CI/CD Integration
|
|
40
|
+
|
|
41
|
+
### GitHub Actions
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
- name: Korext Compliance Check
|
|
45
|
+
run: |
|
|
46
|
+
npm install -g korext
|
|
47
|
+
korext enforce ./src \
|
|
48
|
+
--pack cmmc-level2-v1 \
|
|
49
|
+
--format sarif
|
|
50
|
+
env:
|
|
51
|
+
KOREXT_API_TOKEN: ${{ secrets.KOREXT_API_TOKEN }}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Exit Codes
|
|
55
|
+
|
|
56
|
+
| Code | Meaning |
|
|
57
|
+
|------|---------|
|
|
58
|
+
| `0` | Clean (no critical or high violations) |
|
|
59
|
+
| `1` | Violations found |
|
|
60
|
+
| `2` | Error |
|
|
61
|
+
|
|
62
|
+
Writes GitHub Actions Step Summary via `GITHUB_STEP_SUMMARY` when detected.
|
|
63
|
+
|
|
64
|
+
## SARIF Output
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
korext enforce ./src --format sarif > results.sarif
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Generates OASIS SARIF 2.1.0 for CI scanner integration (GitHub Code Scanning, Azure DevOps, etc.).
|
|
71
|
+
|
|
72
|
+
## Offline Mode
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
korext rules sync
|
|
76
|
+
korext enforce ./src --offline
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Cached rules enforce locally with zero network calls. Status output shows "Offline (local rules only)".
|
|
80
|
+
|
|
81
|
+
## Supported Compliance Frameworks
|
|
82
|
+
|
|
83
|
+
OWASP Top 10 | PCI-DSS | HIPAA | GDPR | SOC 2 | NIST SP 800-53 | NIST SP 800-171 | CMMC Level 2/3 | FedRAMP | ISO 27001 | DORA | NIS2 | CIS Benchmarks | UK DPA | Australian Privacy Act | APPI (Japan) | PDPA (Singapore, Taiwan) | and 25+ more
|
|
84
|
+
|
|
85
|
+
## Key Features
|
|
86
|
+
|
|
87
|
+
- 478 rules across 44 policy packs
|
|
88
|
+
- Three-layer governance: regulatory, technical standards (CWE, OWASP), security intelligence (MITRE ATT&CK)
|
|
89
|
+
- 9 jurisdiction coverage (US, EU, UK, Canada, Australia, New Zealand, Japan, Taiwan, Singapore)
|
|
90
|
+
- SARIF 2.1.0 output for CI scanner integration
|
|
91
|
+
- GitHub Actions Step Summary generation
|
|
92
|
+
- Offline mode with cached rules
|
|
93
|
+
- Custom policy packs from uploaded documents
|
|
94
|
+
|
|
95
|
+
## Environment Variables
|
|
96
|
+
|
|
97
|
+
| Variable | Description |
|
|
98
|
+
|----------|-------------|
|
|
99
|
+
| `KOREXT_API_TOKEN` | API authentication token (recommended) |
|
|
100
|
+
| `KOREXT_TOKEN` | Deprecated alias (shows warning) |
|
|
36
101
|
|
|
37
102
|
## Links
|
|
38
103
|
|
|
39
|
-
- Website: https://www.korext.com
|
|
40
|
-
- Dashboard: https://app.korext.com
|
|
41
|
-
-
|
|
104
|
+
- Website: [korext.com](https://www.korext.com)
|
|
105
|
+
- Dashboard: [app.korext.com](https://app.korext.com)
|
|
106
|
+
- VS Code Extension: [marketplace.visualstudio.com](https://marketplace.visualstudio.com/items?itemName=Korext.korext)
|
|
107
|
+
- LinkedIn: [linkedin.com/company/korext](https://www.linkedin.com/company/korext)
|
|
108
|
+
- GitHub: [github.com/Korext](https://github.com/Korext)
|
|
109
|
+
- Support: support@korext.com
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
**Publisher**: Korext
|
|
114
|
+
**License**: Proprietary
|
|
115
|
+
**Version**: 0.8.0
|
package/bin/korext.js
CHANGED
|
@@ -34,8 +34,17 @@ function saveConfig(config) {
|
|
|
34
34
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
let _deprecationWarned = false;
|
|
37
38
|
function getToken() {
|
|
38
|
-
|
|
39
|
+
if (process.env.KOREXT_API_TOKEN) return process.env.KOREXT_API_TOKEN;
|
|
40
|
+
if (process.env.KOREXT_TOKEN) {
|
|
41
|
+
if (!_deprecationWarned) {
|
|
42
|
+
console.warn('\x1b[33m⚠ KOREXT_TOKEN is deprecated. Use KOREXT_API_TOKEN instead.\x1b[0m');
|
|
43
|
+
_deprecationWarned = true;
|
|
44
|
+
}
|
|
45
|
+
return process.env.KOREXT_TOKEN;
|
|
46
|
+
}
|
|
47
|
+
return getConfig().token || null;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
function getLanguageFromExt(ext) {
|
|
@@ -48,6 +57,90 @@ function getLanguageFromExt(ext) {
|
|
|
48
57
|
return map[ext] || 'plaintext';
|
|
49
58
|
}
|
|
50
59
|
|
|
60
|
+
// ─── LOCAL ENGINE: Rule Definition Caching + Offline Analysis ─────────────────
|
|
61
|
+
|
|
62
|
+
const RULES_CACHE_FILE = path.join(CONFIG_DIR, 'rule-definitions.json');
|
|
63
|
+
|
|
64
|
+
function getRuleDefinitionsCache() {
|
|
65
|
+
if (fs.existsSync(RULES_CACHE_FILE)) {
|
|
66
|
+
try {
|
|
67
|
+
const cached = JSON.parse(fs.readFileSync(RULES_CACHE_FILE, 'utf-8'));
|
|
68
|
+
if (cached && cached.rules && cached.packs) return cached;
|
|
69
|
+
} catch { /* corrupt cache, ignore */ }
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function saveRuleDefinitionsCache(payload) {
|
|
75
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
76
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
fs.writeFileSync(RULES_CACHE_FILE, JSON.stringify(payload, null, 2), { mode: 0o600 });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function fetchAndCacheRules() {
|
|
82
|
+
const token = getToken();
|
|
83
|
+
const res = await fetch(`${API_URL}/api/rules/definitions`, {
|
|
84
|
+
method: 'GET',
|
|
85
|
+
headers: {
|
|
86
|
+
'Content-Type': 'application/json',
|
|
87
|
+
...(token && { 'Authorization': `Bearer ${token}` })
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
91
|
+
const payload = await res.json();
|
|
92
|
+
saveRuleDefinitionsCache(payload);
|
|
93
|
+
return payload;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Run rules locally against code using cached regex definitions.
|
|
98
|
+
* Mirrors korext-core/src/engine/localEngine.ts analyzeLocally().
|
|
99
|
+
*/
|
|
100
|
+
function analyzeLocally(code, packId, definitions) {
|
|
101
|
+
const pack = definitions.packs[packId];
|
|
102
|
+
if (!pack) return [];
|
|
103
|
+
|
|
104
|
+
const violations = [];
|
|
105
|
+
const lines = code.split('\n');
|
|
106
|
+
|
|
107
|
+
for (const ruleId of pack.rules) {
|
|
108
|
+
const rule = definitions.rules[ruleId];
|
|
109
|
+
if (!rule) continue;
|
|
110
|
+
if (rule.checkMode === 'server-only') continue;
|
|
111
|
+
if (!rule.patterns || rule.patterns.length === 0) continue;
|
|
112
|
+
|
|
113
|
+
for (const pattern of rule.patterns) {
|
|
114
|
+
try {
|
|
115
|
+
const re = new RegExp(pattern.regex, pattern.flags || 'gi');
|
|
116
|
+
lines.forEach((line, i) => {
|
|
117
|
+
re.lastIndex = 0;
|
|
118
|
+
if (re.test(line)) {
|
|
119
|
+
violations.push({
|
|
120
|
+
ruleName: rule.label,
|
|
121
|
+
severity: rule.severity,
|
|
122
|
+
explanation: pattern.message || rule.message || `${rule.label} violation detected`,
|
|
123
|
+
line: i + 1,
|
|
124
|
+
column: 0,
|
|
125
|
+
snippet: line.trim().slice(0, 90),
|
|
126
|
+
detectedBy: 'local-static'
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
} catch { /* invalid regex, skip */ }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Deduplicate by ruleId + line + message
|
|
135
|
+
const seen = new Set();
|
|
136
|
+
return violations.filter(v => {
|
|
137
|
+
const key = `${v.ruleName}:${v.line}:${v.explanation}`;
|
|
138
|
+
if (seen.has(key)) return false;
|
|
139
|
+
seen.add(key);
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
51
144
|
function findFiles(dir, fileList = []) {
|
|
52
145
|
if (!fs.existsSync(dir)) return fileList;
|
|
53
146
|
const files = fs.readdirSync(dir);
|
|
@@ -160,19 +253,51 @@ program
|
|
|
160
253
|
.description('Statically analyze files in a directory against Korext policies')
|
|
161
254
|
.option('-p, --pack <packId>', 'Policy Pack ID to enforce', 'web')
|
|
162
255
|
.option('-f, --format <format>', 'Output format (text, json, sarif)', 'text')
|
|
256
|
+
.option('--offline', 'Force local-only analysis using cached rule definitions (no server calls)', false)
|
|
257
|
+
.option('--sync-rules', 'Fetch and cache latest rule definitions before running analysis', false)
|
|
163
258
|
.action(async (dirArg, options) => {
|
|
164
259
|
const dir = dirArg || '.';
|
|
165
260
|
const pack = options.pack;
|
|
166
261
|
const format = options.format.toLowerCase();
|
|
167
262
|
const isText = format === 'text';
|
|
168
263
|
|
|
264
|
+
let forceOffline = options.offline;
|
|
265
|
+
let localDefinitions = null;
|
|
266
|
+
|
|
169
267
|
if (isText) {
|
|
170
268
|
console.log(`\n${chalk.bold.hex('#F27D26')('▲ KOREXT CLI ENFORCEMENT ENGINE')} v${version}`);
|
|
171
269
|
console.log(chalk.dim('======================================='));
|
|
172
270
|
}
|
|
173
271
|
|
|
272
|
+
// Pre-flight: sync rules if requested
|
|
273
|
+
if (options.syncRules) {
|
|
274
|
+
const syncSpinner = isText ? ora('Syncing rule definitions...').start() : null;
|
|
275
|
+
try {
|
|
276
|
+
const defs = await fetchAndCacheRules();
|
|
277
|
+
if (syncSpinner) syncSpinner.succeed(chalk.green(`Synced ${defs.ruleCount} rules, ${defs.packCount} packs (v${defs.version})`));
|
|
278
|
+
} catch (e) {
|
|
279
|
+
if (syncSpinner) syncSpinner.warn(chalk.yellow(`Rule sync failed: ${e.message}. Will use cached definitions if available.`));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Load cached rule definitions for offline/fallback use
|
|
284
|
+
localDefinitions = getRuleDefinitionsCache();
|
|
285
|
+
|
|
286
|
+
if (forceOffline) {
|
|
287
|
+
if (!localDefinitions) {
|
|
288
|
+
if (isText) console.error(chalk.red('\n✖ Offline mode requires cached rule definitions.'));
|
|
289
|
+
if (isText) console.error(chalk.dim(` Run ${chalk.green('korext rules sync')} or ${chalk.green('korext enforce --sync-rules')} first while online.`));
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
if (isText) {
|
|
293
|
+
console.log(chalk.yellow('\n⚡ Offline mode: using local rule engine (regex-based analysis)'));
|
|
294
|
+
console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules · ${localDefinitions.packCount} packs`));
|
|
295
|
+
console.log(chalk.dim(' Note: AI-powered deep analysis is unavailable in offline mode.\n'));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
174
299
|
const token = getToken();
|
|
175
|
-
if (!token && isText) {
|
|
300
|
+
if (!token && !forceOffline && isText) {
|
|
176
301
|
console.log(chalk.yellow('\n⚠ Warning: No authentication token found.'));
|
|
177
302
|
console.log(chalk.dim('Anonymous evaluation runs are limited to 20 requests per hour per IP.'));
|
|
178
303
|
console.log(chalk.dim(`Run ${chalk.green('korext login')} to authenticate for unlimited CI/CD analytics.\n`));
|
|
@@ -204,6 +329,8 @@ program
|
|
|
204
329
|
|
|
205
330
|
if (isText) console.log(`Found ${files.length} files. Starting analysis with pack: ${chalk.cyan(pack)}...\n`);
|
|
206
331
|
|
|
332
|
+
let usedLocalEngine = false;
|
|
333
|
+
|
|
207
334
|
for (let i = 0; i < files.length; i++) {
|
|
208
335
|
const file = files[i];
|
|
209
336
|
const displayPath = path.relative(process.cwd(), file);
|
|
@@ -221,66 +348,94 @@ program
|
|
|
221
348
|
const ext = path.extname(file);
|
|
222
349
|
const language = getLanguageFromExt(ext);
|
|
223
350
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
method: 'POST',
|
|
227
|
-
headers: {
|
|
228
|
-
'Content-Type': 'application/json',
|
|
229
|
-
...(token && { 'Authorization': `Bearer ${token}` })
|
|
230
|
-
},
|
|
231
|
-
body: JSON.stringify({
|
|
232
|
-
fileContent,
|
|
233
|
-
language,
|
|
234
|
-
fileName: file,
|
|
235
|
-
packId: pack,
|
|
236
|
-
requestSignature: false
|
|
237
|
-
})
|
|
238
|
-
});
|
|
351
|
+
// ── Analysis: server-first with local fallback ─────────────────────
|
|
352
|
+
let fileViolations = [];
|
|
239
353
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
354
|
+
if (forceOffline) {
|
|
355
|
+
// Offline mode: local engine only
|
|
356
|
+
fileViolations = analyzeLocally(fileContent, pack, localDefinitions);
|
|
357
|
+
usedLocalEngine = true;
|
|
358
|
+
} else {
|
|
359
|
+
// Online mode: try server, fall back to local on failure
|
|
360
|
+
try {
|
|
361
|
+
const res = await fetch(`${API_URL}/api/ide/analyze`, {
|
|
362
|
+
method: 'POST',
|
|
363
|
+
headers: {
|
|
364
|
+
'Content-Type': 'application/json',
|
|
365
|
+
...(token && { 'Authorization': `Bearer ${token}` })
|
|
366
|
+
},
|
|
367
|
+
body: JSON.stringify({
|
|
368
|
+
fileContent,
|
|
369
|
+
language,
|
|
370
|
+
fileName: file,
|
|
371
|
+
packId: pack,
|
|
372
|
+
requestSignature: false
|
|
373
|
+
})
|
|
374
|
+
});
|
|
245
375
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
376
|
+
if (!res.ok) {
|
|
377
|
+
throw new Error(`HTTP ${res.status}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const result = await res.json();
|
|
381
|
+
|
|
382
|
+
if (result.skipped) {
|
|
383
|
+
if (fileSpinner) fileSpinner.info(chalk.yellow(`Skipped ${displayPath}: ${result.reason}`));
|
|
384
|
+
report.summary.skippedFiles++;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
253
387
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
else if (v.severity === 'medium') { severityTag = chalk.yellow.bold('MED'); report.summary.medium++; }
|
|
268
|
-
else { report.summary.low++; }
|
|
269
|
-
|
|
270
|
-
if (isText) {
|
|
271
|
-
console.log(` ${chalk.dim(v.line + ':' + (v.column || 0))} ${severityTag} ${v.ruleName}`);
|
|
272
|
-
console.log(` ${chalk.dim('↳')} ${chalk.italic(v.explanation)} `);
|
|
388
|
+
fileViolations = result.violations || [];
|
|
389
|
+
|
|
390
|
+
// Cache rule definitions on first successful server response (opportunistic sync)
|
|
391
|
+
if (!localDefinitions && i === 0) {
|
|
392
|
+
fetchAndCacheRules().catch(() => {});
|
|
393
|
+
}
|
|
394
|
+
} catch (e) {
|
|
395
|
+
// Server unreachable — fall back to local engine
|
|
396
|
+
if (localDefinitions) {
|
|
397
|
+
if (!usedLocalEngine && isText && i === 0) {
|
|
398
|
+
// Show fallback banner once
|
|
399
|
+
console.log(chalk.yellow(`\n⚡ Server unreachable. Falling back to local engine (regex-based analysis).`));
|
|
400
|
+
console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules\n`));
|
|
273
401
|
}
|
|
274
|
-
|
|
402
|
+
fileViolations = analyzeLocally(fileContent, pack, localDefinitions);
|
|
403
|
+
usedLocalEngine = true;
|
|
404
|
+
forceOffline = true; // Don't retry server for remaining files
|
|
405
|
+
} else {
|
|
406
|
+
if (fileSpinner) fileSpinner.fail(chalk.red(`Failed to analyze ${displayPath}: ${e.message}`));
|
|
407
|
+
report.summary.errorFiles++;
|
|
408
|
+
continue;
|
|
275
409
|
}
|
|
276
|
-
report.results.push({ file: displayPath, violations: storedV });
|
|
277
|
-
} else {
|
|
278
|
-
if (fileSpinner) fileSpinner.stop();
|
|
279
|
-
report.results.push({ file: displayPath, violations: [] });
|
|
280
410
|
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
report.summary.scannedFiles++;
|
|
414
|
+
report.summary.totalViolations += fileViolations.length;
|
|
415
|
+
|
|
416
|
+
if (fileViolations.length > 0) {
|
|
417
|
+
if (fileSpinner) fileSpinner.stop();
|
|
418
|
+
if (isText) console.log(`\n📄 ${chalk.bold.underline(displayPath)}`);
|
|
419
|
+
|
|
420
|
+
const storedV = [];
|
|
421
|
+
for (const v of fileViolations) {
|
|
422
|
+
let severityTag = chalk.blue('LOW');
|
|
423
|
+
if (v.severity === 'critical') { severityTag = chalk.bgRed.white.bold(' CRITICAL '); report.summary.critical++; }
|
|
424
|
+
else if (v.severity === 'high') { severityTag = chalk.red.bold('HIGH'); report.summary.high++; }
|
|
425
|
+
else if (v.severity === 'medium') { severityTag = chalk.yellow.bold('MED'); report.summary.medium++; }
|
|
426
|
+
else { report.summary.low++; }
|
|
427
|
+
|
|
428
|
+
if (isText) {
|
|
429
|
+
const localTag = v.detectedBy === 'local-static' ? chalk.dim(' [local]') : '';
|
|
430
|
+
console.log(` ${chalk.dim(v.line + ':' + (v.column || 0))} ${severityTag} ${v.ruleName}${localTag}`);
|
|
431
|
+
console.log(` ${chalk.dim('↳')} ${chalk.italic(v.explanation)} `);
|
|
432
|
+
}
|
|
433
|
+
storedV.push(v);
|
|
434
|
+
}
|
|
435
|
+
report.results.push({ file: displayPath, violations: storedV });
|
|
436
|
+
} else {
|
|
437
|
+
if (fileSpinner) fileSpinner.stop();
|
|
438
|
+
report.results.push({ file: displayPath, violations: [] });
|
|
284
439
|
}
|
|
285
440
|
}
|
|
286
441
|
|
|
@@ -313,6 +468,9 @@ program
|
|
|
313
468
|
console.log(JSON.stringify(sarif, null, 2));
|
|
314
469
|
} else {
|
|
315
470
|
console.log('\n' + chalk.dim('======================================='));
|
|
471
|
+
if (usedLocalEngine) {
|
|
472
|
+
console.log(chalk.dim('Engine: LOCAL (regex-based, cached rules)'));
|
|
473
|
+
}
|
|
316
474
|
if (report.summary.totalViolations === 0 && report.summary.errorFiles === 0) {
|
|
317
475
|
console.log(chalk.green.bold('✔ Success! Found 0 policy violations. Your code is clean.\n'));
|
|
318
476
|
} else {
|
|
@@ -359,6 +517,43 @@ program
|
|
|
359
517
|
process.exit(0);
|
|
360
518
|
});
|
|
361
519
|
|
|
520
|
+
// ─── RULE SYNC COMMAND ──────────────────────────────────────────────────────
|
|
521
|
+
|
|
522
|
+
const rulesCmd = program.command('rules').description('Rule definition management commands');
|
|
523
|
+
|
|
524
|
+
rulesCmd
|
|
525
|
+
.command('sync')
|
|
526
|
+
.description('Download and cache rule definitions for offline enforcement')
|
|
527
|
+
.action(async () => {
|
|
528
|
+
console.log(chalk.bold.hex('#F27D26')('\n▲ KOREXT RULE SYNC'));
|
|
529
|
+
console.log(chalk.dim('======================================='));
|
|
530
|
+
const spinner = ora('Fetching rule definitions...').start();
|
|
531
|
+
try {
|
|
532
|
+
const defs = await fetchAndCacheRules();
|
|
533
|
+
spinner.succeed(chalk.green(`Cached ${defs.ruleCount} rules across ${defs.packCount} packs`));
|
|
534
|
+
console.log(chalk.dim(` Version: ${defs.version}`));
|
|
535
|
+
console.log(chalk.dim(` Saved to: ${RULES_CACHE_FILE}`));
|
|
536
|
+
console.log(chalk.dim(` Generated: ${defs.generatedAt}`));
|
|
537
|
+
|
|
538
|
+
// List available packs
|
|
539
|
+
console.log(`\n${chalk.bold('Available packs for offline enforcement:')}`);
|
|
540
|
+
for (const [packId, pack] of Object.entries(defs.packs)) {
|
|
541
|
+
const ruleCount = pack.rules ? pack.rules.length : 0;
|
|
542
|
+
console.log(` ${chalk.cyan(packId)} — ${pack.label} (${ruleCount} rules)`);
|
|
543
|
+
}
|
|
544
|
+
console.log(`\n Run: ${chalk.green('korext enforce . --offline --pack <packId>')} to enforce offline.\n`);
|
|
545
|
+
} catch (e) {
|
|
546
|
+
spinner.fail(chalk.red(`Failed to fetch rule definitions: ${e.message}`));
|
|
547
|
+
const cached = getRuleDefinitionsCache();
|
|
548
|
+
if (cached) {
|
|
549
|
+
console.log(chalk.yellow(` Existing cache still available (v${cached.version}, ${cached.ruleCount} rules)`));
|
|
550
|
+
} else {
|
|
551
|
+
console.log(chalk.dim(' No cached definitions available. Connect to the network and try again.'));
|
|
552
|
+
}
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
362
557
|
// ─── POLICY COMMANDS ─────────────────────────────────────────────────────────
|
|
363
558
|
|
|
364
559
|
const LOCAL_DRAFT_DIR = path.join(process.cwd(), '.korext');
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "korext",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Korext Command Line Interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/korext.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"korext": "bin/korext.js"
|
|
9
9
|
},
|
|
10
|
-
"author": "Korext",
|
|
10
|
+
"author": "Korext <support@korext.com>",
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"chalk": "^4.1.2",
|
|
@@ -30,5 +30,23 @@
|
|
|
30
30
|
"glob",
|
|
31
31
|
"node-fetch",
|
|
32
32
|
"ora"
|
|
33
|
+
],
|
|
34
|
+
"homepage": "https://www.korext.com",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"email": "support@korext.com"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"governance",
|
|
40
|
+
"compliance",
|
|
41
|
+
"security",
|
|
42
|
+
"cli",
|
|
43
|
+
"cicd",
|
|
44
|
+
"sarif",
|
|
45
|
+
"AI",
|
|
46
|
+
"code review",
|
|
47
|
+
"PCI-DSS",
|
|
48
|
+
"HIPAA",
|
|
49
|
+
"OWASP",
|
|
50
|
+
"korext"
|
|
33
51
|
]
|
|
34
52
|
}
|