praxis-sec 1.2.0 → 1.2.2
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/assets/praxis-architecture.svg +304 -0
- package/assets/praxis-logo.svg +38 -0
- package/cli/agents/abom-generator.js +1 -1
- package/cli/agents/agent-attestation-agent.js +10 -1
- package/cli/agents/agent-config-scanner.js +1 -1
- package/cli/agents/ai-infra-inventory-agent.js +482 -482
- package/cli/agents/base-agent.js +1 -1
- package/cli/agents/endpoint-agent-abuse-agent.js +1 -1
- package/cli/agents/html-reporter.js +10 -10
- package/cli/agents/index.js +2 -2
- package/cli/agents/injection-tester.js +8 -1
- package/cli/agents/mcp-security-agent.js +600 -594
- package/cli/agents/memory-poisoning-agent.js +1 -1
- package/cli/agents/model-file-scanner.js +1 -1
- package/cli/agents/orchestrator.js +375 -355
- package/cli/agents/prompt-injection-prober.js +228 -228
- package/cli/bin/praxis.js +7 -3
- package/cli/commands/agent-fix.js +1091 -1245
- package/cli/commands/audit.js +1232 -1216
- package/cli/commands/baseline.js +3 -2
- package/cli/commands/benchmark.js +1 -1
- package/cli/commands/ci.js +50 -25
- package/cli/commands/deps.js +11 -5
- package/cli/commands/diff.js +2 -1
- package/cli/commands/env-audit.js +1 -1
- package/cli/commands/fix.js +1 -1
- package/cli/commands/legal.js +2 -1
- package/cli/commands/mcp.js +3 -2
- package/cli/commands/openclaw.js +3 -6
- package/cli/commands/red-team.js +351 -350
- package/cli/commands/remediate.js +1 -1
- package/cli/commands/rotate.js +1 -1
- package/cli/commands/rules.js +1 -1
- package/cli/commands/scan-standard.js +3 -6
- package/cli/commands/scan.js +554 -554
- package/cli/commands/score.js +1 -1
- package/cli/commands/undo.js +22 -77
- package/cli/commands/vibe-check.js +3 -2
- package/cli/commands/watch.js +6 -5
- package/cli/core/fix-plan.js +274 -0
- package/cli/core/fs.js +27 -0
- package/cli/core/git-clone.js +8 -6
- package/cli/core/glob.js +56 -0
- package/cli/core/output/html-theme.js +158 -158
- package/cli/core/output/json.js +56 -48
- package/cli/core/output/sarif.js +2 -2
- package/cli/core/paths.js +91 -0
- package/cli/core/web/jobs.js +2 -2
- package/cli/core/web/server.js +19 -8
- package/cli/data/threatpacks/latest.json +41 -41
- package/cli/integrations/github-action.js +136 -0
- package/cli/utils/cache-manager.js +2 -1
- package/cli/utils/plugin-loader.js +15 -95
- package/cli/utils/rule-import.js +227 -227
- package/cli/utils/rule-registry.js +425 -425
- package/cli/utils/scan-fingerprint.js +1 -1
- package/cli/utils/score-history.js +118 -118
- package/docs/USAGE.md +16 -9
- package/docs/design/WEB-UI.md +4 -5
- package/package.json +81 -71
package/cli/commands/baseline.js
CHANGED
|
@@ -16,10 +16,11 @@ import fs from 'fs';
|
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import chalk from 'chalk';
|
|
18
18
|
import ora from 'ora';
|
|
19
|
+
import { displayPath } from '../core/paths.js';
|
|
19
20
|
import { buildOrchestrator } from '../agents/index.js';
|
|
20
21
|
import { SECRET_PATTERNS, SKIP_DIRS, SKIP_EXTENSIONS, SKIP_FILENAMES, MAX_FILE_SIZE } from '../utils/patterns.js';
|
|
21
22
|
import { isHighEntropyMatch } from '../utils/entropy.js';
|
|
22
|
-
import fg from '
|
|
23
|
+
import fg from '../core/glob.js';
|
|
23
24
|
|
|
24
25
|
const BASELINE_FILE = '.praxis/baseline.json';
|
|
25
26
|
|
|
@@ -28,7 +29,7 @@ const BASELINE_FILE = '.praxis/baseline.json';
|
|
|
28
29
|
* Uses rule + relative file path + first 40 chars of matched text.
|
|
29
30
|
*/
|
|
30
31
|
function fingerprint(finding, rootPath) {
|
|
31
|
-
const relFile =
|
|
32
|
+
const relFile = displayPath(finding.file || '', rootPath);
|
|
32
33
|
const matched = (finding.matched || '').slice(0, 40);
|
|
33
34
|
return `${finding.rule}:${relFile}:${matched}`;
|
|
34
35
|
}
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
} from '../utils/patterns.js';
|
|
35
35
|
import { isHighEntropyMatch, getConfidence } from '../utils/entropy.js';
|
|
36
36
|
import * as output from '../utils/output.js';
|
|
37
|
-
import fg from '
|
|
37
|
+
import fg from '../core/glob.js';
|
|
38
38
|
|
|
39
39
|
// =============================================================================
|
|
40
40
|
// INDUSTRY BENCHMARKS (aggregated from public research)
|
package/cli/commands/ci.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import fs from 'fs';
|
|
21
21
|
import path from 'path';
|
|
22
|
+
import { displayPath } from '../core/paths.js';
|
|
22
23
|
import { renderFindingsSARIF } from '../core/output/sarif.js';
|
|
23
24
|
import { execFileSync } from 'child_process';
|
|
24
25
|
import { buildOrchestrator } from '../agents/index.js';
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
import { isHighEntropyMatch, getConfidence } from '../utils/entropy.js';
|
|
38
39
|
import { ThreatIntel } from '../utils/threat-intel.js';
|
|
39
40
|
import * as intelOrchestrator from '../utils/intel/index.js';
|
|
40
|
-
import fg from '
|
|
41
|
+
import fg from '../core/glob.js';
|
|
41
42
|
|
|
42
43
|
// =============================================================================
|
|
43
44
|
// MAIN COMMAND
|
|
@@ -50,6 +51,12 @@ export async function ciCommand(targetPath = '.', options = {}) {
|
|
|
50
51
|
const alwaysFailOn = options.alwaysFailOn || null;
|
|
51
52
|
const sarifPath = options.sarif || null;
|
|
52
53
|
|
|
54
|
+
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 100 ||
|
|
55
|
+
[failOn, alwaysFailOn].some(value => value && !['critical', 'high', 'medium', 'low'].includes(value))) {
|
|
56
|
+
console.error('[praxis] Invalid CI gate: threshold must be 0-100 and severity must be critical, high, medium, or low.');
|
|
57
|
+
process.exit(2);
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
if (!fs.existsSync(absolutePath)) {
|
|
54
61
|
console.error(`[praxis] Path does not exist: ${absolutePath}`);
|
|
55
62
|
process.exit(1);
|
|
@@ -99,16 +106,26 @@ export async function ciCommand(targetPath = '.', options = {}) {
|
|
|
99
106
|
|
|
100
107
|
// ── Agent Scan ───────────────────────────────────────────────────────────
|
|
101
108
|
const orchestrator = buildOrchestrator();
|
|
102
|
-
const results = await orchestrator.runAll(absolutePath, { quiet: true }); // praxis-ignore — orchestrator result, not LLM output triggering actions
|
|
109
|
+
const results = await orchestrator.runAll(absolutePath, { quiet: true, deep: options.deep }); // praxis-ignore — orchestrator result, not LLM output triggering actions
|
|
103
110
|
const agentFindings = results.findings;
|
|
111
|
+
let scanComplete = results.agentResults.every(agent => agent.success);
|
|
112
|
+
if (!scanComplete) console.error('[praxis] Scan incomplete: one or more agents failed.');
|
|
104
113
|
|
|
105
114
|
// ── Dependency Audit ─────────────────────────────────────────────────────
|
|
106
115
|
let depVulns = [];
|
|
116
|
+
let dependencyAudit = options.deps === false ? 'skipped' : 'not-applicable';
|
|
107
117
|
if (options.deps !== false) {
|
|
108
118
|
try {
|
|
109
119
|
const depResult = await runDepsAudit(absolutePath);
|
|
110
120
|
depVulns = depResult.vulns || [];
|
|
111
|
-
|
|
121
|
+
dependencyAudit = depResult.error ? 'failed' : (depResult.pm ? 'complete' : 'not-applicable');
|
|
122
|
+
} catch {
|
|
123
|
+
dependencyAudit = 'failed';
|
|
124
|
+
}
|
|
125
|
+
if (dependencyAudit === 'failed') {
|
|
126
|
+
scanComplete = false;
|
|
127
|
+
console.error('[praxis] Dependency audit incomplete. Install the package manager audit tool and check registry access, or explicitly use --no-deps.');
|
|
128
|
+
}
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
// ── Merge & Deduplicate ──────────────────────────────────────────────────
|
|
@@ -140,6 +157,8 @@ export async function ciCommand(targetPath = '.', options = {}) {
|
|
|
140
157
|
scoringEngine.saveToHistory(absolutePath, scoreResult);
|
|
141
158
|
|
|
142
159
|
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
160
|
+
const gateFindings = [...allFindings, ...depVulns];
|
|
161
|
+
const floorFindings = [...preBaselineFindings, ...depVulns];
|
|
143
162
|
|
|
144
163
|
// ── SARIF Output ─────────────────────────────────────────────────────────
|
|
145
164
|
if (sarifPath) {
|
|
@@ -163,12 +182,15 @@ export async function ciCommand(targetPath = '.', options = {}) {
|
|
|
163
182
|
}])),
|
|
164
183
|
...(options.includeFindings ? {
|
|
165
184
|
findings: allFindings.map(f => ({
|
|
166
|
-
file:
|
|
185
|
+
file: displayPath(f.file, absolutePath),
|
|
167
186
|
line: f.line, rule: f.rule, severity: f.severity,
|
|
168
187
|
})),
|
|
169
188
|
} : {}),
|
|
170
189
|
threshold,
|
|
171
|
-
|
|
190
|
+
scanComplete,
|
|
191
|
+
dependencyAudit,
|
|
192
|
+
floorPass: determinePass(scoreResult, [], 0, null, alwaysFailOn, floorFindings),
|
|
193
|
+
pass: scanComplete && determinePass(scoreResult, gateFindings, threshold, failOn, alwaysFailOn, floorFindings),
|
|
172
194
|
duration: `${duration}s`,
|
|
173
195
|
}, null, 2));
|
|
174
196
|
} else {
|
|
@@ -182,7 +204,7 @@ export async function ciCommand(targetPath = '.', options = {}) {
|
|
|
182
204
|
if (critical > 0) {
|
|
183
205
|
console.log(`[praxis] Critical findings:`);
|
|
184
206
|
for (const f of allFindings.filter(f => f.severity === 'critical').slice(0, 5)) {
|
|
185
|
-
const rel =
|
|
207
|
+
const rel = displayPath(f.file, absolutePath);
|
|
186
208
|
console.log(` - ${f.rule} at ${rel}:${f.line}`);
|
|
187
209
|
}
|
|
188
210
|
}
|
|
@@ -192,7 +214,7 @@ export async function ciCommand(targetPath = '.', options = {}) {
|
|
|
192
214
|
}
|
|
193
215
|
}
|
|
194
216
|
|
|
195
|
-
// ── GitHub PR Inline Annotations
|
|
217
|
+
// ── GitHub PR Inline Annotations ───────────────────────────
|
|
196
218
|
emitGitHubAnnotations(allFindings, absolutePath);
|
|
197
219
|
|
|
198
220
|
// ── GitHub PR Comment ──────────────────────────────────────────────────
|
|
@@ -200,12 +222,12 @@ export async function ciCommand(targetPath = '.', options = {}) {
|
|
|
200
222
|
try {
|
|
201
223
|
postPRComment(scoreResult, allFindings, depVulns, absolutePath, duration);
|
|
202
224
|
} catch (err) {
|
|
203
|
-
console.
|
|
225
|
+
console.error(`[praxis] Warning: Could not post PR comment: ${err.message}`);
|
|
204
226
|
}
|
|
205
227
|
}
|
|
206
228
|
|
|
207
229
|
// ── Exit Code ────────────────────────────────────────────────────────────
|
|
208
|
-
const pass = determinePass(scoreResult,
|
|
230
|
+
const pass = scanComplete && determinePass(scoreResult, gateFindings, threshold, failOn, alwaysFailOn, floorFindings);
|
|
209
231
|
if (!pass) {
|
|
210
232
|
if (!options.json) {
|
|
211
233
|
if (failOn) {
|
|
@@ -229,14 +251,7 @@ export async function ciCommand(targetPath = '.', options = {}) {
|
|
|
229
251
|
// HELPERS
|
|
230
252
|
// =============================================================================
|
|
231
253
|
|
|
232
|
-
function determinePass(scoreResult, findings, threshold, failOn, alwaysFailOn, preBaselineFindings = null) {
|
|
233
|
-
if (failOn) {
|
|
234
|
-
const sevOrder = ['critical', 'high', 'medium', 'low'];
|
|
235
|
-
const failIndex = sevOrder.indexOf(failOn);
|
|
236
|
-
if (failIndex === -1) return scoreResult.score >= threshold;
|
|
237
|
-
const blockingSevs = sevOrder.slice(0, failIndex + 1);
|
|
238
|
-
return !findings.some(f => blockingSevs.includes(f.severity));
|
|
239
|
-
}
|
|
254
|
+
export function determinePass(scoreResult, findings, threshold, failOn, alwaysFailOn, preBaselineFindings = null) {
|
|
240
255
|
if (alwaysFailOn) {
|
|
241
256
|
const sevOrder = ['critical', 'high', 'medium', 'low'];
|
|
242
257
|
const floorIndex = sevOrder.indexOf(alwaysFailOn);
|
|
@@ -246,6 +261,13 @@ function determinePass(scoreResult, findings, threshold, failOn, alwaysFailOn, p
|
|
|
246
261
|
const floorSet = preBaselineFindings || findings;
|
|
247
262
|
if (floorSet.some(f => floorSevs.includes(f.severity))) return false;
|
|
248
263
|
}
|
|
264
|
+
if (failOn) {
|
|
265
|
+
const sevOrder = ['critical', 'high', 'medium', 'low'];
|
|
266
|
+
const failIndex = sevOrder.indexOf(failOn);
|
|
267
|
+
if (failIndex === -1) return scoreResult.score >= threshold;
|
|
268
|
+
const blockingSevs = sevOrder.slice(0, failIndex + 1);
|
|
269
|
+
return !findings.some(f => blockingSevs.includes(f.severity));
|
|
270
|
+
}
|
|
249
271
|
return scoreResult.score >= threshold;
|
|
250
272
|
}
|
|
251
273
|
|
|
@@ -253,19 +275,22 @@ function emitGitHubAnnotations(findings, rootPath) {
|
|
|
253
275
|
if (process.env.GITHUB_ACTIONS !== 'true') return;
|
|
254
276
|
for (const f of findings) {
|
|
255
277
|
if (!f.file || !f.line) continue;
|
|
256
|
-
const rel =
|
|
278
|
+
const rel = displayPath(f.file, rootPath);
|
|
257
279
|
const level = ['critical', 'high'].includes(f.severity) ? 'error' : 'warning';
|
|
258
280
|
const col = f.column || 1;
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
|
|
281
|
+
const escapeData = value => String(value).replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
|
|
282
|
+
const escapeProperty = value => escapeData(value).replace(/:/g, '%3A').replace(/,/g, '%2C');
|
|
283
|
+
const title = escapeProperty(f.title || f.rule || 'Security Finding');
|
|
284
|
+
// Matched text may be a live credential. Annotations only need the description.
|
|
285
|
+
const msg = escapeData(String(f.description || 'Security finding detected').slice(0, 300));
|
|
286
|
+
console.error(`::${level} file=${escapeProperty(rel)},line=${f.line},col=${col},title=${title}::${msg}`);
|
|
262
287
|
}
|
|
263
288
|
}
|
|
264
289
|
|
|
265
290
|
/**
|
|
266
291
|
* SARIF for GitHub Code Scanning.
|
|
267
292
|
*
|
|
268
|
-
* Delegates to the shared serializer in `cli/core/output/sarif.js
|
|
293
|
+
* Delegates to the shared serializer in `cli/core/output/sarif.js`. This
|
|
269
294
|
* used to be a private copy, which is why the GitHub Action's SARIF carried no
|
|
270
295
|
* `security-severity` at all — the fix landed in the registry and never reached here.
|
|
271
296
|
*
|
|
@@ -301,7 +326,7 @@ function postPRComment(scoreResult, findings, depVulns, rootPath, duration) {
|
|
|
301
326
|
const parsed = JSON.parse(prJson);
|
|
302
327
|
prNumber = String(parsed.number);
|
|
303
328
|
} catch {
|
|
304
|
-
console.
|
|
329
|
+
console.error('[praxis] No PR detected — skipping PR comment');
|
|
305
330
|
return;
|
|
306
331
|
}
|
|
307
332
|
}
|
|
@@ -326,7 +351,7 @@ function postPRComment(scoreResult, findings, depVulns, rootPath, duration) {
|
|
|
326
351
|
body += `### Critical & High Findings\n\n`;
|
|
327
352
|
body += `| Severity | File | Issue |\n|----------|------|-------|\n`;
|
|
328
353
|
for (const f of findings.filter(f => f.severity === 'critical' || f.severity === 'high').slice(0, 20)) {
|
|
329
|
-
const rel =
|
|
354
|
+
const rel = displayPath(f.file, rootPath);
|
|
330
355
|
body += `| ${f.severity.toUpperCase()} | \`${rel}:${f.line}\` | ${(f.title || f.rule).slice(0, 60)} |\n`;
|
|
331
356
|
}
|
|
332
357
|
body += '\n';
|
|
@@ -344,7 +369,7 @@ function postPRComment(scoreResult, findings, depVulns, rootPath, duration) {
|
|
|
344
369
|
stdio: ['pipe', 'pipe', 'pipe'], // praxis-ignore
|
|
345
370
|
});
|
|
346
371
|
|
|
347
|
-
console.
|
|
372
|
+
console.error(`[praxis] PR comment posted on #${prNumber}`);
|
|
348
373
|
}
|
|
349
374
|
|
|
350
375
|
function checkIntelFreshness(maxAge) {
|
package/cli/commands/deps.js
CHANGED
|
@@ -229,7 +229,10 @@ function parseNpmAudit(jsonStr) {
|
|
|
229
229
|
try {
|
|
230
230
|
data = JSON.parse(jsonStr);
|
|
231
231
|
} catch {
|
|
232
|
-
|
|
232
|
+
throw new Error('Dependency audit did not produce valid JSON');
|
|
233
|
+
}
|
|
234
|
+
if (!data || data.error || (!data.vulnerabilities && !data.advisories)) {
|
|
235
|
+
throw new Error('Dependency audit did not produce an audit report');
|
|
233
236
|
}
|
|
234
237
|
|
|
235
238
|
const vulns = [];
|
|
@@ -331,11 +334,14 @@ function parsePipAudit(jsonStr) {
|
|
|
331
334
|
try {
|
|
332
335
|
data = JSON.parse(jsonStr);
|
|
333
336
|
} catch {
|
|
334
|
-
|
|
337
|
+
throw new Error('Dependency audit did not produce valid JSON');
|
|
335
338
|
}
|
|
336
339
|
|
|
340
|
+
const dependencies = Array.isArray(data) ? data : data?.dependencies;
|
|
341
|
+
if (!Array.isArray(dependencies)) throw new Error('Dependency audit did not produce an audit report');
|
|
342
|
+
|
|
337
343
|
const vulns = [];
|
|
338
|
-
for (const pkg of
|
|
344
|
+
for (const pkg of dependencies) {
|
|
339
345
|
for (const vuln of (pkg.vulns || [])) {
|
|
340
346
|
const fixVersion = vuln.fix_versions?.[0];
|
|
341
347
|
vulns.push({
|
|
@@ -500,7 +506,7 @@ function printDepFindings(vulns, pm) {
|
|
|
500
506
|
|
|
501
507
|
/**
|
|
502
508
|
* Run the dependency audit for a given path and return normalized vulnerabilities.
|
|
503
|
-
* Returns { pm, vulns }
|
|
509
|
+
* Returns { pm, vulns, error? }. An unavailable audit is never a clean audit.
|
|
504
510
|
* Does not print anything — used programmatically by other commands.
|
|
505
511
|
*/
|
|
506
512
|
export async function runDepsAudit(rootPath) {
|
|
@@ -511,6 +517,6 @@ export async function runDepsAudit(rootPath) {
|
|
|
511
517
|
const vulns = runAudit(pm, rootPath);
|
|
512
518
|
return { pm, vulns };
|
|
513
519
|
} catch {
|
|
514
|
-
return { pm, vulns: [] };
|
|
520
|
+
return { pm, vulns: [], error: `${pm.name} dependency audit could not be completed` };
|
|
515
521
|
}
|
|
516
522
|
}
|
package/cli/commands/diff.js
CHANGED
|
@@ -16,6 +16,7 @@ import { execFileSync } from 'child_process';
|
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import chalk from 'chalk';
|
|
18
18
|
import ora from 'ora';
|
|
19
|
+
import { displayPath } from '../core/paths.js';
|
|
19
20
|
import { SECRET_PATTERNS, SECURITY_PATTERNS, SKIP_EXTENSIONS, SKIP_FILENAMES } from '../utils/patterns.js';
|
|
20
21
|
import { buildOrchestrator } from '../agents/index.js';
|
|
21
22
|
import { ScoringEngine } from '../agents/scoring-engine.js';
|
|
@@ -165,7 +166,7 @@ export async function diffCommand(ref, options) {
|
|
|
165
166
|
const sevColor = f.severity === 'critical' ? chalk.red :
|
|
166
167
|
f.severity === 'high' ? chalk.yellow :
|
|
167
168
|
f.severity === 'medium' ? chalk.cyan : chalk.gray;
|
|
168
|
-
const relPath =
|
|
169
|
+
const relPath = displayPath(f.file, absolutePath);
|
|
169
170
|
console.log(` ${sevColor(`[${f.severity.toUpperCase()}]`)} ${chalk.white(f.title)}`);
|
|
170
171
|
console.log(chalk.gray(` ${relPath}:${f.line} → ${f.fix || f.description}`));
|
|
171
172
|
shown++;
|
package/cli/commands/fix.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
MAX_FILE_SIZE
|
|
24
24
|
} from '../utils/patterns.js';
|
|
25
25
|
import { isHighEntropyMatch } from '../utils/entropy.js';
|
|
26
|
-
import fg from '
|
|
26
|
+
import fg from '../core/glob.js';
|
|
27
27
|
import * as output from '../utils/output.js';
|
|
28
28
|
|
|
29
29
|
// =============================================================================
|
package/cli/commands/legal.js
CHANGED
|
@@ -19,6 +19,7 @@ import fs from 'fs';
|
|
|
19
19
|
import path from 'path';
|
|
20
20
|
import chalk from 'chalk';
|
|
21
21
|
import ora from 'ora';
|
|
22
|
+
import { displayPath } from '../core/paths.js';
|
|
22
23
|
import { LegalRiskAgent } from '../agents/legal-risk-agent.js';
|
|
23
24
|
import * as output from '../utils/output.js';
|
|
24
25
|
|
|
@@ -136,7 +137,7 @@ export async function legalCommand(targetPath = '.', options = {}) {
|
|
|
136
137
|
const riskLabel = RISK_LABELS[riskKey] || riskKey;
|
|
137
138
|
|
|
138
139
|
console.log(` ${sevBadge} ${chalk.white.bold(f.title)}`);
|
|
139
|
-
console.log(` ${riskColor(`[${riskLabel}]`)} ${chalk.gray(
|
|
140
|
+
console.log(` ${riskColor(`[${riskLabel}]`)} ${chalk.gray(displayPath(f.file, absolutePath))}`);
|
|
140
141
|
console.log();
|
|
141
142
|
console.log(` ${chalk.gray(f.description)}`);
|
|
142
143
|
console.log();
|
package/cli/commands/mcp.js
CHANGED
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
|
|
35
35
|
import fs from 'fs';
|
|
36
36
|
import path from 'path';
|
|
37
|
-
import
|
|
37
|
+
import { displayPath } from '../core/paths.js';
|
|
38
|
+
import fg from '../core/glob.js';
|
|
38
39
|
import { SECRET_PATTERNS, SKIP_DIRS, SKIP_EXTENSIONS, SKIP_FILENAMES, TEST_FILE_PATTERNS, MAX_FILE_SIZE } from '../utils/patterns.js';
|
|
39
40
|
import { isHighEntropyMatch } from '../utils/entropy.js';
|
|
40
41
|
import { buildOrchestrator } from '../agents/index.js';
|
|
@@ -313,7 +314,7 @@ async function scanRepo({ path: targetPath, agents: agentFilter, llm = false, ou
|
|
|
313
314
|
severity: f.severity,
|
|
314
315
|
category: f.category,
|
|
315
316
|
rule: f.rule,
|
|
316
|
-
file: f.file ?
|
|
317
|
+
file: f.file ? displayPath(f.file, rootPath) : null,
|
|
317
318
|
line: f.line,
|
|
318
319
|
description: f.description,
|
|
319
320
|
remediation: f.remediation,
|
package/cli/commands/openclaw.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import fs from 'fs';
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import chalk from 'chalk';
|
|
18
|
+
import { displayPath } from '../core/paths.js';
|
|
18
19
|
import * as output from '../utils/output.js';
|
|
19
20
|
import { AgentConfigScanner } from '../agents/agent-config-scanner.js';
|
|
20
21
|
import { MCPSecurityAgent } from '../agents/mcp-security-agent.js';
|
|
@@ -138,11 +139,7 @@ async function runJsonMode(absolutePath, options) {
|
|
|
138
139
|
const findings = [...configFindings, ...mcpFindings];
|
|
139
140
|
const normFindings = findings.map(f => ({
|
|
140
141
|
...f,
|
|
141
|
-
file:
|
|
142
|
-
.replace(/\\/g, '/')
|
|
143
|
-
.replace(/^[a-zA-Z]:\/+/, '')
|
|
144
|
-
.replace(/^.*\/Praxis\/showcase-target\//, 'showcase-target/')
|
|
145
|
-
.replace(/^.*\/Praxis\//, ''),
|
|
142
|
+
file: displayPath(f.file, absolutePath),
|
|
146
143
|
}));
|
|
147
144
|
const result = {
|
|
148
145
|
findings: normFindings,
|
|
@@ -174,7 +171,7 @@ function printFindings(findings, rootPath) {
|
|
|
174
171
|
findings.sort((a, b) => (sevOrder[a.severity] ?? 4) - (sevOrder[b.severity] ?? 4));
|
|
175
172
|
|
|
176
173
|
for (const f of findings) {
|
|
177
|
-
const relFile =
|
|
174
|
+
const relFile = displayPath(f.file, rootPath);
|
|
178
175
|
const sevLabel = f.severity === 'critical' ? chalk.red.bold('CRITICAL')
|
|
179
176
|
: f.severity === 'high' ? chalk.yellow('HIGH')
|
|
180
177
|
: chalk.blue('MEDIUM');
|