fivosense 0.1.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/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- package/.github/workflows/ci.yml +52 -0
- package/BLUEPRINT.md +215 -0
- package/BUILD_PLAN.md +175 -0
- package/CONTRIBUTING.md +80 -0
- package/DOCS_VERIFICATION.md +232 -0
- package/FINAL_CHECKLIST.md +263 -0
- package/FINAL_SUMMARY.md +238 -0
- package/GITHUB_PUSH.md +64 -0
- package/LICENSE +21 -0
- package/PROGRESS.md +153 -0
- package/README.md +443 -0
- package/RELEASE_READY.md +201 -0
- package/SECURITY.md +211 -0
- package/SECURITY_DEEP_AUDIT.md +331 -0
- package/TODO.md +52 -0
- package/dist/ai/judge.d.ts +36 -0
- package/dist/ai/judge.d.ts.map +1 -0
- package/dist/ai/judge.js +75 -0
- package/dist/ai/judge.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +39 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/editors/vscode.d.ts +30 -0
- package/dist/editors/vscode.d.ts.map +1 -0
- package/dist/editors/vscode.js +103 -0
- package/dist/editors/vscode.js.map +1 -0
- package/dist/engine/adversary.d.ts +24 -0
- package/dist/engine/adversary.d.ts.map +1 -0
- package/dist/engine/adversary.js +83 -0
- package/dist/engine/adversary.js.map +1 -0
- package/dist/engine/graph.d.ts +38 -0
- package/dist/engine/graph.d.ts.map +1 -0
- package/dist/engine/graph.js +131 -0
- package/dist/engine/graph.js.map +1 -0
- package/dist/engine/reach.d.ts +22 -0
- package/dist/engine/reach.d.ts.map +1 -0
- package/dist/engine/reach.js +107 -0
- package/dist/engine/reach.js.map +1 -0
- package/dist/engine/sinks.d.ts +52 -0
- package/dist/engine/sinks.d.ts.map +1 -0
- package/dist/engine/sinks.js +96 -0
- package/dist/engine/sinks.js.map +1 -0
- package/dist/engine/sources.d.ts +35 -0
- package/dist/engine/sources.d.ts.map +1 -0
- package/dist/engine/sources.js +59 -0
- package/dist/engine/sources.js.map +1 -0
- package/dist/engine/taint.d.ts +37 -0
- package/dist/engine/taint.d.ts.map +1 -0
- package/dist/engine/taint.js +83 -0
- package/dist/engine/taint.js.map +1 -0
- package/dist/engine/verify.d.ts +20 -0
- package/dist/engine/verify.d.ts.map +1 -0
- package/dist/engine/verify.js +65 -0
- package/dist/engine/verify.js.map +1 -0
- package/dist/features/badge.d.ts +20 -0
- package/dist/features/badge.d.ts.map +1 -0
- package/dist/features/badge.js +86 -0
- package/dist/features/badge.js.map +1 -0
- package/dist/features/fix.d.ts +20 -0
- package/dist/features/fix.d.ts.map +1 -0
- package/dist/features/fix.js +115 -0
- package/dist/features/fix.js.map +1 -0
- package/dist/features/roast.d.ts +23 -0
- package/dist/features/roast.d.ts.map +1 -0
- package/dist/features/roast.js +96 -0
- package/dist/features/roast.js.map +1 -0
- package/dist/hooks/agent.d.ts +19 -0
- package/dist/hooks/agent.d.ts.map +1 -0
- package/dist/hooks/agent.js +69 -0
- package/dist/hooks/agent.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/destructive.d.ts +35 -0
- package/dist/rules/destructive.d.ts.map +1 -0
- package/dist/rules/destructive.js +117 -0
- package/dist/rules/destructive.js.map +1 -0
- package/dist/rules/secrets.d.ts +29 -0
- package/dist/rules/secrets.d.ts.map +1 -0
- package/dist/rules/secrets.js +100 -0
- package/dist/rules/secrets.js.map +1 -0
- package/package.json +56 -0
- package/skill/SKILL.md +86 -0
- package/skill/prompts/path-judge.md +22 -0
- package/src/ai/judge.ts +100 -0
- package/src/cli/index.ts +46 -0
- package/src/editors/vscode.ts +125 -0
- package/src/engine/adversary.ts +100 -0
- package/src/engine/graph.ts +167 -0
- package/src/engine/reach.ts +141 -0
- package/src/engine/sinks.ts +113 -0
- package/src/engine/sources.ts +71 -0
- package/src/engine/taint.ts +117 -0
- package/src/engine/verify.ts +94 -0
- package/src/features/badge.ts +102 -0
- package/src/features/fix.ts +138 -0
- package/src/features/roast.ts +110 -0
- package/src/hooks/agent.ts +84 -0
- package/src/index.ts +147 -0
- package/src/rules/destructive.ts +131 -0
- package/src/rules/secrets.ts +120 -0
- package/test/engine.test.ts +110 -0
- package/test/features.test.ts +131 -0
- package/test/phase3.test.ts +129 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fix Verification - Re-analyze code after fix to check for regressions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { buildDataFlowGraph, getVulnerablePaths } from './graph.js';
|
|
6
|
+
import { generateTaintTraces } from './taint.js';
|
|
7
|
+
|
|
8
|
+
export interface VerificationResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
originalIssues: number;
|
|
11
|
+
remainingIssues: number;
|
|
12
|
+
newIssues: number;
|
|
13
|
+
regression: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Verify that a fix actually resolved the vulnerability
|
|
19
|
+
*/
|
|
20
|
+
export function verifyFix(
|
|
21
|
+
originalCode: string,
|
|
22
|
+
fixedCode: string,
|
|
23
|
+
targetLine: number
|
|
24
|
+
): VerificationResult {
|
|
25
|
+
// Analyze original code
|
|
26
|
+
const originalGraph = buildDataFlowGraph(originalCode);
|
|
27
|
+
const originalTraces = generateTaintTraces(originalGraph, 'original.js');
|
|
28
|
+
const originalVulns = originalTraces.filter(t => !t.sanitized);
|
|
29
|
+
|
|
30
|
+
// Analyze fixed code
|
|
31
|
+
const fixedGraph = buildDataFlowGraph(fixedCode);
|
|
32
|
+
const fixedTraces = generateTaintTraces(fixedGraph, 'fixed.js');
|
|
33
|
+
const fixedVulns = fixedTraces.filter(t => !t.sanitized);
|
|
34
|
+
|
|
35
|
+
// Check for target vulnerability
|
|
36
|
+
const targetVuln = originalVulns.find(v => v.location.line === targetLine);
|
|
37
|
+
const targetFixed = !fixedVulns.find(v => v.location.line === targetLine);
|
|
38
|
+
|
|
39
|
+
// Check for regressions (new vulnerabilities introduced)
|
|
40
|
+
const newIssues = fixedVulns.filter(fv =>
|
|
41
|
+
!originalVulns.some(ov =>
|
|
42
|
+
ov.location.line === fv.location.line &&
|
|
43
|
+
ov.category === fv.category
|
|
44
|
+
)
|
|
45
|
+
).length;
|
|
46
|
+
|
|
47
|
+
const regression = newIssues > 0;
|
|
48
|
+
|
|
49
|
+
let message = '';
|
|
50
|
+
if (targetFixed && !regression) {
|
|
51
|
+
message = '✅ Fix verified: vulnerability resolved, no regressions';
|
|
52
|
+
} else if (targetFixed && regression) {
|
|
53
|
+
message = `⚠️ Fix applied but introduced ${newIssues} new issue(s)`;
|
|
54
|
+
} else if (!targetFixed) {
|
|
55
|
+
message = '❌ Fix failed: vulnerability still present';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
success: targetFixed && !regression,
|
|
60
|
+
originalIssues: originalVulns.length,
|
|
61
|
+
remainingIssues: fixedVulns.length,
|
|
62
|
+
newIssues,
|
|
63
|
+
regression,
|
|
64
|
+
message,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Verify multiple fixes
|
|
70
|
+
*/
|
|
71
|
+
export function verifyMultipleFixes(
|
|
72
|
+
originalCode: string,
|
|
73
|
+
fixedCode: string
|
|
74
|
+
): VerificationResult {
|
|
75
|
+
const originalGraph = buildDataFlowGraph(originalCode);
|
|
76
|
+
const originalTraces = generateTaintTraces(originalGraph, 'original.js');
|
|
77
|
+
const originalVulns = originalTraces.filter(t => !t.sanitized);
|
|
78
|
+
|
|
79
|
+
const fixedGraph = buildDataFlowGraph(fixedCode);
|
|
80
|
+
const fixedTraces = generateTaintTraces(fixedGraph, 'fixed.js');
|
|
81
|
+
const fixedVulns = fixedTraces.filter(t => !t.sanitized);
|
|
82
|
+
|
|
83
|
+
const resolved = originalVulns.length - fixedVulns.length;
|
|
84
|
+
const newIssues = Math.max(0, fixedVulns.length - originalVulns.length);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
success: fixedVulns.length < originalVulns.length && newIssues === 0,
|
|
88
|
+
originalIssues: originalVulns.length,
|
|
89
|
+
remainingIssues: fixedVulns.length,
|
|
90
|
+
newIssues,
|
|
91
|
+
regression: newIssues > 0,
|
|
92
|
+
message: `Resolved ${resolved} issue(s), ${newIssues} new issue(s) introduced`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Badge Generator - Creates shareable security report cards
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { AuditResult } from '../index.js';
|
|
6
|
+
|
|
7
|
+
export interface SecurityBadge {
|
|
8
|
+
grade: string;
|
|
9
|
+
score: number;
|
|
10
|
+
color: string;
|
|
11
|
+
markdown: string;
|
|
12
|
+
shield: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate security badge
|
|
17
|
+
*/
|
|
18
|
+
export function generateBadge(result: AuditResult): SecurityBadge {
|
|
19
|
+
const grade = calculateGrade(result);
|
|
20
|
+
const score = calculateScore(result);
|
|
21
|
+
const color = getGradeColor(grade);
|
|
22
|
+
|
|
23
|
+
const shield = `https://img.shields.io/badge/security-${grade}-${color}`;
|
|
24
|
+
|
|
25
|
+
const markdown = `## 🛡️ Security Report Card
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
**Grade:** ${grade}
|
|
30
|
+
**Score:** ${score}/100
|
|
31
|
+
|
|
32
|
+
### Summary
|
|
33
|
+
- Total Issues: ${result.summary.total}
|
|
34
|
+
- Critical: ${result.summary.critical}
|
|
35
|
+
- High: ${result.summary.high}
|
|
36
|
+
- Medium: ${result.summary.medium}
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
Scanned by [FivoSense](https://github.com/fivosense) — Neuro-symbolic AI security scanner
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
grade,
|
|
44
|
+
score,
|
|
45
|
+
color,
|
|
46
|
+
markdown,
|
|
47
|
+
shield,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Calculate security grade (A+ to F)
|
|
53
|
+
*/
|
|
54
|
+
function calculateGrade(result: AuditResult): string {
|
|
55
|
+
const { summary } = result;
|
|
56
|
+
|
|
57
|
+
if (summary.total === 0) return 'A+';
|
|
58
|
+
if (summary.critical === 0 && summary.high === 0) return 'A';
|
|
59
|
+
if (summary.critical === 0 && summary.high <= 2) return 'B';
|
|
60
|
+
if (summary.critical === 0 && summary.high <= 5) return 'C';
|
|
61
|
+
if (summary.critical <= 1) return 'D';
|
|
62
|
+
return 'F';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Calculate numeric security score (0-100)
|
|
67
|
+
*/
|
|
68
|
+
function calculateScore(result: AuditResult): number {
|
|
69
|
+
const { summary } = result;
|
|
70
|
+
|
|
71
|
+
// Start at 100, deduct for issues
|
|
72
|
+
let score = 100;
|
|
73
|
+
score -= summary.critical * 20; // -20 per critical
|
|
74
|
+
score -= summary.high * 10; // -10 per high
|
|
75
|
+
score -= summary.medium * 5; // -5 per medium
|
|
76
|
+
|
|
77
|
+
return Math.max(0, score);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get color for grade
|
|
82
|
+
*/
|
|
83
|
+
function getGradeColor(grade: string): string {
|
|
84
|
+
const colors: Record<string, string> = {
|
|
85
|
+
'A+': 'brightgreen',
|
|
86
|
+
'A': 'green',
|
|
87
|
+
'B': 'yellowgreen',
|
|
88
|
+
'C': 'yellow',
|
|
89
|
+
'D': 'orange',
|
|
90
|
+
'F': 'red',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return colors[grade] || 'lightgrey';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Generate badge markdown for README
|
|
98
|
+
*/
|
|
99
|
+
export function generateBadgeMarkdown(result: AuditResult): string {
|
|
100
|
+
const badge = generateBadge(result);
|
|
101
|
+
return badge.markdown;
|
|
102
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-fix Generator - Suggests and applies security fixes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { TaintTrace } from '../engine/taint.js';
|
|
6
|
+
|
|
7
|
+
export interface SecurityFix {
|
|
8
|
+
original: string;
|
|
9
|
+
fixed: string;
|
|
10
|
+
explanation: string;
|
|
11
|
+
confidence: number;
|
|
12
|
+
type: 'sanitize' | 'parameterize' | 'encode' | 'validate';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate fix for a vulnerability
|
|
17
|
+
*/
|
|
18
|
+
export function generateFix(trace: TaintTrace, code: string): SecurityFix | null {
|
|
19
|
+
const { category, location } = trace;
|
|
20
|
+
|
|
21
|
+
switch (category) {
|
|
22
|
+
case 'sql':
|
|
23
|
+
return generateSQLFix(trace, code);
|
|
24
|
+
case 'xss':
|
|
25
|
+
return generateXSSFix(trace, code);
|
|
26
|
+
case 'command':
|
|
27
|
+
return generateCommandFix(trace, code);
|
|
28
|
+
default:
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate SQL injection fix
|
|
35
|
+
*/
|
|
36
|
+
function generateSQLFix(trace: TaintTrace, code: string): SecurityFix {
|
|
37
|
+
// Extract the vulnerable line
|
|
38
|
+
const lines = code.split('\n');
|
|
39
|
+
const line = lines[trace.location.line - 1] || '';
|
|
40
|
+
|
|
41
|
+
// Detect query pattern
|
|
42
|
+
if (line.includes('db.execute') || line.includes('db.query')) {
|
|
43
|
+
return {
|
|
44
|
+
original: line.trim(),
|
|
45
|
+
fixed: line.replace(/`([^`]+)`/, (match, query) => {
|
|
46
|
+
// Convert template literal to parameterized query
|
|
47
|
+
const parameterized = query.replace(/\$\{([^}]+)\}/g, '?');
|
|
48
|
+
return `'${parameterized}', [${query.match(/\$\{([^}]+)\}/g)?.map((m: string) => m.slice(2, -1)).join(', ') || ''}]`;
|
|
49
|
+
}),
|
|
50
|
+
explanation: 'Use parameterized queries to prevent SQL injection',
|
|
51
|
+
confidence: 0.9,
|
|
52
|
+
type: 'parameterize',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
original: line.trim(),
|
|
58
|
+
fixed: line.trim(),
|
|
59
|
+
explanation: 'Manual review required - complex SQL pattern',
|
|
60
|
+
confidence: 0.5,
|
|
61
|
+
type: 'validate',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate XSS fix
|
|
67
|
+
*/
|
|
68
|
+
function generateXSSFix(trace: TaintTrace, code: string): SecurityFix {
|
|
69
|
+
const lines = code.split('\n');
|
|
70
|
+
const line = lines[trace.location.line - 1] || '';
|
|
71
|
+
|
|
72
|
+
if (line.includes('res.send')) {
|
|
73
|
+
return {
|
|
74
|
+
original: line.trim(),
|
|
75
|
+
fixed: line.replace(/res\.send\(([^)]+)\)/, 'res.send(escapeHtml($1))'),
|
|
76
|
+
explanation: 'Escape HTML to prevent XSS',
|
|
77
|
+
confidence: 0.85,
|
|
78
|
+
type: 'encode',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (line.includes('innerHTML')) {
|
|
83
|
+
return {
|
|
84
|
+
original: line.trim(),
|
|
85
|
+
fixed: line.replace(/\.innerHTML\s*=/, '.textContent ='),
|
|
86
|
+
explanation: 'Use textContent instead of innerHTML to prevent XSS',
|
|
87
|
+
confidence: 0.9,
|
|
88
|
+
type: 'encode',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
original: line.trim(),
|
|
94
|
+
fixed: line.trim(),
|
|
95
|
+
explanation: 'Manual review required - XSS context unclear',
|
|
96
|
+
confidence: 0.5,
|
|
97
|
+
type: 'encode',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Generate command injection fix
|
|
103
|
+
*/
|
|
104
|
+
function generateCommandFix(trace: TaintTrace, code: string): SecurityFix {
|
|
105
|
+
const lines = code.split('\n');
|
|
106
|
+
const line = lines[trace.location.line - 1] || '';
|
|
107
|
+
|
|
108
|
+
if (line.includes('exec(')) {
|
|
109
|
+
return {
|
|
110
|
+
original: line.trim(),
|
|
111
|
+
fixed: line.replace(/exec\(([^)]+)\)/, 'execFile(command, args)'),
|
|
112
|
+
explanation: 'Use execFile with array arguments to prevent command injection',
|
|
113
|
+
confidence: 0.8,
|
|
114
|
+
type: 'validate',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
original: line.trim(),
|
|
120
|
+
fixed: line.trim(),
|
|
121
|
+
explanation: 'Validate and sanitize all command arguments',
|
|
122
|
+
confidence: 0.6,
|
|
123
|
+
type: 'validate',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Apply fix to code
|
|
129
|
+
*/
|
|
130
|
+
export function applyFix(code: string, fix: SecurityFix, lineNumber: number): string {
|
|
131
|
+
const lines = code.split('\n');
|
|
132
|
+
if (lineNumber < 1 || lineNumber > lines.length) {
|
|
133
|
+
return code;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
lines[lineNumber - 1] = fix.fixed;
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roast Mode - Generates viral, shareable roasts for insecure code
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { AuditResult } from '../index.js';
|
|
6
|
+
|
|
7
|
+
export interface Roast {
|
|
8
|
+
title: string;
|
|
9
|
+
message: string;
|
|
10
|
+
severity: 'mild' | 'spicy' | 'brutal';
|
|
11
|
+
emoji: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate roast based on audit results
|
|
16
|
+
*/
|
|
17
|
+
export function generateRoast(result: AuditResult): Roast {
|
|
18
|
+
const { summary } = result;
|
|
19
|
+
const total = summary.total;
|
|
20
|
+
const critical = summary.critical;
|
|
21
|
+
const high = summary.high;
|
|
22
|
+
|
|
23
|
+
if (total === 0) {
|
|
24
|
+
return {
|
|
25
|
+
title: 'Clean Code Champion',
|
|
26
|
+
message: '🎉 No vulnerabilities found! Your code is cleaner than your browser history.',
|
|
27
|
+
severity: 'mild',
|
|
28
|
+
emoji: '✨',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (critical >= 3) {
|
|
33
|
+
return {
|
|
34
|
+
title: 'Security Nightmare',
|
|
35
|
+
message: `💀 ${critical} critical vulnerabilities? Even script kiddies are embarrassed for you. This code is more open than a 24/7 convenience store.`,
|
|
36
|
+
severity: 'brutal',
|
|
37
|
+
emoji: '💀',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (critical >= 1) {
|
|
42
|
+
return {
|
|
43
|
+
title: 'Living Dangerously',
|
|
44
|
+
message: `🔥 ${critical} critical issue(s) detected. Your code has more holes than Swiss cheese. SQL injection goes brrr.`,
|
|
45
|
+
severity: 'spicy',
|
|
46
|
+
emoji: '🔥',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (high >= 3) {
|
|
51
|
+
return {
|
|
52
|
+
title: 'Almost There',
|
|
53
|
+
message: `⚠️ ${high} high-severity issues. You're one XSS away from trending on HackerNews for the wrong reasons.`,
|
|
54
|
+
severity: 'spicy',
|
|
55
|
+
emoji: '⚠️',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
title: 'Needs Improvement',
|
|
61
|
+
message: `📝 ${total} security issue(s) found. Not terrible, but your future self will judge you.`,
|
|
62
|
+
severity: 'mild',
|
|
63
|
+
emoji: '📝',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Format roast for display
|
|
69
|
+
*/
|
|
70
|
+
export function formatRoast(roast: Roast): string {
|
|
71
|
+
return `
|
|
72
|
+
${roast.emoji} ${roast.title} ${roast.emoji}
|
|
73
|
+
|
|
74
|
+
${roast.message}
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate shareable roast badge
|
|
80
|
+
*/
|
|
81
|
+
export function generateRoastBadge(result: AuditResult): string {
|
|
82
|
+
const roast = generateRoast(result);
|
|
83
|
+
const grade = getSecurityGrade(result);
|
|
84
|
+
|
|
85
|
+
return `
|
|
86
|
+
## 🛡️ FivoSense Security Roast
|
|
87
|
+
|
|
88
|
+
**Grade:** ${grade}
|
|
89
|
+
**Verdict:** ${roast.title}
|
|
90
|
+
|
|
91
|
+
${roast.message}
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
Roasted by [FivoSense](https://github.com/fivosense) — Neuro-symbolic security scanner
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get security grade (A-F)
|
|
100
|
+
*/
|
|
101
|
+
function getSecurityGrade(result: AuditResult): string {
|
|
102
|
+
const { summary } = result;
|
|
103
|
+
|
|
104
|
+
if (summary.total === 0) return 'A+';
|
|
105
|
+
if (summary.critical === 0 && summary.high === 0) return 'A';
|
|
106
|
+
if (summary.critical === 0 && summary.high <= 2) return 'B';
|
|
107
|
+
if (summary.critical === 0) return 'C';
|
|
108
|
+
if (summary.critical <= 1) return 'D';
|
|
109
|
+
return 'F';
|
|
110
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent PreToolUse Hook - Blocks dangerous AI actions in real-time
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { detectDestructive } from '../rules/destructive.js';
|
|
6
|
+
import { detectSecrets } from '../rules/secrets.js';
|
|
7
|
+
|
|
8
|
+
export interface BlockResult {
|
|
9
|
+
blocked: boolean;
|
|
10
|
+
reason: string;
|
|
11
|
+
severity: 'critical' | 'high' | 'medium';
|
|
12
|
+
suggestion?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if proposed action should be blocked
|
|
17
|
+
*/
|
|
18
|
+
export function checkAction(action: string, code?: string): BlockResult {
|
|
19
|
+
// Check for destructive commands
|
|
20
|
+
const destructive = detectDestructive(action);
|
|
21
|
+
if (destructive.length > 0) {
|
|
22
|
+
return {
|
|
23
|
+
blocked: true,
|
|
24
|
+
reason: `Destructive command detected: ${destructive[0].description}`,
|
|
25
|
+
severity: destructive[0].severity,
|
|
26
|
+
suggestion: 'Review the command carefully before executing',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check for hardcoded secrets in code
|
|
31
|
+
if (code) {
|
|
32
|
+
const secrets = detectSecrets(code);
|
|
33
|
+
if (secrets.length > 0) {
|
|
34
|
+
return {
|
|
35
|
+
blocked: true,
|
|
36
|
+
reason: `Hardcoded secret detected: ${secrets[0].description}`,
|
|
37
|
+
severity: 'high',
|
|
38
|
+
suggestion: 'Use environment variables instead',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
blocked: false,
|
|
45
|
+
reason: '',
|
|
46
|
+
severity: 'medium',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* PreToolUse hook for AI agents
|
|
52
|
+
* Returns exit code 2 if action should be blocked
|
|
53
|
+
*/
|
|
54
|
+
export function preToolUseHook(toolName: string, args: any): number {
|
|
55
|
+
// Check file write operations
|
|
56
|
+
if (toolName === 'write_file' || toolName === 'edit_file') {
|
|
57
|
+
const content = args.content || args.new_content || '';
|
|
58
|
+
const result = checkAction('', content);
|
|
59
|
+
|
|
60
|
+
if (result.blocked) {
|
|
61
|
+
console.error(`\n❌ BLOCKED: ${result.reason}`);
|
|
62
|
+
if (result.suggestion) {
|
|
63
|
+
console.error(`💡 Suggestion: ${result.suggestion}`);
|
|
64
|
+
}
|
|
65
|
+
return 2; // Exit code 2 signals block
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check shell commands
|
|
70
|
+
if (toolName === 'bash' || toolName === 'execute') {
|
|
71
|
+
const command = args.command || args.cmd || '';
|
|
72
|
+
const result = checkAction(command);
|
|
73
|
+
|
|
74
|
+
if (result.blocked) {
|
|
75
|
+
console.error(`\n❌ BLOCKED: ${result.reason}`);
|
|
76
|
+
if (result.suggestion) {
|
|
77
|
+
console.error(`💡 Suggestion: ${result.suggestion}`);
|
|
78
|
+
}
|
|
79
|
+
return 2;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return 0; // Allow
|
|
84
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FivoSense - Neuro-symbolic AI security plugin
|
|
3
|
+
* Entry point for the engine
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, statSync } from 'fs';
|
|
7
|
+
import { buildDataFlowGraph, getVulnerablePaths } from './engine/graph.js';
|
|
8
|
+
import { generateTaintTraces, getVulnerableTraces, formatTrace } from './engine/taint.js';
|
|
9
|
+
import { detectSecrets } from './rules/secrets.js';
|
|
10
|
+
import { detectDestructive } from './rules/destructive.js';
|
|
11
|
+
|
|
12
|
+
export interface AuditResult {
|
|
13
|
+
vulnerabilities: any[];
|
|
14
|
+
secrets: any[];
|
|
15
|
+
destructive: any[];
|
|
16
|
+
summary: {
|
|
17
|
+
total: number;
|
|
18
|
+
critical: number;
|
|
19
|
+
high: number;
|
|
20
|
+
medium: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Security: Max file size to prevent memory exhaustion
|
|
25
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Audit a file for security issues
|
|
29
|
+
*/
|
|
30
|
+
export async function auditFile(filepath: string): Promise<AuditResult> {
|
|
31
|
+
// Security check: File size limit
|
|
32
|
+
const stats = statSync(filepath);
|
|
33
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
34
|
+
throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${MAX_FILE_SIZE / 1024 / 1024}MB)`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const code = readFileSync(filepath, 'utf-8');
|
|
38
|
+
return auditCode(code, filepath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Audit code string for security issues
|
|
43
|
+
*/
|
|
44
|
+
export async function auditCode(code: string, filename = 'input.js'): Promise<AuditResult> {
|
|
45
|
+
// Security check: Code length
|
|
46
|
+
if (code.length > MAX_FILE_SIZE) {
|
|
47
|
+
throw new Error(`Code too large: ${(code.length / 1024 / 1024).toFixed(2)}MB (max ${MAX_FILE_SIZE / 1024 / 1024}MB)`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Build data-flow graph
|
|
51
|
+
const graph = buildDataFlowGraph(code, filename);
|
|
52
|
+
|
|
53
|
+
// Generate taint traces
|
|
54
|
+
const allTraces = generateTaintTraces(graph, filename);
|
|
55
|
+
const vulnerabilities = getVulnerableTraces(allTraces);
|
|
56
|
+
|
|
57
|
+
// Detect secrets
|
|
58
|
+
const secrets = detectSecrets(code);
|
|
59
|
+
|
|
60
|
+
// Detect destructive commands
|
|
61
|
+
const destructive = detectDestructive(code);
|
|
62
|
+
|
|
63
|
+
// Calculate summary
|
|
64
|
+
const criticalCount = vulnerabilities.filter(v => v.severity === 'critical').length +
|
|
65
|
+
destructive.filter(d => d.severity === 'critical').length;
|
|
66
|
+
const highCount = vulnerabilities.filter(v => v.severity === 'high').length +
|
|
67
|
+
secrets.length +
|
|
68
|
+
destructive.filter(d => d.severity === 'high').length;
|
|
69
|
+
const mediumCount = vulnerabilities.filter(v => v.severity === 'medium').length;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
vulnerabilities,
|
|
73
|
+
secrets,
|
|
74
|
+
destructive,
|
|
75
|
+
summary: {
|
|
76
|
+
total: vulnerabilities.length + secrets.length + destructive.length,
|
|
77
|
+
critical: criticalCount,
|
|
78
|
+
high: highCount,
|
|
79
|
+
medium: mediumCount,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Format audit result for display
|
|
86
|
+
*/
|
|
87
|
+
export function formatAuditResult(result: AuditResult): string {
|
|
88
|
+
const lines: string[] = [];
|
|
89
|
+
|
|
90
|
+
lines.push('🛡️ FivoSense Security Audit\n');
|
|
91
|
+
lines.push('═'.repeat(50));
|
|
92
|
+
lines.push('');
|
|
93
|
+
|
|
94
|
+
// Summary
|
|
95
|
+
lines.push('📊 Summary:');
|
|
96
|
+
lines.push(` Total findings: ${result.summary.total}`);
|
|
97
|
+
lines.push(` Critical: ${result.summary.critical}`);
|
|
98
|
+
lines.push(` High: ${result.summary.high}`);
|
|
99
|
+
lines.push(` Medium: ${result.summary.medium}`);
|
|
100
|
+
lines.push('');
|
|
101
|
+
|
|
102
|
+
// Vulnerabilities
|
|
103
|
+
if (result.vulnerabilities.length > 0) {
|
|
104
|
+
lines.push('❌ Vulnerabilities:');
|
|
105
|
+
lines.push('');
|
|
106
|
+
result.vulnerabilities.forEach((vuln, i) => {
|
|
107
|
+
lines.push(`${i + 1}. ${formatTrace(vuln)}`);
|
|
108
|
+
lines.push('');
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Secrets
|
|
113
|
+
if (result.secrets.length > 0) {
|
|
114
|
+
lines.push('🔑 Hardcoded Secrets:');
|
|
115
|
+
lines.push('');
|
|
116
|
+
result.secrets.forEach((secret, i) => {
|
|
117
|
+
lines.push(`${i + 1}. [${secret.severity.toUpperCase()}] ${secret.description}`);
|
|
118
|
+
lines.push(` Line ${secret.line}: ${secret.match}`);
|
|
119
|
+
lines.push('');
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Destructive commands
|
|
124
|
+
if (result.destructive.length > 0) {
|
|
125
|
+
lines.push('💥 Destructive Commands:');
|
|
126
|
+
lines.push('');
|
|
127
|
+
result.destructive.forEach((cmd, i) => {
|
|
128
|
+
lines.push(`${i + 1}. [${cmd.severity.toUpperCase()}] ${cmd.description}`);
|
|
129
|
+
lines.push(` Category: ${cmd.category}`);
|
|
130
|
+
lines.push('');
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (result.summary.total === 0) {
|
|
135
|
+
lines.push('✅ No security issues found!');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return lines.join('\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Export main functions
|
|
142
|
+
export * from './engine/graph.js';
|
|
143
|
+
export * from './engine/taint.js';
|
|
144
|
+
export * from './engine/sources.js';
|
|
145
|
+
export * from './engine/sinks.js';
|
|
146
|
+
export * from './rules/secrets.js';
|
|
147
|
+
export * from './rules/destructive.js';
|