fivosense 0.1.4 → 0.1.6

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 (56) hide show
  1. package/COMPLETE_SUMMARY.md +412 -0
  2. package/DOCUMENTATION.md +608 -0
  3. package/FINAL_VERIFICATION.md +316 -0
  4. package/README.md +198 -316
  5. package/VERIFICATION_CHECKLIST.md +307 -0
  6. package/dist/ai/client.d.ts +27 -0
  7. package/dist/ai/client.d.ts.map +1 -0
  8. package/dist/ai/client.js +167 -0
  9. package/dist/ai/client.js.map +1 -0
  10. package/dist/ai/judge.d.ts +3 -3
  11. package/dist/ai/judge.d.ts.map +1 -1
  12. package/dist/ai/judge.js +43 -14
  13. package/dist/ai/judge.js.map +1 -1
  14. package/dist/cli/index.js +48 -7
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/core/orchestrator.d.ts +31 -0
  17. package/dist/core/orchestrator.d.ts.map +1 -0
  18. package/dist/core/orchestrator.js +205 -0
  19. package/dist/core/orchestrator.js.map +1 -0
  20. package/dist/core/scope.d.ts +29 -0
  21. package/dist/core/scope.d.ts.map +1 -0
  22. package/dist/core/scope.js +143 -0
  23. package/dist/core/scope.js.map +1 -0
  24. package/dist/engine/adversary.d.ts +3 -2
  25. package/dist/engine/adversary.d.ts.map +1 -1
  26. package/dist/engine/adversary.js +43 -12
  27. package/dist/engine/adversary.js.map +1 -1
  28. package/dist/engine/poc.d.ts +20 -0
  29. package/dist/engine/poc.d.ts.map +1 -0
  30. package/dist/engine/poc.js +176 -0
  31. package/dist/engine/poc.js.map +1 -0
  32. package/dist/features/index.d.ts +7 -0
  33. package/dist/features/index.d.ts.map +1 -0
  34. package/dist/features/index.js +7 -0
  35. package/dist/features/index.js.map +1 -0
  36. package/dist/hooks/git.d.ts +31 -0
  37. package/dist/hooks/git.d.ts.map +1 -0
  38. package/dist/hooks/git.js +155 -0
  39. package/dist/hooks/git.js.map +1 -0
  40. package/mcp/index.js +48 -20
  41. package/mcp/package-lock.json +382 -0
  42. package/mcp/package.json +1 -1
  43. package/package.json +1 -1
  44. package/src/ai/client.ts +219 -0
  45. package/src/ai/judge.ts +51 -14
  46. package/src/cli/index.ts +46 -7
  47. package/src/core/orchestrator.ts +259 -0
  48. package/src/core/scope.ts +168 -0
  49. package/src/engine/adversary.ts +48 -12
  50. package/src/engine/poc.ts +212 -0
  51. package/src/features/index.ts +7 -0
  52. package/src/hooks/git.ts +187 -0
  53. package/vscode-extension/fivosense-vscode-0.1.0.vsix +0 -0
  54. package/vscode-extension/package-lock.json +4 -4
  55. package/vscode-extension/package.json +3 -3
  56. package/vscode-extension/src/extension.ts +65 -11
package/src/ai/judge.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  * AI Path Judge - Uses host AI to determine exploitability
3
3
  */
4
4
 
5
+ import { callAI, getAIProviderFromEnv, type AIProvider } from './client.js';
6
+
5
7
  export interface PathJudgment {
6
8
  exploitable: boolean;
7
9
  confidence: number;
@@ -80,21 +82,56 @@ export function parsePathJudgment(response: string): PathJudgment | null {
80
82
  }
81
83
 
82
84
  /**
83
- * Placeholder for host AI integration
84
- * In Phase 2, this will call the actual host AI (Claude/etc.)
85
+ * Judge path exploitability using AI
85
86
  */
86
- export async function judgePathWithAI(context: PathContext): Promise<PathJudgment> {
87
- const prompt = buildPathJudgePrompt(context);
87
+ export async function judgePathWithAI(
88
+ context: PathContext,
89
+ provider?: AIProvider
90
+ ): Promise<PathJudgment> {
91
+ // Get provider from env if not provided
92
+ const aiProvider = provider || getAIProviderFromEnv();
88
93
 
89
- // TODO: Phase 2 - integrate with host AI
90
- // For now, return a conservative judgment
91
- console.warn('⚠️ AI path judgment not yet integrated - using conservative defaults');
94
+ // If no AI provider available, return conservative judgment
95
+ if (!aiProvider) {
96
+ console.warn('⚠️ No AI provider configured - using conservative defaults');
97
+ console.warn('💡 Set OPENAI_API_KEY, ANTHROPIC_API_KEY, or OLLAMA_HOST to enable AI judgment');
98
+
99
+ return {
100
+ exploitable: true, // Conservative: assume exploitable
101
+ confidence: 0.7,
102
+ reasoning: 'AI judgment not configured - marked as potentially exploitable',
103
+ severity: 'high',
104
+ recommendation: 'Configure AI provider or review manually',
105
+ };
106
+ }
92
107
 
93
- return {
94
- exploitable: true, // Conservative: assume exploitable until AI confirms otherwise
95
- confidence: 0.7,
96
- reasoning: 'AI judgment not yet integrated - marked as potentially exploitable',
97
- severity: 'high',
98
- recommendation: 'Manual review required until AI integration complete',
99
- };
108
+ try {
109
+ const prompt = buildPathJudgePrompt(context);
110
+ const response = await callAI(aiProvider, prompt);
111
+
112
+ const judgment = parsePathJudgment(response.text);
113
+
114
+ if (!judgment) {
115
+ console.warn('⚠️ Failed to parse AI response - using conservative defaults');
116
+ return {
117
+ exploitable: true,
118
+ confidence: 0.6,
119
+ reasoning: 'Failed to parse AI response',
120
+ severity: 'high',
121
+ recommendation: 'Review manually',
122
+ };
123
+ }
124
+
125
+ return judgment;
126
+ } catch (error) {
127
+ console.warn(`⚠️ AI judgment failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
128
+
129
+ return {
130
+ exploitable: true,
131
+ confidence: 0.7,
132
+ reasoning: `AI judgment failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
133
+ severity: 'high',
134
+ recommendation: 'Review manually',
135
+ };
136
+ }
100
137
  }
package/src/cli/index.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { auditFile, formatAuditResult } from '../index.js';
7
+ import { generateRoast, formatRoast, generateBadge } from '../features/index.js';
7
8
 
8
9
  const args = process.argv.slice(2);
9
10
 
@@ -12,26 +13,64 @@ if (args.length === 0) {
12
13
  🛡️ FivoSense - Neuro-symbolic AI Security Scanner
13
14
 
14
15
  Usage:
15
- npx fivosense <file>
16
- npx fivosense audit <file>
16
+ fivosense <file> Scan a file for vulnerabilities
17
+ fivosense --roast <file> Get roasted for security issues 🔥
18
+ fivosense --badge <file> Get your security grade badge
17
19
 
18
20
  Example:
19
- npx fivosense src/server.js
21
+ fivosense src/server.js
22
+ fivosense --roast src/api.js
23
+ fivosense --badge src/app.js
20
24
  `);
21
25
  process.exit(0);
22
26
  }
23
27
 
24
- const command = args[0];
25
- const filepath = args[1] || args[0];
28
+ // Parse command and file
29
+ let mode = 'scan';
30
+ let filepath = args[0];
31
+
32
+ if (args[0] === '--roast' || args[0] === '-r') {
33
+ mode = 'roast';
34
+ filepath = args[1];
35
+ } else if (args[0] === '--badge' || args[0] === '-b') {
36
+ mode = 'badge';
37
+ filepath = args[1];
38
+ } else if (args[0] === 'audit') {
39
+ filepath = args[1];
40
+ }
41
+
42
+ if (!filepath) {
43
+ console.error('\n❌ Error: Please provide a file to scan\n');
44
+ process.exit(1);
45
+ }
26
46
 
27
47
  async function main() {
28
48
  try {
29
49
  console.log(`\n🔍 Auditing ${filepath}...\n`);
30
50
 
31
51
  const result = await auditFile(filepath);
32
- const output = formatAuditResult(result);
33
52
 
34
- console.log(output);
53
+ if (mode === 'roast') {
54
+ // Roast mode
55
+ const roast = generateRoast(result);
56
+ const roastText = formatRoast(roast);
57
+ console.log('\n' + roastText + '\n');
58
+ } else if (mode === 'badge') {
59
+ // Badge mode
60
+ const badge = generateBadge(result);
61
+ console.log('\n🛡️ Security Badge\n');
62
+ console.log(`Grade: ${badge.grade}`);
63
+ console.log(`Score: ${badge.score}/100`);
64
+ console.log(`\nFindings:`);
65
+ console.log(` Critical: ${result.summary.critical}`);
66
+ console.log(` High: ${result.summary.high}`);
67
+ console.log(` Medium: ${result.summary.medium}`);
68
+ console.log();
69
+ } else {
70
+ // Normal scan mode
71
+ const output = formatAuditResult(result);
72
+ console.log(output);
73
+ }
35
74
 
36
75
  // Exit with error code if critical/high findings
37
76
  if (result.summary.critical > 0 || result.summary.high > 0) {
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Orchestrator - Coordinates analysis pipeline and flow control
3
+ */
4
+
5
+ import { buildDataFlowGraph } from '../engine/graph.js';
6
+ import { generateTaintTraces, type TaintTrace } from '../engine/taint.js';
7
+ import { detectSecrets } from '../rules/secrets.js';
8
+ import { detectDestructive } from '../rules/destructive.js';
9
+ import { judgePathWithAI } from '../ai/judge.js';
10
+ import { verifyWithAdversary } from '../engine/adversary.js';
11
+ import { generatePoC } from '../engine/poc.js';
12
+ import { getDiffScope, filterFindingsByScope, type CodeScope } from './scope.js';
13
+ import type { AuditResult } from '../index.js';
14
+ import type { AIProvider } from '../ai/client.js';
15
+
16
+ export interface OrchestratorOptions {
17
+ enableAI?: boolean;
18
+ enableAdversarial?: boolean;
19
+ enablePoC?: boolean;
20
+ scopeToDiff?: boolean;
21
+ diffBase?: string;
22
+ aiProvider?: AIProvider;
23
+ verbose?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Main orchestration pipeline
28
+ */
29
+ export async function orchestrateAudit(
30
+ code: string,
31
+ filepath: string,
32
+ options: OrchestratorOptions = {}
33
+ ): Promise<AuditResult> {
34
+ const {
35
+ enableAI = false,
36
+ enableAdversarial = false,
37
+ enablePoC = false,
38
+ scopeToDiff = false,
39
+ diffBase = 'main',
40
+ aiProvider,
41
+ verbose = false,
42
+ } = options;
43
+
44
+ if (verbose) {
45
+ console.log(`🔍 Starting audit: ${filepath}`);
46
+ console.log(` AI Judge: ${enableAI ? '✅' : '❌'}`);
47
+ console.log(` Adversarial: ${enableAdversarial ? '✅' : '❌'}`);
48
+ console.log(` PoC Generation: ${enablePoC ? '✅' : '❌'}`);
49
+ console.log(` Scope to diff: ${scopeToDiff ? '✅' : '❌'}`);
50
+ }
51
+
52
+ // Step 1: Get scope if needed
53
+ let scope: CodeScope | undefined;
54
+ if (scopeToDiff) {
55
+ if (verbose) console.log(`📋 Getting diff scope (base: ${diffBase})...`);
56
+ scope = await getDiffScope(diffBase);
57
+ if (verbose) console.log(` ${scope.files.length} files in scope`);
58
+ }
59
+
60
+ // Step 2: Build data-flow graph
61
+ if (verbose) console.log(`🔨 Building data-flow graph...`);
62
+ const graph = buildDataFlowGraph(code, filepath);
63
+
64
+ // Step 3: Taint analysis
65
+ if (verbose) console.log(`🔍 Running taint analysis...`);
66
+ const traces = generateTaintTraces(graph, filepath);
67
+
68
+ // Convert traces to vulnerabilities format
69
+ const allVulnerabilities = traces.map(trace => ({
70
+ finding: trace.finding,
71
+ severity: trace.severity,
72
+ category: trace.category,
73
+ cwe: trace.cwe,
74
+ path: trace.path.split(' → '),
75
+ evidence: trace.evidence,
76
+ location: trace.location,
77
+ sanitized: trace.sanitized,
78
+ confidence: 0.8,
79
+ }));
80
+
81
+ // Step 4: Filter by scope if enabled
82
+ let vulnerabilities = allVulnerabilities;
83
+ if (scope && scopeToDiff) {
84
+ if (verbose) console.log(`🎯 Filtering by scope...`);
85
+ vulnerabilities = filterFindingsByScope(vulnerabilities, filepath, scope);
86
+ if (verbose) console.log(` ${vulnerabilities.length} vulnerabilities in scope`);
87
+ }
88
+
89
+ // Step 5: AI judgment (if enabled)
90
+ if (enableAI && vulnerabilities.length > 0) {
91
+ if (verbose) console.log(`🤖 Running AI judgment...`);
92
+
93
+ for (let i = 0; i < vulnerabilities.length; i++) {
94
+ const vuln = vulnerabilities[i];
95
+
96
+ try {
97
+ const judgment = await judgePathWithAI({
98
+ source: vuln.path[0] || 'unknown',
99
+ sourceType: 'user input',
100
+ sourceLoc: `line ${vuln.location.line}`,
101
+ sink: vuln.path[vuln.path.length - 1] || 'unknown',
102
+ sinkType: vuln.category,
103
+ category: vuln.category,
104
+ cwe: vuln.cwe,
105
+ dataFlow: vuln.path.join(' → '),
106
+ codeSnippet: code.split('\n').slice(
107
+ Math.max(0, vuln.location.line - 3),
108
+ vuln.location.line + 2
109
+ ).join('\n'),
110
+ language: filepath.endsWith('.ts') || filepath.endsWith('.tsx') ? 'typescript' : 'javascript',
111
+ }, aiProvider);
112
+
113
+ // Update vulnerability with AI judgment
114
+ vuln.confidence = judgment.confidence;
115
+
116
+ if (verbose) {
117
+ console.log(` [${i + 1}/${vulnerabilities.length}] ${vuln.finding}: ${judgment.exploitable ? '❌ Exploitable' : '✅ Safe'} (confidence: ${judgment.confidence})`);
118
+ }
119
+ } catch (error) {
120
+ if (verbose) {
121
+ console.warn(` [${i + 1}/${vulnerabilities.length}] AI judgment failed:`, error);
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ // Step 6: Adversarial verification (if enabled)
128
+ if (enableAdversarial && vulnerabilities.length > 0) {
129
+ if (verbose) console.log(`⚔️ Running adversarial verification...`);
130
+
131
+ for (let i = 0; i < vulnerabilities.length; i++) {
132
+ const vuln = vulnerabilities[i];
133
+
134
+ try {
135
+ const trace = traces.find((t: TaintTrace) =>
136
+ t.category === vuln.category && t.location.line === vuln.location.line
137
+ );
138
+
139
+ if (trace) {
140
+ const adversary = await verifyWithAdversary(trace, code, aiProvider);
141
+
142
+ // Update confidence based on adversarial result
143
+ vuln.confidence = Math.min(vuln.confidence, adversary.confidence);
144
+
145
+ if (verbose) {
146
+ console.log(` [${i + 1}/${vulnerabilities.length}] ${adversary.exploitable ? '⚔️ Exploit found' : '🛡️ Defense holds'}`);
147
+ }
148
+ }
149
+ } catch (error) {
150
+ if (verbose) {
151
+ console.warn(` [${i + 1}/${vulnerabilities.length}] Adversarial verification failed:`, error);
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ // Step 7: PoC generation (if enabled)
158
+ if (enablePoC && vulnerabilities.length > 0) {
159
+ if (verbose) console.log(`💣 Generating PoCs...`);
160
+
161
+ for (const vuln of vulnerabilities) {
162
+ try {
163
+ const trace = traces.find((t: TaintTrace) =>
164
+ t.category === vuln.category && t.location.line === vuln.location.line
165
+ );
166
+
167
+ if (trace) {
168
+ const poc = generatePoC(trace);
169
+ // Attach PoC to vulnerability (could extend Vulnerability type)
170
+ (vuln as any).poc = poc;
171
+ }
172
+ } catch (error) {
173
+ if (verbose) {
174
+ console.warn(` Failed to generate PoC:`, error);
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ // Step 8: Secret detection
181
+ if (verbose) console.log(`🔑 Detecting secrets...`);
182
+ const secrets = detectSecrets(code);
183
+
184
+ // Step 9: Destructive command detection
185
+ if (verbose) console.log(`💥 Detecting destructive commands...`);
186
+ const destructive = detectDestructive(code);
187
+
188
+ // Step 10: Build summary
189
+ const summary = {
190
+ total: vulnerabilities.length + secrets.length + destructive.length,
191
+ critical: vulnerabilities.filter((v: any) => v.severity === 'critical').length,
192
+ high: vulnerabilities.filter((v: any) => v.severity === 'high').length + secrets.length,
193
+ medium: vulnerabilities.filter((v: any) => v.severity === 'medium').length + destructive.length,
194
+ };
195
+
196
+ if (verbose) {
197
+ console.log(`\n📊 Audit complete:`);
198
+ console.log(` Total: ${summary.total}`);
199
+ console.log(` Critical: ${summary.critical}`);
200
+ console.log(` High: ${summary.high}`);
201
+ console.log(` Medium: ${summary.medium}\n`);
202
+ }
203
+
204
+ return {
205
+ vulnerabilities,
206
+ secrets,
207
+ destructive,
208
+ summary,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Quick audit (no AI, no scope)
214
+ */
215
+ export async function quickAudit(code: string, filepath: string): Promise<AuditResult> {
216
+ return orchestrateAudit(code, filepath, {
217
+ enableAI: false,
218
+ enableAdversarial: false,
219
+ enablePoC: false,
220
+ scopeToDiff: false,
221
+ verbose: false,
222
+ });
223
+ }
224
+
225
+ /**
226
+ * Full audit (with AI and adversarial)
227
+ */
228
+ export async function fullAudit(
229
+ code: string,
230
+ filepath: string,
231
+ aiProvider?: AIProvider
232
+ ): Promise<AuditResult> {
233
+ return orchestrateAudit(code, filepath, {
234
+ enableAI: true,
235
+ enableAdversarial: true,
236
+ enablePoC: true,
237
+ scopeToDiff: false,
238
+ aiProvider,
239
+ verbose: true,
240
+ });
241
+ }
242
+
243
+ /**
244
+ * Diff-scoped audit (only changed code)
245
+ */
246
+ export async function diffAudit(
247
+ code: string,
248
+ filepath: string,
249
+ diffBase: string = 'main'
250
+ ): Promise<AuditResult> {
251
+ return orchestrateAudit(code, filepath, {
252
+ enableAI: false,
253
+ enableAdversarial: false,
254
+ enablePoC: false,
255
+ scopeToDiff: true,
256
+ diffBase,
257
+ verbose: true,
258
+ });
259
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Scope Management - Track and filter relevant code changes
3
+ */
4
+
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+
8
+ const execAsync = promisify(exec);
9
+
10
+ export interface CodeScope {
11
+ files: string[];
12
+ lines: Map<string, Set<number>>;
13
+ changedFunctions: Map<string, string[]>;
14
+ }
15
+
16
+ /**
17
+ * Get diff scope for current changes
18
+ */
19
+ export async function getDiffScope(base: string = 'main'): Promise<CodeScope> {
20
+ const scope: CodeScope = {
21
+ files: [],
22
+ lines: new Map(),
23
+ changedFunctions: new Map(),
24
+ };
25
+
26
+ try {
27
+ // Get changed files
28
+ const { stdout: filesOutput } = await execAsync(`git diff --name-only ${base}...HEAD`);
29
+ const files = filesOutput
30
+ .split('\n')
31
+ .filter(f => f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.jsx') || f.endsWith('.tsx'))
32
+ .filter(f => f.trim().length > 0);
33
+
34
+ scope.files = files;
35
+
36
+ // Get changed lines for each file
37
+ for (const file of files) {
38
+ try {
39
+ const { stdout: diffOutput } = await execAsync(`git diff ${base}...HEAD -- ${file}`);
40
+ const changedLines = parseDiffLines(diffOutput);
41
+ scope.lines.set(file, changedLines);
42
+ } catch (error) {
43
+ console.warn(`Failed to get diff for ${file}:`, error);
44
+ }
45
+ }
46
+
47
+ return scope;
48
+ } catch (error) {
49
+ console.warn('Failed to get diff scope:', error);
50
+ return scope;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Parse diff output to extract changed line numbers
56
+ */
57
+ function parseDiffLines(diff: string): Set<number> {
58
+ const lines = new Set<number>();
59
+ const diffLines = diff.split('\n');
60
+
61
+ let currentLine = 0;
62
+
63
+ for (const line of diffLines) {
64
+ // Parse hunk header: @@ -1,5 +1,7 @@
65
+ const hunkMatch = line.match(/^@@\s+-\d+,?\d*\s+\+(\d+),?(\d*)\s+@@/);
66
+ if (hunkMatch) {
67
+ currentLine = parseInt(hunkMatch[1], 10);
68
+ continue;
69
+ }
70
+
71
+ // Track added/modified lines
72
+ if (line.startsWith('+') && !line.startsWith('+++')) {
73
+ lines.add(currentLine);
74
+ currentLine++;
75
+ } else if (!line.startsWith('-')) {
76
+ currentLine++;
77
+ }
78
+ }
79
+
80
+ return lines;
81
+ }
82
+
83
+ /**
84
+ * Filter findings to only those in changed scope
85
+ */
86
+ export function filterFindingsByScope<T extends { location?: { line: number } }>(
87
+ findings: T[],
88
+ file: string,
89
+ scope: CodeScope
90
+ ): T[] {
91
+ const changedLines = scope.lines.get(file);
92
+
93
+ if (!changedLines || changedLines.size === 0) {
94
+ // No scope info, include all findings
95
+ return findings;
96
+ }
97
+
98
+ return findings.filter(finding => {
99
+ const line = finding.location?.line;
100
+ if (!line) return false;
101
+
102
+ // Include if exact line changed
103
+ if (changedLines.has(line)) return true;
104
+
105
+ // Include if within 5 lines of a change (context)
106
+ for (const changedLine of changedLines) {
107
+ if (Math.abs(line - changedLine) <= 5) return true;
108
+ }
109
+
110
+ return false;
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Get scope for staged changes
116
+ */
117
+ export async function getStagedScope(): Promise<CodeScope> {
118
+ const scope: CodeScope = {
119
+ files: [],
120
+ lines: new Map(),
121
+ changedFunctions: new Map(),
122
+ };
123
+
124
+ try {
125
+ // Get staged files
126
+ const { stdout: filesOutput } = await execAsync('git diff --cached --name-only --diff-filter=ACM');
127
+ const files = filesOutput
128
+ .split('\n')
129
+ .filter(f => f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.jsx') || f.endsWith('.tsx'))
130
+ .filter(f => f.trim().length > 0);
131
+
132
+ scope.files = files;
133
+
134
+ // Get changed lines for each file
135
+ for (const file of files) {
136
+ try {
137
+ const { stdout: diffOutput } = await execAsync(`git diff --cached -- ${file}`);
138
+ const changedLines = parseDiffLines(diffOutput);
139
+ scope.lines.set(file, changedLines);
140
+ } catch (error) {
141
+ console.warn(`Failed to get staged diff for ${file}:`, error);
142
+ }
143
+ }
144
+
145
+ return scope;
146
+ } catch (error) {
147
+ console.warn('Failed to get staged scope:', error);
148
+ return scope;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Check if a line is in scope
154
+ */
155
+ export function isLineInScope(file: string, line: number, scope: CodeScope): boolean {
156
+ const changedLines = scope.lines.get(file);
157
+ if (!changedLines) return false;
158
+
159
+ // Exact match
160
+ if (changedLines.has(line)) return true;
161
+
162
+ // Context match (within 5 lines)
163
+ for (const changedLine of changedLines) {
164
+ if (Math.abs(line - changedLine) <= 5) return true;
165
+ }
166
+
167
+ return false;
168
+ }
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { TaintTrace } from './taint.js';
6
+ import { callAI, getAIProviderFromEnv, type AIProvider } from '../ai/client.js';
6
7
 
7
8
  export interface AdversarialResult {
8
9
  exploitable: boolean;
@@ -79,22 +80,57 @@ export function parseAdversarialResult(response: string): AdversarialResult | nu
79
80
  }
80
81
 
81
82
  /**
82
- * Placeholder for adversarial verification
83
+ * Verify exploitability using adversarial AI
83
84
  */
84
85
  export async function verifyWithAdversary(
85
86
  trace: TaintTrace,
86
- code: string
87
+ code: string,
88
+ provider?: AIProvider
87
89
  ): Promise<AdversarialResult> {
88
- const prompt = buildAdversarialPrompt(trace, code);
90
+ // Get provider from env if not provided
91
+ const aiProvider = provider || getAIProviderFromEnv();
89
92
 
90
- // TODO: Phase 3 - integrate with host AI
91
- console.warn('⚠️ Adversarial verification not yet integrated');
93
+ // If no AI provider available, return conservative result
94
+ if (!aiProvider) {
95
+ console.warn('⚠️ No AI provider configured for adversarial verification');
96
+ console.warn('💡 Set OPENAI_API_KEY, ANTHROPIC_API_KEY, or OLLAMA_HOST to enable');
97
+
98
+ return {
99
+ exploitable: true,
100
+ confidence: 0.7,
101
+ attackVector: 'Adversarial verification not configured',
102
+ payload: '',
103
+ reasoning: 'Marked as potentially exploitable - configure AI provider to verify',
104
+ };
105
+ }
92
106
 
93
- return {
94
- exploitable: true,
95
- confidence: 0.7,
96
- attackVector: 'Adversarial verification not yet integrated',
97
- payload: '',
98
- reasoning: 'Marked as potentially exploitable until AI attacker confirms',
99
- };
107
+ try {
108
+ const prompt = buildAdversarialPrompt(trace, code);
109
+ const response = await callAI(aiProvider, prompt);
110
+
111
+ const result = parseAdversarialResult(response.text);
112
+
113
+ if (!result) {
114
+ console.warn('⚠️ Failed to parse adversarial response');
115
+ return {
116
+ exploitable: true,
117
+ confidence: 0.6,
118
+ attackVector: 'Failed to parse AI response',
119
+ payload: '',
120
+ reasoning: 'Parser error - marked as potentially exploitable',
121
+ };
122
+ }
123
+
124
+ return result;
125
+ } catch (error) {
126
+ console.warn(`⚠️ Adversarial verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
127
+
128
+ return {
129
+ exploitable: true,
130
+ confidence: 0.7,
131
+ attackVector: `AI verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
132
+ payload: '',
133
+ reasoning: 'Marked as potentially exploitable due to verification error',
134
+ };
135
+ }
100
136
  }