praxis-sec 1.0.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.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/ai-defense/cost-protection.md +292 -0
  4. package/ai-defense/llm-security-checklist.md +324 -0
  5. package/ai-defense/prompt-injection-patterns.js +283 -0
  6. package/ai-defense/system-prompt-armor.md +327 -0
  7. package/checklists/launch-day.md +168 -0
  8. package/cli/agents/abom-generator.js +225 -0
  9. package/cli/agents/agent-attestation-agent.js +318 -0
  10. package/cli/agents/agent-config-scanner.js +787 -0
  11. package/cli/agents/agent-telemetry-agent.js +415 -0
  12. package/cli/agents/agentic-security-agent.js +296 -0
  13. package/cli/agents/agentic-supply-chain-agent.js +463 -0
  14. package/cli/agents/ai-infra-inventory-agent.js +449 -0
  15. package/cli/agents/api-fuzzer.js +345 -0
  16. package/cli/agents/auth-bypass-agent.js +348 -0
  17. package/cli/agents/base-agent.js +280 -0
  18. package/cli/agents/cicd-scanner.js +300 -0
  19. package/cli/agents/config-auditor.js +757 -0
  20. package/cli/agents/deep-analyzer.js +776 -0
  21. package/cli/agents/endpoint-agent-abuse-agent.js +404 -0
  22. package/cli/agents/exception-handler-agent.js +187 -0
  23. package/cli/agents/git-history-scanner.js +169 -0
  24. package/cli/agents/governance-audits.js +138 -0
  25. package/cli/agents/hermes-security-agent.js +536 -0
  26. package/cli/agents/html-reporter.js +1125 -0
  27. package/cli/agents/index.js +147 -0
  28. package/cli/agents/injection-tester.js +502 -0
  29. package/cli/agents/legal-risk-agent.js +328 -0
  30. package/cli/agents/llm-redteam.js +199 -0
  31. package/cli/agents/managed-agent-scanner.js +333 -0
  32. package/cli/agents/mcp-security-agent.js +588 -0
  33. package/cli/agents/memory-poisoning-agent.js +305 -0
  34. package/cli/agents/mobile-scanner.js +231 -0
  35. package/cli/agents/model-file-scanner.js +259 -0
  36. package/cli/agents/orchestrator.js +355 -0
  37. package/cli/agents/pii-compliance-agent.js +301 -0
  38. package/cli/agents/policy-engine.js +229 -0
  39. package/cli/agents/prompt-injection-prober.js +224 -0
  40. package/cli/agents/rag-security-agent.js +204 -0
  41. package/cli/agents/recon-agent.js +207 -0
  42. package/cli/agents/sbom-generator.js +265 -0
  43. package/cli/agents/scoring-engine.js +273 -0
  44. package/cli/agents/ssrf-prober.js +130 -0
  45. package/cli/agents/stateful-watcher.js +238 -0
  46. package/cli/agents/supabase-rls-agent.js +154 -0
  47. package/cli/agents/supply-chain-agent.js +857 -0
  48. package/cli/agents/swarm-orchestrator.js +200 -0
  49. package/cli/agents/verifier-agent.js +303 -0
  50. package/cli/agents/vibe-coding-agent.js +250 -0
  51. package/cli/bin/praxis.js +866 -0
  52. package/cli/commands/abom.js +73 -0
  53. package/cli/commands/agent-fix.js +1245 -0
  54. package/cli/commands/audit.js +1180 -0
  55. package/cli/commands/autofix.js +383 -0
  56. package/cli/commands/baseline.js +193 -0
  57. package/cli/commands/benchmark.js +327 -0
  58. package/cli/commands/checklist.js +223 -0
  59. package/cli/commands/ci.js +403 -0
  60. package/cli/commands/deps.js +516 -0
  61. package/cli/commands/diff.js +200 -0
  62. package/cli/commands/doctor.js +195 -0
  63. package/cli/commands/env-audit.js +349 -0
  64. package/cli/commands/fix.js +218 -0
  65. package/cli/commands/guard.js +396 -0
  66. package/cli/commands/hooks.js +278 -0
  67. package/cli/commands/init.js +514 -0
  68. package/cli/commands/legal.js +158 -0
  69. package/cli/commands/live-advisories.js +241 -0
  70. package/cli/commands/mcp.js +660 -0
  71. package/cli/commands/openclaw.js +386 -0
  72. package/cli/commands/red-team.js +350 -0
  73. package/cli/commands/redteam.js +78 -0
  74. package/cli/commands/remediate.js +797 -0
  75. package/cli/commands/rotate.js +768 -0
  76. package/cli/commands/rules.js +196 -0
  77. package/cli/commands/scan-mcp.js +534 -0
  78. package/cli/commands/scan-skill.js +588 -0
  79. package/cli/commands/scan-standard.js +251 -0
  80. package/cli/commands/scan.js +524 -0
  81. package/cli/commands/score.js +449 -0
  82. package/cli/commands/shell.js +514 -0
  83. package/cli/commands/team-report.js +398 -0
  84. package/cli/commands/undo.js +161 -0
  85. package/cli/commands/update-intel.js +126 -0
  86. package/cli/commands/vibe-check.js +276 -0
  87. package/cli/commands/watch.js +757 -0
  88. package/cli/commands/web.js +63 -0
  89. package/cli/core/ast/guardrail-detector.js +141 -0
  90. package/cli/core/ast/index.js +22 -0
  91. package/cli/core/ast/parser.js +676 -0
  92. package/cli/core/ast/scope-tree.js +287 -0
  93. package/cli/core/ast/taint-tracker.js +158 -0
  94. package/cli/core/branding.js +37 -0
  95. package/cli/core/env.js +38 -0
  96. package/cli/core/errors.js +61 -0
  97. package/cli/core/fs.js +62 -0
  98. package/cli/core/output/compliance.js +90 -0
  99. package/cli/core/output/html-theme.js +158 -0
  100. package/cli/core/output/index.js +57 -0
  101. package/cli/core/output/json.js +48 -0
  102. package/cli/core/output/sarif.js +240 -0
  103. package/cli/core/version.js +67 -0
  104. package/cli/core/web/jobs.js +183 -0
  105. package/cli/core/web/projects.js +146 -0
  106. package/cli/core/web/server.js +439 -0
  107. package/cli/data/atlas-knowledge.json +5640 -0
  108. package/cli/data/eaa-catalog.json +39 -0
  109. package/cli/data/known-mcps.json +26 -0
  110. package/cli/data/probes/prompt-injection-corpus.json +271 -0
  111. package/cli/data/threat-intel.json +85 -0
  112. package/cli/data/threatpacks/latest.json +41 -0
  113. package/cli/hooks/patterns.js +313 -0
  114. package/cli/hooks/post-tool-use.js +140 -0
  115. package/cli/hooks/pre-tool-use.js +186 -0
  116. package/cli/index.js +90 -0
  117. package/cli/providers/llm-provider.js +766 -0
  118. package/cli/utils/autofix-rules.js +74 -0
  119. package/cli/utils/cache-manager.js +310 -0
  120. package/cli/utils/compliance-map.js +191 -0
  121. package/cli/utils/entropy.js +132 -0
  122. package/cli/utils/fix-ledger.js +127 -0
  123. package/cli/utils/hermes-tool-registry.js +252 -0
  124. package/cli/utils/intel/cache.js +61 -0
  125. package/cli/utils/intel/http.js +88 -0
  126. package/cli/utils/intel/index.js +235 -0
  127. package/cli/utils/intel/merge.js +229 -0
  128. package/cli/utils/intel/sources/epss.js +54 -0
  129. package/cli/utils/intel/sources/ghsa.js +81 -0
  130. package/cli/utils/intel/sources/gitguardian.js +40 -0
  131. package/cli/utils/intel/sources/gitleaks.js +101 -0
  132. package/cli/utils/intel/sources/kev.js +38 -0
  133. package/cli/utils/intel/sources/nvd.js +84 -0
  134. package/cli/utils/intel/sources/osv.js +132 -0
  135. package/cli/utils/intel/sources/phylum.js +44 -0
  136. package/cli/utils/intel/sources/snyk.js +46 -0
  137. package/cli/utils/intel/sources/socket.js +69 -0
  138. package/cli/utils/intel/sources/sonatype.js +84 -0
  139. package/cli/utils/intel/sources/threatpack.js +69 -0
  140. package/cli/utils/mcp-trust.js +60 -0
  141. package/cli/utils/output.js +251 -0
  142. package/cli/utils/patterns.js +1130 -0
  143. package/cli/utils/pdf-generator.js +94 -0
  144. package/cli/utils/plugin-loader.js +364 -0
  145. package/cli/utils/rule-import.js +228 -0
  146. package/cli/utils/rule-registry.js +426 -0
  147. package/cli/utils/scan-fingerprint.js +109 -0
  148. package/cli/utils/scan-playbook.js +312 -0
  149. package/cli/utils/score-history.js +119 -0
  150. package/cli/utils/secrets-verifier.js +247 -0
  151. package/cli/utils/security-memory.js +296 -0
  152. package/cli/utils/standards/atlas-knowledge.js +87 -0
  153. package/cli/utils/standards/index.js +127 -0
  154. package/cli/utils/standards/sources/avid.js +45 -0
  155. package/cli/utils/standards/sources/eu-ai-act.js +89 -0
  156. package/cli/utils/standards/sources/google-saif.js +39 -0
  157. package/cli/utils/standards/sources/iso-42001.js +94 -0
  158. package/cli/utils/standards/sources/mitre-atlas.js +54 -0
  159. package/cli/utils/standards/sources/nist-ai-600-1.js +45 -0
  160. package/cli/utils/standards/sources/owasp-llm.js +45 -0
  161. package/cli/utils/standards/sources/owasp-ml.js +45 -0
  162. package/cli/utils/threat-intel.js +265 -0
  163. package/configs/firebase/firestore-rules.txt +215 -0
  164. package/configs/firebase/security-checklist.md +236 -0
  165. package/configs/firebase/storage-rules.txt +206 -0
  166. package/configs/gitignore-template +258 -0
  167. package/configs/nextjs-security-headers.js +220 -0
  168. package/configs/praxisignore-template +50 -0
  169. package/configs/supabase/secure-client.ts +225 -0
  170. package/configs/supabase/security-checklist.md +278 -0
  171. package/docs/THIRD_PARTY_NOTICES.md +26 -0
  172. package/docs/THREAT_INTEL.md +292 -0
  173. package/docs/USAGE.md +1205 -0
  174. package/docs/design/WEB-UI.md +82 -0
  175. package/package.json +71 -0
  176. package/scripts/check-determinism.mjs +119 -0
  177. package/snippets/README.md +122 -0
  178. package/snippets/api-security/api-security-checklist.md +412 -0
  179. package/snippets/api-security/cors-config.ts +322 -0
  180. package/snippets/api-security/input-validation.ts +430 -0
  181. package/snippets/auth/jwt-checklist.md +322 -0
  182. package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
  183. package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Red Team Command
3
+ * =================
4
+ *
5
+ * Run all security agents against the codebase.
6
+ * This is the main entry point for the multi-agent security audit.
7
+ *
8
+ * USAGE:
9
+ * npx praxis-sec red-team [path] Full multi-agent audit
10
+ * npx praxis-sec red-team . --agents injection,auth Run specific agents
11
+ * npx praxis-sec red-team . --json JSON output
12
+ * npx praxis-sec red-team . --html report.html Generate HTML report
13
+ * npx praxis-sec red-team . --sarif SARIF output
14
+ */
15
+
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import { renderFindingsSARIF } from '../core/output/sarif.js';
19
+ import chalk from 'chalk';
20
+ import ora from 'ora';
21
+ import { buildOrchestratorAsync } from '../agents/index.js';
22
+ import { SwarmOrchestrator } from '../agents/swarm-orchestrator.js';
23
+ import { ReconAgent } from '../agents/recon-agent.js';
24
+ import { ScoringEngine } from '../agents/scoring-engine.js';
25
+ import { PolicyEngine } from '../agents/policy-engine.js';
26
+ import { HTMLReporter } from '../agents/html-reporter.js';
27
+ import { SBOMGenerator } from '../agents/sbom-generator.js';
28
+ import { autoDetectProvider } from '../providers/llm-provider.js';
29
+ import { runDepsAudit } from './deps.js';
30
+ import * as output from '../utils/output.js';
31
+ import { printBanner } from '../utils/output.js';
32
+
33
+ export async function redTeamCommand(targetPath = '.', options = {}) {
34
+ const absolutePath = path.resolve(targetPath);
35
+
36
+ // Any of these formats owns stdout; decorative output must not follow it.
37
+ const machineOutput = !!(options.json || options.sarif || options.html || options.sbom);
38
+
39
+ if (!fs.existsSync(absolutePath)) {
40
+ output.error(`Path does not exist: ${absolutePath}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ console.log();
45
+
46
+ let findings = [];
47
+ let recon = {};
48
+ let agentResults = [];
49
+
50
+ // ── 1a. Swarm mode (parallel execution via best available provider) ────────
51
+ if (options.swarm) {
52
+ printBanner();
53
+ output.header('AI Swarm Mode');
54
+ console.log();
55
+
56
+ const swarm = SwarmOrchestrator.create(absolutePath, {
57
+ provider: options.provider,
58
+ model: options.model,
59
+ verbose: options.verbose,
60
+ budgetCents: options.budget ?? 200,
61
+ });
62
+
63
+ if (!swarm) {
64
+ output.error('Swarm mode requires DEEPSEEK_API_KEY or MOONSHOT_API_KEY. Set one and retry.');
65
+ process.exit(1);
66
+ }
67
+
68
+ const reconSpinner = ora({ text: 'Mapping attack surface...', color: 'cyan' }).start();
69
+ const reconAgent = new ReconAgent();
70
+ const reconResult = await reconAgent.analyze({ rootPath: absolutePath });
71
+ recon = Array.isArray(reconResult) ? {} : reconResult;
72
+ const files = await reconAgent.discoverFiles(absolutePath);
73
+ reconSpinner.succeed(chalk.green('Attack surface mapped'));
74
+
75
+ const providerLabel = swarm.provider?.name || 'AI';
76
+ const swarmSpinner = ora({ text: `Deploying ${chalk.cyan('23 swarm agents')} via ${providerLabel}...`, color: 'cyan' }).start();
77
+ try {
78
+ findings = await swarm.run(absolutePath, recon, files);
79
+ swarmSpinner.succeed(chalk.green(`Swarm complete — ${findings.length} finding(s)`));
80
+ } catch (err) {
81
+ swarmSpinner.fail(chalk.red(`Swarm failed: ${err.message}`));
82
+ process.exit(1);
83
+ }
84
+
85
+ agentResults = [{ agent: 'KimiSwarm', category: 'swarm', findingCount: findings.length, success: true }];
86
+
87
+ } else {
88
+ // ── 1b. Standard local orchestration ───────────────────────────────────
89
+ printBanner();
90
+ output.header('Multi-Agent Security Audit');
91
+ console.log();
92
+
93
+ const orchestrator = await buildOrchestratorAsync(absolutePath, { quiet: true });
94
+
95
+ const agentFilter = options.agents
96
+ ? options.agents.split(',').map(a => a.trim())
97
+ : null;
98
+
99
+ const orchestratorOpts = {
100
+ verbose: options.verbose,
101
+ agents: agentFilter,
102
+ };
103
+ if (options.deep) orchestratorOpts.deep = true;
104
+ if (options.local) orchestratorOpts.local = true;
105
+ if (options.model) orchestratorOpts.model = options.model;
106
+ if (options.provider) orchestratorOpts.provider = options.provider;
107
+ if (options.baseUrl) orchestratorOpts.baseUrl = options.baseUrl;
108
+ if (options.budget) orchestratorOpts.budget = options.budget;
109
+
110
+ const results = await orchestrator.runAll(absolutePath, orchestratorOpts); // praxis-ignore — orchestrator result, not LLM output triggering actions
111
+ ({ recon, findings, agentResults } = results);
112
+ }
113
+
114
+ // ── 2. Dependency audit ─────────────────────────────────────────────────────
115
+ let depVulns = [];
116
+ if (!options.noDeps) {
117
+ const depSpinner = ora({ text: 'Auditing dependencies...', color: 'cyan' }).start();
118
+ try {
119
+ const depResult = await runDepsAudit(absolutePath);
120
+ depVulns = depResult.vulns || [];
121
+ depSpinner.succeed(
122
+ depVulns.length === 0
123
+ ? chalk.green('Dependencies: clean')
124
+ : chalk.yellow(`Dependencies: ${depVulns.length} CVE(s)`)
125
+ );
126
+ } catch {
127
+ depSpinner.succeed(chalk.gray('Dependencies: skipped'));
128
+ }
129
+ }
130
+
131
+ // ── 3. Apply policy ─────────────────────────────────────────────────────────
132
+ const policy = PolicyEngine.load(absolutePath);
133
+ const filteredFindings = policy.applyPolicy(findings);
134
+
135
+ // ── 4. Score ────────────────────────────────────────────────────────────────
136
+ const scoringEngine = new ScoringEngine();
137
+ const scoreResult = scoringEngine.compute(filteredFindings, depVulns);
138
+ scoringEngine.saveToHistory(absolutePath, scoreResult);
139
+
140
+ // ── 5. AI classification (if provider available) ────────────────────────────
141
+ if (options.ai !== false) {
142
+ const provider = autoDetectProvider(absolutePath, {
143
+ provider: options.provider,
144
+ baseUrl: options.baseUrl,
145
+ model: options.model,
146
+ });
147
+ if (provider && filteredFindings.length > 0 && filteredFindings.length <= 50) {
148
+ const aiSpinner = ora({ text: `Classifying ${filteredFindings.length} finding(s) with ${provider.name}...`, color: 'cyan' }).start();
149
+ try {
150
+ const classifications = await provider.classify(filteredFindings);
151
+ // Merge classifications back into findings
152
+ for (const cl of classifications) {
153
+ const finding = filteredFindings.find(f => `${f.file}:${f.line}` === cl.id);
154
+ if (finding) {
155
+ finding.aiClassification = cl.classification;
156
+ finding.aiReason = cl.reason;
157
+ finding.aiFix = cl.fix;
158
+ }
159
+ }
160
+ aiSpinner.succeed(chalk.green(`AI classification complete (${provider.name})`));
161
+ } catch (err) {
162
+ aiSpinner.fail(chalk.yellow(`AI classification failed: ${err.message}`));
163
+ }
164
+ }
165
+ }
166
+
167
+ // ── 6. Output ───────────────────────────────────────────────────────────────
168
+ if (options.json) {
169
+ outputJSON(scoreResult, filteredFindings, recon, agentResults);
170
+ } else if (options.sarif) {
171
+ outputSARIF(filteredFindings, absolutePath);
172
+ } else if (options.html) {
173
+ const reporter = new HTMLReporter();
174
+ const htmlPath = typeof options.html === 'string' ? options.html : 'praxis-report.html';
175
+ reporter.generateToFile(scoreResult, filteredFindings, recon, absolutePath, htmlPath, agentResults);
176
+ output.success(`HTML report saved to ${htmlPath}`);
177
+ } else {
178
+ printResults(scoreResult, filteredFindings, recon, agentResults, depVulns, absolutePath);
179
+ }
180
+
181
+ // ── 7. SBOM (if requested) ──────────────────────────────────────────────────
182
+ if (options.sbom) {
183
+ const sbomGen = new SBOMGenerator();
184
+ const sbomPath = typeof options.sbom === 'string' ? options.sbom : 'sbom.json';
185
+ sbomGen.generateToFile(absolutePath, sbomPath);
186
+ output.success(`SBOM saved to ${sbomPath}`);
187
+ }
188
+
189
+ // ── 8. Policy evaluation ────────────────────────────────────────────────────
190
+ const violations = policy.evaluate(scoreResult, filteredFindings, {
191
+ agentResults,
192
+ depsRun: options.deps !== false,
193
+ secretsRun: true,
194
+ depVulns,
195
+ });
196
+ if (violations.length > 0) {
197
+ console.log();
198
+ console.log(chalk.red.bold(' Policy Violations:'));
199
+ for (const v of violations.slice(0, 10)) {
200
+ console.log(chalk.red(` ✗ ${v.message}`));
201
+ }
202
+ if (violations.length > 10) {
203
+ console.log(chalk.gray(` ... and ${violations.length - 10} more`));
204
+ }
205
+ }
206
+
207
+ // ── 9. Trend ────────────────────────────────────────────────────────────────
208
+ // Suppressed for machine output. `scan redteam . --sarif > results.sarif` must
209
+ // produce a parseable file; printing a trend line after the SARIF document
210
+ // corrupts it, and `audit.js` already guards the same way via `machineOutput`.
211
+ if (!machineOutput) {
212
+ const trend = scoringEngine.getTrend(absolutePath, scoreResult.score);
213
+ if (trend) {
214
+ const arrow = trend.diff > 0 ? chalk.green('↑') : trend.diff < 0 ? chalk.red('↓') : chalk.gray('→');
215
+ console.log();
216
+ console.log(chalk.gray(` Trend: ${trend.previousScore} → ${trend.currentScore} ${arrow} (${trend.diff > 0 ? '+' : ''}${trend.diff})`));
217
+ }
218
+
219
+ console.log();
220
+ }
221
+
222
+ // Exit code
223
+ process.exit(scoreResult.score >= 75 ? 0 : 1);
224
+ }
225
+
226
+ // =============================================================================
227
+ // OUTPUT FORMATTERS
228
+ // =============================================================================
229
+
230
+ function printResults(scoreResult, findings, recon, agentResults, depVulns, rootPath) { // praxis-ignore
231
+ const GRADE_COLOR = { A: chalk.green.bold, B: chalk.cyan.bold, C: chalk.yellow.bold, D: chalk.red, F: chalk.red.bold };
232
+ const SEV_COLOR = { critical: chalk.red.bold, high: chalk.yellow, medium: chalk.blue, low: chalk.gray };
233
+
234
+ // ── Score ───────────────────────────────────────────────────────────────────
235
+ console.log();
236
+ const gradeColor = GRADE_COLOR[scoreResult.grade.letter] || chalk.white;
237
+ const scoreColor = scoreResult.score >= 75 ? chalk.green.bold : scoreResult.score >= 60 ? chalk.yellow.bold : chalk.red.bold;
238
+
239
+ console.log(
240
+ chalk.white.bold(' Security Score: ') +
241
+ scoreColor(`${scoreResult.score}/100 `) +
242
+ gradeColor(scoreResult.grade.letter) +
243
+ chalk.gray(` — ${scoreResult.grade.label}`)
244
+ );
245
+ console.log(chalk.cyan(' ' + '─'.repeat(58)));
246
+ console.log();
247
+
248
+ // ── Category breakdown ──────────────────────────────────────────────────────
249
+ for (const [key, cat] of Object.entries(scoreResult.categories)) {
250
+ const count = Object.values(cat.counts).reduce((a, b) => a + b, 0);
251
+ const icon = count === 0 ? chalk.green('✔') : chalk.red('✘');
252
+ const status = count === 0 ? chalk.green('clean') : chalk.red(`${count} issue(s)`);
253
+ const deduction = cat.deduction > 0 ? chalk.red(`-${cat.deduction} pts`) : chalk.gray('+0');
254
+ console.log(` ${icon} ${chalk.white.bold(cat.label.padEnd(22))} ${status.padEnd(25)} ${deduction}`);
255
+ }
256
+
257
+ // Dependencies row
258
+ const depCount = depVulns.length;
259
+ const depIcon = depCount === 0 ? chalk.green('✔') : chalk.red('✘');
260
+ const depDeduction = scoreResult.categories.deps?.deduction || 0;
261
+ console.log(` ${depIcon} ${chalk.white.bold('Dependencies'.padEnd(22))} ${depCount === 0 ? chalk.green('clean') : chalk.red(`${depCount} CVE(s)`)}`);
262
+
263
+ // ── Top findings ────────────────────────────────────────────────────────────
264
+ if (findings.length > 0) {
265
+ console.log();
266
+ console.log(chalk.yellow.bold(` Top Findings (${Math.min(findings.length, 20)} of ${findings.length})`));
267
+ console.log(chalk.yellow(' ' + '─'.repeat(58)));
268
+
269
+ for (const f of findings.slice(0, 20)) {
270
+ const relFile = path.relative(rootPath, f.file).replace(/\\/g, '/');
271
+ const sevColor = SEV_COLOR[f.severity] || chalk.white;
272
+ const aiTag = f.aiClassification === 'FALSE_POSITIVE'
273
+ ? chalk.gray(' [FP]')
274
+ : f.aiClassification === 'REAL' ? chalk.red(' [REAL]') : '';
275
+
276
+ console.log(` ${sevColor(`[${f.severity.toUpperCase()}]`.padEnd(12))} ${chalk.white(`${relFile}:${f.line}`)}${aiTag}`);
277
+ console.log(` ${chalk.gray(' ')} ${f.title || f.rule}`);
278
+ if (f.aiFix || f.fix) {
279
+ console.log(` ${chalk.gray(' Fix:')} ${chalk.green((f.aiFix || f.fix).slice(0, 80))}`);
280
+ }
281
+ }
282
+ }
283
+
284
+ // ── Attack surface summary ──────────────────────────────────────────────────
285
+ if (recon) {
286
+ console.log();
287
+ console.log(chalk.cyan(' Attack Surface:'));
288
+ if (recon.frameworks?.length) console.log(chalk.gray(` Frameworks: ${recon.frameworks.join(', ')}`));
289
+ if (recon.databases?.length) console.log(chalk.gray(` Databases: ${recon.databases.join(', ')}`));
290
+ if (recon.authPatterns?.length) console.log(chalk.gray(` Auth: ${recon.authPatterns.join(', ')}`));
291
+ if (recon.apiRoutes?.length) console.log(chalk.gray(` API Routes: ${recon.apiRoutes.length} discovered`));
292
+ }
293
+
294
+ // ── Next steps ──────────────────────────────────────────────────────────────
295
+ if (findings.length > 0) {
296
+ console.log();
297
+ console.log(chalk.yellow.bold(' Next steps:'));
298
+ console.log(chalk.gray(' 1. Review and fix findings above'));
299
+ console.log(chalk.gray(' 2. Run again: ') + chalk.cyan('npx praxis-sec red-team .'));
300
+ console.log(chalk.gray(' 3. Generate report: ') + chalk.cyan('npx praxis-sec red-team . --html report.html'));
301
+ console.log(chalk.gray(' 4. Set policy: ') + chalk.cyan('npx praxis-sec policy init'));
302
+ } else {
303
+ console.log();
304
+ output.success('All agents report clean — safe to ship!');
305
+ }
306
+
307
+ console.log(chalk.cyan(' ' + '═'.repeat(58)));
308
+ }
309
+
310
+ function outputJSON(scoreResult, findings, recon, agentResults) {
311
+ console.log(JSON.stringify({
312
+ score: scoreResult.score,
313
+ grade: scoreResult.grade.letter,
314
+ gradeLabel: scoreResult.grade.label,
315
+ totalFindings: findings.length,
316
+ categories: Object.fromEntries(
317
+ Object.entries(scoreResult.categories).map(([k, v]) => [k, {
318
+ label: v.label,
319
+ findingCount: Object.values(v.counts).reduce((a, b) => a + b, 0),
320
+ deduction: v.deduction,
321
+ counts: v.counts,
322
+ }])
323
+ ),
324
+ findings: findings.map(f => ({
325
+ file: f.file,
326
+ line: f.line,
327
+ severity: f.severity,
328
+ category: f.category,
329
+ rule: f.rule,
330
+ title: f.title,
331
+ description: f.description,
332
+ fix: f.fix,
333
+ aiClassification: f.aiClassification,
334
+ aiFix: f.aiFix,
335
+ })),
336
+ recon,
337
+ agents: agentResults,
338
+ }, null, 2));
339
+ }
340
+
341
+ /**
342
+ * SARIF for GitHub Code Scanning.
343
+ *
344
+ * Delegates to the shared serializer in `cli/core/output/sarif.js` (P-IMP-062); this was
345
+ * a private copy hardcoding a driver version of `4.0.0` and carrying no
346
+ * `security-severity`.
347
+ */
348
+ function outputSARIF(findings, rootPath) {
349
+ console.log(renderFindingsSARIF(findings, { rootPath }));
350
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Praxis Red Team CLI Command (Dynamic AI DAST & Endpoint Prober)
3
+ * ===============================================================
4
+ *
5
+ * Actively probes live AI/LLM endpoints for prompt injection, jailbreaks,
6
+ * and tool hijacking.
7
+ *
8
+ * USAGE:
9
+ * praxis redteam https://api.example.com/v1/chat
10
+ * praxis redteam http://localhost:8000/predict --header "Authorization: Bearer token"
11
+ * praxis redteam http://localhost:3000/api/agent --json
12
+ */
13
+
14
+ import chalk from 'chalk';
15
+ import ora from 'ora';
16
+ import { LLMRedTeamEngine } from '../agents/llm-redteam.js';
17
+ import * as output from '../utils/output.js';
18
+ import { printBanner } from '../core/branding.js';
19
+
20
+ export async function redteamCommand(endpoint, options = {}) {
21
+ if (!endpoint) {
22
+ output.error('Target endpoint URL is required: praxis redteam <url>');
23
+ process.exit(1);
24
+ }
25
+
26
+ if (!options.json) {
27
+ printBanner();
28
+ output.header(`Dynamic AI Red Team: ${endpoint}`);
29
+ console.log();
30
+ }
31
+
32
+ const spinner = options.json ? null : ora({ text: 'Executing dynamic behavioral probes...', color: 'cyan' }).start();
33
+
34
+ try {
35
+ const headers = {};
36
+ if (options.header) {
37
+ const parts = Array.isArray(options.header) ? options.header : [options.header];
38
+ for (const h of parts) {
39
+ const [k, ...v] = h.split(':');
40
+ if (k && v.length) headers[k.trim()] = v.join(':').trim();
41
+ }
42
+ }
43
+
44
+ const report = await LLMRedTeamEngine.probeEndpoint(endpoint, {
45
+ headers,
46
+ timeoutMs: options.timeout ? parseInt(options.timeout, 10) : 10000,
47
+ });
48
+
49
+ if (spinner) spinner.succeed(chalk.green('Probing complete'));
50
+
51
+ if (options.json) {
52
+ console.log(JSON.stringify(report, null, 2));
53
+ return report;
54
+ }
55
+
56
+ console.log();
57
+ console.log(chalk.bold(` Endpoint Security Score: ${report.securityScore}/100`));
58
+ console.log(chalk.gray(` Probes executed: ${report.totalProbes} | Passed: ${report.passedCount} | Failed: ${report.failedCount}`));
59
+ console.log();
60
+
61
+ for (const res of report.probeResults) {
62
+ const statusIcon = res.isVulnerable ? chalk.red('✖ FAILED (Vulnerable)') : chalk.green('✔ PASSED (Refused/Protected)');
63
+ console.log(` ${statusIcon} ${chalk.bold(res.name)} ${chalk.gray(`[${res.severity}]`)}`);
64
+ if (res.isVulnerable && res.responseSnippet) {
65
+ console.log(chalk.red(` Response snippet: "${res.responseSnippet}..."`));
66
+ }
67
+ }
68
+ console.log();
69
+
70
+ return report;
71
+ } catch (err) {
72
+ if (spinner) spinner.fail(chalk.red(`Red team probing failed: ${err.message}`));
73
+ else output.error(`Red team probing failed: ${err.message}`);
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ export default redteamCommand;