Phase 7: Built-in Hooks (audit_log, dangerous_confirmation, security_scan) Phase 8: Plugin system (PluginManager, PluginSandbox, PluginManifest) Phase 9: Skills registry (SkillRegistry, local/plugin/MCP loaders) Phase 10: TeamLeader, RemoteTransport, BackgroundTaskManager
184 lines
5.9 KiB
Python
184 lines
5.9 KiB
Python
"""安全扫描 Hook - Phase 7.2
|
|
|
|
扫描工具调用和结果中的敏感信息。
|
|
"""
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
from app.agents.tools.hooks.types import (
|
|
ExecutionContext,
|
|
HookResult,
|
|
)
|
|
|
|
|
|
# 敏感信息模式
|
|
SENSITIVE_PATTERNS = {
|
|
"api_key": [
|
|
r"api[_-]?key['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_\-]{20,}",
|
|
r"apikey['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_\-]{20,}",
|
|
],
|
|
"password": [
|
|
r"password['\"]?\s*[:=]\s*['\"]?[^\s'\"]{8,}",
|
|
r"passwd['\"]?\s*[:=]\s*['\"]?[^\s'\"]{8,}",
|
|
r"secret['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_\-]{20,}",
|
|
],
|
|
"token": [
|
|
r"token['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_\-\.]{20,}",
|
|
r"bearer\s+[a-zA-Z0-9_\-\.]+",
|
|
r"ghp_[a-zA-Z0-9]{36}",
|
|
r"sk-[a-zA-Z0-9]{48}",
|
|
],
|
|
"private_key": [
|
|
r"-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----",
|
|
r"-----END (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----",
|
|
],
|
|
"ip_address": [
|
|
r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b",
|
|
],
|
|
"email": [
|
|
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
|
|
],
|
|
}
|
|
|
|
|
|
class SecurityScanHook:
|
|
"""安全扫描 Hook
|
|
|
|
扫描工具输入和输出中的敏感信息,进行脱敏处理。
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
redact: bool = True,
|
|
block_on_detect: bool = False,
|
|
):
|
|
"""
|
|
Args:
|
|
redact: 是否对敏感信息进行脱敏
|
|
block_on_detect: 检测到敏感信息时是否阻止执行
|
|
"""
|
|
self.redact = redact
|
|
self.block_on_detect = block_on_detect
|
|
self._compiled_patterns = {
|
|
name: [re.compile(p, re.IGNORECASE) for p in patterns]
|
|
for name, patterns in SENSITIVE_PATTERNS.items()
|
|
}
|
|
|
|
async def pre_tool_use(self, context: ExecutionContext) -> HookResult:
|
|
"""扫描输入参数"""
|
|
detected = self._scan_dict(context.tool_input)
|
|
|
|
if detected:
|
|
context.metadata["security_detected"] = detected
|
|
|
|
if self.block_on_detect:
|
|
return HookResult(
|
|
hook_name="security_scan",
|
|
success=False,
|
|
continue_execution=False,
|
|
error=f"检测到敏感信息: {', '.join(detected.keys())}",
|
|
metadata={"detected": detected, "blocked": True},
|
|
)
|
|
|
|
if self.redact:
|
|
redacted_input = self._redact_dict(context.tool_input.copy())
|
|
return HookResult(
|
|
hook_name="security_scan",
|
|
success=True,
|
|
continue_execution=True,
|
|
modified_input=redacted_input,
|
|
metadata={"detected": detected, "redacted": True},
|
|
)
|
|
|
|
return HookResult(
|
|
hook_name="security_scan",
|
|
success=True,
|
|
continue_execution=True,
|
|
)
|
|
|
|
async def post_tool_use(self, context: ExecutionContext, result: Any) -> HookResult:
|
|
"""扫描输出结果"""
|
|
if isinstance(result, dict):
|
|
detected = self._scan_dict(result)
|
|
|
|
if detected:
|
|
context.metadata["security_detected_output"] = detected
|
|
|
|
if self.redact:
|
|
redacted_result = self._redact_dict(result.copy())
|
|
return HookResult(
|
|
hook_name="security_scan",
|
|
success=True,
|
|
continue_execution=True,
|
|
modified_output=redacted_result,
|
|
metadata={"detected": detected, "redacted": True},
|
|
)
|
|
|
|
elif isinstance(result, str):
|
|
detected = self._scan_string(result)
|
|
if detected:
|
|
context.metadata["security_detected_output"] = detected
|
|
|
|
if self.redact:
|
|
redacted_result = self._redact_string(result)
|
|
return HookResult(
|
|
hook_name="security_scan",
|
|
success=True,
|
|
continue_execution=True,
|
|
modified_output=redacted_result,
|
|
metadata={"detected": detected, "redacted": True},
|
|
)
|
|
|
|
return HookResult(
|
|
hook_name="security_scan",
|
|
success=True,
|
|
continue_execution=True,
|
|
modified_output=result,
|
|
)
|
|
|
|
def _scan_dict(self, data: dict[str, Any]) -> dict[str, list[str]]:
|
|
"""扫描字典中的敏感信息"""
|
|
result: dict[str, list[str]] = {}
|
|
|
|
for key, value in data.items():
|
|
if isinstance(value, str):
|
|
found = self._scan_string(value)
|
|
if found:
|
|
result[key] = found
|
|
|
|
return result
|
|
|
|
def _scan_string(self, text: str) -> list[str]:
|
|
"""扫描字符串中的敏感信息"""
|
|
found_types = []
|
|
|
|
for name, patterns in self._compiled_patterns.items():
|
|
for pattern in patterns:
|
|
if pattern.search(text):
|
|
if name not in found_types:
|
|
found_types.append(name)
|
|
break
|
|
|
|
return found_types
|
|
|
|
def _redact_dict(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
"""脱敏字典中的敏感信息"""
|
|
for key, value in data.items():
|
|
if isinstance(value, str):
|
|
data[key] = self._redact_string(value)
|
|
elif isinstance(value, dict):
|
|
data[key] = self._redact_dict(value)
|
|
elif isinstance(value, list):
|
|
data[key] = [self._redact_string(v) if isinstance(v, str) else v for v in value]
|
|
|
|
return data
|
|
|
|
def _redact_string(self, text: str) -> str:
|
|
"""脱敏字符串中的敏感信息"""
|
|
for name, patterns in self._compiled_patterns.items():
|
|
for pattern in patterns:
|
|
text = pattern.sub(f"[REDACTED:{name}]", text)
|
|
|
|
return text
|