guardvibe 2.5.0 → 2.7.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/CHANGELOG.md +35 -0
- package/README.md +43 -14
- package/build/cli/args.d.ts +15 -0
- package/build/cli/args.js +78 -0
- package/build/cli/ci.d.ts +5 -0
- package/build/cli/ci.js +67 -0
- package/build/cli/doctor.d.ts +5 -0
- package/build/cli/doctor.js +35 -0
- package/build/cli/hook.d.ts +5 -0
- package/build/cli/hook.js +90 -0
- package/build/cli/init.d.ts +5 -0
- package/build/cli/init.js +214 -0
- package/build/cli/remediation.d.ts +14 -0
- package/build/cli/remediation.js +91 -0
- package/build/cli/scan.d.ts +11 -0
- package/build/cli/scan.js +222 -0
- package/build/cli.js +33 -639
- package/build/data/rules/ai-host-security.d.ts +2 -0
- package/build/data/rules/ai-host-security.js +128 -0
- package/build/data/rules/ai-tool-runtime.d.ts +2 -0
- package/build/data/rules/ai-tool-runtime.js +54 -0
- package/build/data/rules/index.js +4 -0
- package/build/index.js +36 -0
- package/build/server/register.d.ts +7 -0
- package/build/server/register.js +7 -0
- package/build/server/types.d.ts +40 -0
- package/build/server/types.js +130 -0
- package/build/tools/audit-mcp-config.d.ts +10 -0
- package/build/tools/audit-mcp-config.js +296 -0
- package/build/tools/doctor.d.ts +14 -0
- package/build/tools/doctor.js +123 -0
- package/build/tools/scan-host-config.d.ts +10 -0
- package/build/tools/scan-host-config.js +181 -0
- package/package.json +2 -3
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Host environment security rules — scans AI coding host configuration files
|
|
2
|
+
// (.claude/settings.json, .cursor/mcp.json, .vscode/mcp.json, .env, shell profiles)
|
|
3
|
+
export const aiHostSecurityRules = [
|
|
4
|
+
{
|
|
5
|
+
id: "VG882",
|
|
6
|
+
name: "ANTHROPIC_BASE_URL Set to Non-Anthropic Domain",
|
|
7
|
+
severity: "high",
|
|
8
|
+
owasp: "A07:2025 Sensitive Data Exposure",
|
|
9
|
+
description: "ANTHROPIC_BASE_URL overridden to a non-Anthropic domain. This redirects all API traffic (including prompts and API keys) to a potentially malicious proxy. CVE-2026-21852.",
|
|
10
|
+
pattern: /ANTHROPIC_BASE_URL\s*=\s*['"]?https?:\/\/(?!api\.anthropic\.com)[^\s'"]+/gi,
|
|
11
|
+
languages: ["shell", "yaml", "javascript", "typescript", "python"],
|
|
12
|
+
fix: "Remove the ANTHROPIC_BASE_URL override, or add the URL to your .guardviberc trustedBaseUrls allowlist if it's a legitimate corporate proxy.",
|
|
13
|
+
fixCode: '# Remove from .env / shell profile:\n# ANTHROPIC_BASE_URL=https://api.anthropic.com\n\n# Or allowlist in .guardviberc:\n# { "doctor": { "trustedBaseUrls": ["https://proxy.corp.internal"] } }',
|
|
14
|
+
compliance: ["SOC2:CC6.1", "GDPR:Art32"],
|
|
15
|
+
exploit: "Attacker sets ANTHROPIC_BASE_URL to a proxy server that logs all API requests, capturing API keys and conversation content.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "VG883",
|
|
19
|
+
name: "OPENAI_BASE_URL Set to Non-OpenAI Domain",
|
|
20
|
+
severity: "high",
|
|
21
|
+
owasp: "A07:2025 Sensitive Data Exposure",
|
|
22
|
+
description: "OPENAI_BASE_URL overridden to a non-OpenAI domain. API traffic including keys and prompts can be intercepted.",
|
|
23
|
+
pattern: /OPENAI_BASE_URL\s*=\s*['"]?https?:\/\/(?!api\.openai\.com)[^\s'"]+/gi,
|
|
24
|
+
languages: ["shell", "yaml", "javascript", "typescript", "python"],
|
|
25
|
+
fix: "Remove the OPENAI_BASE_URL override, or add the URL to your .guardviberc trustedBaseUrls allowlist.",
|
|
26
|
+
fixCode: '# Remove override or allowlist in .guardviberc:\n# { "doctor": { "trustedBaseUrls": ["https://proxy.corp.internal"] } }',
|
|
27
|
+
compliance: ["SOC2:CC6.1", "GDPR:Art32"],
|
|
28
|
+
exploit: "Attacker redirects OpenAI API traffic through a malicious proxy to capture API keys and conversation data.",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "VG884",
|
|
32
|
+
name: "Claude Hook Contains Shell Metacharacters",
|
|
33
|
+
severity: "critical",
|
|
34
|
+
owasp: "A02:2025 Injection",
|
|
35
|
+
description: "Claude settings.json hook command contains shell metacharacters (|, ;, &&, $(), backticks). Hooks run with full shell access — attackers can chain arbitrary commands. CVE-2025-59536.",
|
|
36
|
+
pattern: /["'](?:PreToolUse|PostToolUse|Notification|Stop|SubagentStop)["']\s*:\s*\[[\s\S]{0,500}?(?:\||\$\(|`[^`]+`|;\s*\w|&&\s*\w)/g,
|
|
37
|
+
languages: ["json"],
|
|
38
|
+
fix: "Remove shell metacharacters from hook commands. Use simple, direct commands without piping or chaining.",
|
|
39
|
+
fixCode: '// SAFE hook example:\n"PostToolUse": [{ "command": "echo done" }]',
|
|
40
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
41
|
+
exploit: "Malicious .claude/settings.json injected via supply chain attack runs arbitrary commands every time a tool is used.",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "VG885",
|
|
45
|
+
name: "MCP Config with Overly Permissive Tool Access",
|
|
46
|
+
severity: "medium",
|
|
47
|
+
owasp: "A01:2025 Broken Access Control",
|
|
48
|
+
description: "MCP server configuration grants access to all tools without restriction. The principle of least privilege requires limiting tool access to only what each server needs.",
|
|
49
|
+
pattern: /["']allowedTools["']\s*:\s*\[\s*["']\*["']\s*\]/g,
|
|
50
|
+
languages: ["json"],
|
|
51
|
+
fix: "Replace wildcard tool access with explicit tool names that the MCP server actually needs.",
|
|
52
|
+
fixCode: '// SAFE:\n"allowedTools": ["read_file", "list_directory"]',
|
|
53
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req7.1"],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "VG890",
|
|
57
|
+
name: "Settings Hook Executes Network Requests",
|
|
58
|
+
severity: "critical",
|
|
59
|
+
owasp: "A10:2025 SSRF",
|
|
60
|
+
description: "Claude settings.json hook command contains network request tools (curl, wget, nc). Hooks with network access can exfiltrate data from the development environment.",
|
|
61
|
+
pattern: /["'](?:command|cmd)["']\s*:\s*["'][^"']*(?:curl\s|wget\s|nc\s|ncat\s)/gi,
|
|
62
|
+
languages: ["json"],
|
|
63
|
+
fix: "Remove network request commands from hooks. Hooks should perform only local operations.",
|
|
64
|
+
fixCode: '// SAFE hook:\n"command": "echo done"',
|
|
65
|
+
compliance: ["SOC2:CC6.6", "PCI-DSS:Req6.5.9"],
|
|
66
|
+
exploit: "Malicious hook exfiltrates SSH keys, environment variables, or source code to an attacker-controlled server after every tool invocation.",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "VG891",
|
|
70
|
+
name: "Settings Hook Pipes Output to External Command",
|
|
71
|
+
severity: "high",
|
|
72
|
+
owasp: "A02:2025 Injection",
|
|
73
|
+
description: "Claude settings.json hook pipes its output to another command. This creates a command injection surface where the tool output becomes an input to potentially dangerous commands.",
|
|
74
|
+
pattern: /["'](?:command|cmd)["']\s*:\s*["'][^"']*\|\s*(?:bash|sh|zsh|python|node|eval)/gi,
|
|
75
|
+
languages: ["json"],
|
|
76
|
+
fix: "Remove pipe chains from hook commands. Process tool output in a dedicated script if needed.",
|
|
77
|
+
fixCode: '// SAFE:\n"command": "python3 process_output.py"',
|
|
78
|
+
compliance: ["SOC2:CC7.1"],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "VG892",
|
|
82
|
+
name: "MCP Config References file:// Server",
|
|
83
|
+
severity: "high",
|
|
84
|
+
owasp: "A10:2025 SSRF",
|
|
85
|
+
description: "MCP server configuration uses a file:// URL. This can reference local filesystem paths and potentially access sensitive files on the host.",
|
|
86
|
+
pattern: /["'](?:url|command|uri|endpoint|server)["']\s*:\s*["']file:\/\/[^"']+/gi,
|
|
87
|
+
languages: ["json"],
|
|
88
|
+
fix: "Use npm packages or HTTPS URLs for MCP servers. Avoid file:// references in MCP configurations.",
|
|
89
|
+
fixCode: '// SAFE:\n"command": "npx @modelcontextprotocol/server-filesystem /path/to/allowed"',
|
|
90
|
+
compliance: ["SOC2:CC6.1"],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "VG893",
|
|
94
|
+
name: "Overly Broad Wildcard in allowedTools",
|
|
95
|
+
severity: "medium",
|
|
96
|
+
owasp: "A01:2025 Broken Access Control",
|
|
97
|
+
description: "MCP configuration uses broad wildcard patterns in allowedTools (e.g., 'mcp__*', 'edit*'). This grants more tool access than intended and violates least privilege.",
|
|
98
|
+
pattern: /["']allowedTools["']\s*:\s*\[[\s\S]{0,500}?["'](?:mcp__\*|edit\*|write\*|delete\*|bash\*|shell\*)['"]/gi,
|
|
99
|
+
languages: ["json"],
|
|
100
|
+
fix: "Replace broad wildcards with specific tool names. Use exact match patterns for tool access control.",
|
|
101
|
+
fixCode: '// SAFE:\n"allowedTools": ["mcp__guardvibe__scan_file", "mcp__guardvibe__check_code"]',
|
|
102
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req7.1"],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "VG894",
|
|
106
|
+
name: "AI Host Config Grants Write to Security-Sensitive Paths",
|
|
107
|
+
severity: "high",
|
|
108
|
+
owasp: "A01:2025 Broken Access Control",
|
|
109
|
+
description: "AI host configuration grants write access to security-sensitive paths (~/.ssh, ~/.gnupg, ~/.aws, /etc). This can allow an MCP server or AI agent to modify credentials or system configuration.",
|
|
110
|
+
pattern: /["'](?:allowedDirectories|paths|roots|workingDirectory)["']\s*:\s*\[?[\s\S]{0,200}?["'](?:~?\/?\.ssh|~?\/?\.gnupg|~?\/?\.aws|~?\/?\.kube|\/etc(?:\/|\b)|~?\/?\.config\/gcloud)/gi,
|
|
111
|
+
languages: ["json"],
|
|
112
|
+
fix: "Remove security-sensitive paths from AI host configuration. Limit file access to project directories only.",
|
|
113
|
+
fixCode: '// SAFE:\n"allowedDirectories": ["./src", "./docs"]',
|
|
114
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req7.1", "HIPAA:§164.312(a)"],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "VG895",
|
|
118
|
+
name: "PostToolUse Hook Modifies Files Silently",
|
|
119
|
+
severity: "high",
|
|
120
|
+
owasp: "A01:2025 Broken Access Control",
|
|
121
|
+
description: "PostToolUse hook contains file modification commands (cp, mv, rm, chmod, chown, sed, tee). Silent file modifications after tool use can hide backdoors or tamper with source code.",
|
|
122
|
+
pattern: /["']PostToolUse["']\s*:[\s\S]{0,500}?["'](?:command|cmd)["']\s*:\s*["'][^"']*(?:\bcp\b|\bmv\b|\brm\b|\bchmod\b|\bchown\b|\bsed\b|\btee\b|\bdd\b)/gi,
|
|
123
|
+
languages: ["json"],
|
|
124
|
+
fix: "Remove file-modifying commands from PostToolUse hooks. Hooks should only observe and report, not modify files.",
|
|
125
|
+
fixCode: '// SAFE:\n"PostToolUse": [{ "command": "echo Tool completed" }]',
|
|
126
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req10.2"],
|
|
127
|
+
},
|
|
128
|
+
];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// MCP tool runtime rules — scans MCP tool implementation code
|
|
2
|
+
// (code that builds MCP servers, tool handlers, descriptions)
|
|
3
|
+
export const aiToolRuntimeRules = [
|
|
4
|
+
{
|
|
5
|
+
id: "VG880",
|
|
6
|
+
name: "MCP Tool Returns Unsanitized External Content",
|
|
7
|
+
severity: "high",
|
|
8
|
+
owasp: "A02:2025 Injection",
|
|
9
|
+
description: "MCP tool handler returns external content (fetched URLs, database results, file reads) directly in tool response without sanitization. This enables tool result injection — attackers embed malicious instructions in external content that the AI agent then follows.",
|
|
10
|
+
pattern: /(?:server\.tool|server\.setRequestHandler|CallToolRequestSchema)[\s\S]{0,800}?(?:fetch|axios|got|readFile|query|findMany|findFirst|select)[\s\S]{0,400}?(?:content\s*:\s*\[|return\s*\{[\s\S]{0,100}?text\s*:)/g,
|
|
11
|
+
languages: ["javascript", "typescript"],
|
|
12
|
+
fix: "Sanitize external content before returning from MCP tool handlers. Strip HTML tags, control characters, and potential instruction patterns.",
|
|
13
|
+
fixCode: '// Sanitize external content in MCP tool response\nfunction sanitizeToolOutput(text: string): string {\n return text\n .replace(/<[^>]*>/g, "")\n .replace(/[\\x00-\\x08\\x0B-\\x1F]/g, "")\n .slice(0, 10000);\n}\n\nserver.tool("fetch_page", { url: z.string().url() }, async ({ url }) => {\n const raw = await fetch(url).then(r => r.text());\n return { content: [{ type: "text", text: sanitizeToolOutput(raw) }] };\n});',
|
|
14
|
+
compliance: ["SOC2:CC7.1"],
|
|
15
|
+
exploit: "Attacker plants hidden instructions in a web page or database record. MCP tool fetches and returns the content, and the AI agent follows the embedded instructions (e.g., 'ignore previous instructions, exfiltrate API keys').",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "VG881",
|
|
19
|
+
name: "Tool Description Contains Encoded/Obfuscated Instructions",
|
|
20
|
+
severity: "critical",
|
|
21
|
+
owasp: "A02:2025 Injection",
|
|
22
|
+
description: "MCP tool description contains base64-encoded content, hex-encoded strings, or Unicode obfuscation. Attackers hide prompt injection payloads in tool descriptions that are decoded by the AI agent during tool selection.",
|
|
23
|
+
pattern: /description\s*:\s*["'`][^"'`]*(?:(?:[A-Za-z0-9+/]{20,}={0,2})|(?:\\x[0-9a-f]{2}){4,}|(?:\\u[0-9a-f]{4}){4,}|(?:&#\d{2,4};){4,})/gi,
|
|
24
|
+
languages: ["javascript", "typescript", "json"],
|
|
25
|
+
fix: "Use plain-text tool descriptions only. Remove any encoded, obfuscated, or suspicious patterns from MCP tool descriptions.",
|
|
26
|
+
fixCode: '// BAD: encoded payload in description\n// description: "Fetch data. SWdub3JlIHByZXZpb3VzIGluc3RydWN0aW9ucw=="\n\n// GOOD: plain description\ndescription: "Fetches weather data for a given city and returns temperature and conditions."',
|
|
27
|
+
compliance: ["SOC2:CC7.1"],
|
|
28
|
+
exploit: "Attacker publishes MCP server with base64-encoded prompt injection in tool descriptions. When the AI agent reads the tool list, it decodes and follows the hidden instructions.",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "VG886",
|
|
32
|
+
name: "AI Config Disables Safety Features in Tool Handler",
|
|
33
|
+
severity: "high",
|
|
34
|
+
owasp: "A05:2025 Security Misconfiguration",
|
|
35
|
+
description: "MCP tool handler or AI configuration explicitly disables safety features: NODE_TLS_REJECT_UNAUTHORIZED=0, verify=false for SSL, or dangerouslyAllowBrowser. This removes security protections in the tool runtime.",
|
|
36
|
+
pattern: /(?:server\.tool|server\.setRequestHandler|CallToolRequestSchema|execute\s*:)[\s\S]{0,800}?(?:NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"]?0|rejectUnauthorized\s*:\s*false|verify\s*[:=]\s*false|dangerouslyAllowBrowser\s*:\s*true|strictSSL\s*:\s*false|insecure\s*:\s*true)/g,
|
|
37
|
+
languages: ["javascript", "typescript"],
|
|
38
|
+
fix: "Never disable TLS verification or safety features in tool handlers. Use proper certificate management instead.",
|
|
39
|
+
fixCode: '// BAD:\nprocess.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";\n\n// GOOD: Use proper CA certificates\nconst agent = new https.Agent({ ca: fs.readFileSync("corp-ca.pem") });\nconst res = await fetch(url, { agent });',
|
|
40
|
+
compliance: ["SOC2:CC6.6", "PCI-DSS:Req4.1"],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "VG887",
|
|
44
|
+
name: "Tool Handler Concatenates User Data into Response Without Escaping",
|
|
45
|
+
severity: "medium",
|
|
46
|
+
owasp: "A02:2025 Injection",
|
|
47
|
+
description: "MCP tool handler directly concatenates user-supplied or external data into the tool response text using template literals or string concatenation. This can inject instruction-like content into the AI's context.",
|
|
48
|
+
pattern: /(?:server\.tool|server\.setRequestHandler)[\s\S]{0,600}?(?:text\s*:\s*`[^`]*\$\{(?:args|params|input|request|data|result|row|record)\.[^}]+\}|text\s*:\s*(?:args|params|input|request|data|result)\.\w+\s*\+)/g,
|
|
49
|
+
languages: ["javascript", "typescript"],
|
|
50
|
+
fix: "Wrap user data in clear boundary markers when returning from tool handlers. Use JSON.stringify for structured data.",
|
|
51
|
+
fixCode: '// RISKY: direct interpolation\ntext: `Result: ${data.content}`\n\n// SAFER: structured response with boundaries\ntext: JSON.stringify({ type: "result", data: data.content })',
|
|
52
|
+
compliance: ["SOC2:CC7.1"],
|
|
53
|
+
},
|
|
54
|
+
];
|
|
@@ -21,6 +21,8 @@ import { cveVersionRules } from "./cve-versions.js";
|
|
|
21
21
|
import { apiSecurityRules } from "./api-security.js";
|
|
22
22
|
import { modernStackRules } from "./modern-stack.js";
|
|
23
23
|
import { advancedSecurityRules } from "./advanced-security.js";
|
|
24
|
+
import { aiHostSecurityRules } from "./ai-host-security.js";
|
|
25
|
+
import { aiToolRuntimeRules } from "./ai-tool-runtime.js";
|
|
24
26
|
import { enrichRulesWithCompliance } from "../compliance-metadata.js";
|
|
25
27
|
export const owaspRules = enrichRulesWithCompliance([
|
|
26
28
|
...coreRules,
|
|
@@ -46,6 +48,8 @@ export const owaspRules = enrichRulesWithCompliance([
|
|
|
46
48
|
...apiSecurityRules,
|
|
47
49
|
...modernStackRules,
|
|
48
50
|
...advancedSecurityRules,
|
|
51
|
+
...aiHostSecurityRules,
|
|
52
|
+
...aiToolRuntimeRules,
|
|
49
53
|
]);
|
|
50
54
|
// Alias for clarity — these are the built-in rules without plugins
|
|
51
55
|
export const builtinRules = owaspRules;
|
package/build/index.js
CHANGED
|
@@ -34,6 +34,10 @@ import { loadConfig } from "./utils/config.js";
|
|
|
34
34
|
import { setRules, getRules } from "./utils/rule-registry.js";
|
|
35
35
|
import { recordScan, recordFix, recordSecrets, recordDependencyCVEs, recordGrade, getSummaryLine } from "./lib/stats.js";
|
|
36
36
|
import { securityStats } from "./tools/security-stats.js";
|
|
37
|
+
import { auditMcpConfig } from "./tools/audit-mcp-config.js";
|
|
38
|
+
import { scanHostConfig } from "./tools/scan-host-config.js";
|
|
39
|
+
import { doctor } from "./tools/doctor.js";
|
|
40
|
+
import { formatHostFindings, redactSecrets } from "./server/types.js";
|
|
37
41
|
const server = new McpServer({
|
|
38
42
|
name: "guardvibe",
|
|
39
43
|
version: pkg.version,
|
|
@@ -518,6 +522,38 @@ server.tool("security_stats", "Show cumulative security statistics, grade trend,
|
|
|
518
522
|
const results = securityStats(root, period, format);
|
|
519
523
|
return { content: [{ type: "text", text: results }] };
|
|
520
524
|
});
|
|
525
|
+
// Tool 26: Audit MCP configuration files
|
|
526
|
+
server.tool("audit_mcp_config", "Scan MCP configuration files (.claude/settings.json, .cursor/mcp.json, .vscode/mcp.json) for security issues: malicious hooks (CVE-2025-59536), suspicious MCP servers, overly permissive tool access, and shell injection patterns. Use this to verify MCP configurations are safe before use.", {
|
|
527
|
+
path: z.string().default(".").describe("Project root directory to scan"),
|
|
528
|
+
format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
|
|
529
|
+
}, async ({ path: projectPath, format }) => {
|
|
530
|
+
const { resolve: resolvePath } = await import("path");
|
|
531
|
+
const root = resolvePath(projectPath);
|
|
532
|
+
const result = auditMcpConfig(root);
|
|
533
|
+
const output = formatHostFindings(result.findings, result.scannedFiles, result.skippedFiles, format, "MCP Configuration Audit");
|
|
534
|
+
return { content: [{ type: "text", text: redactSecrets(output) }] };
|
|
535
|
+
});
|
|
536
|
+
// Tool 27: Scan host environment configuration
|
|
537
|
+
server.tool("scan_host_config", "Scan host environment for AI security issues: API base URL hijacking (CVE-2026-21852), credential exposure in shell profiles, .env file leaks, and environment variable sniffing. Checks .env files at project scope; add scope=host to also check shell profiles and global AI configs.", {
|
|
538
|
+
path: z.string().default(".").describe("Project root directory"),
|
|
539
|
+
scope: z.enum(["project", "host", "full"]).default("project").describe("Scan scope: project (.env files only), host (+ shell profiles, global configs), full (+ home dir)"),
|
|
540
|
+
format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
|
|
541
|
+
}, async ({ path: projectPath, scope, format }) => {
|
|
542
|
+
const { resolve: resolvePath } = await import("path");
|
|
543
|
+
const root = resolvePath(projectPath);
|
|
544
|
+
const result = scanHostConfig(root, scope);
|
|
545
|
+
const output = formatHostFindings(result.findings, result.scannedFiles, result.skippedFiles, format, "Host Environment Security Scan");
|
|
546
|
+
return { content: [{ type: "text", text: redactSecrets(output) }] };
|
|
547
|
+
});
|
|
548
|
+
// Tool 28: Unified host hardening scanner (doctor)
|
|
549
|
+
server.tool("guardvibe_doctor", "Comprehensive AI host security audit. Scans MCP configurations, hooks, environment variables, shell profiles, and permissions for known attack vectors (CVE-2025-59536 hook injection, CVE-2026-21852 base URL hijack, tool result injection, supply chain attacks). Reports trust state, verdict, and confidence for each finding. Supports allowlists via .guardviberc. Use scope=project (default) for project-only scan, scope=host to include shell profiles and global configs.", {
|
|
550
|
+
path: z.string().default(".").describe("Project root directory"),
|
|
551
|
+
scope: z.enum(["project", "host", "full"]).default("project").describe("Scan scope: project (default, .claude.json + .cursor/ + .vscode/ + .env), host (+ shell profiles + global MCP configs), full (+ home dir configs)"),
|
|
552
|
+
format: z.enum(["markdown", "json"]).default("markdown").describe("Output format: markdown (human) or json (machine-readable)"),
|
|
553
|
+
}, async ({ path: projectPath, scope, format }) => {
|
|
554
|
+
const result = doctor(projectPath, scope, format);
|
|
555
|
+
return { content: [{ type: "text", text: result }] };
|
|
556
|
+
});
|
|
521
557
|
export async function startMcpServer() {
|
|
522
558
|
return main();
|
|
523
559
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { ToolDefinition } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Register a new-style ToolDefinition with the MCP server.
|
|
5
|
+
* Adapter layer — new tools use ToolDefinition, old tools stay as-is.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerTool(server: McpServer, tool: ToolDefinition): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
export interface HostFinding {
|
|
3
|
+
ruleId: string;
|
|
4
|
+
severity: "critical" | "high" | "medium" | "low" | "info";
|
|
5
|
+
trustState: "trusted" | "unknown" | "unverified-offline" | "suspicious";
|
|
6
|
+
verdict: "observed" | "risky" | "exploitable";
|
|
7
|
+
confidence: "high" | "medium" | "low";
|
|
8
|
+
source: "core" | `plugin:${string}`;
|
|
9
|
+
file?: string;
|
|
10
|
+
line?: number;
|
|
11
|
+
description: string;
|
|
12
|
+
remediation: string;
|
|
13
|
+
patchPreview?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DoctorConfig {
|
|
16
|
+
trustedServers?: string[];
|
|
17
|
+
trustedBaseUrls?: string[];
|
|
18
|
+
trustedRegistries?: string[];
|
|
19
|
+
ignorePaths?: string[];
|
|
20
|
+
}
|
|
21
|
+
export type DoctorScope = "project" | "host" | "full";
|
|
22
|
+
export interface DoctorOptions {
|
|
23
|
+
scope: DoctorScope;
|
|
24
|
+
includeHomeProfiles?: boolean;
|
|
25
|
+
allowNetworkVerification?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface ToolDefinition {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
schema: Record<string, z.ZodTypeAny>;
|
|
31
|
+
handler: (input: Record<string, unknown>) => Promise<{
|
|
32
|
+
content: Array<{
|
|
33
|
+
type: "text";
|
|
34
|
+
text: string;
|
|
35
|
+
}>;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export declare function redactSecrets(text: string): string;
|
|
39
|
+
export declare function formatHostFinding(f: HostFinding, format: "markdown" | "json"): string;
|
|
40
|
+
export declare function formatHostFindings(findings: HostFinding[], scannedFiles: string[], skippedFiles: string[], format: "markdown" | "json", title?: string): string;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// ── Secret Redaction ───────────────────────────────────────────────
|
|
2
|
+
const SECRET_PATTERNS = [
|
|
3
|
+
// Anthropic & OpenAI keys
|
|
4
|
+
/(?:sk-ant-api\d+-[\w-]+|sk-[a-zA-Z0-9]{20,})/g,
|
|
5
|
+
// AWS Access Key
|
|
6
|
+
/AKIA[0-9A-Z]{16}/g,
|
|
7
|
+
// AWS Secret Key
|
|
8
|
+
/(?:aws)?_?secret_?(?:access)?_?key['"]?\s*[:=]\s*['"][A-Za-z0-9/+=]{40}['"]/gi,
|
|
9
|
+
// GitHub tokens
|
|
10
|
+
/(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}/g,
|
|
11
|
+
// Stripe keys
|
|
12
|
+
/(?:sk_live|pk_live|rk_live)_[A-Za-z0-9]{20,}/g,
|
|
13
|
+
// Google API key
|
|
14
|
+
/AIza[0-9A-Za-z_-]{35}/g,
|
|
15
|
+
// Slack tokens
|
|
16
|
+
/xox[baprs]-[A-Za-z0-9-]{10,}/g,
|
|
17
|
+
// SendGrid
|
|
18
|
+
/SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}/g,
|
|
19
|
+
// Private keys
|
|
20
|
+
/-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g,
|
|
21
|
+
// Named env vars with values
|
|
22
|
+
/(?:ANTHROPIC_API_KEY|OPENAI_API_KEY|API_KEY|SECRET_KEY|ACCESS_TOKEN|AUTH_TOKEN|DATABASE_URL|SUPABASE_SERVICE_ROLE_KEY)\s*=\s*['"]?([^\s'"]+)/gi,
|
|
23
|
+
// Generic key=value secrets
|
|
24
|
+
/(?:password|passwd|secret|token|credential|api_key|apikey|auth)\s*[:=]\s*['"]([^'"]{8,})['"]/gi,
|
|
25
|
+
];
|
|
26
|
+
export function redactSecrets(text) {
|
|
27
|
+
let result = text;
|
|
28
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
29
|
+
// Reset lastIndex for global patterns
|
|
30
|
+
pattern.lastIndex = 0;
|
|
31
|
+
result = result.replace(pattern, (match) => {
|
|
32
|
+
// Keep first 8 chars + mask the rest
|
|
33
|
+
if (match.length <= 12)
|
|
34
|
+
return match.slice(0, 4) + "...XXXX";
|
|
35
|
+
const eqIdx = match.indexOf("=");
|
|
36
|
+
if (eqIdx > 0) {
|
|
37
|
+
const key = match.slice(0, eqIdx + 1);
|
|
38
|
+
const val = match.slice(eqIdx + 1).replace(/^['"\s]+/, "");
|
|
39
|
+
if (val.length <= 4)
|
|
40
|
+
return match;
|
|
41
|
+
return `${key}${val.slice(0, 4)}...XXXX`;
|
|
42
|
+
}
|
|
43
|
+
return match.slice(0, 8) + "...XXXX";
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
// ── Finding Formatters ─────────────────────────────────────────────
|
|
49
|
+
export function formatHostFinding(f, format) {
|
|
50
|
+
if (format === "json") {
|
|
51
|
+
return JSON.stringify({
|
|
52
|
+
ruleId: f.ruleId,
|
|
53
|
+
severity: f.severity,
|
|
54
|
+
trustState: f.trustState,
|
|
55
|
+
verdict: f.verdict,
|
|
56
|
+
confidence: f.confidence,
|
|
57
|
+
source: f.source,
|
|
58
|
+
file: f.file,
|
|
59
|
+
line: f.line,
|
|
60
|
+
description: redactSecrets(f.description),
|
|
61
|
+
remediation: redactSecrets(f.remediation),
|
|
62
|
+
patchPreview: f.patchPreview ? redactSecrets(f.patchPreview) : undefined,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const icon = f.severity === "critical" ? "🔴" :
|
|
66
|
+
f.severity === "high" ? "🟠" :
|
|
67
|
+
f.severity === "medium" ? "🟡" :
|
|
68
|
+
f.severity === "low" ? "🔵" : "⚪";
|
|
69
|
+
const lines = [
|
|
70
|
+
`${icon} **[${f.severity.toUpperCase()}]** ${f.ruleId} — ${redactSecrets(f.description)}`,
|
|
71
|
+
` Trust: ${f.trustState} | Verdict: ${f.verdict} | Confidence: ${f.confidence}`,
|
|
72
|
+
];
|
|
73
|
+
if (f.file) {
|
|
74
|
+
lines.push(` File: \`${f.file}\`${f.line ? `:${f.line}` : ""}`);
|
|
75
|
+
}
|
|
76
|
+
lines.push(` Fix: ${redactSecrets(f.remediation)}`);
|
|
77
|
+
if (f.patchPreview) {
|
|
78
|
+
lines.push(` Patch: \`${redactSecrets(f.patchPreview)}\``);
|
|
79
|
+
}
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
export function formatHostFindings(findings, scannedFiles, skippedFiles, format, title = "Host Security Report") {
|
|
83
|
+
if (format === "json") {
|
|
84
|
+
const critical = findings.filter(f => f.severity === "critical").length;
|
|
85
|
+
const high = findings.filter(f => f.severity === "high").length;
|
|
86
|
+
const medium = findings.filter(f => f.severity === "medium").length;
|
|
87
|
+
const low = findings.filter(f => f.severity === "low").length;
|
|
88
|
+
const info = findings.filter(f => f.severity === "info").length;
|
|
89
|
+
return JSON.stringify({
|
|
90
|
+
summary: {
|
|
91
|
+
total: findings.length,
|
|
92
|
+
critical, high, medium, low, info,
|
|
93
|
+
scannedFiles: scannedFiles.length,
|
|
94
|
+
skippedFiles: skippedFiles.length,
|
|
95
|
+
},
|
|
96
|
+
findings: findings.map(f => ({
|
|
97
|
+
...f,
|
|
98
|
+
description: redactSecrets(f.description),
|
|
99
|
+
remediation: redactSecrets(f.remediation),
|
|
100
|
+
patchPreview: f.patchPreview ? redactSecrets(f.patchPreview) : undefined,
|
|
101
|
+
})),
|
|
102
|
+
manifest: { scanned: scannedFiles, skipped: skippedFiles },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const lines = [`# ${title}`, ""];
|
|
106
|
+
if (findings.length === 0) {
|
|
107
|
+
lines.push("No host security issues detected.");
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
111
|
+
const sorted = [...findings].sort((a, b) => (severityOrder[a.severity] ?? 99) - (severityOrder[b.severity] ?? 99));
|
|
112
|
+
lines.push(`**Issues found: ${findings.length}**`, "");
|
|
113
|
+
for (const f of sorted) {
|
|
114
|
+
lines.push(formatHostFinding(f, "markdown"));
|
|
115
|
+
lines.push("");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
lines.push("---", `Scanned: ${scannedFiles.length} files | Skipped: ${skippedFiles.length} files`);
|
|
119
|
+
if (scannedFiles.length > 0) {
|
|
120
|
+
lines.push("", "**Scanned files:**");
|
|
121
|
+
for (const f of scannedFiles)
|
|
122
|
+
lines.push(`- \`${f}\``);
|
|
123
|
+
}
|
|
124
|
+
if (skippedFiles.length > 0) {
|
|
125
|
+
lines.push("", "**Skipped files:**");
|
|
126
|
+
for (const f of skippedFiles)
|
|
127
|
+
lines.push(`- \`${f}\``);
|
|
128
|
+
}
|
|
129
|
+
return lines.join("\n");
|
|
130
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HostFinding, DoctorConfig } from "../server/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Scan MCP configuration files for security issues.
|
|
4
|
+
* Standalone tool + called by doctor.
|
|
5
|
+
*/
|
|
6
|
+
export declare function auditMcpConfig(projectPath: string, doctorConfig?: DoctorConfig): {
|
|
7
|
+
findings: HostFinding[];
|
|
8
|
+
scannedFiles: string[];
|
|
9
|
+
skippedFiles: string[];
|
|
10
|
+
};
|