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.
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/ai-defense/cost-protection.md +292 -0
- package/ai-defense/llm-security-checklist.md +324 -0
- package/ai-defense/prompt-injection-patterns.js +283 -0
- package/ai-defense/system-prompt-armor.md +327 -0
- package/checklists/launch-day.md +168 -0
- package/cli/agents/abom-generator.js +225 -0
- package/cli/agents/agent-attestation-agent.js +318 -0
- package/cli/agents/agent-config-scanner.js +787 -0
- package/cli/agents/agent-telemetry-agent.js +415 -0
- package/cli/agents/agentic-security-agent.js +296 -0
- package/cli/agents/agentic-supply-chain-agent.js +463 -0
- package/cli/agents/ai-infra-inventory-agent.js +449 -0
- package/cli/agents/api-fuzzer.js +345 -0
- package/cli/agents/auth-bypass-agent.js +348 -0
- package/cli/agents/base-agent.js +280 -0
- package/cli/agents/cicd-scanner.js +300 -0
- package/cli/agents/config-auditor.js +757 -0
- package/cli/agents/deep-analyzer.js +776 -0
- package/cli/agents/endpoint-agent-abuse-agent.js +404 -0
- package/cli/agents/exception-handler-agent.js +187 -0
- package/cli/agents/git-history-scanner.js +169 -0
- package/cli/agents/governance-audits.js +138 -0
- package/cli/agents/hermes-security-agent.js +536 -0
- package/cli/agents/html-reporter.js +1125 -0
- package/cli/agents/index.js +147 -0
- package/cli/agents/injection-tester.js +502 -0
- package/cli/agents/legal-risk-agent.js +328 -0
- package/cli/agents/llm-redteam.js +199 -0
- package/cli/agents/managed-agent-scanner.js +333 -0
- package/cli/agents/mcp-security-agent.js +588 -0
- package/cli/agents/memory-poisoning-agent.js +305 -0
- package/cli/agents/mobile-scanner.js +231 -0
- package/cli/agents/model-file-scanner.js +259 -0
- package/cli/agents/orchestrator.js +355 -0
- package/cli/agents/pii-compliance-agent.js +301 -0
- package/cli/agents/policy-engine.js +229 -0
- package/cli/agents/prompt-injection-prober.js +224 -0
- package/cli/agents/rag-security-agent.js +204 -0
- package/cli/agents/recon-agent.js +207 -0
- package/cli/agents/sbom-generator.js +265 -0
- package/cli/agents/scoring-engine.js +273 -0
- package/cli/agents/ssrf-prober.js +130 -0
- package/cli/agents/stateful-watcher.js +238 -0
- package/cli/agents/supabase-rls-agent.js +154 -0
- package/cli/agents/supply-chain-agent.js +857 -0
- package/cli/agents/swarm-orchestrator.js +200 -0
- package/cli/agents/verifier-agent.js +303 -0
- package/cli/agents/vibe-coding-agent.js +250 -0
- package/cli/bin/praxis.js +866 -0
- package/cli/commands/abom.js +73 -0
- package/cli/commands/agent-fix.js +1245 -0
- package/cli/commands/audit.js +1180 -0
- package/cli/commands/autofix.js +383 -0
- package/cli/commands/baseline.js +193 -0
- package/cli/commands/benchmark.js +327 -0
- package/cli/commands/checklist.js +223 -0
- package/cli/commands/ci.js +403 -0
- package/cli/commands/deps.js +516 -0
- package/cli/commands/diff.js +200 -0
- package/cli/commands/doctor.js +195 -0
- package/cli/commands/env-audit.js +349 -0
- package/cli/commands/fix.js +218 -0
- package/cli/commands/guard.js +396 -0
- package/cli/commands/hooks.js +278 -0
- package/cli/commands/init.js +514 -0
- package/cli/commands/legal.js +158 -0
- package/cli/commands/live-advisories.js +241 -0
- package/cli/commands/mcp.js +660 -0
- package/cli/commands/openclaw.js +386 -0
- package/cli/commands/red-team.js +350 -0
- package/cli/commands/redteam.js +78 -0
- package/cli/commands/remediate.js +797 -0
- package/cli/commands/rotate.js +768 -0
- package/cli/commands/rules.js +196 -0
- package/cli/commands/scan-mcp.js +534 -0
- package/cli/commands/scan-skill.js +588 -0
- package/cli/commands/scan-standard.js +251 -0
- package/cli/commands/scan.js +524 -0
- package/cli/commands/score.js +449 -0
- package/cli/commands/shell.js +514 -0
- package/cli/commands/team-report.js +398 -0
- package/cli/commands/undo.js +161 -0
- package/cli/commands/update-intel.js +126 -0
- package/cli/commands/vibe-check.js +276 -0
- package/cli/commands/watch.js +757 -0
- package/cli/commands/web.js +63 -0
- package/cli/core/ast/guardrail-detector.js +141 -0
- package/cli/core/ast/index.js +22 -0
- package/cli/core/ast/parser.js +676 -0
- package/cli/core/ast/scope-tree.js +287 -0
- package/cli/core/ast/taint-tracker.js +158 -0
- package/cli/core/branding.js +37 -0
- package/cli/core/env.js +38 -0
- package/cli/core/errors.js +61 -0
- package/cli/core/fs.js +62 -0
- package/cli/core/output/compliance.js +90 -0
- package/cli/core/output/html-theme.js +158 -0
- package/cli/core/output/index.js +57 -0
- package/cli/core/output/json.js +48 -0
- package/cli/core/output/sarif.js +240 -0
- package/cli/core/version.js +67 -0
- package/cli/core/web/jobs.js +183 -0
- package/cli/core/web/projects.js +146 -0
- package/cli/core/web/server.js +439 -0
- package/cli/data/atlas-knowledge.json +5640 -0
- package/cli/data/eaa-catalog.json +39 -0
- package/cli/data/known-mcps.json +26 -0
- package/cli/data/probes/prompt-injection-corpus.json +271 -0
- package/cli/data/threat-intel.json +85 -0
- package/cli/data/threatpacks/latest.json +41 -0
- package/cli/hooks/patterns.js +313 -0
- package/cli/hooks/post-tool-use.js +140 -0
- package/cli/hooks/pre-tool-use.js +186 -0
- package/cli/index.js +90 -0
- package/cli/providers/llm-provider.js +766 -0
- package/cli/utils/autofix-rules.js +74 -0
- package/cli/utils/cache-manager.js +310 -0
- package/cli/utils/compliance-map.js +191 -0
- package/cli/utils/entropy.js +132 -0
- package/cli/utils/fix-ledger.js +127 -0
- package/cli/utils/hermes-tool-registry.js +252 -0
- package/cli/utils/intel/cache.js +61 -0
- package/cli/utils/intel/http.js +88 -0
- package/cli/utils/intel/index.js +235 -0
- package/cli/utils/intel/merge.js +229 -0
- package/cli/utils/intel/sources/epss.js +54 -0
- package/cli/utils/intel/sources/ghsa.js +81 -0
- package/cli/utils/intel/sources/gitguardian.js +40 -0
- package/cli/utils/intel/sources/gitleaks.js +101 -0
- package/cli/utils/intel/sources/kev.js +38 -0
- package/cli/utils/intel/sources/nvd.js +84 -0
- package/cli/utils/intel/sources/osv.js +132 -0
- package/cli/utils/intel/sources/phylum.js +44 -0
- package/cli/utils/intel/sources/snyk.js +46 -0
- package/cli/utils/intel/sources/socket.js +69 -0
- package/cli/utils/intel/sources/sonatype.js +84 -0
- package/cli/utils/intel/sources/threatpack.js +69 -0
- package/cli/utils/mcp-trust.js +60 -0
- package/cli/utils/output.js +251 -0
- package/cli/utils/patterns.js +1130 -0
- package/cli/utils/pdf-generator.js +94 -0
- package/cli/utils/plugin-loader.js +364 -0
- package/cli/utils/rule-import.js +228 -0
- package/cli/utils/rule-registry.js +426 -0
- package/cli/utils/scan-fingerprint.js +109 -0
- package/cli/utils/scan-playbook.js +312 -0
- package/cli/utils/score-history.js +119 -0
- package/cli/utils/secrets-verifier.js +247 -0
- package/cli/utils/security-memory.js +296 -0
- package/cli/utils/standards/atlas-knowledge.js +87 -0
- package/cli/utils/standards/index.js +127 -0
- package/cli/utils/standards/sources/avid.js +45 -0
- package/cli/utils/standards/sources/eu-ai-act.js +89 -0
- package/cli/utils/standards/sources/google-saif.js +39 -0
- package/cli/utils/standards/sources/iso-42001.js +94 -0
- package/cli/utils/standards/sources/mitre-atlas.js +54 -0
- package/cli/utils/standards/sources/nist-ai-600-1.js +45 -0
- package/cli/utils/standards/sources/owasp-llm.js +45 -0
- package/cli/utils/standards/sources/owasp-ml.js +45 -0
- package/cli/utils/threat-intel.js +265 -0
- package/configs/firebase/firestore-rules.txt +215 -0
- package/configs/firebase/security-checklist.md +236 -0
- package/configs/firebase/storage-rules.txt +206 -0
- package/configs/gitignore-template +258 -0
- package/configs/nextjs-security-headers.js +220 -0
- package/configs/praxisignore-template +50 -0
- package/configs/supabase/secure-client.ts +225 -0
- package/configs/supabase/security-checklist.md +278 -0
- package/docs/THIRD_PARTY_NOTICES.md +26 -0
- package/docs/THREAT_INTEL.md +292 -0
- package/docs/USAGE.md +1205 -0
- package/docs/design/WEB-UI.md +82 -0
- package/package.json +71 -0
- package/scripts/check-determinism.mjs +119 -0
- package/snippets/README.md +122 -0
- package/snippets/api-security/api-security-checklist.md +412 -0
- package/snippets/api-security/cors-config.ts +322 -0
- package/snippets/api-security/input-validation.ts +430 -0
- package/snippets/auth/jwt-checklist.md +322 -0
- package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
- package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Praxis CLI
|
|
5
|
+
* ==========
|
|
6
|
+
*
|
|
7
|
+
* Canonical entry point. The 38 legacy `praxis` commands are reorganized
|
|
8
|
+
* into six verb-led groups so a new user reading `--help` builds an accurate
|
|
9
|
+
* mental model in 30 seconds:
|
|
10
|
+
*
|
|
11
|
+
* praxis scan run security scans
|
|
12
|
+
* praxis fix apply remediations
|
|
13
|
+
* praxis agents audit AI/agent surface (skills, MCP, attestation, BOM)
|
|
14
|
+
* praxis intel threat-feed + advisory operations
|
|
15
|
+
* praxis report format/diff/share existing scan results
|
|
16
|
+
* praxis project init, hooks, watch, doctor, baseline, plugins, etc.
|
|
17
|
+
*
|
|
18
|
+
* Plus three top-level shortcuts that preserve the indie/vibe-coder voice:
|
|
19
|
+
* praxis vibe emoji-graded score (A=immaculate, F=cooked)
|
|
20
|
+
* praxis score numeric 0-100 score
|
|
21
|
+
* praxis (no args) drops into the interactive REPL
|
|
22
|
+
*
|
|
23
|
+
* The legacy `praxis` binary continues to expose all 38 original commands
|
|
24
|
+
* unchanged for back-compat — that surface lives in `cli/bin/praxis.js`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { program } from 'commander';
|
|
28
|
+
import chalk from 'chalk';
|
|
29
|
+
import { fileURLToPath } from 'url';
|
|
30
|
+
import { dirname, resolve } from 'path';
|
|
31
|
+
|
|
32
|
+
import { printBanner, TAGLINE, SUBTITLE } from '../core/branding.js';
|
|
33
|
+
import { toolVersion } from '../core/version.js';
|
|
34
|
+
import { loadDotEnv } from '../core/env.js';
|
|
35
|
+
|
|
36
|
+
// Load .env from the working directory early so API keys and LLM settings
|
|
37
|
+
// (OPENAI_API_KEY, OPENAI_BASE_URL, PRAXIS_LLM_MODEL, ...) are available to
|
|
38
|
+
// every command. Real environment variables always take precedence.
|
|
39
|
+
loadDotEnv(process.cwd());
|
|
40
|
+
|
|
41
|
+
// Existing command implementations (unchanged — praxis is a new surface,
|
|
42
|
+
// not a reimplementation).
|
|
43
|
+
import { scanCommand } from '../commands/scan.js';
|
|
44
|
+
import { checklistCommand } from '../commands/checklist.js';
|
|
45
|
+
import { initCommand } from '../commands/init.js';
|
|
46
|
+
import { fixCommand as legacyFixCommand } from '../commands/fix.js';
|
|
47
|
+
import { guardCommand } from '../commands/guard.js';
|
|
48
|
+
import { mcpCommand } from '../commands/mcp.js';
|
|
49
|
+
import { remediateCommand } from '../commands/remediate.js';
|
|
50
|
+
import { rotateCommand } from '../commands/rotate.js';
|
|
51
|
+
import { agentFixCommand } from '../commands/agent-fix.js';
|
|
52
|
+
import { undoCommand } from '../commands/undo.js';
|
|
53
|
+
import { shellCommand } from '../commands/shell.js';
|
|
54
|
+
import { depsCommand } from '../commands/deps.js';
|
|
55
|
+
import { scoreCommand } from '../commands/score.js';
|
|
56
|
+
import { redTeamCommand } from '../commands/red-team.js';
|
|
57
|
+
import { watchCommand } from '../commands/watch.js';
|
|
58
|
+
import { auditCommand } from '../commands/audit.js';
|
|
59
|
+
import { doctorCommand } from '../commands/doctor.js';
|
|
60
|
+
import { baselineCommand } from '../commands/baseline.js';
|
|
61
|
+
import { ciCommand } from '../commands/ci.js';
|
|
62
|
+
import { diffCommand } from '../commands/diff.js';
|
|
63
|
+
import { vibeCheckCommand } from '../commands/vibe-check.js';
|
|
64
|
+
import { webCommand } from '../commands/web.js';
|
|
65
|
+
import { rulesListCommand, rulesExportCommand, rulesImportCommand } from '../commands/rules.js';
|
|
66
|
+
import { benchmarkCommand } from '../commands/benchmark.js';
|
|
67
|
+
import { openclawCommand } from '../commands/openclaw.js';
|
|
68
|
+
import { scanSkillCommand } from '../commands/scan-skill.js';
|
|
69
|
+
import { scanMcpCommand } from '../commands/scan-mcp.js';
|
|
70
|
+
import { scanStandardCommand } from '../commands/scan-standard.js';
|
|
71
|
+
import { abomCommand } from '../commands/abom.js';
|
|
72
|
+
import { updateIntelCommand } from '../commands/update-intel.js';
|
|
73
|
+
import { hooksCommand } from '../commands/hooks.js';
|
|
74
|
+
import { legalCommand } from '../commands/legal.js';
|
|
75
|
+
import { runLiveAdvisories } from '../commands/live-advisories.js';
|
|
76
|
+
import { envAuditCommand } from '../commands/env-audit.js';
|
|
77
|
+
import { autofixCommand } from '../commands/autofix.js';
|
|
78
|
+
import { teamReportCommand } from '../commands/team-report.js';
|
|
79
|
+
import { redteamCommand } from '../commands/redteam.js';
|
|
80
|
+
import { memoryCommand } from '../utils/security-memory.js';
|
|
81
|
+
import { playbookCommand } from '../utils/scan-playbook.js';
|
|
82
|
+
import { listPluginFiles, scaffoldPlugin } from '../utils/plugin-loader.js';
|
|
83
|
+
import { SBOMGenerator } from '../agents/sbom-generator.js';
|
|
84
|
+
import { PolicyEngine } from '../agents/policy-engine.js';
|
|
85
|
+
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// PROGRAM SETUP
|
|
88
|
+
// =============================================================================
|
|
89
|
+
|
|
90
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
91
|
+
const __dirname = dirname(__filename);
|
|
92
|
+
const VERSION = toolVersion();
|
|
93
|
+
|
|
94
|
+
program
|
|
95
|
+
.name('praxis')
|
|
96
|
+
.description(`${TAGLINE} ${SUBTITLE}`)
|
|
97
|
+
.version(VERSION)
|
|
98
|
+
.addHelpText('before', renderHelpBanner());
|
|
99
|
+
|
|
100
|
+
function renderHelpBanner() {
|
|
101
|
+
return [
|
|
102
|
+
'',
|
|
103
|
+
chalk.cyan('██████╗ ██████╗ █████╗ ██╗ ██╗██╗███████╗'),
|
|
104
|
+
chalk.cyan('██╔══██╗██╔══██╗██╔══██╗╚██╗██╔╝██║██╔════╝'),
|
|
105
|
+
chalk.cyan('██████╔╝██████╔╝███████║ ╚███╔╝ ██║███████╗'),
|
|
106
|
+
chalk.cyan('██╔═══╝ ██╔══██╗██╔══██║ ██╔██╗ ██║╚════██║'),
|
|
107
|
+
chalk.cyan('██║ ██║ ██║██║ ██║██╔╝ ██╗██║███████║'),
|
|
108
|
+
chalk.cyan('╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝'),
|
|
109
|
+
'',
|
|
110
|
+
chalk.white.bold(` ${TAGLINE}`),
|
|
111
|
+
chalk.gray(` ${SUBTITLE}`),
|
|
112
|
+
'',
|
|
113
|
+
].join('\n');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// =============================================================================
|
|
117
|
+
// scan — run security scans
|
|
118
|
+
// =============================================================================
|
|
119
|
+
|
|
120
|
+
const scan = program
|
|
121
|
+
.command('scan')
|
|
122
|
+
.description('Run security scans (default = full audit)');
|
|
123
|
+
|
|
124
|
+
scan
|
|
125
|
+
.command('full [path]', { isDefault: true })
|
|
126
|
+
.description('Full audit: secrets + 28 agents + deps + score + remediation plan')
|
|
127
|
+
.option('--json', 'Output results as JSON')
|
|
128
|
+
.option('--sarif', 'Output results in SARIF format')
|
|
129
|
+
.option('--csv', 'Output results as CSV')
|
|
130
|
+
.option('--md', 'Output results as Markdown')
|
|
131
|
+
.option('--html [file]', 'HTML report path (default: praxis-report.html)')
|
|
132
|
+
.option('--html-dir [dir]', 'Generate granular multi-page report suite in directory')
|
|
133
|
+
.option('--compare', 'Show detailed comparison with last scan')
|
|
134
|
+
.option('--timeout <ms>', 'Per-agent timeout in milliseconds (default: 30000)', parseInt)
|
|
135
|
+
.option('--no-deps', 'Skip dependency audit')
|
|
136
|
+
.option('--no-ai', 'Skip AI classification')
|
|
137
|
+
.option('--no-cache', 'Force full rescan')
|
|
138
|
+
.option('--baseline', 'Only show findings not in the baseline')
|
|
139
|
+
.option('--pdf [file]', 'Generate PDF report (requires Chrome/Chromium)')
|
|
140
|
+
.option('--deep', 'LLM-powered taint analysis for critical/high findings')
|
|
141
|
+
.option('--think', 'Enable extended thinking mode')
|
|
142
|
+
.option('--local', 'Use local Ollama model for deep analysis')
|
|
143
|
+
.option('--model <model>', 'LLM model for deep/AI analysis')
|
|
144
|
+
.option('--provider <name>', 'LLM provider')
|
|
145
|
+
.option('--base-url <url>', 'Custom OpenAI-compatible endpoint')
|
|
146
|
+
.option('--budget <cents>', 'Max spend in cents for deep analysis (default: 50)', parseInt)
|
|
147
|
+
.option('--verify', 'Check if leaked secrets are still active')
|
|
148
|
+
.option('--include-legal', 'Also run the legal risk scan')
|
|
149
|
+
.option('--agentic [iterations]', 'Agentic scan→fix→verify loop', (v) => v ? parseInt(v) : true)
|
|
150
|
+
.option('--agentic-target <score>', 'Target security score for agentic loop', parseInt)
|
|
151
|
+
.option('--hermes-only', 'Run only Hermes-relevant agents')
|
|
152
|
+
.option('--fail-below <threshold>', 'Exit 1 if score is below threshold')
|
|
153
|
+
.option('-v, --verbose', 'Verbose output')
|
|
154
|
+
.action(auditCommand);
|
|
155
|
+
|
|
156
|
+
scan
|
|
157
|
+
.command('secrets [path]')
|
|
158
|
+
.description('Fast pattern-based secret scan only (no agents)')
|
|
159
|
+
.option('-v, --verbose', 'Show all files being scanned')
|
|
160
|
+
.option('--no-color', 'Disable colored output')
|
|
161
|
+
.option('--json', 'Output results as JSON')
|
|
162
|
+
.option('--sarif', 'Output results in SARIF format')
|
|
163
|
+
.option('--include-tests', 'Also scan test files')
|
|
164
|
+
.option('--no-cache', 'Force full rescan')
|
|
165
|
+
.action(scanCommand);
|
|
166
|
+
|
|
167
|
+
scan
|
|
168
|
+
.command('changed [ref]')
|
|
169
|
+
.description('Scan only files changed since <ref> (default: HEAD)')
|
|
170
|
+
.option('--staged', 'Scan only staged changes')
|
|
171
|
+
.option('--json', 'Output results as JSON')
|
|
172
|
+
.option('-p, --path <path>', 'Project path (default: cwd)')
|
|
173
|
+
.option('--timeout <ms>', 'Per-agent timeout in milliseconds', parseInt)
|
|
174
|
+
.action(diffCommand);
|
|
175
|
+
|
|
176
|
+
scan
|
|
177
|
+
.command('env [path]')
|
|
178
|
+
.description('Credential health check: .env coverage, source cross-ref, git history')
|
|
179
|
+
.option('--json', 'Output results as JSON')
|
|
180
|
+
.action(envAuditCommand);
|
|
181
|
+
|
|
182
|
+
scan
|
|
183
|
+
.command('redteam [path]')
|
|
184
|
+
.description('28 agents in parallel — adversarial scan of 80+ attack classes')
|
|
185
|
+
.option('--agents <list>', 'Comma-separated list of agents to run')
|
|
186
|
+
.option('--json', 'Output results as JSON')
|
|
187
|
+
.option('--sarif', 'Output results in SARIF format')
|
|
188
|
+
.option('--html [file]', 'Generate HTML security report')
|
|
189
|
+
.option('--sbom [file]', 'Generate CycloneDX SBOM')
|
|
190
|
+
.option('--no-deps', 'Skip dependency audit')
|
|
191
|
+
.option('--no-ai', 'Skip AI classification')
|
|
192
|
+
.option('--deep', 'LLM-powered taint analysis')
|
|
193
|
+
.option('--swarm', 'AI swarm mode — 23 parallel agents via DeepSeek/Kimi')
|
|
194
|
+
.option('--think', 'Enable extended thinking mode')
|
|
195
|
+
.option('--local', 'Use local Ollama model')
|
|
196
|
+
.option('--model <model>', 'LLM model')
|
|
197
|
+
.option('--provider <name>', 'LLM provider')
|
|
198
|
+
.option('--base-url <url>', 'Custom OpenAI-compatible endpoint')
|
|
199
|
+
.option('--budget <cents>', 'Max spend in cents', parseInt)
|
|
200
|
+
.option('-v, --verbose', 'Verbose output')
|
|
201
|
+
.action(redTeamCommand);
|
|
202
|
+
|
|
203
|
+
scan
|
|
204
|
+
.command('standard [name] [path]')
|
|
205
|
+
.description('Filter findings by AI-security standard (owasp-llm, mitre-atlas, ...)')
|
|
206
|
+
.option('--list', 'List available standards and exit')
|
|
207
|
+
.option('--control <id>', 'Filter to a single control within the standard (e.g., LLM01)')
|
|
208
|
+
.option('--json', 'Output results as JSON')
|
|
209
|
+
.option('--sarif', 'Output results in SARIF format')
|
|
210
|
+
.option('--format <name>', 'Output format from the registry (json, sarif)')
|
|
211
|
+
.action((name, targetPath, options) => scanStandardCommand(name, targetPath, options));
|
|
212
|
+
|
|
213
|
+
scan
|
|
214
|
+
.command('ci [path]')
|
|
215
|
+
.description('CI/CD pipeline mode: scan, score, exit 1 on failure')
|
|
216
|
+
.option('--threshold <score>', 'Minimum passing score (default: 75)', parseInt)
|
|
217
|
+
.option('--fail-on <severity>', 'Fail on findings at this severity or above')
|
|
218
|
+
.option('--always-fail-on <severity>', 'Severity floor that even an accepted baseline cannot suppress')
|
|
219
|
+
.option('--include-findings', 'Include the finding list (file/rule/severity) in JSON output for diffing')
|
|
220
|
+
.option('--sarif <file>', 'Write SARIF output for GitHub Code Scanning')
|
|
221
|
+
.option('--json', 'JSON output')
|
|
222
|
+
.option('--no-deps', 'Skip dependency audit')
|
|
223
|
+
.option('--baseline', 'Only check new findings (not in baseline)')
|
|
224
|
+
.option('--github-pr', 'Post findings as a GitHub PR comment')
|
|
225
|
+
.option('--strict-intel', 'Fail if threat-intel feed is stale')
|
|
226
|
+
.option('--max-intel-age <duration>', 'Max acceptable intel age', '7d')
|
|
227
|
+
.action(ciCommand);
|
|
228
|
+
|
|
229
|
+
// =============================================================================
|
|
230
|
+
// fix — apply remediations
|
|
231
|
+
// =============================================================================
|
|
232
|
+
|
|
233
|
+
const fix = program
|
|
234
|
+
.command('fix')
|
|
235
|
+
.description('Apply remediations (LLM-guided, deterministic, or PR-based)');
|
|
236
|
+
|
|
237
|
+
fix
|
|
238
|
+
.command('interactive [path]', { isDefault: true })
|
|
239
|
+
.description('Interactive LLM-guided fixes: scan → plan → diff → ask → apply → verify')
|
|
240
|
+
.option('--plan-only', 'Generate plans for review but never write changes')
|
|
241
|
+
.option('--severity <level>', 'Minimum severity to fix', 'low')
|
|
242
|
+
.option('--provider <name>', 'LLM provider')
|
|
243
|
+
.option('--model <model>', 'Specific model name')
|
|
244
|
+
.option('--think', 'Enable extended thinking')
|
|
245
|
+
.option('--allow-dirty', 'Allow running with uncommitted changes')
|
|
246
|
+
.option('--branch [name]', 'Create a branch and commit one fix per file')
|
|
247
|
+
.option('--pr', 'Push branch and open a PR via gh CLI (requires --branch)')
|
|
248
|
+
.option('--yolo', 'Auto-accept every plan without prompting')
|
|
249
|
+
.option('--auto-low', 'Auto-accept plans marked risk:low')
|
|
250
|
+
.option('--sandbox', 'Verify each fix in a Docker sandbox')
|
|
251
|
+
.option('--max-attempts <n>', 'Max plan attempts per file (verification ladder retries)', parseInt)
|
|
252
|
+
.option('--ci', 'Non-interactive CI/CD mode (auto-accept fixes)')
|
|
253
|
+
.action(agentFixCommand);
|
|
254
|
+
|
|
255
|
+
fix
|
|
256
|
+
.command('quick [path]')
|
|
257
|
+
.description('Deterministic secret fixer: rewrite source + write .env (no LLM)')
|
|
258
|
+
.option('--dry-run', 'Preview changes without writing')
|
|
259
|
+
.option('--yes', 'Apply all fixes without prompting')
|
|
260
|
+
.option('--stage', 'Run git add on modified files')
|
|
261
|
+
.option('--all', 'Also fix common agent findings (debug, TLS bypass, shell injection)')
|
|
262
|
+
.action(remediateCommand);
|
|
263
|
+
|
|
264
|
+
fix
|
|
265
|
+
.command('from-report [path]')
|
|
266
|
+
.description('Apply LLM fixes from a deep-analysis JSON report and open a PR')
|
|
267
|
+
.option('--report <file>', 'Path to praxis JSON report')
|
|
268
|
+
.option('--severity <level>', 'Minimum severity to fix')
|
|
269
|
+
.option('--dry-run', 'Preview without applying')
|
|
270
|
+
.option('--yes', 'Skip confirmation')
|
|
271
|
+
.action((targetPath, options) => autofixCommand({ ...options, path: targetPath }));
|
|
272
|
+
|
|
273
|
+
fix
|
|
274
|
+
.command('rotate [path]')
|
|
275
|
+
.description('Open provider dashboards to revoke exposed secrets')
|
|
276
|
+
.option('--provider <name>', 'Only rotate secrets for a specific provider')
|
|
277
|
+
.option('--plan <file>', 'Execute a rotation plan')
|
|
278
|
+
.action(rotateCommand);
|
|
279
|
+
|
|
280
|
+
fix
|
|
281
|
+
.command('undo [path]')
|
|
282
|
+
.description('Revert the last fix applied by `praxis fix interactive` (or all with --all)')
|
|
283
|
+
.option('--all', 'Revert every fix in the log')
|
|
284
|
+
.option('--dry-run', 'Show what would be reverted')
|
|
285
|
+
.action(undoCommand);
|
|
286
|
+
|
|
287
|
+
fix
|
|
288
|
+
.command('env-template')
|
|
289
|
+
.description('Generate a .env.example with placeholder values from found secrets')
|
|
290
|
+
.option('--dry-run', 'Preview without writing')
|
|
291
|
+
.action(legacyFixCommand);
|
|
292
|
+
|
|
293
|
+
// =============================================================================
|
|
294
|
+
// agents — AI-agent surface security
|
|
295
|
+
// =============================================================================
|
|
296
|
+
|
|
297
|
+
const agents = program
|
|
298
|
+
.command('agents')
|
|
299
|
+
.description('Audit the AI agent surface (configs, skills, MCP, attestation)');
|
|
300
|
+
|
|
301
|
+
agents
|
|
302
|
+
.command('audit [path]', { isDefault: true })
|
|
303
|
+
.description('Audit agent configs (CLAUDE.md, .cursorrules, MCP servers, skills)')
|
|
304
|
+
.option('--fix', 'Auto-harden agent configurations')
|
|
305
|
+
.option('--preflight', 'Exit non-zero on critical findings (for CI)')
|
|
306
|
+
.option('--red-team', 'Simulate adversarial attacks against agent configs')
|
|
307
|
+
.option('--json', 'Output results as JSON')
|
|
308
|
+
.action(openclawCommand);
|
|
309
|
+
|
|
310
|
+
agents
|
|
311
|
+
.command('skill [target]')
|
|
312
|
+
.description('Vet an AI agent skill before installing it')
|
|
313
|
+
.option('--all', 'Scan all skills defined in openclaw.json')
|
|
314
|
+
.option('--json', 'Output results as JSON')
|
|
315
|
+
.action(scanSkillCommand);
|
|
316
|
+
|
|
317
|
+
agents
|
|
318
|
+
.command('mcp [target]')
|
|
319
|
+
.description("Vet an MCP server's tool manifest before connecting")
|
|
320
|
+
.option('--test-live', 'Perform runtime JSON-RPC handshakes and tool fuzzing')
|
|
321
|
+
.option('--json', 'Output results as JSON')
|
|
322
|
+
.action(scanMcpCommand);
|
|
323
|
+
|
|
324
|
+
agents
|
|
325
|
+
.command('bom [path]')
|
|
326
|
+
.description('Generate Agent Bill of Materials (CycloneDX ABOM)')
|
|
327
|
+
.option('-o, --output <file>', 'Output file path', 'abom.json')
|
|
328
|
+
.option('--json', 'Output to stdout as JSON')
|
|
329
|
+
.action(abomCommand);
|
|
330
|
+
|
|
331
|
+
agents
|
|
332
|
+
.command('serve')
|
|
333
|
+
.description('Start praxis as an MCP server (Claude Desktop, Cursor, Windsurf)')
|
|
334
|
+
.action(mcpCommand);
|
|
335
|
+
|
|
336
|
+
program
|
|
337
|
+
.command('redteam [endpoint]')
|
|
338
|
+
.description('Dynamic AI red teaming & behavioral prober for live LLM endpoints (DAST)')
|
|
339
|
+
.option('--header <header>', 'Custom HTTP header (can specify multiple)')
|
|
340
|
+
.option('--timeout <ms>', 'Timeout in ms (default: 10000)')
|
|
341
|
+
.option('--json', 'Output results as JSON')
|
|
342
|
+
.action(redteamCommand);
|
|
343
|
+
|
|
344
|
+
// =============================================================================
|
|
345
|
+
// intel — threat-intelligence operations
|
|
346
|
+
// =============================================================================
|
|
347
|
+
|
|
348
|
+
const intel = program
|
|
349
|
+
.command('intel')
|
|
350
|
+
.description('Threat-intelligence: feed updates, advisories, dependency CVEs');
|
|
351
|
+
|
|
352
|
+
intel
|
|
353
|
+
.command('update')
|
|
354
|
+
.description('Refresh threat intel: OSV, GHSA, KEV, EPSS, NVD, Gitleaks, +paid')
|
|
355
|
+
.option('--only <sources>', 'Comma-separated subset (e.g. osv,kev,epss)')
|
|
356
|
+
.option('--force', 'Ignore per-source TTL caches')
|
|
357
|
+
.option('--list', 'Print available sources and exit')
|
|
358
|
+
.action(updateIntelCommand);
|
|
359
|
+
|
|
360
|
+
intel
|
|
361
|
+
.command('deps [path]')
|
|
362
|
+
.description('Audit deps via package manager (npm/yarn/pnpm/pip-audit/bundler-audit)')
|
|
363
|
+
.option('--fix', 'Run package manager fix command after auditing')
|
|
364
|
+
.action(depsCommand);
|
|
365
|
+
|
|
366
|
+
intel
|
|
367
|
+
.command('advisories [path]')
|
|
368
|
+
.description('Check deps against live advisory feeds (OSV.dev, GitHub Advisories)')
|
|
369
|
+
.option('--ecosystem <type>', 'Filter by ecosystem (npm, PyPI)')
|
|
370
|
+
.option('--json', 'Output as JSON')
|
|
371
|
+
.action(async (targetPath = '.', options) => {
|
|
372
|
+
const absolutePath = resolve(targetPath);
|
|
373
|
+
try {
|
|
374
|
+
const result = await runLiveAdvisories(absolutePath, options);
|
|
375
|
+
if (options.json) {
|
|
376
|
+
console.log(JSON.stringify(result, null, 2));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
console.log();
|
|
380
|
+
console.log(chalk.cyan.bold(' Praxis — Live Advisories'));
|
|
381
|
+
console.log(chalk.gray(` Checked ${result.checked} dependencies against OSV.dev`));
|
|
382
|
+
console.log();
|
|
383
|
+
if (result.advisories.length === 0) {
|
|
384
|
+
console.log(chalk.green(' ✔ No known advisories for your current dependency versions.\n'));
|
|
385
|
+
} else {
|
|
386
|
+
const malware = result.advisories.filter(a => a.isMalware);
|
|
387
|
+
const vulns = result.advisories.filter(a => !a.isMalware);
|
|
388
|
+
if (malware.length > 0) {
|
|
389
|
+
console.log(chalk.red.bold(` !! ${malware.length} MALWARE ADVISORY(S) FOUND`));
|
|
390
|
+
for (const a of malware) {
|
|
391
|
+
console.log(chalk.red(` ${a.package}@${a.version} — ${a.id}: ${a.summary.slice(0, 80)}`));
|
|
392
|
+
}
|
|
393
|
+
console.log();
|
|
394
|
+
}
|
|
395
|
+
if (vulns.length > 0) {
|
|
396
|
+
console.log(chalk.yellow(` ${vulns.length} vulnerability advisory(s):`));
|
|
397
|
+
for (const a of vulns) {
|
|
398
|
+
const sev = a.severity === 'critical' ? chalk.red.bold(a.severity)
|
|
399
|
+
: a.severity === 'high' ? chalk.yellow(a.severity)
|
|
400
|
+
: chalk.blue(a.severity);
|
|
401
|
+
console.log(` ${sev} ${a.package}@${a.version} — ${a.id}`);
|
|
402
|
+
}
|
|
403
|
+
console.log();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
} catch (err) {
|
|
407
|
+
console.error(chalk.red(` Error: ${err.message}\n`));
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// =============================================================================
|
|
413
|
+
// report — format & share scan results
|
|
414
|
+
// =============================================================================
|
|
415
|
+
|
|
416
|
+
const report = program
|
|
417
|
+
.command('report')
|
|
418
|
+
.description('Format, diff, or share existing scan results');
|
|
419
|
+
|
|
420
|
+
report
|
|
421
|
+
.command('team [file]')
|
|
422
|
+
.description('Convert Hermes Agent team output into a Praxis report')
|
|
423
|
+
.option('--html [path]', 'Save as HTML report')
|
|
424
|
+
.option('--json', 'JSON output')
|
|
425
|
+
.action(teamReportCommand);
|
|
426
|
+
|
|
427
|
+
report
|
|
428
|
+
.command('legal [path]')
|
|
429
|
+
.description('Legal risk audit: DMCA, leaked-source derivatives, IP disputes')
|
|
430
|
+
.option('--json', 'Output results as JSON')
|
|
431
|
+
.action(legalCommand);
|
|
432
|
+
|
|
433
|
+
report
|
|
434
|
+
.command('checklist')
|
|
435
|
+
.description('Run the launch-day security checklist interactively')
|
|
436
|
+
.option('--no-interactive', 'Print checklist without prompts')
|
|
437
|
+
.action(checklistCommand);
|
|
438
|
+
|
|
439
|
+
report
|
|
440
|
+
.command('sbom [path]')
|
|
441
|
+
.description('Generate Software Bill of Materials (CycloneDX SBOM)')
|
|
442
|
+
.option('-o, --output <file>', 'Output file path', 'sbom.json')
|
|
443
|
+
.action((targetPath = '.', options) => {
|
|
444
|
+
const absolutePath = resolve(targetPath);
|
|
445
|
+
const sbom = new SBOMGenerator();
|
|
446
|
+
sbom.generateToFile(absolutePath, options.output);
|
|
447
|
+
console.log(chalk.green(`✔ SBOM saved to ${options.output}`));
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
report
|
|
451
|
+
.command('benchmark [path]')
|
|
452
|
+
.description('Compare your security score against industry averages')
|
|
453
|
+
.option('--json', 'Output results as JSON')
|
|
454
|
+
.action(benchmarkCommand);
|
|
455
|
+
|
|
456
|
+
// =============================================================================
|
|
457
|
+
// project — setup & state
|
|
458
|
+
// =============================================================================
|
|
459
|
+
|
|
460
|
+
const project = program
|
|
461
|
+
.command('project')
|
|
462
|
+
.description('Project setup, hooks, watch, doctor, baseline, plugins, policies');
|
|
463
|
+
|
|
464
|
+
project
|
|
465
|
+
.command('init')
|
|
466
|
+
.description('Initialize security configs in your project')
|
|
467
|
+
.option('-f, --force', 'Overwrite existing files')
|
|
468
|
+
.option('--gitignore', 'Only copy .gitignore')
|
|
469
|
+
.option('--headers', 'Only copy security headers config')
|
|
470
|
+
.option('--agents', 'Only add security rules to AI agent instruction files')
|
|
471
|
+
.option('--openclaw', 'Generate a hardened openclaw.json template')
|
|
472
|
+
.option('--hermes', 'Bootstrap Hermes Agent security config')
|
|
473
|
+
.option('--from <url>', 'Fetch a pre-built Hermes config bundle from a setup URL')
|
|
474
|
+
.action(initCommand);
|
|
475
|
+
|
|
476
|
+
project
|
|
477
|
+
.command('doctor')
|
|
478
|
+
.description('Diagnose environment: Node.js, git, API keys, cache, dependencies')
|
|
479
|
+
.action(doctorCommand);
|
|
480
|
+
|
|
481
|
+
project
|
|
482
|
+
.command('hooks [action]')
|
|
483
|
+
.description('Manage Claude Code hooks — real-time security gate on tool calls')
|
|
484
|
+
.action(hooksCommand);
|
|
485
|
+
|
|
486
|
+
project
|
|
487
|
+
.command('guard [action]')
|
|
488
|
+
.description('Install pre-commit/pre-push git hook to block secret commits')
|
|
489
|
+
.option('--pre-commit', 'Install as pre-commit hook instead of pre-push')
|
|
490
|
+
.option('--generate-hooks', 'Generate defensive Claude Code hooks')
|
|
491
|
+
.action(guardCommand);
|
|
492
|
+
|
|
493
|
+
project
|
|
494
|
+
.command('watch [path]')
|
|
495
|
+
.description('Continuous monitoring: watch files for security issues in real-time')
|
|
496
|
+
.option('--poll', 'Use polling mode')
|
|
497
|
+
.option('--configs', 'Watch only agent config files')
|
|
498
|
+
.option('--deep', 'Run full agent scanning on changes')
|
|
499
|
+
.option('--stateful', 'Keep Kimi K2.6 conversation context between scans')
|
|
500
|
+
.option('--model <model>', 'LLM model for stateful watch')
|
|
501
|
+
.option('--provider <name>', 'LLM provider for stateful watch')
|
|
502
|
+
.option('--status', 'Show current watch status and exit')
|
|
503
|
+
.option('--threshold <score>', 'Alert when score drops below threshold', parseInt)
|
|
504
|
+
.option('--debounce <ms>', 'Debounce interval in ms', parseInt)
|
|
505
|
+
.option('--slack [webhook]', 'Post findings to Slack webhook URL')
|
|
506
|
+
.option('--pr-comment', 'Post inline findings as GitHub PR review comments')
|
|
507
|
+
.action(watchCommand);
|
|
508
|
+
|
|
509
|
+
project
|
|
510
|
+
.command('baseline [path]')
|
|
511
|
+
.description('Create/manage a findings baseline — only report new findings')
|
|
512
|
+
.option('--diff', 'Show what changed since baseline')
|
|
513
|
+
.option('--clear', 'Remove the baseline')
|
|
514
|
+
.action(baselineCommand);
|
|
515
|
+
|
|
516
|
+
project
|
|
517
|
+
.command('memory [subcommand]')
|
|
518
|
+
.description('Manage false-positive memory (list / forget <key> / clear)')
|
|
519
|
+
.argument('[args...]')
|
|
520
|
+
.action((subcommand, args, options) => memoryCommand(subcommand, args, options));
|
|
521
|
+
|
|
522
|
+
project
|
|
523
|
+
.command('playbook [subcommand]')
|
|
524
|
+
.description('Manage repo-specific LLM context playbook (show / add-note "text")')
|
|
525
|
+
.argument('[args...]')
|
|
526
|
+
.action((subcommand, args, options) => playbookCommand(subcommand, args, options));
|
|
527
|
+
|
|
528
|
+
project
|
|
529
|
+
.command('plugins [action]')
|
|
530
|
+
.description('Manage custom security agent plugins from .praxis/agents/')
|
|
531
|
+
.argument('[args...]')
|
|
532
|
+
.action((action, args, options) => {
|
|
533
|
+
const rootPath = resolve(process.cwd());
|
|
534
|
+
if (action === 'new') {
|
|
535
|
+
const pluginName = args?.[0] || 'my-rule';
|
|
536
|
+
try {
|
|
537
|
+
const filePath = scaffoldPlugin(rootPath, pluginName);
|
|
538
|
+
console.log(chalk.green(` ✔ Plugin scaffolded: ${filePath}`));
|
|
539
|
+
} catch (err) {
|
|
540
|
+
console.error(chalk.red(` Error: ${err.message}`));
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
const plugins = listPluginFiles(rootPath);
|
|
545
|
+
if (plugins.length === 0) {
|
|
546
|
+
console.log('\n No custom plugins found in .praxis/agents/');
|
|
547
|
+
console.log(chalk.gray(' Create one with: praxis project plugins new my-rule\n'));
|
|
548
|
+
} else {
|
|
549
|
+
console.log(`\n ${chalk.cyan.bold('Custom Plugins')} — ${plugins.length} found\n`);
|
|
550
|
+
for (const p of plugins) {
|
|
551
|
+
console.log(` ${chalk.white(p.name)} ${chalk.gray(`(${(p.size / 1024).toFixed(1)} KB) ${p.path}`)}`);
|
|
552
|
+
}
|
|
553
|
+
console.log();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
project
|
|
559
|
+
.command('policy <action>')
|
|
560
|
+
.description('Manage security policies (init: create policy template)')
|
|
561
|
+
.action((action) => {
|
|
562
|
+
if (action === 'init') {
|
|
563
|
+
const policyPath = PolicyEngine.generateTemplate(process.cwd());
|
|
564
|
+
console.log(chalk.green(`✔ Policy template created: ${policyPath}`));
|
|
565
|
+
console.log(chalk.gray(' Edit .praxis.policy.json to configure your security policy.'));
|
|
566
|
+
} else {
|
|
567
|
+
console.log(chalk.yellow(`Unknown policy action: ${action}. Use: policy init`));
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// =============================================================================
|
|
572
|
+
// Top-level shortcuts (preserve indie voice)
|
|
573
|
+
// =============================================================================
|
|
574
|
+
|
|
575
|
+
const rules = program.command('rules').description('Inspect and export Praxis detection rules');
|
|
576
|
+
|
|
577
|
+
rules
|
|
578
|
+
.command('list')
|
|
579
|
+
.description('Summarise the rule inventory by source, severity and portability')
|
|
580
|
+
.option('--json', 'Output results as JSON')
|
|
581
|
+
.action(rulesListCommand);
|
|
582
|
+
|
|
583
|
+
rules
|
|
584
|
+
.command('export')
|
|
585
|
+
.description('Write a Semgrep-compatible rule bundle plus a portability manifest')
|
|
586
|
+
.option('-o, --out <dir>', 'Output directory', 'praxis-rules')
|
|
587
|
+
.option('--allow-invalid', 'Export even if some patterns fail validation', false)
|
|
588
|
+
.action(rulesExportCommand);
|
|
589
|
+
|
|
590
|
+
rules
|
|
591
|
+
.command('import <bundle>')
|
|
592
|
+
.description('Load a Praxis portable rule bundle (JSON) and optionally emit a runnable plugin')
|
|
593
|
+
.option('--write-plugin <dir>', 'Write a runnable plugin to this directory (e.g. .praxis/agents)')
|
|
594
|
+
.option('--name <name>', 'Plugin class name prefix', 'PortableRules')
|
|
595
|
+
.option('--json', 'Output results as JSON')
|
|
596
|
+
.action(rulesImportCommand);
|
|
597
|
+
|
|
598
|
+
program
|
|
599
|
+
.command('web')
|
|
600
|
+
.description('Local web UI for scans and scan projects (read-only; loopback by default)')
|
|
601
|
+
.option('--port <port>', 'Port to listen on', '7317')
|
|
602
|
+
.option('--host <host>', 'Host to bind (non-loopback requires --allow-remote and --token)', '127.0.0.1')
|
|
603
|
+
.option('--allow-remote', 'Permit a non-loopback bind (requires --token)', false)
|
|
604
|
+
.option('--token <token>', 'Bearer token required for every request when remotely bound')
|
|
605
|
+
.action(webCommand);
|
|
606
|
+
|
|
607
|
+
program
|
|
608
|
+
.command('vibe [path]')
|
|
609
|
+
.description('Vibe-graded security score with emoji and shareable badge')
|
|
610
|
+
.option('--badge', 'Generate a shields.io markdown badge for your README')
|
|
611
|
+
.action(vibeCheckCommand);
|
|
612
|
+
|
|
613
|
+
program
|
|
614
|
+
.command('score [path]')
|
|
615
|
+
.description('Compute a 0-100 security health score for your project')
|
|
616
|
+
.option('--no-deps', 'Skip dependency audit')
|
|
617
|
+
.action(scoreCommand);
|
|
618
|
+
|
|
619
|
+
program
|
|
620
|
+
.command('hooks [action]')
|
|
621
|
+
.description('Manage Claude Code hooks — real-time security gate on tool calls')
|
|
622
|
+
.action(hooksCommand);
|
|
623
|
+
|
|
624
|
+
// =============================================================================
|
|
625
|
+
// Legacy top-level surface (back-compat)
|
|
626
|
+
// =============================================================================
|
|
627
|
+
// The original flat `praxis` command set is re-exposed here so existing docs,
|
|
628
|
+
// scripts, GitHub Actions, and Claude Code skills keep working unchanged.
|
|
629
|
+
|
|
630
|
+
const legacy = (name, description, flags) => {
|
|
631
|
+
const cmd = program.command(name).description(description);
|
|
632
|
+
for (const [flag, help, parse] of flags) {
|
|
633
|
+
if (parse) cmd.option(flag, help, parse);
|
|
634
|
+
else cmd.option(flag, help);
|
|
635
|
+
}
|
|
636
|
+
return cmd;
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
legacy('ci [path]', 'CI/CD mode: scan, score, exit 1 on failure (alias of `scan ci`)', [
|
|
640
|
+
['--threshold <score>', 'Minimum passing score (default: 75)', parseInt],
|
|
641
|
+
['--fail-on <severity>', 'Fail on findings at this severity or above'],
|
|
642
|
+
['--always-fail-on <severity>', 'Severity floor that even an accepted baseline cannot suppress'],
|
|
643
|
+
['--include-findings', 'Include the finding list (file/rule/severity) in JSON output for diffing'],
|
|
644
|
+
['--sarif <file>', 'Write SARIF output for GitHub Code Scanning'],
|
|
645
|
+
['--json', 'JSON output'],
|
|
646
|
+
['--no-deps', 'Skip dependency audit'],
|
|
647
|
+
['--baseline', 'Only check new findings (not in baseline)'],
|
|
648
|
+
['--github-pr', 'Post findings as a GitHub PR comment'],
|
|
649
|
+
['--strict-intel', 'Fail if threat-intel feed is stale'],
|
|
650
|
+
['--max-intel-age <duration>', 'Max acceptable intel age', undefined],
|
|
651
|
+
]).action(ciCommand);
|
|
652
|
+
|
|
653
|
+
legacy('audit [path]', 'Audit agent configs (CLAUDE.md, .cursorrules, MCP, skills) — alias of `agents audit`', [
|
|
654
|
+
['--fix', 'Auto-harden agent configurations'],
|
|
655
|
+
['--preflight', 'Exit non-zero on critical findings (for CI)'],
|
|
656
|
+
['--red-team', 'Simulate adversarial attacks against agent configs'],
|
|
657
|
+
['--json', 'Output results as JSON'],
|
|
658
|
+
]).action(openclawCommand);
|
|
659
|
+
|
|
660
|
+
legacy('openclaw [path]', 'Agent-config security audit (alias of `agents audit`)', [
|
|
661
|
+
['--fix', 'Auto-harden agent configurations'],
|
|
662
|
+
['--preflight', 'Exit non-zero on critical findings (for CI)'],
|
|
663
|
+
['--red-team', 'Simulate adversarial attacks against agent configs'],
|
|
664
|
+
['--json', 'Output results as JSON'],
|
|
665
|
+
]).action(openclawCommand);
|
|
666
|
+
|
|
667
|
+
legacy('scan-mcp [target]', 'Vet an MCP server tool manifest (alias of `agents mcp`)', [
|
|
668
|
+
['--json', 'Output results as JSON'],
|
|
669
|
+
]).action(scanMcpCommand);
|
|
670
|
+
|
|
671
|
+
legacy('mcp', 'Start praxis as an MCP server over stdio (alias of `agents serve`)', [])
|
|
672
|
+
.action(mcpCommand);
|
|
673
|
+
|
|
674
|
+
legacy('scan-skill [target]', 'Vet an AI agent skill (alias of `agents skill`)', [
|
|
675
|
+
['--all', 'Scan all skills defined in openclaw.json'],
|
|
676
|
+
['--json', 'Output results as JSON'],
|
|
677
|
+
]).action(scanSkillCommand);
|
|
678
|
+
|
|
679
|
+
legacy('scan-standard [name] [path]', 'Filter findings by AI-security standard (alias of `scan standard`)', [
|
|
680
|
+
['--list', 'List available standards and exit'],
|
|
681
|
+
['--control <id>', 'Filter to a single control within the standard'],
|
|
682
|
+
['--json', 'Output results as JSON'],
|
|
683
|
+
['--sarif', 'Output results in SARIF format'],
|
|
684
|
+
['--format <name>', 'Output format from the registry (json, sarif)'],
|
|
685
|
+
]).action((name, targetPath, options) => scanStandardCommand(name, targetPath, options));
|
|
686
|
+
|
|
687
|
+
legacy('update-intel', 'Refresh threat intel feeds (alias of `intel update`)', [
|
|
688
|
+
['--only <sources>', 'Comma-separated subset (e.g. osv,kev,epss)'],
|
|
689
|
+
['--force', 'Ignore per-source TTL caches'],
|
|
690
|
+
['--list', 'Print available sources and exit'],
|
|
691
|
+
]).action(updateIntelCommand);
|
|
692
|
+
|
|
693
|
+
legacy('deps [path]', 'Audit dependency CVEs (alias of `intel deps`)', [
|
|
694
|
+
['--fix', 'Run package manager fix command after auditing'],
|
|
695
|
+
]).action(depsCommand);
|
|
696
|
+
|
|
697
|
+
legacy('advisories [path]', 'Check deps against live advisory feeds (alias of `intel advisories`)', [
|
|
698
|
+
['--ecosystem <type>', 'Filter by ecosystem (npm, PyPI)'],
|
|
699
|
+
['--json', 'Output as JSON'],
|
|
700
|
+
]).action(async (targetPath = '.', options) => {
|
|
701
|
+
const absolutePath = resolve(targetPath);
|
|
702
|
+
try {
|
|
703
|
+
const result = await runLiveAdvisories(absolutePath, options);
|
|
704
|
+
if (options.json) {
|
|
705
|
+
console.log(JSON.stringify(result, null, 2));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
console.log();
|
|
709
|
+
console.log(chalk.cyan.bold(' Praxis — Live Advisories'));
|
|
710
|
+
console.log(chalk.gray(` Checked ${result.checked} dependencies against OSV.dev`));
|
|
711
|
+
console.log();
|
|
712
|
+
if (result.advisories.length === 0) {
|
|
713
|
+
console.log(chalk.green(' ✔ No known advisories for your current dependency versions.\n'));
|
|
714
|
+
} else {
|
|
715
|
+
const malware = result.advisories.filter(a => a.isMalware);
|
|
716
|
+
const vulns = result.advisories.filter(a => !a.isMalware);
|
|
717
|
+
if (malware.length > 0) {
|
|
718
|
+
console.log(chalk.red.bold(` !! ${malware.length} MALWARE ADVISORY(S) FOUND`));
|
|
719
|
+
for (const a of malware) {
|
|
720
|
+
console.log(chalk.red(` ${a.package}@${a.version} — ${a.id}: ${a.summary.slice(0, 80)}`));
|
|
721
|
+
}
|
|
722
|
+
console.log();
|
|
723
|
+
}
|
|
724
|
+
if (vulns.length > 0) {
|
|
725
|
+
console.log(chalk.yellow(` ${vulns.length} vulnerability advisory(s):`));
|
|
726
|
+
for (const a of vulns) {
|
|
727
|
+
const sev = a.severity === 'critical' ? chalk.red.bold(a.severity)
|
|
728
|
+
: a.severity === 'high' ? chalk.yellow(a.severity)
|
|
729
|
+
: chalk.blue(a.severity);
|
|
730
|
+
console.log(` ${sev} ${a.package}@${a.version} — ${a.id}`);
|
|
731
|
+
}
|
|
732
|
+
console.log();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
} catch (err) {
|
|
736
|
+
console.error(chalk.red(` Error: ${err.message}\n`));
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
legacy('remediate [path]', 'Deterministic secret fixer: rewrite source + write .env (alias of `fix quick`)', [
|
|
742
|
+
['--dry-run', 'Preview changes without writing'],
|
|
743
|
+
['--yes', 'Apply all fixes without prompting'],
|
|
744
|
+
['--stage', 'Run git add on modified files'],
|
|
745
|
+
['--all', 'Also fix common agent findings'],
|
|
746
|
+
]).action(remediateCommand);
|
|
747
|
+
|
|
748
|
+
legacy('rotate [path]', 'Open provider dashboards to revoke exposed secrets (alias of `fix rotate`)', [
|
|
749
|
+
['--provider <name>', 'Only rotate secrets for a specific provider'],
|
|
750
|
+
['--plan <file>', 'Execute a rotation plan'],
|
|
751
|
+
]).action(rotateCommand);
|
|
752
|
+
|
|
753
|
+
legacy('undo [path]', 'Revert the last fix (alias of `fix undo`)', [
|
|
754
|
+
['--all', 'Revert every fix in the log'],
|
|
755
|
+
['--dry-run', 'Show what would be reverted'],
|
|
756
|
+
]).action(undoCommand);
|
|
757
|
+
|
|
758
|
+
legacy('env-template', 'Generate .env.example from found secrets (alias of `fix env-template`)', [
|
|
759
|
+
['--dry-run', 'Preview without writing'],
|
|
760
|
+
]).action(legacyFixCommand);
|
|
761
|
+
|
|
762
|
+
legacy('red-team [path]', 'Adversarial agent pack scan (alias of `scan redteam`)', [
|
|
763
|
+
['--agents <list>', 'Comma-separated list of agents to run'],
|
|
764
|
+
['--json', 'Output results as JSON'],
|
|
765
|
+
['--sarif', 'Output results in SARIF format'],
|
|
766
|
+
['--html [file]', 'Generate HTML security report'],
|
|
767
|
+
['--sbom [file]', 'Generate CycloneDX SBOM'],
|
|
768
|
+
['--no-deps', 'Skip dependency audit'],
|
|
769
|
+
['--no-ai', 'Skip AI classification'],
|
|
770
|
+
['--deep', 'LLM-powered taint analysis'],
|
|
771
|
+
['--swarm', 'AI swarm mode via DeepSeek/Kimi'],
|
|
772
|
+
['--think', 'Enable extended thinking mode'],
|
|
773
|
+
['--local', 'Use local Ollama model'],
|
|
774
|
+
['--model <model>', 'LLM model'],
|
|
775
|
+
['--provider <name>', 'LLM provider'],
|
|
776
|
+
['--base-url <url>', 'Custom OpenAI-compatible endpoint'],
|
|
777
|
+
['--budget <cents>', 'Max spend in cents', parseInt],
|
|
778
|
+
['-v, --verbose', 'Verbose output'],
|
|
779
|
+
]).action(redTeamCommand);
|
|
780
|
+
|
|
781
|
+
legacy('abom [path]', 'Generate Agent Bill of Materials (alias of `agents bom`)', [
|
|
782
|
+
['-o, --output <file>', 'Output file path', undefined],
|
|
783
|
+
['--json', 'Output to stdout as JSON'],
|
|
784
|
+
]).action(abomCommand);
|
|
785
|
+
|
|
786
|
+
legacy('legal [path]', 'Legal risk audit (alias of `report legal`)', [
|
|
787
|
+
['--json', 'Output results as JSON'],
|
|
788
|
+
]).action(legalCommand);
|
|
789
|
+
|
|
790
|
+
legacy('team [file]', 'Convert Hermes Agent output into a Praxis report (alias of `report team`)', [
|
|
791
|
+
['--html [path]', 'Save as HTML report'],
|
|
792
|
+
['--json', 'JSON output'],
|
|
793
|
+
]).action(teamReportCommand);
|
|
794
|
+
|
|
795
|
+
legacy('checklist', 'Run the launch-day security checklist (alias of `report checklist`)', [
|
|
796
|
+
['--no-interactive', 'Print checklist without prompts'],
|
|
797
|
+
]).action(checklistCommand);
|
|
798
|
+
|
|
799
|
+
legacy('benchmark [path]', 'Compare your score against industry averages (alias of `report benchmark`)', [
|
|
800
|
+
['--json', 'Output results as JSON'],
|
|
801
|
+
]).action(benchmarkCommand);
|
|
802
|
+
|
|
803
|
+
legacy('init', 'Initialize security configs in your project (alias of `project init`)', [
|
|
804
|
+
['-f, --force', 'Overwrite existing files'],
|
|
805
|
+
['--gitignore', 'Only copy .gitignore'],
|
|
806
|
+
['--headers', 'Only copy security headers config'],
|
|
807
|
+
['--agents', 'Only add security rules to AI agent instruction files'],
|
|
808
|
+
['--openclaw', 'Generate a hardened openclaw.json template'],
|
|
809
|
+
['--hermes', 'Bootstrap Hermes Agent security config'],
|
|
810
|
+
['--from <url>', 'Fetch a pre-built Hermes config bundle from a setup URL'],
|
|
811
|
+
]).action(initCommand);
|
|
812
|
+
|
|
813
|
+
legacy('doctor', 'Diagnose environment (alias of `project doctor`)', []).action(doctorCommand);
|
|
814
|
+
|
|
815
|
+
legacy('baseline [path]', 'Manage findings baseline (alias of `project baseline`)', [
|
|
816
|
+
['--diff', 'Show what changed since baseline'],
|
|
817
|
+
['--clear', 'Remove the baseline'],
|
|
818
|
+
]).action(baselineCommand);
|
|
819
|
+
|
|
820
|
+
legacy('guard [action]', 'Install pre-commit/pre-push secret guard (alias of `project guard`)', [
|
|
821
|
+
['--pre-commit', 'Install as pre-commit hook instead of pre-push'],
|
|
822
|
+
['--generate-hooks', 'Generate defensive Claude Code hooks'],
|
|
823
|
+
]).action(guardCommand);
|
|
824
|
+
|
|
825
|
+
legacy('watch [path]', 'Continuous security monitoring (alias of `project watch`)', [
|
|
826
|
+
['--poll', 'Use polling mode'],
|
|
827
|
+
['--configs', 'Watch only agent config files'],
|
|
828
|
+
['--deep', 'Run full agent scanning on changes'],
|
|
829
|
+
['--stateful', 'Keep Kimi K2.6 conversation context between scans'],
|
|
830
|
+
['--model <model>', 'LLM model for stateful watch'],
|
|
831
|
+
['--provider <name>', 'LLM provider for stateful watch'],
|
|
832
|
+
['--status', 'Show current watch status and exit'],
|
|
833
|
+
['--threshold <score>', 'Alert when score drops below threshold', parseInt],
|
|
834
|
+
['--debounce <ms>', 'Debounce interval in ms', parseInt],
|
|
835
|
+
['--slack [webhook]', 'Post findings to Slack webhook URL'],
|
|
836
|
+
['--pr-comment', 'Post inline findings as GitHub PR review comments'],
|
|
837
|
+
]).action(watchCommand);
|
|
838
|
+
|
|
839
|
+
legacy('shell', 'Interactive REPL (same as `praxis` with no args on a TTY)', [])
|
|
840
|
+
.action(() => shellCommand('.', {}).then(() => process.exit(0)).catch(() => process.exit(1)));
|
|
841
|
+
|
|
842
|
+
// =============================================================================
|
|
843
|
+
// PARSE AND RUN
|
|
844
|
+
// =============================================================================
|
|
845
|
+
|
|
846
|
+
if (process.argv.length === 2 && process.stdin.isTTY) {
|
|
847
|
+
// No args + TTY → interactive REPL.
|
|
848
|
+
shellCommand('.', {}).then(() => process.exit(0)).catch(() => process.exit(1));
|
|
849
|
+
} else if (process.argv.length === 2) {
|
|
850
|
+
printBanner(VERSION);
|
|
851
|
+
console.log(chalk.yellow('Quick start:\n'));
|
|
852
|
+
console.log(chalk.white(' praxis scan . ') + chalk.gray('# Full audit'));
|
|
853
|
+
console.log(chalk.white(' praxis fix . ') + chalk.gray('# Interactive LLM-guided fixes'));
|
|
854
|
+
console.log(chalk.white(' praxis agents audit . ') + chalk.gray('# Audit AI agent surface'));
|
|
855
|
+
console.log(chalk.white(' praxis intel update ') + chalk.gray('# Refresh threat intel'));
|
|
856
|
+
console.log(chalk.white(' praxis project init ') + chalk.gray('# Add security configs'));
|
|
857
|
+
console.log(chalk.white(' praxis vibe . ') + chalk.gray('# Emoji-graded score'));
|
|
858
|
+
console.log(chalk.white(' praxis --help ') + chalk.gray('# Show all groups'));
|
|
859
|
+
console.log();
|
|
860
|
+
console.log(chalk.gray('Migrating from `praxis`? Any legacy command (audit, ci, mcp, ...) still —'));
|
|
861
|
+
console.log(chalk.gray('the `praxis` binary continues to work with full back-compat.'));
|
|
862
|
+
console.log();
|
|
863
|
+
process.exit(0);
|
|
864
|
+
} else {
|
|
865
|
+
program.parse();
|
|
866
|
+
}
|