Skip to content

Commit c9fd7ff

Browse files
committed
feat: MCP 安全守卫 + 死循环监控集成
- 新增 mcp-guard.mjs 检测外部 AI 回复中的恶意注入 - 5 类检测:命令注入/凭证窃取/base64载荷/越界指令/供应链攻击 - 集成到 stop-hook.mjs,事件驱动零延迟 - 检测到注入时自动注入安全告警并记录到日志
1 parent dea4486 commit c9fd7ff

2 files changed

Lines changed: 180 additions & 1 deletion

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// MCP 安全守卫:检测外部 AI 回复中的恶意注入
2+
// 被 stop-hook 调用,事件驱动,零延迟
3+
import fs from "fs";
4+
import path from "path";
5+
import { fileURLToPath } from "url";
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
8+
9+
// ── 恶意模式定义 ──
10+
const PATTERNS = {
11+
// 命令注入:试图通过 shell 执行任意代码
12+
commandInjection: [
13+
/\|\s*(bash|sh|zsh|cmd|powershell|pwsh)\b/i,
14+
/`[^`]*`\s*\|/i, // 反引号内容管道
15+
/\b(curl|wget|fetch)\s+[-a-zA-Z]*\s*h[tt]*ps?:\/\//i, // 下载远程脚本
16+
/\b(powershell|pwsh)\s+(-Command|-EncodedCommand|-c)\s+/i,
17+
/\bInvoke-(Expression|WebRequest|Command)\b/i,
18+
/\bStart-Process\s+/i,
19+
/\b(exec|eval)\s*\(/i,
20+
/\bsystem\s*\(/i,
21+
/\bchmod\s+\+x\b/i,
22+
],
23+
24+
// 敏感路径/凭证读取
25+
credentialTheft: [
26+
/~\/\.ssh\//i,
27+
/id_rsa/i,
28+
/\.env\b/i,
29+
/credentials\.json/i,
30+
/config\.json.*(?:token|key|secret)/i,
31+
/etc\/shadow/i,
32+
/etc\/passwd/i,
33+
/AppData.*\\\.claude\\settings\.json/i,
34+
/ANTHROPIC_API_KEY|OPENAI_API_KEY/i,
35+
/MINERU_API_TOKEN/i,
36+
],
37+
38+
// base64 隐藏载荷(解码+管道到 shell)
39+
base64Payload: [
40+
/[\w+/]{100,}[\s]*\|[\s]*(base64|openssl|bash)/i,
41+
/echo\s+[\w+/]{50,}[\s]*\|[\s]*(base64|openssl)/i,
42+
/atob\([^)]+\)[\s]*[.]/i, // JS base64 decode + method call
43+
],
44+
45+
// 越界指令:试图修改系统配置或权限
46+
tampering: [
47+
/CLAUDE\.md/i,
48+
/settings\.json/i,
49+
/permissions\.allow/i,
50+
/permissions\.deny/i,
51+
/hooks\.Stop/i,
52+
/sudo\s+.*(?:rm|chmod|chown)\s+/i,
53+
/reg\s+delete/i,
54+
/netsh\s+advfirewall/i,
55+
/Set-(MpPreference|ExecutionPolicy)\b/i,
56+
],
57+
58+
// 典型后门/供应链攻击特征
59+
backdoor: [
60+
/npm\s+(install|add)\s+.*?--registry\s+/i, // 篡改注册表
61+
/npm\s+(install|add)\s+.*?--index-url/i, // 篡改 npm 源
62+
/pip\s+install\s+.*?--index-url/i, // 篡改 pip 源
63+
/npm\s+install\s+(?:--save\s+)?[^@\s]+@[^/\s]+\/[^\s]+/i, // 可疑版本号
64+
/\.\/configure.*--prefix.*\/usr/i, // 覆盖系统路径
65+
],
66+
};
67+
68+
const SEVERITY_LABELS = {
69+
commandInjection: "严重",
70+
credentialTheft: "严重",
71+
base64Payload: "重要",
72+
tampering: "严重",
73+
backdoor: "严重",
74+
};
75+
76+
// ── 主检测函数 ──
77+
export function scanResponse(text) {
78+
if (!text || text.length < 20) return [];
79+
80+
const alerts = [];
81+
82+
for (const [category, patterns] of Object.entries(PATTERNS)) {
83+
for (const regex of patterns) {
84+
const match = text.match(regex);
85+
if (match) {
86+
// 排除匹配在引用块或注释中的情况
87+
const context = extractContext(text, match.index);
88+
if (isInQuoteOrComment(context)) continue;
89+
90+
alerts.push({
91+
category,
92+
severity: SEVERITY_LABELS[category] || "一般",
93+
pattern: regex.source.substring(0, 60),
94+
matched: match[0].substring(0, 80),
95+
position: match.index,
96+
line: (text.substring(0, match.index).match(/\n/g) || []).length + 1,
97+
});
98+
}
99+
}
100+
}
101+
102+
return alerts;
103+
}
104+
105+
// ── 提取匹配位置上下文(前 100 字符)──
106+
function extractContext(text, pos) {
107+
const start = Math.max(0, pos - 100);
108+
return text.substring(start, pos + 100);
109+
}
110+
111+
// ── 判断是否在引用块或代码注释中 ──
112+
function isInQuoteOrComment(context) {
113+
// 简单判断:如果前面有 > (markdown 引用)或 //(注释)则跳过
114+
const lines = context.split("\n");
115+
const lastLine = lines[lines.length - 1];
116+
if (/^\s*>\s/.test(lastLine)) return true; // markdown 引用
117+
if (/^\s*\/\/\s/.test(lastLine)) return true; // 单行注释
118+
return false;
119+
}
120+
121+
// ── 生成告警报告 ──
122+
export function formatAlert(alerts, source) {
123+
if (alerts.length === 0) return null;
124+
125+
const lines = [
126+
`🔴 MCP 安全告警 — ${alerts.length} 项异常(来源: ${source})`,
127+
"=".repeat(50),
128+
];
129+
130+
for (const a of alerts) {
131+
lines.push(`[${a.severity}] ${a.category}`);
132+
lines.push(` 位置: 第 ${a.line} 行`);
133+
lines.push(` 匹配: ${a.matched}`);
134+
lines.push("");
135+
}
136+
137+
lines.push("操作: 已自动拦截,请审查后确认是否放行");
138+
return lines.join("\n");
139+
}
140+
141+
// ── 生成注入摘要(输出到 .jsonl)──
142+
export function logAlert(alerts) {
143+
const logFile = path.join(__dirname, "deadloop-monitor.jsonl");
144+
const entry = {
145+
t: new Date().toISOString(),
146+
level: "security",
147+
event: "mcp_guard_alert",
148+
count: alerts.length,
149+
alerts: alerts.map(a => ({
150+
sev: a.severity,
151+
cat: a.category,
152+
line: a.line,
153+
})),
154+
};
155+
try {
156+
fs.appendFileSync(logFile, JSON.stringify(entry) + "\n", "utf-8");
157+
} catch {}
158+
}

mcp-servers/deadloop-monitor/stop-hook.mjs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#!/usr/bin/env node
2-
// Stop hook:收到 Stop 事件时检测死循环,检测到则触发 AutoIt 打断+注入
2+
// Stop hook:收到 Stop 事件时检测死循环 + MCP 安全守卫
33
// 检测阈值从 config.mjs 读取,支持 preset 切换和 settings.json 覆盖
44

55
import fs from "fs";
6+
import { scanResponse, formatAlert, logAlert } from "./mcp-guard.mjs";
67
import path from "path";
78
import { fileURLToPath } from "url";
89

@@ -85,6 +86,26 @@ const last = assistantTexts[assistantTexts.length - 1];
8586
const prev = assistantTexts[assistantTexts.length - 2];
8687
const sim = jaccardSim(last, prev);
8788

89+
// ── MCP 安全守卫:检测外部 AI 回复中的恶意注入 ──
90+
const alerts = scanResponse(last);
91+
if (alerts.length > 0) {
92+
logAlert(alerts);
93+
const report = formatAlert(alerts, transcriptPath);
94+
console.error("[mcp-guard]", JSON.stringify({ alerts: alerts.length, categories: [...new Set(alerts.map(a => a.category))] }));
95+
// 注入安全告警
96+
const AUTOIT_EXE = path.join(__dirname, "deadloop_control.exe");
97+
if (fs.existsSync(AUTOIT_EXE)) {
98+
const tmpFile = path.join(__dirname, ".mcp_guard_alert.txt");
99+
fs.writeFileSync(tmpFile, report, "utf-8");
100+
try {
101+
const { execSync } = await import("child_process");
102+
execSync(`"${AUTOIT_EXE}" inject_file "${tmpFile}"`, { timeout: 10000 });
103+
} catch {}
104+
try { fs.unlinkSync(tmpFile); } catch {}
105+
}
106+
process.exit(0);
107+
}
108+
88109
// 使用可配置阈值
89110
if (sim < threshold) process.exit(0);
90111

0 commit comments

Comments
 (0)