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,514 @@
1
+ /**
2
+ * Init Command
3
+ * ============
4
+ *
5
+ * Initialize security configurations in the current project.
6
+ * Copies pre-configured security files from praxis.
7
+ *
8
+ * USAGE:
9
+ * praxis init Copy all security configs
10
+ * praxis init --gitignore Only copy .gitignore
11
+ * praxis init --headers Only copy security headers
12
+ * praxis init -f Force overwrite existing files
13
+ *
14
+ * FILES COPIED:
15
+ * - .gitignore (merged with existing if present)
16
+ * - nextjs-security-headers.js (for Next.js projects)
17
+ */
18
+
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+ import { fileURLToPath } from 'url';
22
+ import chalk from 'chalk';
23
+ import * as output from '../utils/output.js';
24
+
25
+ // Get the directory of this module (for finding config files)
26
+ const __filename = fileURLToPath(import.meta.url); // praxis-ignore — module's own path via import.meta.url, not user input
27
+ const __dirname = path.dirname(__filename);
28
+ const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
29
+
30
+ // =============================================================================
31
+ // MAIN INIT FUNCTION
32
+ // =============================================================================
33
+
34
+ export async function initCommand(options = {}) {
35
+ const targetDir = process.cwd();
36
+
37
+ console.log();
38
+ output.header('Initializing Security Configs');
39
+ console.log();
40
+ console.log(chalk.gray(`Target directory: ${targetDir}`));
41
+ console.log();
42
+
43
+ const results = {
44
+ copied: [],
45
+ skipped: [],
46
+ merged: [],
47
+ errors: []
48
+ };
49
+
50
+ // Determine which files to copy.
51
+ // If a specific flag is set, only run that category.
52
+ // With no flags, run everything.
53
+ // Handle --openclaw flag separately
54
+ if (options.openclaw) {
55
+ return handleOpenClawInit(targetDir, options.force, results);
56
+ }
57
+
58
+ // Handle --hermes --from <url>
59
+ if (options.hermes) {
60
+ return handleHermesInit(targetDir, options);
61
+ }
62
+
63
+ const hasSpecificFlag = options.gitignore || options.headers || options.agents;
64
+ const copyGitignore = hasSpecificFlag ? !!options.gitignore : true;
65
+ const copyHeaders = hasSpecificFlag ? !!options.headers : true;
66
+ const copyAgents = hasSpecificFlag ? !!options.agents : true;
67
+
68
+ // Copy .gitignore
69
+ if (copyGitignore) {
70
+ await handleGitignore(targetDir, options.force, results);
71
+ }
72
+
73
+ // Copy security headers
74
+ if (copyHeaders) {
75
+ await handleSecurityHeaders(targetDir, options.force, results);
76
+ }
77
+
78
+ // Append security rules to AI agent instruction files
79
+ if (copyAgents) {
80
+ await handleAgentFiles(targetDir, options.force, results);
81
+ }
82
+
83
+ // Print summary
84
+ printSummary(results);
85
+ }
86
+
87
+ // =============================================================================
88
+ // GITIGNORE HANDLING
89
+ // =============================================================================
90
+
91
+ async function handleGitignore(targetDir, force, results) {
92
+ // Note: We use 'gitignore-template' because npm excludes dotfiles from packages
93
+ const sourcePath = path.join(PACKAGE_ROOT, 'configs', 'gitignore-template');
94
+ const targetPath = path.join(targetDir, '.gitignore');
95
+
96
+ // Check if source exists
97
+ if (!fs.existsSync(sourcePath)) {
98
+ results.errors.push({
99
+ file: '.gitignore',
100
+ error: 'Source file not found in praxis package'
101
+ });
102
+ return;
103
+ }
104
+
105
+ const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
106
+
107
+ // Check if target exists
108
+ if (fs.existsSync(targetPath)) {
109
+ if (force) {
110
+ // Overwrite
111
+ fs.writeFileSync(targetPath, sourceContent);
112
+ results.copied.push('.gitignore (overwritten)');
113
+ } else {
114
+ // Merge: append praxis patterns to existing
115
+ const existingContent = fs.readFileSync(targetPath, 'utf-8');
116
+
117
+ // Check if already has praxis content
118
+ if (existingContent.includes('# PRAXIS')) {
119
+ results.skipped.push('.gitignore (already contains praxis patterns)');
120
+ return;
121
+ }
122
+
123
+ // Append praxis section
124
+ const mergedContent = existingContent.trim() + '\n\n' +
125
+ '# =============================================================================\n' +
126
+ '# PRAXIS ADDITIONS\n' +
127
+ '# Added by: npx praxis-sec init\n' +
128
+ '# =============================================================================\n\n' +
129
+ extractSecurityPatterns(sourceContent);
130
+
131
+ fs.writeFileSync(targetPath, mergedContent);
132
+ results.merged.push('.gitignore');
133
+ }
134
+ } else {
135
+ // Create new
136
+ fs.writeFileSync(targetPath, sourceContent);
137
+ results.copied.push('.gitignore');
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Extract the most important security patterns from our .gitignore
143
+ */
144
+ function extractSecurityPatterns(fullGitignore) {
145
+ // Extract key sections
146
+ const patterns = `
147
+ # Environment files
148
+ .env
149
+ .env.local
150
+ .env*.local
151
+ *.env
152
+
153
+ # Private keys & certificates
154
+ *.pem
155
+ *.key
156
+ *.p12
157
+ *.pfx
158
+
159
+ # Credentials
160
+ *credentials*
161
+ *.secrets.json
162
+ secrets.yml
163
+ secrets.yaml
164
+
165
+ # Service accounts
166
+ **/service-account*.json
167
+ *-firebase-adminsdk-*.json
168
+
169
+ # AWS
170
+ .aws/credentials
171
+
172
+ # Database files
173
+ *.sqlite
174
+ *.sqlite3
175
+ *.db
176
+
177
+ # Logs (may contain sensitive data)
178
+ *.log
179
+ logs/
180
+ `;
181
+
182
+ return patterns.trim();
183
+ }
184
+
185
+ // =============================================================================
186
+ // SECURITY HEADERS HANDLING
187
+ // =============================================================================
188
+
189
+ async function handleSecurityHeaders(targetDir, force, results) {
190
+ const sourcePath = path.join(PACKAGE_ROOT, 'configs', 'nextjs-security-headers.js');
191
+ const targetPath = path.join(targetDir, 'security-headers.config.js');
192
+
193
+ // Check if source exists
194
+ if (!fs.existsSync(sourcePath)) {
195
+ results.errors.push({
196
+ file: 'security-headers.config.js',
197
+ error: 'Source file not found in praxis package'
198
+ });
199
+ return;
200
+ }
201
+
202
+ // Detect if this is a Next.js project
203
+ const packageJsonPath = path.join(targetDir, 'package.json');
204
+ let isNextProject = false;
205
+
206
+ if (fs.existsSync(packageJsonPath)) {
207
+ try {
208
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
209
+ isNextProject = !!(packageJson.dependencies?.next || packageJson.devDependencies?.next);
210
+ } catch {
211
+ // Ignore parse errors
212
+ }
213
+ }
214
+
215
+ // Check if target exists
216
+ if (fs.existsSync(targetPath) && !force) {
217
+ results.skipped.push('security-headers.config.js (already exists, use -f to overwrite)');
218
+ return;
219
+ }
220
+
221
+ // Copy the file
222
+ const content = fs.readFileSync(sourcePath, 'utf-8');
223
+ fs.writeFileSync(targetPath, content);
224
+ results.copied.push('security-headers.config.js');
225
+
226
+ // Show integration instructions
227
+ if (isNextProject) {
228
+ console.log(chalk.cyan('\nNext.js detected! Add to your next.config.js:\n'));
229
+ console.log(chalk.gray(' const { securityHeadersConfig } = require(\'./security-headers.config.js\');'));
230
+ console.log(chalk.gray(' module.exports = { ...securityHeadersConfig, /* your config */ };'));
231
+ console.log();
232
+ }
233
+ }
234
+
235
+ // =============================================================================
236
+ // AGENT FILE HANDLING
237
+ // =============================================================================
238
+
239
+ // Marker used for idempotency — if this string is present we already added our section.
240
+ const AGENT_MARKER = '<!-- praxis -->';
241
+
242
+ // The security rules block appended to existing agent instruction files.
243
+ const AGENT_SECTION = `
244
+ ## Security (praxis)
245
+ ${AGENT_MARKER}
246
+ - Always run \`npx praxis-sec scan .\` before committing code
247
+ - Never hardcode API keys, tokens, or credentials in source files — use environment variables
248
+ - If hardcoded secrets are found or generated, run \`npx praxis-sec remediate\` to auto-fix them
249
+ `;
250
+
251
+ // Standalone content for a new Cursor rules file.
252
+ const CURSOR_RULE_CONTENT = `---
253
+ description: Prevent API key leaks and credential exposure in generated code
254
+ alwaysApply: true
255
+ ---
256
+
257
+ - Always run \`npx praxis-sec scan .\` before committing code
258
+ - Never hardcode API keys, tokens, or credentials in source files — use environment variables
259
+ - If hardcoded secrets are found or generated, run \`npx praxis-sec remediate\` to auto-fix them
260
+ `;
261
+
262
+ async function handleAgentFiles(targetDir, force, results) {
263
+ // Files where we append a section if they already exist, or create if they don't.
264
+ const appendTargets = [
265
+ { file: 'CLAUDE.md', label: 'CLAUDE.md' },
266
+ { file: '.windsurfrules', label: '.windsurfrules' },
267
+ { file: path.join('.github', 'copilot-instructions.md'), label: '.github/copilot-instructions.md' },
268
+ ];
269
+
270
+ for (const { file, label } of appendTargets) {
271
+ const targetPath = path.join(targetDir, file);
272
+
273
+ if (fs.existsSync(targetPath)) {
274
+ const existing = fs.readFileSync(targetPath, 'utf-8');
275
+ if (existing.includes(AGENT_MARKER)) {
276
+ results.skipped.push(`${label} (already contains praxis rules)`);
277
+ continue;
278
+ }
279
+ if (force || true) { // always append unless already present
280
+ fs.writeFileSync(targetPath, existing.trimEnd() + '\n' + AGENT_SECTION);
281
+ results.merged.push(label);
282
+ }
283
+ } else {
284
+ // Ensure parent directory exists (e.g. .github/)
285
+ const parentDir = path.dirname(targetPath);
286
+ if (!fs.existsSync(parentDir)) {
287
+ fs.mkdirSync(parentDir, { recursive: true });
288
+ }
289
+ fs.writeFileSync(targetPath, AGENT_SECTION.trim() + '\n');
290
+ results.copied.push(label);
291
+ }
292
+ }
293
+
294
+ // Cursor rules — dedicated file, no merging needed.
295
+ const cursorRulesDir = path.join(targetDir, '.cursor', 'rules');
296
+ const cursorRulesFile = path.join(cursorRulesDir, 'praxis.mdc');
297
+
298
+ if (fs.existsSync(cursorRulesFile) && !force) {
299
+ results.skipped.push('.cursor/rules/praxis.mdc (already exists, use -f to overwrite)');
300
+ } else {
301
+ if (!fs.existsSync(cursorRulesDir)) {
302
+ fs.mkdirSync(cursorRulesDir, { recursive: true });
303
+ }
304
+ fs.writeFileSync(cursorRulesFile, CURSOR_RULE_CONTENT.trim() + '\n');
305
+ results.copied.push('.cursor/rules/praxis.mdc');
306
+ }
307
+ }
308
+
309
+ // =============================================================================
310
+ // HERMES AGENT INIT
311
+ // =============================================================================
312
+
313
+ async function handleHermesInit(targetDir, options) {
314
+ const fromUrl = options.from;
315
+
316
+ if (!fromUrl) {
317
+ console.error(chalk.red('\nError: --hermes requires --from <setup-url>'));
318
+ console.error(chalk.gray(' Generate a setup URL at: '));
319
+ console.error(chalk.gray(' Then run: npx praxis-sec init --hermes --from <url>\n'));
320
+ process.exit(1);
321
+ }
322
+
323
+ // Validate the URL is well-formed and from a trusted origin.
324
+ // Allow HTTPS (any host — the security is HTTPS + explicit user opt-in via
325
+ // `praxis init --from <url>`) and localhost for local development.
326
+ let parsed;
327
+ try {
328
+ parsed = new URL(fromUrl);
329
+ } catch {
330
+ console.error(chalk.red('\nError: Invalid URL: ' + fromUrl + '\n'));
331
+ process.exit(1);
332
+ }
333
+
334
+ const isLocal = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
335
+ const isHttps = parsed.protocol === 'https:';
336
+ if (!isLocal && !isHttps) {
337
+ console.error(chalk.red('\nError: Setup URL must use HTTPS (got: ' + parsed.protocol + '//)'));
338
+ console.error(chalk.gray(' Or use http://localhost for local development.\n'));
339
+ process.exit(1);
340
+ }
341
+
342
+ console.log();
343
+ output.header('Hermes Agent Security Setup');
344
+ console.log();
345
+ console.log(chalk.gray('Fetching config from:'), chalk.cyan(fromUrl));
346
+ console.log();
347
+
348
+ // Fetch the config bundle
349
+ let data;
350
+ try {
351
+ const { default: fetch } = await import('node-fetch').catch(() => ({ default: globalThis.fetch }));
352
+ const res = await fetch(fromUrl, { headers: { 'Accept': 'application/json' } });
353
+ if (!res.ok) {
354
+ const body = await res.json().catch(() => ({}));
355
+ throw new Error(body.error ?? `HTTP ${res.status}`);
356
+ }
357
+ data = await res.json();
358
+ } catch (err) {
359
+ console.error(chalk.red('\nFailed to fetch setup config: ' + err.message + '\n'));
360
+ process.exit(1);
361
+ }
362
+
363
+ if (!data.files || !Array.isArray(data.files) || data.files.length === 0) {
364
+ console.error(chalk.red('\nInvalid config bundle — no files returned.\n'));
365
+ process.exit(1);
366
+ }
367
+
368
+ // Write each file
369
+ const written = [];
370
+ const skipped = [];
371
+
372
+ for (const { path: filePath, content } of data.files) {
373
+ // Sanitize path — no traversal
374
+ const normalized = path.normalize(filePath).replace(/^(\.\.(\/|\\|$))+/, '');
375
+ const absPath = path.join(targetDir, normalized);
376
+
377
+ if (fs.existsSync(absPath) && !options.force) {
378
+ skipped.push(normalized);
379
+ continue;
380
+ }
381
+
382
+ const dir = path.dirname(absPath);
383
+ if (!fs.existsSync(dir)) {
384
+ fs.mkdirSync(dir, { recursive: true });
385
+ }
386
+ fs.writeFileSync(absPath, content, 'utf-8');
387
+ written.push(normalized);
388
+ }
389
+
390
+ // Print results
391
+ console.log(chalk.green.bold('Files written:'));
392
+ for (const f of written) {
393
+ console.log(chalk.green(` ✔ ${f}`));
394
+ }
395
+ if (skipped.length > 0) {
396
+ console.log();
397
+ console.log(chalk.yellow.bold('Skipped (already exist — use -f to overwrite):'));
398
+ for (const f of skipped) {
399
+ console.log(chalk.yellow(` → ${f}`));
400
+ }
401
+ }
402
+
403
+ console.log();
404
+ console.log(chalk.cyan.bold('Next steps:'));
405
+ console.log(chalk.white(' 1.') + ' Populate your baseline: ' + chalk.cyan('npx praxis-sec audit .'));
406
+ console.log(chalk.white(' 2.') + ' Auto-fix findings: ' + chalk.cyan('npx praxis-sec audit . --agentic 3 --agentic-target 80'));
407
+ console.log(chalk.white(' 3.') + ' Commit everything and push — CI runs on every PR.');
408
+ console.log();
409
+ }
410
+
411
+ // =============================================================================
412
+ // OPENCLAW HARDENED CONFIG
413
+ // =============================================================================
414
+
415
+ const HARDENED_OPENCLAW = `{
416
+ "// SECURITY": "Generated by praxis init --openclaw — hardened defaults",
417
+
418
+ "// host": "Bind to localhost only — never 0.0.0.0 (CVE-2026-25253 ClawJacked)",
419
+ "host": "127.0.0.1",
420
+ "port": 3100,
421
+
422
+ "// auth": "Always require authentication — prevents unauthorized agent takeover",
423
+ "auth": {
424
+ "type": "apiKey",
425
+ "key": "\${OPENCLAW_API_KEY}"
426
+ },
427
+
428
+ "// url": "Use wss:// for all non-localhost connections (encrypted WebSocket)",
429
+ "url": "wss://localhost:3100",
430
+
431
+ "// safeBins": "Allowlist of binaries the agent can execute — block everything else",
432
+ "safeBins": ["node", "git", "npx", "npm"],
433
+
434
+ "// skills": "Only add verified skills from trusted sources — ClawHavoc had 1,184 malicious skills",
435
+ "skills": [],
436
+
437
+ "// logging": "Enable audit logging for security monitoring",
438
+ "logging": {
439
+ "level": "info",
440
+ "auditLog": true
441
+ }
442
+ }
443
+ `;
444
+
445
+ async function handleOpenClawInit(targetDir, force, results) {
446
+ const targetPath = path.join(targetDir, 'openclaw.json');
447
+
448
+ if (fs.existsSync(targetPath) && !force) {
449
+ results.skipped.push('openclaw.json (already exists, use -f to overwrite)');
450
+ } else {
451
+ fs.writeFileSync(targetPath, HARDENED_OPENCLAW.trim() + '\n');
452
+ results.copied.push('openclaw.json (hardened template)');
453
+ }
454
+
455
+ printSummary(results);
456
+
457
+ console.log(chalk.cyan('Important:'));
458
+ console.log(chalk.white(' 1.') + ' Set the OPENCLAW_API_KEY environment variable');
459
+ console.log(chalk.white(' 2.') + ' Only add verified skills from trusted sources');
460
+ console.log(chalk.white(' 3.') + ' Run ' + chalk.cyan('npx praxis-sec openclaw .') + ' to verify security');
461
+ console.log();
462
+ }
463
+
464
+ // =============================================================================
465
+ // SUMMARY
466
+ // =============================================================================
467
+
468
+ function printSummary(results) {
469
+ console.log();
470
+ console.log(chalk.cyan('='.repeat(60)));
471
+ console.log(chalk.cyan.bold(' Summary'));
472
+ console.log(chalk.cyan('='.repeat(60)));
473
+ console.log();
474
+
475
+ if (results.copied.length > 0) {
476
+ console.log(chalk.green.bold('Created:'));
477
+ for (const file of results.copied) {
478
+ console.log(chalk.green(` \u2714 ${file}`));
479
+ }
480
+ console.log();
481
+ }
482
+
483
+ if (results.merged.length > 0) {
484
+ console.log(chalk.blue.bold('Merged:'));
485
+ for (const file of results.merged) {
486
+ console.log(chalk.blue(` \u2194 ${file} (appended praxis patterns)`));
487
+ }
488
+ console.log();
489
+ }
490
+
491
+ if (results.skipped.length > 0) {
492
+ console.log(chalk.yellow.bold('Skipped:'));
493
+ for (const file of results.skipped) {
494
+ console.log(chalk.yellow(` \u2192 ${file}`));
495
+ }
496
+ console.log();
497
+ }
498
+
499
+ if (results.errors.length > 0) {
500
+ console.log(chalk.red.bold('Errors:'));
501
+ for (const { file, error } of results.errors) {
502
+ console.log(chalk.red(` \u2718 ${file}: ${error}`));
503
+ }
504
+ console.log();
505
+ }
506
+
507
+ // Next steps
508
+ console.log(chalk.cyan('Next steps:'));
509
+ console.log(chalk.white(' 1.') + ' Review the copied files and customize for your project');
510
+ console.log(chalk.white(' 2.') + ' Run ' + chalk.cyan('npx praxis-sec scan .') + ' to check for secrets');
511
+ console.log(chalk.white(' 3.') + ' Run ' + chalk.cyan('npx praxis-sec checklist') + ' before launching');
512
+ console.log();
513
+ console.log(chalk.cyan('='.repeat(60)));
514
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Legal Command
3
+ * =============
4
+ *
5
+ * Scans dependency manifests for packages that carry legal risk:
6
+ * active DMCA takedowns, leaked-source derivatives, IP disputes,
7
+ * and license violations.
8
+ *
9
+ * USAGE:
10
+ * praxis legal [path] Scan for legally risky dependencies
11
+ * praxis legal . --json JSON output
12
+ *
13
+ * EXIT CODES:
14
+ * 0 Clean — no legally risky packages found
15
+ * 1 Findings — one or more legally risky packages detected
16
+ */
17
+
18
+ import fs from 'fs';
19
+ import path from 'path';
20
+ import chalk from 'chalk';
21
+ import ora from 'ora';
22
+ import { LegalRiskAgent } from '../agents/legal-risk-agent.js';
23
+ import * as output from '../utils/output.js';
24
+
25
+ // =============================================================================
26
+ // RISK LABELS & COLORS
27
+ // =============================================================================
28
+
29
+ const RISK_COLORS = {
30
+ dmca: chalk.red.bold,
31
+ 'ip-dispute': chalk.red,
32
+ 'leaked-source': chalk.yellow.bold,
33
+ 'license-violation': chalk.yellow,
34
+ };
35
+
36
+ const RISK_LABELS = {
37
+ dmca: 'DMCA Takedown',
38
+ 'ip-dispute': 'IP Dispute',
39
+ 'leaked-source': 'Leaked Source',
40
+ 'license-violation': 'License Violation',
41
+ };
42
+
43
+ const SEVERITY_COLORS = {
44
+ critical: chalk.bgRed.white.bold,
45
+ high: chalk.red.bold,
46
+ medium: chalk.yellow,
47
+ low: chalk.blue,
48
+ };
49
+
50
+ // =============================================================================
51
+ // MAIN COMMAND
52
+ // =============================================================================
53
+
54
+ export async function legalCommand(targetPath = '.', options = {}) {
55
+ const absolutePath = path.resolve(targetPath);
56
+
57
+ if (!fs.existsSync(absolutePath)) {
58
+ output.error(`Path does not exist: ${absolutePath}`);
59
+ process.exit(1);
60
+ }
61
+
62
+ if (!options.json) {
63
+ console.log();
64
+ output.header('Legal Risk Audit');
65
+ console.log(chalk.gray(' Scanning for DMCA notices, leaked-source derivatives, and IP disputes'));
66
+ console.log();
67
+ }
68
+
69
+ // ── Run the agent ──────────────────────────────────────────────────────────
70
+ const spinner = options.json
71
+ ? null
72
+ : ora({ text: 'Scanning dependency manifests…', color: 'cyan' }).start();
73
+
74
+ const agent = new LegalRiskAgent();
75
+ let findings = [];
76
+
77
+ try {
78
+ findings = await agent.analyze({ rootPath: absolutePath, files: [] });
79
+ if (spinner) spinner.stop();
80
+ } catch (err) {
81
+ if (spinner) spinner.stop();
82
+ output.error(`Legal scan failed: ${err.message}`);
83
+ process.exit(1);
84
+ }
85
+
86
+ // ── JSON output ────────────────────────────────────────────────────────────
87
+ if (options.json) {
88
+ console.log(JSON.stringify({ findings, total: findings.length }, null, 2));
89
+ process.exit(findings.length > 0 ? 1 : 0);
90
+ }
91
+
92
+ // ── Human-readable output ──────────────────────────────────────────────────
93
+ if (findings.length === 0) {
94
+ output.success('No legally risky packages found.');
95
+ console.log();
96
+ console.log(chalk.gray(' Scanned: package.json, requirements.txt, Cargo.toml, go.mod'));
97
+ console.log();
98
+ return;
99
+ }
100
+
101
+ // Group by severity
102
+ const bySeverity = { critical: [], high: [], medium: [], low: [] };
103
+ for (const f of findings) {
104
+ (bySeverity[f.severity] || bySeverity.medium).push(f);
105
+ }
106
+
107
+ const total = findings.length;
108
+ const critCount = bySeverity.critical.length;
109
+ const highCount = bySeverity.high.length;
110
+
111
+ // Summary line
112
+ console.log(
113
+ chalk.red.bold(` ${total} legal risk finding${total === 1 ? '' : 's'}`),
114
+ chalk.gray('—'),
115
+ critCount > 0 ? chalk.red.bold(`${critCount} critical`) + chalk.gray(', ') : '',
116
+ highCount > 0 ? chalk.red(`${highCount} high`) : '',
117
+ );
118
+ console.log();
119
+
120
+ // Print findings
121
+ for (const severity of ['critical', 'high', 'medium', 'low']) {
122
+ const group = bySeverity[severity];
123
+ if (group.length === 0) continue;
124
+
125
+ for (const f of group) {
126
+ const sevBadge = SEVERITY_COLORS[severity]
127
+ ? SEVERITY_COLORS[severity](` ${severity.toUpperCase()} `)
128
+ : chalk.gray(` ${severity.toUpperCase()} `);
129
+
130
+ // Extract risk type from rule: LEGAL_RISK_DMCA → dmca
131
+ const riskKey = f.rule
132
+ .replace('LEGAL_RISK_', '')
133
+ .toLowerCase()
134
+ .replace(/_/g, '-');
135
+ const riskColor = RISK_COLORS[riskKey] || chalk.white;
136
+ const riskLabel = RISK_LABELS[riskKey] || riskKey;
137
+
138
+ console.log(` ${sevBadge} ${chalk.white.bold(f.title)}`);
139
+ console.log(` ${riskColor(`[${riskLabel}]`)} ${chalk.gray(path.relative(absolutePath, f.file) || f.file)}`);
140
+ console.log();
141
+ console.log(` ${chalk.gray(f.description)}`);
142
+ console.log();
143
+ if (f.fix) {
144
+ console.log(` ${chalk.cyan('Fix:')} ${chalk.gray(f.fix)}`);
145
+ }
146
+ console.log();
147
+ console.log(chalk.gray(' ' + '─'.repeat(56)));
148
+ console.log();
149
+ }
150
+ }
151
+
152
+ // Footer
153
+ console.log(chalk.yellow.bold(' ⚠ Shipping legally risky packages can expose your project to IP liability.'));
154
+ console.log(chalk.gray(' Review each finding and remove the affected dependency before releasing.'));
155
+ console.log();
156
+
157
+ process.exit(1);
158
+ }