guardvibe 3.0.57 → 3.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/README.md +1 -1
- package/build/cli/deep-scan.d.ts +1 -0
- package/build/cli/deep-scan.js +79 -0
- package/build/cli.js +5 -0
- package/build/index.js +9 -5
- package/build/tools/deep-scan.d.ts +13 -2
- package/build/tools/deep-scan.js +50 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -228,7 +228,7 @@ Malicious postinstall scripts, unpinned GitHub Actions, typosquat detection
|
|
|
228
228
|
| `verify_fix` | Verify a security fix was applied correctly — returns fixed/still_vulnerable/new_issues |
|
|
229
229
|
| `security_workflow` | Get recommended tool workflow for your current task (writing, pre-commit, PR review, etc.) |
|
|
230
230
|
| `auth_coverage` | **Auth coverage map** — enumerate routes, parse middleware matchers, detect auth guards, report coverage % |
|
|
231
|
-
| `deep_scan` | **LLM-powered deep analysis** — IDOR, business logic, race conditions,
|
|
231
|
+
| `deep_scan` | **LLM-powered deep analysis** — IDOR, business logic, race conditions, auth bypass. Defaults to Claude Haiku 4.5 (~cents/scan). Pass `model: 'sonnet'` for deeper analysis. CLI: `npx guardvibe deep-scan <file> --focus idor` |
|
|
232
232
|
| `full_audit` | **Single source of truth** — runs ALL checks in one call, returns PASS/FAIL/WARN verdict + score + coverage % + deterministic result hash |
|
|
233
233
|
| `remediation_plan` | **Remediation plan** — generates section-by-section fix checklist after audit |
|
|
234
234
|
| `verify_remediation` | **Remediation verification** — compares before/after audit, flags skipped sections |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDeepScan(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI: guardvibe deep-scan <file>
|
|
3
|
+
* LLM-powered deep security analysis.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync, statSync } from "node:fs";
|
|
6
|
+
import { resolve, extname } from "node:path";
|
|
7
|
+
import { parseArgs } from "./args.js";
|
|
8
|
+
import { buildDeepScanPrompt, callLLM, parseDeepScanResult, formatDeepScanFindings, DEFAULT_MAX_BYTES, } from "../tools/deep-scan.js";
|
|
9
|
+
const EXT_TO_LANG = {
|
|
10
|
+
".ts": "typescript", ".tsx": "typescript", ".mts": "typescript", ".cts": "typescript",
|
|
11
|
+
".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
|
|
12
|
+
".py": "python", ".go": "go", ".rb": "ruby", ".java": "java",
|
|
13
|
+
".rs": "rust", ".php": "php", ".cs": "csharp",
|
|
14
|
+
};
|
|
15
|
+
const VALID_FOCUS = ["all", "idor", "business-logic", "auth-bypass", "race-condition"];
|
|
16
|
+
export async function runDeepScan(args) {
|
|
17
|
+
const { flags, positional } = parseArgs(args);
|
|
18
|
+
const file = positional[0];
|
|
19
|
+
if (!file) {
|
|
20
|
+
console.error(" [ERR] Please specify a file: npx guardvibe deep-scan <file>");
|
|
21
|
+
console.error("");
|
|
22
|
+
console.error(" Options:");
|
|
23
|
+
console.error(" --focus <area> all (default) | idor | business-logic | auth-bypass | race-condition");
|
|
24
|
+
console.error(" --model <model> haiku (default, ~cents/scan) | sonnet (deeper, more expensive)");
|
|
25
|
+
console.error(" --max-bytes <n> Truncate input to N bytes (default 10000)");
|
|
26
|
+
console.error(" --format <type> markdown (default) | json");
|
|
27
|
+
console.error("");
|
|
28
|
+
console.error(" Requires ANTHROPIC_API_KEY (or OPENAI_API_KEY) environment variable.");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const path = resolve(file);
|
|
32
|
+
let content;
|
|
33
|
+
try {
|
|
34
|
+
const stat = statSync(path);
|
|
35
|
+
if (!stat.isFile()) {
|
|
36
|
+
console.error(` [ERR] Not a file: ${path}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
content = readFileSync(path, "utf-8");
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
console.error(` [ERR] Cannot read file: ${path}`);
|
|
43
|
+
console.error(` ${e.message}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) {
|
|
47
|
+
console.error(" [ERR] No LLM API key. Set ANTHROPIC_API_KEY or OPENAI_API_KEY in your environment.");
|
|
48
|
+
console.error(" Default model is Claude Haiku 4.5 — typically ~cents per scan.");
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const focusArg = flags.focus ?? "all";
|
|
52
|
+
if (!VALID_FOCUS.includes(focusArg)) {
|
|
53
|
+
console.error(` [ERR] Invalid --focus: ${focusArg}. Use one of: ${VALID_FOCUS.join(", ")}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const focus = focusArg;
|
|
57
|
+
const modelArg = flags.model ?? "haiku";
|
|
58
|
+
if (modelArg !== "haiku" && modelArg !== "sonnet") {
|
|
59
|
+
console.error(` [ERR] Invalid --model: ${modelArg}. Use haiku or sonnet.`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const model = modelArg;
|
|
63
|
+
const maxBytes = flags["max-bytes"] != null ? Number(flags["max-bytes"]) : DEFAULT_MAX_BYTES;
|
|
64
|
+
if (!Number.isFinite(maxBytes) || maxBytes < 500 || maxBytes > 50_000) {
|
|
65
|
+
console.error(` [ERR] --max-bytes must be 500..50000 (got ${flags["max-bytes"]})`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const format = (flags.format === "json" ? "json" : "markdown");
|
|
69
|
+
const language = EXT_TO_LANG[extname(path).toLowerCase()] ?? "unknown";
|
|
70
|
+
const prompt = buildDeepScanPrompt(content, language, [], focus);
|
|
71
|
+
const llmResponse = await callLLM(prompt, { model, maxBytes });
|
|
72
|
+
if (llmResponse === null) {
|
|
73
|
+
console.error(" [ERR] LLM call failed — check API key validity and network.");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const findings = parseDeepScanResult(llmResponse);
|
|
77
|
+
const output = formatDeepScanFindings(findings, format);
|
|
78
|
+
console.log(output);
|
|
79
|
+
}
|
package/build/cli.js
CHANGED
|
@@ -28,6 +28,7 @@ function printUsage() {
|
|
|
28
28
|
npx guardvibe check-cmd "<cmd>" Check if a shell command is safe to execute
|
|
29
29
|
npx guardvibe auth-coverage [path] Auth coverage analysis (Next.js routes)
|
|
30
30
|
npx guardvibe compliance [path] Compliance report (--framework SOC2|GDPR|...)
|
|
31
|
+
npx guardvibe deep-scan <file> LLM-powered deep scan (IDOR, business logic, race conditions)
|
|
31
32
|
npx guardvibe init <platform> Setup MCP server configuration
|
|
32
33
|
npx guardvibe hook install Install pre-commit security hook
|
|
33
34
|
npx guardvibe hook uninstall Remove pre-commit security hook
|
|
@@ -152,6 +153,10 @@ async function main() {
|
|
|
152
153
|
const { runCompliance } = await import("./cli/compliance.js");
|
|
153
154
|
await runCompliance(subArgs);
|
|
154
155
|
}
|
|
156
|
+
else if (command === "deep-scan") {
|
|
157
|
+
const { runDeepScan } = await import("./cli/deep-scan.js");
|
|
158
|
+
await runDeepScan(subArgs);
|
|
159
|
+
}
|
|
155
160
|
else {
|
|
156
161
|
console.error(` Unknown command: ${command}`);
|
|
157
162
|
printUsage();
|
package/build/index.js
CHANGED
|
@@ -863,20 +863,24 @@ server.tool("auth_coverage", "Analyze authentication coverage across Next.js App
|
|
|
863
863
|
return { content: [{ type: "text", text: output }] };
|
|
864
864
|
});
|
|
865
865
|
// Tool 32: LLM-powered deep scan
|
|
866
|
-
server.tool("deep_scan", "LLM-powered deep security analysis for vulnerabilities that pattern-matching cannot detect: IDOR, business logic flaws, race conditions, stale auth, mass assignment, privilege escalation.
|
|
866
|
+
server.tool("deep_scan", "LLM-powered deep security analysis for vulnerabilities that pattern-matching cannot detect: IDOR, business logic flaws, race conditions, stale auth, mass assignment, privilege escalation. Defaults to Claude Haiku 4.5 (~cents per scan); pass `model: 'sonnet'` for deeper analysis at higher cost. Requires ANTHROPIC_API_KEY or OPENAI_API_KEY env var.", {
|
|
867
867
|
code: z.string().describe("Code to analyze"),
|
|
868
868
|
language: z.string().describe("Programming language"),
|
|
869
869
|
context: z.string().optional().describe("Additional context (e.g., 'This is a payment endpoint')"),
|
|
870
870
|
existingFindings: z.array(z.string()).default([]).describe("Already-detected findings to avoid duplicating"),
|
|
871
|
+
focus: z.enum(["all", "idor", "business-logic", "auth-bypass", "race-condition"]).default("all").describe("Focus area — narrows the prompt to a specific vulnerability class"),
|
|
872
|
+
model: z.enum(["haiku", "sonnet"]).default("haiku").describe("LLM model. haiku = fast & cheap (default), sonnet = deeper analysis"),
|
|
873
|
+
maxBytes: z.number().int().min(500).max(50_000).default(10_000).describe("Max prompt size in bytes — caps cost. Code over this limit is truncated."),
|
|
871
874
|
format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
|
|
872
|
-
}, async ({ code, language, context, existingFindings, format }) => {
|
|
873
|
-
const prompt = buildDeepScanPrompt(code, language, existingFindings);
|
|
874
|
-
const
|
|
875
|
+
}, async ({ code, language, context, existingFindings, focus, model, maxBytes, format }) => {
|
|
876
|
+
const prompt = buildDeepScanPrompt(code, language, existingFindings, focus);
|
|
877
|
+
const fullPrompt = context ? `${prompt}\n\nAdditional context: ${context}` : prompt;
|
|
878
|
+
const llmResponse = await callLLM(fullPrompt, { model, maxBytes });
|
|
875
879
|
if (llmResponse === null) {
|
|
876
880
|
return {
|
|
877
881
|
content: [{
|
|
878
882
|
type: "text",
|
|
879
|
-
text: "## Deep Scan — Setup Required\n\nNo LLM API key found. Set one of:\n- `ANTHROPIC_API_KEY` — uses Claude\n- `OPENAI_API_KEY` — uses GPT-4o\n\nThe deep scan sends code to the LLM API for semantic vulnerability analysis.",
|
|
883
|
+
text: "## Deep Scan — Setup Required\n\nNo LLM API key found. Set one of:\n- `ANTHROPIC_API_KEY` — uses Claude (default: Haiku 4.5; pass `model: 'sonnet'` for deeper analysis)\n- `OPENAI_API_KEY` — uses GPT-4o-mini / GPT-4o\n\nThe deep scan sends code to the LLM API for semantic vulnerability analysis. Default cost is a few cents per scan with Haiku.",
|
|
880
884
|
}],
|
|
881
885
|
};
|
|
882
886
|
}
|
|
@@ -12,10 +12,14 @@ export interface DeepScanFinding {
|
|
|
12
12
|
location: string;
|
|
13
13
|
fix: string;
|
|
14
14
|
}
|
|
15
|
+
export type DeepScanFocus = "all" | "idor" | "business-logic" | "auth-bypass" | "race-condition";
|
|
16
|
+
export type DeepScanModel = "haiku" | "sonnet";
|
|
17
|
+
export declare const MODEL_IDS: Record<DeepScanModel, string>;
|
|
18
|
+
export declare const DEFAULT_MAX_BYTES = 10000;
|
|
15
19
|
/**
|
|
16
20
|
* Build a structured prompt for the LLM to analyze code.
|
|
17
21
|
*/
|
|
18
|
-
export declare function buildDeepScanPrompt(code: string, language: string, existingFindings: string[]): string;
|
|
22
|
+
export declare function buildDeepScanPrompt(code: string, language: string, existingFindings: string[], focus?: DeepScanFocus): string;
|
|
19
23
|
/**
|
|
20
24
|
* Parse LLM response into structured findings.
|
|
21
25
|
* Handles raw JSON, JSON in markdown code blocks, and malformed responses.
|
|
@@ -25,9 +29,16 @@ export declare function parseDeepScanResult(response: string): DeepScanFinding[]
|
|
|
25
29
|
* Format deep scan findings as markdown or JSON.
|
|
26
30
|
*/
|
|
27
31
|
export declare function formatDeepScanFindings(findings: DeepScanFinding[], format: "markdown" | "json"): string;
|
|
32
|
+
export interface CallLLMOptions {
|
|
33
|
+
model?: DeepScanModel;
|
|
34
|
+
maxBytes?: number;
|
|
35
|
+
}
|
|
28
36
|
/**
|
|
29
37
|
* Call an LLM API for deep analysis. Uses native fetch.
|
|
30
38
|
* Supports Anthropic (ANTHROPIC_API_KEY) or OpenAI (OPENAI_API_KEY).
|
|
31
39
|
* Returns null if no API key is available.
|
|
40
|
+
*
|
|
41
|
+
* Defaults to Haiku 4.5 for cost; pass `model: "sonnet"` for higher-quality analysis.
|
|
42
|
+
* `maxBytes` truncates the prompt to keep cost bounded (default 10 KB).
|
|
32
43
|
*/
|
|
33
|
-
export declare function callLLM(prompt: string): Promise<string | null>;
|
|
44
|
+
export declare function callLLM(prompt: string, options?: CallLLMOptions): Promise<string | null>;
|
package/build/tools/deep-scan.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Uses native fetch — no extra dependencies.
|
|
7
7
|
*/
|
|
8
|
-
const
|
|
8
|
+
const ALL_AREAS = [
|
|
9
9
|
"IDOR (Insecure Direct Object Reference) — can users access resources belonging to other users?",
|
|
10
10
|
"Business logic flaws — are there authorization bypasses, price manipulation, or state machine violations?",
|
|
11
11
|
"Race conditions — are there TOCTOU issues, double-spend, or concurrent mutation without locking?",
|
|
@@ -13,18 +13,50 @@ const FOCUS_AREAS = [
|
|
|
13
13
|
"Mass assignment — can users set fields they shouldn't (role, isAdmin, price)?",
|
|
14
14
|
"Privilege escalation — can a regular user perform admin actions through parameter manipulation?",
|
|
15
15
|
];
|
|
16
|
+
const FOCUS_AREAS = {
|
|
17
|
+
all: ALL_AREAS,
|
|
18
|
+
idor: [
|
|
19
|
+
"IDOR (Insecure Direct Object Reference) — can users access resources belonging to other users?",
|
|
20
|
+
"Missing ownership scope on database queries (where: { id } instead of { id, userId })",
|
|
21
|
+
"URL/path parameters used directly as DB keys without authorization gate",
|
|
22
|
+
],
|
|
23
|
+
"business-logic": [
|
|
24
|
+
"Authorization bypass via parameter manipulation (e.g., role/isAdmin in body)",
|
|
25
|
+
"Price/amount manipulation or coupon stacking",
|
|
26
|
+
"State machine violations (skip steps, replay completed actions)",
|
|
27
|
+
"Idempotency / replay protection on payment / order paths",
|
|
28
|
+
],
|
|
29
|
+
"auth-bypass": [
|
|
30
|
+
"Missing or insufficient auth check before sensitive operations",
|
|
31
|
+
"Stale tokens / sessions still accepted after revoke",
|
|
32
|
+
"Cookie / JWT validation skipped on a subset of routes",
|
|
33
|
+
"Privilege-elevation through parameter manipulation",
|
|
34
|
+
],
|
|
35
|
+
"race-condition": [
|
|
36
|
+
"TOCTOU between read and write (check-then-act without locking)",
|
|
37
|
+
"Concurrent rate-limit increments without atomic ops",
|
|
38
|
+
"Double-spend / double-grant via parallel requests",
|
|
39
|
+
"Optimistic-update races on shared mutable state",
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
export const MODEL_IDS = {
|
|
43
|
+
haiku: "claude-haiku-4-5-20251001",
|
|
44
|
+
sonnet: "claude-sonnet-4-6",
|
|
45
|
+
};
|
|
46
|
+
export const DEFAULT_MAX_BYTES = 10_000;
|
|
16
47
|
/**
|
|
17
48
|
* Build a structured prompt for the LLM to analyze code.
|
|
18
49
|
*/
|
|
19
|
-
export function buildDeepScanPrompt(code, language, existingFindings) {
|
|
50
|
+
export function buildDeepScanPrompt(code, language, existingFindings, focus = "all") {
|
|
51
|
+
const areas = FOCUS_AREAS[focus] ?? FOCUS_AREAS.all;
|
|
20
52
|
const lines = [
|
|
21
53
|
"You are a senior application security engineer performing a deep code review.",
|
|
22
54
|
"Analyze the following code for security vulnerabilities that automated pattern-matching scanners miss.",
|
|
23
55
|
"",
|
|
24
|
-
|
|
56
|
+
`## Focus Areas (${focus})`,
|
|
25
57
|
"",
|
|
26
58
|
];
|
|
27
|
-
for (const area of
|
|
59
|
+
for (const area of areas) {
|
|
28
60
|
lines.push(`- ${area}`);
|
|
29
61
|
}
|
|
30
62
|
lines.push("");
|
|
@@ -123,11 +155,20 @@ export function formatDeepScanFindings(findings, format) {
|
|
|
123
155
|
* Call an LLM API for deep analysis. Uses native fetch.
|
|
124
156
|
* Supports Anthropic (ANTHROPIC_API_KEY) or OpenAI (OPENAI_API_KEY).
|
|
125
157
|
* Returns null if no API key is available.
|
|
158
|
+
*
|
|
159
|
+
* Defaults to Haiku 4.5 for cost; pass `model: "sonnet"` for higher-quality analysis.
|
|
160
|
+
* `maxBytes` truncates the prompt to keep cost bounded (default 10 KB).
|
|
126
161
|
*/
|
|
127
|
-
export async function callLLM(prompt) {
|
|
162
|
+
export async function callLLM(prompt, options = {}) {
|
|
128
163
|
// guardvibe-ignore — API URLs are hardcoded trusted endpoints, not user-controlled
|
|
129
164
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
130
165
|
const openaiKey = process.env.OPENAI_API_KEY;
|
|
166
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
167
|
+
const model = options.model ?? "haiku";
|
|
168
|
+
// Truncate prompt to keep token budget bounded
|
|
169
|
+
const trimmedPrompt = prompt.length > maxBytes
|
|
170
|
+
? prompt.slice(0, maxBytes) + "\n\n[truncated by GuardVibe to stay within budget]"
|
|
171
|
+
: prompt;
|
|
131
172
|
if (anthropicKey) {
|
|
132
173
|
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
133
174
|
method: "POST",
|
|
@@ -137,9 +178,9 @@ export async function callLLM(prompt) {
|
|
|
137
178
|
"anthropic-version": "2023-06-01",
|
|
138
179
|
},
|
|
139
180
|
body: JSON.stringify({
|
|
140
|
-
model:
|
|
181
|
+
model: MODEL_IDS[model],
|
|
141
182
|
max_tokens: 2048,
|
|
142
|
-
messages: [{ role: "user", content:
|
|
183
|
+
messages: [{ role: "user", content: trimmedPrompt }],
|
|
143
184
|
}),
|
|
144
185
|
});
|
|
145
186
|
if (!res.ok)
|
|
@@ -155,9 +196,9 @@ export async function callLLM(prompt) {
|
|
|
155
196
|
"Authorization": `Bearer ${openaiKey}`,
|
|
156
197
|
},
|
|
157
198
|
body: JSON.stringify({
|
|
158
|
-
model: "gpt-4o",
|
|
199
|
+
model: model === "sonnet" ? "gpt-4o" : "gpt-4o-mini",
|
|
159
200
|
max_tokens: 2048,
|
|
160
|
-
messages: [{ role: "user", content:
|
|
201
|
+
messages: [{ role: "user", content: trimmedPrompt }],
|
|
161
202
|
}),
|
|
162
203
|
});
|
|
163
204
|
if (!res.ok)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
5
|
"description": "Security MCP for vibe coding. 390 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis, +25 AI-native rules (MCP supply-chain, RAG/vector poisoning, agent loop DoS, public-prefix LLM keys, sandbox bypass). Plus Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
|
|
6
6
|
"type": "module",
|