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,200 @@
1
+ /**
2
+ * SwarmOrchestrator — K2.6-Powered Parallel Security Swarm
3
+ * ==========================================================
4
+ *
5
+ * Instead of running 28 agents locally in Node.js (chunks of 6),
6
+ * --swarm sends the entire task to Kimi K2.6 and lets its native
7
+ * 300-agent swarm handle parallel analysis.
8
+ *
9
+ * Each of Praxis's 23 attack classes is assigned as an explicit
10
+ * sub-agent role. K2.6 fans out, each sub-agent scans for its class,
11
+ * and results are returned as a consolidated findings array.
12
+ *
13
+ * Output is mapped back to Praxis's Finding format so SARIF,
14
+ * HTML reports, and CI exit codes work unchanged.
15
+ *
16
+ * USAGE:
17
+ * npx praxis-sec red-team . --swarm
18
+ * npx praxis-sec red-team . --swarm --provider kimi
19
+ */
20
+
21
+ import fs from 'fs';
22
+ import path from 'path';
23
+ import { createProvider, autoDetectProvider } from '../providers/llm-provider.js';
24
+ import { ReconAgent } from './recon-agent.js';
25
+ import { createFinding } from './base-agent.js';
26
+
27
+ // =============================================================================
28
+ // AGENT ROLE DEFINITIONS — maps Praxis's 23 attack classes to swarm roles
29
+ // =============================================================================
30
+
31
+ const SWARM_ROLES = [
32
+ { id: 'injection', name: 'Injection Tester', desc: 'SQL injection, command injection, LDAP injection, XPath injection, template injection' },
33
+ { id: 'auth-bypass', name: 'Auth Bypass Agent', desc: 'Authentication bypass, authorization flaws, privilege escalation, JWT weaknesses' },
34
+ { id: 'ssrf', name: 'SSRF Prober', desc: 'Server-side request forgery, SSRF via redirects, internal service exposure' },
35
+ { id: 'supply-chain', name: 'Supply Chain Auditor', desc: 'Dependency confusion, typosquatting, malicious packages, outdated deps with CVEs' },
36
+ { id: 'config', name: 'Config Auditor', desc: 'Hardcoded secrets, insecure defaults, exposed debug endpoints, misconfigured CORS' },
37
+ { id: 'llm-redteam', name: 'LLM Red Team', desc: 'Prompt injection, jailbreaks, unsafe LLM output rendering, model inversion' },
38
+ { id: 'mobile', name: 'Mobile Scanner', desc: 'Insecure data storage, weak crypto, insecure communication, exported components' },
39
+ { id: 'git-history', name: 'Git History Scanner', desc: 'Secrets committed in git history, deleted files with sensitive data' },
40
+ { id: 'cicd', name: 'CI/CD Scanner', desc: 'Insecure GitHub Actions, exposed secrets in workflows, artifact poisoning' },
41
+ { id: 'api-fuzzer', name: 'API Fuzzer', desc: 'Missing input validation, mass assignment, insecure direct object references (IDOR)' },
42
+ { id: 'supabase-rls', name: 'Supabase RLS Agent', desc: 'Missing row-level security, exposed Supabase service keys, insecure RLS policies' },
43
+ { id: 'mcp-security', name: 'MCP Security Agent', desc: 'Tool poisoning, MCP server misconfiguration, unsafe tool definitions' },
44
+ { id: 'agentic-security', name: 'Agentic Security Agent', desc: 'Agentic loop vulnerabilities, unsafe tool use, context window attacks' },
45
+ { id: 'rag-security', name: 'RAG Security Agent', desc: 'Prompt injection via retrieved documents, data poisoning, retrieval manipulation' },
46
+ { id: 'pii-compliance', name: 'PII Compliance Agent', desc: 'PII exposure, GDPR/CCPA violations, unencrypted personal data' },
47
+ { id: 'vibe-coding', name: 'Vibe Coding Agent', desc: 'AI-generated code security issues, hardcoded values from iterative prompting' },
48
+ { id: 'exception-handler', name: 'Exception Handler Agent', desc: 'Stack traces in responses, error information disclosure, unhandled exceptions' },
49
+ { id: 'agent-config', name: 'Agent Config Scanner', desc: 'Insecure agent config files (.cursorrules, CLAUDE.md, MCP configs)' },
50
+ { id: 'memory-poisoning', name: 'Memory Poisoning Agent', desc: 'Malicious content in AI memory stores, embedding poisoning' },
51
+ { id: 'managed-agent', name: 'Managed Agent Scanner', desc: 'Insecure managed agent platforms, overprivileged agents' },
52
+ { id: 'hermes-security', name: 'Hermes Security Agent', desc: 'Hermes CLI security, agent tool permissions, orchestrator misconfiguration' },
53
+ { id: 'agent-attestation', name: 'Agent Attestation Agent', desc: 'Missing agent identity verification, unauthenticated agent-to-agent calls' },
54
+ { id: 'agentic-supply-chain', name: 'Agentic Supply Chain Agent', desc: 'Compromised AI integrations, OAuth scope creep, MCP server supply chain' },
55
+ ];
56
+
57
+ // Max file content to include in the swarm prompt (cost control)
58
+ const MAX_FILE_CHARS = 200_000;
59
+ const MAX_FILES = 100;
60
+
61
+ // =============================================================================
62
+ // SWARM ORCHESTRATOR
63
+ // =============================================================================
64
+
65
+ export class SwarmOrchestrator {
66
+ /**
67
+ * @param {object} options
68
+ * @param {object} options.provider — LLM provider (must be Kimi or OpenAI-compatible with tool use)
69
+ * @param {boolean} options.verbose
70
+ * @param {number} options.budgetCents
71
+ */
72
+ constructor(options = {}) {
73
+ this.provider = options.provider;
74
+ this.verbose = options.verbose || false;
75
+ this.budgetCents = options.budgetCents ?? 200;
76
+ }
77
+
78
+ static create(rootPath, options = {}) {
79
+ if (typeof options.provider === 'string') {
80
+ // Explicit provider requested
81
+ const provider = autoDetectProvider(rootPath, { provider: options.provider, model: options.model });
82
+ if (!provider) return null;
83
+ return new SwarmOrchestrator({ provider, verbose: options.verbose, budgetCents: options.budgetCents });
84
+ }
85
+
86
+ // Auto-select: prefer deepseek-flash (1M ctx, cheap) then kimi as fallback
87
+ for (const [providerName, swarmModel] of [
88
+ ['deepseek-flash', 'deepseek-v4-flash'],
89
+ ['kimi', 'moonshot-v1-128k'],
90
+ ]) {
91
+ const provider = autoDetectProvider(rootPath, { provider: providerName, model: swarmModel });
92
+ if (provider) return new SwarmOrchestrator({ provider, verbose: options.verbose, budgetCents: options.budgetCents });
93
+ }
94
+
95
+ return null;
96
+ }
97
+
98
+ /**
99
+ * Run the swarm scan against a codebase.
100
+ *
101
+ * @param {string} rootPath
102
+ * @param {object} reconData — Output from ReconAgent
103
+ * @param {string[]} files — All scannable files
104
+ * @returns {Promise<object[]>} — findings[]
105
+ */
106
+ async run(rootPath, reconData, files) {
107
+ const codeBundle = this._bundleCode(rootPath, files);
108
+ const prompt = this._buildSwarmPrompt(reconData, codeBundle, rootPath);
109
+
110
+ const systemPrompt = `You are a security swarm coordinator. You MUST respond with ONLY a valid JSON object — no prose, no markdown, no explanation, no code fences. Your response must start with { and end with }. Deploy all ${SWARM_ROLES.length} sub-agents, each scanning for their attack class, then output the consolidated JSON findings.`;
111
+
112
+ const jsonInstruction = '\n\nOutput a JSON object with exactly these keys: {"findings":[{"agentId":"<agent-id>","file":"<relative-path>","line":<number>,"severity":"critical|high|medium|low","rule":"<rule-id>","title":"<title>","description":"<description>","remediation":"<fix>"}],"agentSummary":[{"agentId":"<agent-id>","findingCount":<number>,"status":"clean|findings"}]}';
113
+
114
+ const text = await this.provider.complete(systemPrompt, prompt + jsonInstruction, { maxTokens: 8192, jsonMode: true });
115
+ let raw = null;
116
+ try {
117
+ raw = JSON.parse(text || '{}');
118
+ } catch {
119
+ if (this.verbose) console.log(' [Swarm] JSON parse failed. Preview:', text?.slice(0, 200));
120
+ raw = null;
121
+ }
122
+
123
+ return this._mapFindings(raw?.findings ?? [], rootPath);
124
+ }
125
+
126
+ _bundleCode(rootPath, files) {
127
+ let bundle = '';
128
+ let totalChars = 0;
129
+ const selected = files.slice(0, MAX_FILES);
130
+
131
+ for (const filePath of selected) {
132
+ if (totalChars >= MAX_FILE_CHARS) break;
133
+ try {
134
+ const relPath = path.relative(rootPath, filePath);
135
+ const content = fs.readFileSync(filePath, 'utf-8');
136
+ const snippet = content.slice(0, Math.min(8000, MAX_FILE_CHARS - totalChars));
137
+ bundle += `\n\n### ${relPath}\n\`\`\`\n${snippet}\n\`\`\``;
138
+ totalChars += snippet.length;
139
+ } catch { /* skip unreadable */ }
140
+ }
141
+
142
+ return bundle;
143
+ }
144
+
145
+ _buildSwarmPrompt(recon, codeBundle, rootPath) {
146
+ const projectName = path.basename(rootPath);
147
+ const reconSummary = recon
148
+ ? [
149
+ recon.frameworks?.length ? `Frameworks: ${recon.frameworks.join(', ')}` : '',
150
+ recon.databases?.length ? `Databases: ${recon.databases.join(', ')}` : '',
151
+ recon.authPatterns?.length ? `Auth patterns: ${recon.authPatterns.join(', ')}` : '',
152
+ recon.languages?.length ? `Languages: ${recon.languages.join(', ')}` : '',
153
+ ].filter(Boolean).join('\n')
154
+ : '';
155
+
156
+ const agentList = SWARM_ROLES.map((r, i) =>
157
+ ` Sub-agent ${String(i + 1).padStart(2, '0')} [${r.id}] — ${r.name}: ${r.desc}`
158
+ ).join('\n');
159
+
160
+ return `# Security Swarm Task: ${projectName}
161
+
162
+ ## Project Context
163
+ ${reconSummary || 'No recon data available.'}
164
+
165
+ ## Sub-Agent Assignments
166
+ Deploy all ${SWARM_ROLES.length} sub-agents in parallel. Each scans for exactly their assigned attack class:
167
+
168
+ ${agentList}
169
+
170
+ ## Instructions
171
+ 1. Each sub-agent independently analyzes the full codebase for its attack class.
172
+ 2. For each finding, record: agentId (the sub-agent's id), file path, line number, severity, a rule identifier, title, description, the matched snippet, and remediation advice.
173
+ 3. Severity scale: critical (exploitable now), high (likely exploitable), medium (potential issue), low (best practice), info (note).
174
+ 4. Report all findings from all sub-agents in the tool call, even if the list is long.
175
+ 5. If a sub-agent finds nothing, include it in agentSummary with status "clean" and findingCount 0.
176
+
177
+ ## Codebase
178
+ ${codeBundle}`;
179
+ }
180
+
181
+ _mapFindings(rawFindings, rootPath) {
182
+ return rawFindings.map(r => {
183
+ const role = SWARM_ROLES.find(a => a.id === r.agentId) || { name: 'SwarmAgent', id: r.agentId };
184
+ return createFinding({
185
+ file: r.file ? path.resolve(rootPath, r.file) : null,
186
+ line: r.line || 0,
187
+ severity: r.severity || 'medium',
188
+ confidence: 'medium',
189
+ rule: r.rule || `swarm:${role.id}`,
190
+ title: r.title,
191
+ description: r.description,
192
+ matched: r.matched || '',
193
+ remediation: r.remediation || '',
194
+ category: role.name,
195
+ });
196
+ });
197
+ }
198
+ }
199
+
200
+ export default SwarmOrchestrator;
@@ -0,0 +1,303 @@
1
+ /**
2
+ * VerifierAgent — Second-Pass Finding Confirmation
3
+ * ==================================================
4
+ *
5
+ * Runs after all agents complete. Takes high-confidence findings
6
+ * and attempts to confirm or downgrade them by analyzing surrounding
7
+ * code context.
8
+ *
9
+ * Checks:
10
+ * - Is the flagged value static/hardcoded or dynamic (from user input)?
11
+ * - Is there upstream sanitization or validation?
12
+ * - Is the code inside error handling that neutralizes it?
13
+ * - Is the finding in dead/unreachable code?
14
+ *
15
+ * Impact: Unverified findings get downgraded one confidence level.
16
+ */
17
+
18
+ import fs from 'fs';
19
+ import path from 'path';
20
+ import { ASTParser, ScopeTree, TaintTracker, GuardrailDetector } from '../core/ast/index.js';
21
+
22
+ // =============================================================================
23
+ // HEURISTIC PATTERNS
24
+ // =============================================================================
25
+
26
+ /** Sources of user input — if a finding's matched code references these, it's more likely real */
27
+ const USER_INPUT_SOURCES = [
28
+ /req\.body/,
29
+ /req\.query/,
30
+ /req\.params/,
31
+ /req\.headers/,
32
+ /request\.body/,
33
+ /request\.query/,
34
+ /request\.params/,
35
+ /request\.form/,
36
+ /request\.args/,
37
+ /request\.json/,
38
+ /ctx\.request/,
39
+ /ctx\.query/,
40
+ /ctx\.params/,
41
+ /event\.body/,
42
+ /event\.queryStringParameters/,
43
+ /searchParams/,
44
+ /formData/,
45
+ /userinput/i,
46
+ /user_input/i,
47
+ /input\s*\(/,
48
+ /argv/,
49
+ /process\.env/,
50
+ /getenv/,
51
+ ];
52
+
53
+ /** Sanitization/validation indicators — presence near a finding suggests it's protected */
54
+ export const SANITIZATION_PATTERNS = [
55
+ /sanitize/i,
56
+ /validate/i,
57
+ /escape/i,
58
+ /purify/i,
59
+ /DOMPurify/,
60
+ /xss\s*\(/i,
61
+ /htmlencode/i,
62
+ /encodeURI/,
63
+ /encodeURIComponent/,
64
+ /parameterized/i,
65
+ /prepared\s*statement/i,
66
+ /placeholder/i,
67
+ /\?\s*,/,
68
+ /\$\d+/,
69
+ /bindParam/i,
70
+ /bindValue/i,
71
+ /zod/i,
72
+ /yup/i,
73
+ /joi\./i,
74
+ /ajv/i,
75
+ /schema\.parse/i,
76
+ /safeParse/i,
77
+ /validator\./i,
78
+ /parseInt\s*\(/,
79
+ /parseFloat\s*\(/,
80
+ /Number\s*\(/,
81
+ /\.trim\s*\(/,
82
+ /\.replace\s*\(/,
83
+ /allowlist/i,
84
+ /whitelist/i,
85
+ /blocklist/i,
86
+ /blacklist/i,
87
+ ];
88
+
89
+ /** Error handling wrappers — findings inside these are less exploitable */
90
+ const ERROR_HANDLING_PATTERNS = [
91
+ /}\s*catch\s*\(/,
92
+ /\.catch\s*\(/,
93
+ /try\s*\{/,
94
+ /if\s*\(\s*err/,
95
+ /on\s*\(\s*['"]error['"]/,
96
+ /\.on\s*\(\s*['"]error['"]/,
97
+ ];
98
+
99
+ /** Static/hardcoded value indicators — finding uses a constant, not user input */
100
+ const STATIC_VALUE_PATTERNS = [
101
+ /['"][^'"]{0,200}['"]/,
102
+ /const\s+\w+\s*=\s*['"][^'"]*['"]/,
103
+ /^\s*\/\//,
104
+ /^\s*\*/,
105
+ /^\s*#/,
106
+ /TODO|FIXME|HACK|NOTE/,
107
+ ];
108
+
109
+ /** Dead code indicators */
110
+ const DEAD_CODE_PATTERNS = [
111
+ /return\s+/,
112
+ /throw\s+/,
113
+ /process\.exit/,
114
+ /^\s*\/\//,
115
+ ];
116
+
117
+ // =============================================================================
118
+ // VERIFIER AGENT
119
+ // =============================================================================
120
+
121
+ export class VerifierAgent {
122
+ constructor() {
123
+ this.name = 'VerifierAgent';
124
+ this.description = 'Second-pass verification of findings';
125
+ }
126
+
127
+ /**
128
+ * Verify an array of findings by analyzing surrounding code context.
129
+ * Returns findings with added `verified` and `verifierNote` fields.
130
+ *
131
+ * @param {object[]} findings — Findings from all agents (post-dedup)
132
+ * @param {object} options — { verbose }
133
+ * @returns {object[]} — Findings with verification metadata
134
+ */
135
+ verify(findings, options = {}) {
136
+ const fileCache = new Map();
137
+ const astCache = new Map();
138
+
139
+ for (const finding of findings) {
140
+ // Only verify critical and high severity findings
141
+ if (finding.severity !== 'critical' && finding.severity !== 'high') {
142
+ finding.verified = null; // not checked
143
+ continue;
144
+ }
145
+
146
+ const result = this._verifyFinding(finding, fileCache, astCache);
147
+ finding.verified = result.verified;
148
+ finding.verifierNote = result.note;
149
+
150
+ // Downgrade unverified findings one confidence level
151
+ if (!result.verified) {
152
+ if (finding.confidence === 'high') finding.confidence = 'medium';
153
+ else if (finding.confidence === 'medium') finding.confidence = 'low';
154
+ }
155
+ }
156
+
157
+ return findings;
158
+ }
159
+
160
+ /**
161
+ * Verify a single finding by reading surrounding code and running AST taint analysis.
162
+ */
163
+ _verifyFinding(finding, fileCache, astCache = new Map()) {
164
+ const { file, line, matched } = finding;
165
+ if (!file || !line) {
166
+ return { verified: null, note: 'Missing file or line info' };
167
+ }
168
+
169
+ // Read the file (cached)
170
+ let content;
171
+ let lines;
172
+ let scopeTree;
173
+ if (fileCache.has(file)) {
174
+ content = fileCache.get(file);
175
+ lines = content.split('\n');
176
+ scopeTree = astCache.get(file);
177
+ } else {
178
+ try {
179
+ content = fs.readFileSync(file, 'utf-8');
180
+ lines = content.split('\n');
181
+ fileCache.set(file, content);
182
+
183
+ const parsed = ASTParser.parse(content, file);
184
+ scopeTree = ScopeTree.build(parsed.ast, content);
185
+ astCache.set(file, scopeTree);
186
+ } catch {
187
+ return { verified: null, note: 'Could not read file' };
188
+ }
189
+ }
190
+
191
+ // ── AST Step 1: AI Guardrail / Defense framework check ──────────────────
192
+ const protection = GuardrailDetector.checkProtection(finding, content);
193
+ if (protection.isProtected) {
194
+ return {
195
+ verified: false,
196
+ note: `Mitigated: ${protection.reason}`,
197
+ };
198
+ }
199
+
200
+ // ── AST Step 2: Dataflow & Taint Tracker Evaluation ──────────────────────
201
+ const taintEval = TaintTracker.evaluateFinding({
202
+ file,
203
+ line,
204
+ matched,
205
+ code: content,
206
+ scopeTree,
207
+ });
208
+
209
+ if (taintEval.isSanitized) {
210
+ return {
211
+ verified: false,
212
+ note: taintEval.reason || 'Sanitization detected in AST scope',
213
+ };
214
+ }
215
+
216
+ if (taintEval.isStatic) {
217
+ return {
218
+ verified: false,
219
+ note: taintEval.reason || 'Value appears to be static constant',
220
+ };
221
+ }
222
+
223
+ // Get scope boundaries
224
+ const scope = scopeTree ? scopeTree.getScopeAt(line) : null;
225
+ const startLine = scope ? Math.max(0, scope.range.startLine - 1) : Math.max(0, line - 16);
226
+ const endLine = scope ? Math.min(lines.length, scope.range.endLine) : Math.min(lines.length, line + 15);
227
+ const windowText = lines.slice(startLine, endLine).join('\n');
228
+ const beforeText = lines.slice(startLine, Math.max(startLine, line - 1)).join('\n');
229
+ const findingLine = lines[line - 1] || '';
230
+
231
+ // ── Check 3: Is dead code? ──────────────────────────────────────────────
232
+ const isDeadCode = this._isDeadCode(lines, line);
233
+ if (isDeadCode) {
234
+ return {
235
+ verified: false,
236
+ note: 'Finding appears to be in unreachable code (after return/throw)',
237
+ };
238
+ }
239
+
240
+ // ── Check 4: Error handling wrapper ─────────────────────────────────────
241
+ const inErrorHandler = ERROR_HANDLING_PATTERNS.some(p => p.test(beforeText));
242
+ if (inErrorHandler) {
243
+ return {
244
+ verified: false,
245
+ note: 'Finding is inside error handling context, reducing exploitability',
246
+ };
247
+ }
248
+
249
+ // ── Check 5: Confirmed user input ───────────────────────────────────────
250
+ if (taintEval.isTainted || USER_INPUT_SOURCES.some(p => p.test(windowText))) {
251
+ return {
252
+ verified: true,
253
+ note: 'User input flows to this sink within enclosing scope',
254
+ };
255
+ }
256
+
257
+ // Default: cannot determine, keep as-is
258
+ return {
259
+ verified: null,
260
+ note: 'Could not determine verification status from code context',
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Check if the matched code is using a static/hardcoded value.
266
+ */
267
+ _isStaticValue(line, matched) {
268
+ // If the finding line is a comment, it's static
269
+ if (/^\s*(?:\/\/|#|\*|\/\*)/.test(line)) return true;
270
+
271
+ // If the matched text is just a string literal with no interpolation
272
+ if (/^['"][^'"]*['"]$/.test(matched)) return true;
273
+
274
+ // If the line is a const assignment to a string literal
275
+ if (/const\s+\w+\s*=\s*['"][^'"]*['"]/.test(line)) return true;
276
+
277
+ // If it looks like a TODO/placeholder comment
278
+ if (/TODO|FIXME|EXAMPLE|PLACEHOLDER|SAMPLE/i.test(line)) return true;
279
+
280
+ return false;
281
+ }
282
+
283
+ /**
284
+ * Check if a line is after a return/throw (dead code).
285
+ */
286
+ _isDeadCode(lines, lineNum) {
287
+ // Check the 5 lines before the finding for return/throw
288
+ for (let i = Math.max(0, lineNum - 6); i < lineNum - 1; i++) {
289
+ const l = lines[i]?.trim() || '';
290
+ // If a return/throw is found and there's no conditional/block opener after
291
+ if (/^(?:return\s|throw\s|process\.exit)/.test(l)) {
292
+ // Check if there's a } or else between the return and our line
293
+ const between = lines.slice(i + 1, lineNum - 1).join('\n');
294
+ if (!/[{}]|else|case/.test(between)) {
295
+ return true;
296
+ }
297
+ }
298
+ }
299
+ return false;
300
+ }
301
+ }
302
+
303
+ export default VerifierAgent;