guardvibe 3.0.1 → 3.0.3
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/README.md +11 -3
- package/build/cli/audit.d.ts +5 -0
- package/build/cli/audit.js +34 -0
- package/build/cli/scan.js +4 -5
- package/build/cli.js +5 -0
- package/build/data/compliance-metadata.js +104 -0
- package/build/data/rules/advanced-security.js +130 -0
- package/build/data/rules/core.js +13 -0
- package/build/data/rules/modern-stack.js +63 -0
- package/build/data/rules/nextjs.js +13 -0
- package/build/index.js +93 -3
- package/build/tools/auth-coverage.d.ts +46 -0
- package/build/tools/auth-coverage.js +261 -0
- package/build/tools/check-code.js +9 -1
- package/build/tools/check-project.js +9 -1
- package/build/tools/cross-file-taint.d.ts +4 -0
- package/build/tools/cross-file-taint.js +146 -3
- package/build/tools/deep-scan.d.ts +33 -0
- package/build/tools/deep-scan.js +169 -0
- package/build/tools/full-audit.d.ts +81 -0
- package/build/tools/full-audit.js +365 -0
- package/build/tools/scan-directory.js +21 -4
- package/build/tools/taint-analysis.js +28 -7
- package/build/utils/walk-directory.d.ts +2 -1
- package/build/utils/walk-directory.js +10 -2
- package/package.json +2 -2
|
@@ -101,6 +101,41 @@ function parseImports(file, content) {
|
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
// CommonJS: const X = require('./mod') — default require
|
|
105
|
+
{
|
|
106
|
+
const re = /(?:const|let|var)\s+([\w$]+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
107
|
+
let m;
|
|
108
|
+
while ((m = re.exec(line)) !== null) {
|
|
109
|
+
imports.push({
|
|
110
|
+
importer: file,
|
|
111
|
+
source: normalizePath(file, m[2]),
|
|
112
|
+
names: new Map(),
|
|
113
|
+
defaultName: m[1].trim(),
|
|
114
|
+
line: i + 1,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// CommonJS: const { a, b } = require('./mod') — destructured require
|
|
119
|
+
{
|
|
120
|
+
const re = /(?:const|let|var)\s+\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
121
|
+
let m;
|
|
122
|
+
while ((m = re.exec(line)) !== null) {
|
|
123
|
+
const names = new Map();
|
|
124
|
+
for (const spec of m[1].split(",")) {
|
|
125
|
+
const parts = spec.trim().split(/\s*:\s*/);
|
|
126
|
+
const exported = parts[0].trim();
|
|
127
|
+
const local = (parts[1] ?? parts[0]).trim();
|
|
128
|
+
if (exported)
|
|
129
|
+
names.set(local, exported);
|
|
130
|
+
}
|
|
131
|
+
imports.push({
|
|
132
|
+
importer: file,
|
|
133
|
+
source: normalizePath(file, m[2]),
|
|
134
|
+
names,
|
|
135
|
+
line: i + 1,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
104
139
|
}
|
|
105
140
|
return imports;
|
|
106
141
|
}
|
|
@@ -151,6 +186,39 @@ function parseExports(file, content) {
|
|
|
151
186
|
names.set(m[1], m[1]);
|
|
152
187
|
}
|
|
153
188
|
}
|
|
189
|
+
// CommonJS: module.exports = { a, b }
|
|
190
|
+
{
|
|
191
|
+
const re = /module\.exports\s*=\s*\{([^}]+)\}/;
|
|
192
|
+
const m = re.exec(line);
|
|
193
|
+
if (m) {
|
|
194
|
+
for (const spec of m[1].split(",")) {
|
|
195
|
+
const parts = spec.trim().split(/\s*:\s*/);
|
|
196
|
+
const name = parts[0].trim();
|
|
197
|
+
const local = (parts[1] ?? parts[0]).trim();
|
|
198
|
+
if (name)
|
|
199
|
+
names.set(name, local);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// CommonJS: module.exports = funcName (default export)
|
|
204
|
+
{
|
|
205
|
+
const re = /module\.exports\s*=\s*([\w$]+)\s*;?\s*$/;
|
|
206
|
+
const m = re.exec(line);
|
|
207
|
+
if (m && !line.includes("{")) {
|
|
208
|
+
hasDefault = true;
|
|
209
|
+
defaultLocal = m[1];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// CommonJS: exports.name = funcName
|
|
213
|
+
{
|
|
214
|
+
const re = /exports\.([\w$]+)\s*=\s*([\w$]+)/g;
|
|
215
|
+
let m;
|
|
216
|
+
while ((m = re.exec(line)) !== null) {
|
|
217
|
+
if (!line.startsWith("module.")) {
|
|
218
|
+
names.set(m[1], m[2]);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
154
222
|
}
|
|
155
223
|
return { file, names, hasDefault, defaultLocal };
|
|
156
224
|
}
|
|
@@ -242,6 +310,45 @@ function checkParamFlowsToSink(paramName, body, startLine) {
|
|
|
242
310
|
}
|
|
243
311
|
return null;
|
|
244
312
|
}
|
|
313
|
+
// Check if a function parameter flows to a return statement (for return value taint tracking).
|
|
314
|
+
// Returns true only if the param (or a derived variable) IS the return value,
|
|
315
|
+
// not merely referenced inside a function call's arguments.
|
|
316
|
+
function checkParamFlowsToReturn(paramName, body) {
|
|
317
|
+
const lines = body.split("\n");
|
|
318
|
+
const taintedNames = new Set([paramName]);
|
|
319
|
+
const assignPattern = /(?:const|let|var)\s+([\w$]+)\s*=\s*(.*)/;
|
|
320
|
+
for (const line of lines) {
|
|
321
|
+
const m = assignPattern.exec(line);
|
|
322
|
+
if (m) {
|
|
323
|
+
for (const t of taintedNames) {
|
|
324
|
+
if (m[2].includes(t)) {
|
|
325
|
+
taintedNames.add(m[1]);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
for (const line of lines) {
|
|
332
|
+
const returnMatch = /\breturn\s+(.+?)[\s;]*$/.exec(line);
|
|
333
|
+
if (!returnMatch)
|
|
334
|
+
continue;
|
|
335
|
+
const returnExpr = returnMatch[1].trim();
|
|
336
|
+
// Direct return of tainted variable (e.g., "return trimmed;")
|
|
337
|
+
for (const t of taintedNames) {
|
|
338
|
+
if (returnExpr === t)
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
// Return of expression that uses tainted var directly (e.g., "return x + y;")
|
|
342
|
+
// but NOT as argument inside a function call (e.g., "return fn(x)" — x is consumed, not returned)
|
|
343
|
+
// Only match if tainted name appears outside parenthesized call args
|
|
344
|
+
const withoutCalls = returnExpr.replace(/\w+\s*\([^)]*\)/g, "");
|
|
345
|
+
for (const t of taintedNames) {
|
|
346
|
+
if (withoutCalls.includes(t))
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
245
352
|
function findTaintedExports(files) {
|
|
246
353
|
const taintedExports = [];
|
|
247
354
|
for (const file of files) {
|
|
@@ -263,8 +370,25 @@ function findTaintedExports(files) {
|
|
|
263
370
|
taintedParams.set(pIdx, paramAsTainted);
|
|
264
371
|
}
|
|
265
372
|
}
|
|
266
|
-
if
|
|
267
|
-
|
|
373
|
+
// Check if any param flows to a return statement
|
|
374
|
+
const taintedReturnParams = [];
|
|
375
|
+
for (let pIdx = 0; pIdx < fn.params.length; pIdx++) {
|
|
376
|
+
const param = fn.params[pIdx];
|
|
377
|
+
if (!param)
|
|
378
|
+
continue;
|
|
379
|
+
if (checkParamFlowsToReturn(param, fn.body)) {
|
|
380
|
+
taintedReturnParams.push(pIdx);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const returnsTainted = taintedReturnParams.length > 0;
|
|
384
|
+
if (taintedParams.size > 0 || (returnsTainted && taintedReturnParams.length > 0)) {
|
|
385
|
+
taintedExports.push({
|
|
386
|
+
file: file.path,
|
|
387
|
+
exportName: exportedName === "default" ? fn.name : exportedName,
|
|
388
|
+
taintedParams,
|
|
389
|
+
returnsTainted: returnsTainted && taintedReturnParams.length > 0,
|
|
390
|
+
taintedReturnParams,
|
|
391
|
+
});
|
|
268
392
|
}
|
|
269
393
|
}
|
|
270
394
|
}
|
|
@@ -310,7 +434,7 @@ function findTaintedCallSites(files, allImports, taintedExports) {
|
|
|
310
434
|
let changed = true;
|
|
311
435
|
let iterations = 0;
|
|
312
436
|
const taintedSet = new Set(taintedVars.map(v => v.name));
|
|
313
|
-
while (changed && iterations <
|
|
437
|
+
while (changed && iterations < 25) {
|
|
314
438
|
changed = false;
|
|
315
439
|
iterations++;
|
|
316
440
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -353,6 +477,25 @@ function findTaintedCallSites(files, allImports, taintedExports) {
|
|
|
353
477
|
if (!callPattern.test(lines[i]))
|
|
354
478
|
continue;
|
|
355
479
|
const args = extractCallArgs(lines[i], localName);
|
|
480
|
+
// Return value tracking: if function returnsTainted and a tainted arg is passed,
|
|
481
|
+
// mark the receiving variable as tainted
|
|
482
|
+
if (te.returnsTainted) {
|
|
483
|
+
const assignMatch = /(?:const|let|var)\s+([\w$]+)\s*=/.exec(lines[i]);
|
|
484
|
+
if (assignMatch) {
|
|
485
|
+
const receivingVar = assignMatch[1];
|
|
486
|
+
const hasTaintedArg = te.taintedReturnParams.some(pIdx => {
|
|
487
|
+
const arg = args[pIdx];
|
|
488
|
+
if (!arg)
|
|
489
|
+
return false;
|
|
490
|
+
return taintedVars.some(v => arg.includes(v.name)) ||
|
|
491
|
+
TAINT_SOURCES.some(src => { src.pattern.lastIndex = 0; return src.pattern.test(arg); });
|
|
492
|
+
});
|
|
493
|
+
if (hasTaintedArg && !taintedSet.has(receivingVar)) {
|
|
494
|
+
taintedSet.add(receivingVar);
|
|
495
|
+
taintedVars.push({ name: receivingVar, line: i + 1, sourceType: "return-propagated" });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
356
499
|
for (const [paramIdx, sinkInfo] of te.taintedParams) {
|
|
357
500
|
const argAtIdx = args[paramIdx];
|
|
358
501
|
if (!argAtIdx)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-powered deep scan — sends suspicious code to an LLM API for
|
|
3
|
+
* semantic analysis of IDOR, business logic, race conditions, and
|
|
4
|
+
* other issues that pattern-matching alone cannot detect.
|
|
5
|
+
*
|
|
6
|
+
* Uses native fetch — no extra dependencies.
|
|
7
|
+
*/
|
|
8
|
+
export interface DeepScanFinding {
|
|
9
|
+
type: string;
|
|
10
|
+
severity: "critical" | "high" | "medium" | "low";
|
|
11
|
+
description: string;
|
|
12
|
+
location: string;
|
|
13
|
+
fix: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a structured prompt for the LLM to analyze code.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildDeepScanPrompt(code: string, language: string, existingFindings: string[]): string;
|
|
19
|
+
/**
|
|
20
|
+
* Parse LLM response into structured findings.
|
|
21
|
+
* Handles raw JSON, JSON in markdown code blocks, and malformed responses.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseDeepScanResult(response: string): DeepScanFinding[];
|
|
24
|
+
/**
|
|
25
|
+
* Format deep scan findings as markdown or JSON.
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatDeepScanFindings(findings: DeepScanFinding[], format: "markdown" | "json"): string;
|
|
28
|
+
/**
|
|
29
|
+
* Call an LLM API for deep analysis. Uses native fetch.
|
|
30
|
+
* Supports Anthropic (ANTHROPIC_API_KEY) or OpenAI (OPENAI_API_KEY).
|
|
31
|
+
* Returns null if no API key is available.
|
|
32
|
+
*/
|
|
33
|
+
export declare function callLLM(prompt: string): Promise<string | null>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-powered deep scan — sends suspicious code to an LLM API for
|
|
3
|
+
* semantic analysis of IDOR, business logic, race conditions, and
|
|
4
|
+
* other issues that pattern-matching alone cannot detect.
|
|
5
|
+
*
|
|
6
|
+
* Uses native fetch — no extra dependencies.
|
|
7
|
+
*/
|
|
8
|
+
const FOCUS_AREAS = [
|
|
9
|
+
"IDOR (Insecure Direct Object Reference) — can users access resources belonging to other users?",
|
|
10
|
+
"Business logic flaws — are there authorization bypasses, price manipulation, or state machine violations?",
|
|
11
|
+
"Race conditions — are there TOCTOU issues, double-spend, or concurrent mutation without locking?",
|
|
12
|
+
"Stale auth/session — are tokens validated on every request? Can expired sessions still perform actions?",
|
|
13
|
+
"Mass assignment — can users set fields they shouldn't (role, isAdmin, price)?",
|
|
14
|
+
"Privilege escalation — can a regular user perform admin actions through parameter manipulation?",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Build a structured prompt for the LLM to analyze code.
|
|
18
|
+
*/
|
|
19
|
+
export function buildDeepScanPrompt(code, language, existingFindings) {
|
|
20
|
+
const lines = [
|
|
21
|
+
"You are a senior application security engineer performing a deep code review.",
|
|
22
|
+
"Analyze the following code for security vulnerabilities that automated pattern-matching scanners miss.",
|
|
23
|
+
"",
|
|
24
|
+
"## Focus Areas",
|
|
25
|
+
"",
|
|
26
|
+
];
|
|
27
|
+
for (const area of FOCUS_AREAS) {
|
|
28
|
+
lines.push(`- ${area}`);
|
|
29
|
+
}
|
|
30
|
+
lines.push("");
|
|
31
|
+
lines.push("## Code");
|
|
32
|
+
lines.push("");
|
|
33
|
+
lines.push(`Language: ${language}`);
|
|
34
|
+
lines.push("```");
|
|
35
|
+
lines.push(code);
|
|
36
|
+
lines.push("```");
|
|
37
|
+
if (existingFindings.length > 0) {
|
|
38
|
+
lines.push("");
|
|
39
|
+
lines.push("## Already Detected (by pattern scanner)");
|
|
40
|
+
lines.push("Do NOT repeat these — only report NEW findings:");
|
|
41
|
+
lines.push("");
|
|
42
|
+
for (const f of existingFindings) {
|
|
43
|
+
lines.push(`- ${f}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
lines.push("");
|
|
47
|
+
lines.push("## Response Format");
|
|
48
|
+
lines.push("Return ONLY a JSON object with this structure:");
|
|
49
|
+
lines.push("```json");
|
|
50
|
+
lines.push(JSON.stringify({
|
|
51
|
+
findings: [{
|
|
52
|
+
type: "IDOR | race-condition | business-logic | stale-auth | mass-assignment | privilege-escalation",
|
|
53
|
+
severity: "critical | high | medium | low",
|
|
54
|
+
description: "Clear description of the vulnerability",
|
|
55
|
+
location: "line number or code reference",
|
|
56
|
+
fix: "Specific remediation guidance",
|
|
57
|
+
}],
|
|
58
|
+
}, null, 2));
|
|
59
|
+
lines.push("```");
|
|
60
|
+
lines.push("If no vulnerabilities found, return: { \"findings\": [] }");
|
|
61
|
+
return lines.join("\n");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse LLM response into structured findings.
|
|
65
|
+
* Handles raw JSON, JSON in markdown code blocks, and malformed responses.
|
|
66
|
+
*/
|
|
67
|
+
export function parseDeepScanResult(response) {
|
|
68
|
+
if (!response || response.trim().length === 0)
|
|
69
|
+
return [];
|
|
70
|
+
let jsonStr = response.trim();
|
|
71
|
+
// Extract JSON from markdown code block
|
|
72
|
+
const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?```/.exec(jsonStr);
|
|
73
|
+
if (codeBlockMatch) {
|
|
74
|
+
jsonStr = codeBlockMatch[1].trim();
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(jsonStr);
|
|
78
|
+
if (!parsed.findings || !Array.isArray(parsed.findings))
|
|
79
|
+
return [];
|
|
80
|
+
return parsed.findings.filter((f) => f.type && f.severity && f.description && f.location && f.fix);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Format deep scan findings as markdown or JSON.
|
|
88
|
+
*/
|
|
89
|
+
export function formatDeepScanFindings(findings, format) {
|
|
90
|
+
if (format === "json") {
|
|
91
|
+
return JSON.stringify({
|
|
92
|
+
summary: {
|
|
93
|
+
total: findings.length,
|
|
94
|
+
critical: findings.filter(f => f.severity === "critical").length,
|
|
95
|
+
high: findings.filter(f => f.severity === "high").length,
|
|
96
|
+
medium: findings.filter(f => f.severity === "medium").length,
|
|
97
|
+
low: findings.filter(f => f.severity === "low").length,
|
|
98
|
+
},
|
|
99
|
+
findings,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (findings.length === 0) {
|
|
103
|
+
return "## Deep Scan Results\n\nNo additional vulnerabilities found beyond pattern-matching results.";
|
|
104
|
+
}
|
|
105
|
+
const lines = [
|
|
106
|
+
`## Deep Scan Results`,
|
|
107
|
+
``,
|
|
108
|
+
`Found ${findings.length} finding(s) via LLM analysis:`,
|
|
109
|
+
``,
|
|
110
|
+
];
|
|
111
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
112
|
+
findings.sort((a, b) => (severityOrder[a.severity] ?? 4) - (severityOrder[b.severity] ?? 4));
|
|
113
|
+
for (const f of findings) {
|
|
114
|
+
lines.push(`### [${f.severity.toUpperCase()}] ${f.type}`);
|
|
115
|
+
lines.push(`**Location:** ${f.location}`);
|
|
116
|
+
lines.push(`${f.description}`);
|
|
117
|
+
lines.push(`**Fix:** ${f.fix}`);
|
|
118
|
+
lines.push(``);
|
|
119
|
+
}
|
|
120
|
+
return lines.join("\n");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Call an LLM API for deep analysis. Uses native fetch.
|
|
124
|
+
* Supports Anthropic (ANTHROPIC_API_KEY) or OpenAI (OPENAI_API_KEY).
|
|
125
|
+
* Returns null if no API key is available.
|
|
126
|
+
*/
|
|
127
|
+
export async function callLLM(prompt) {
|
|
128
|
+
// guardvibe-ignore — API URLs are hardcoded trusted endpoints, not user-controlled
|
|
129
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
130
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
131
|
+
if (anthropicKey) {
|
|
132
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: {
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
"x-api-key": anthropicKey,
|
|
137
|
+
"anthropic-version": "2023-06-01",
|
|
138
|
+
},
|
|
139
|
+
body: JSON.stringify({
|
|
140
|
+
model: "claude-sonnet-4-6",
|
|
141
|
+
max_tokens: 2048,
|
|
142
|
+
messages: [{ role: "user", content: prompt }],
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
if (!res.ok)
|
|
146
|
+
return null;
|
|
147
|
+
const data = await res.json();
|
|
148
|
+
return data.content?.[0]?.text ?? null;
|
|
149
|
+
}
|
|
150
|
+
if (openaiKey) {
|
|
151
|
+
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: {
|
|
154
|
+
"Content-Type": "application/json",
|
|
155
|
+
"Authorization": `Bearer ${openaiKey}`,
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify({
|
|
158
|
+
model: "gpt-4o",
|
|
159
|
+
max_tokens: 2048,
|
|
160
|
+
messages: [{ role: "user", content: prompt }],
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
if (!res.ok)
|
|
164
|
+
return null;
|
|
165
|
+
const data = await res.json();
|
|
166
|
+
return data.choices?.[0]?.message?.content ?? null;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full Audit — single source of truth for AI assistants.
|
|
3
|
+
* Orchestrates all security tools in one call, produces:
|
|
4
|
+
* - PASS/FAIL/WARN verdict
|
|
5
|
+
* - Unified report across code, secrets, deps, config, taint, auth
|
|
6
|
+
* - Deterministic result hash (same code = same hash)
|
|
7
|
+
* - Coverage metrics (files scanned, rules applied, %)
|
|
8
|
+
*/
|
|
9
|
+
export type AuditVerdict = "PASS" | "WARN" | "FAIL";
|
|
10
|
+
export interface AuditCoverage {
|
|
11
|
+
filesScanned: number;
|
|
12
|
+
filesSkipped: number;
|
|
13
|
+
totalFiles: number;
|
|
14
|
+
coveragePercent: number;
|
|
15
|
+
rulesApplied: number;
|
|
16
|
+
}
|
|
17
|
+
export interface FindingRef {
|
|
18
|
+
ruleId: string;
|
|
19
|
+
severity: string;
|
|
20
|
+
file: string;
|
|
21
|
+
line: number;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface AuditSection {
|
|
25
|
+
name: string;
|
|
26
|
+
status: "ok" | "error" | "skipped";
|
|
27
|
+
findings: number;
|
|
28
|
+
critical: number;
|
|
29
|
+
high: number;
|
|
30
|
+
medium: number;
|
|
31
|
+
details: string;
|
|
32
|
+
}
|
|
33
|
+
export interface AuditResult {
|
|
34
|
+
verdict: AuditVerdict;
|
|
35
|
+
score: number;
|
|
36
|
+
grade: string;
|
|
37
|
+
coverage: AuditCoverage;
|
|
38
|
+
resultHash: string;
|
|
39
|
+
timestamp: string;
|
|
40
|
+
sections: AuditSection[];
|
|
41
|
+
truncation: {
|
|
42
|
+
truncated: boolean;
|
|
43
|
+
maxFindings: number;
|
|
44
|
+
totalFindings: number;
|
|
45
|
+
taintFileCap: number;
|
|
46
|
+
taintFilesProcessed: number;
|
|
47
|
+
};
|
|
48
|
+
summary: {
|
|
49
|
+
totalFindings: number;
|
|
50
|
+
critical: number;
|
|
51
|
+
high: number;
|
|
52
|
+
medium: number;
|
|
53
|
+
};
|
|
54
|
+
actionItems: string[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Compute verdict: PASS (0 critical + 0 high), WARN (high > 0), FAIL (critical > 0)
|
|
58
|
+
*/
|
|
59
|
+
export declare function computeVerdict(critical: number, high: number, _medium: number): AuditVerdict;
|
|
60
|
+
/**
|
|
61
|
+
* Compute coverage metrics from scan results.
|
|
62
|
+
*/
|
|
63
|
+
export declare function computeCoverage(filesScanned: number, filesSkipped: number, rulesApplied: number): AuditCoverage;
|
|
64
|
+
/**
|
|
65
|
+
* Compute deterministic SHA256 hash of findings.
|
|
66
|
+
* Same findings (in any order) = same hash.
|
|
67
|
+
*/
|
|
68
|
+
export declare function computeResultHash(findings: FindingRef[]): string;
|
|
69
|
+
/**
|
|
70
|
+
* Run a full security audit — single source of truth.
|
|
71
|
+
* Orchestrates code scan, secret scan, dependency scan, config audit,
|
|
72
|
+
* taint analysis, and auth coverage in one call.
|
|
73
|
+
*/
|
|
74
|
+
export declare function runFullAudit(path: string, options?: {
|
|
75
|
+
skipDeps?: boolean;
|
|
76
|
+
skipSecrets?: boolean;
|
|
77
|
+
}): Promise<AuditResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Format audit result as markdown or JSON.
|
|
80
|
+
*/
|
|
81
|
+
export declare function formatAuditResult(result: AuditResult, format: "markdown" | "json"): string;
|