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