technosiveuk-ui/SentinelMCP

GitHub: technosiveuk-ui/SentinelMCP

一个面向 AI Agent 的开源 MCP 安全网关,通过策略引擎、数据脱敏和人工审批机制在运行时保护 AI Agent 的工具调用。

Stars: 11 | Forks: 0

SentinelMCP — Protect. Detect. Prevent.
# SentinelMCP **面向 AI Agent 的开源 MCP 安全网关** 由 [Technosive Ltd.](https://technosive.co.uk) 构建 [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) [![Go Version](https://img.shields.io/badge/Go-1.26+-00ADD8?logo=go)](go.mod) [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/09b3336066203400.svg)](https://github.com/technosiveuk-ui/SentinelMCP/actions/workflows/ci.yml) ## 什么是 SentinelMCP? SentinelMCP 是一个用于 **Model Context Protocol (MCP)** 的安全执行引擎,可在 runtime 保护 AI agent 的工具调用。它提供检查、策略执行、PII/secret 脱敏和审计日志功能——部署在您的 AI agent 与它们调用的工具之间。 它采用高性能 Go 语言构建,具备原生的 runtime 图编排、中断/恢复功能以及亚毫秒级的执行延迟——无需额外开发自定义管道即可实现 human-in-the-loop(人工介入)审批工作流。 ## 两种部署模式 根据您的架构需求,SentinelMCP 可以通过两种方式进行部署: ### 1. Proxy 模式(通用) 一个独立的 sidecar 二进制程序,用于拦截 HTTP/SSE MCP 流量。兼容**任何语言**(Python、TypeScript、Go 等)和任何 agent 框架。无需修改任何代码——只需将您的 MCP 流量路由通过该 proxy 即可。 ``` AI Agent (any language) → SentinelMCP Proxy → MCP Server ``` ### 2. Inline SDK 模式(Go 原生) 一个直接导入到您应用中的 Go 库。通过零拷贝图编排**在内存中**封装 MCP 工具调用。提供亚毫秒级的延迟和深度的上下文感知能力——无需网络跳转,也无需独立进程。 ``` Go AI Application → SentinelMCP SDK (in-process) → MCP Server ``` 这种双模式架构是 SentinelMCP 的关键差异化优势:像 Permit 或 Envoy 等竞争产品仅提供网络 proxy。借助 Inline SDK,Go 应用程序能以极低的延迟获得相同的安全执行能力。 ## 核心功能 - **MCP Proxy (Sidecar)** — 适用于任何 MCP 客户端的即插即用 HTTP/SSE proxy。对现有的 agent 框架透明。 - **Inline SDK (Go)** — 用于进程内执行的原生 Go 模块。在 Allow(允许)路径上具有亚毫秒级开销(19μs p99)。 - **Policy 引擎** — 基于本地 YAML 的策略定义,支持热重载。风险等级(低/中/高)映射到相应的执行动作(允许/脱敏/拦截/中断)。 - **数据防泄漏 (DLP)** — 基于正则表达式的 PII 和 secret 脱敏,覆盖工具参数和响应。内置 6 种匹配模式(私钥、密码、API key、信用卡、SSN、电子邮件)并支持自定义正则表达式。 - **传输与认证安全** — 安全网关也必须强化其自身的传输通道。默认采用回环明文(loopback-plaintext)并进行故障关闭(fail-closed)强化:可选的入站 TLS、API key 认证、用于控制审批-恢复流程的管理员 token、拒绝明文/IP字面量上游的严格模式、上游证书锁定(cert pinning)、出站凭证注入以及出站白名单。详见[传输安全](docs/transport-security.md)。 - **Human-in-the-Loop (HITL)** — 通过通用的 Webhook 中断高风险工具调用,并通过本地 API endpoint 恢复执行。由 BoltDB 支持的检查点机制确保持久化的中断/恢复。 - **审计日志** — 将结构化 JSON 日志输出到 `stdout`,并原生集成 OpenTelemetry (OTel) 以对接 SIEM pipeline。 - **状态管理** — 基于 BoltDB 的检查点机制,可在进程重启时持久的执行中断/恢复。 ## 🏗️ 架构与可扩展性 SentinelMCP 的构建严格遵循关注点分离原则,以确保可维护性、可测试性以及无缝的 Open-Core 体验。 `gateway/` 包定义了所有的核心接口和领域类型(Policy、DLP、Risk、Audit),并且对底层的 Eino 框架或 MCP 传输库保持**零依赖**。`adapter/eino/` 包是*唯一*导入 Eino 类型的包。 这种架构边界带来了三大主要优势: 1. **框架无关:** 如果底层编排框架需要更改,只需提供一个新的 adapter;核心安全引擎完全不受影响。 2. **独立可测试:** 核心领域逻辑(策略评估、DLP 脱敏、风险分析)可以进行纯净的单元测试,无需启动 MCP 服务器或 LLM 图。 3. **架构层面强制的 Open-Core:** 企业版实现(Teams/Slack HITL、Nightfall DLP、Control Plane API)只需通过接口替换到 `GatewayConfig` 中。无需对此开源代码库进行任何更改。 ## 快速开始 ### Proxy 模式 **1. 启动 sidecar。** 最快的方式是运行内置的三服务演示——它包含一个带有演示工具的 MCP 上游服务器、一个 sidecar,以及一个测试客户端,用于触发所有的执行流程: ``` docker compose up --build --abort-on-container-exit ``` 要针对您自己的上游服务运行一个持久化的 sidecar,请运行单个容器(或在本地运行:`go run ./cmd/sentinelmcp -config policies.yaml`): ``` docker run --name sentinelmcp \ -p 8080:8080 -p 9090:9090 \ -v ./policies.yaml:/etc/sentinelmcp/config.yaml \ ghcr.io/technosiveuk-ui/sentinelmcp:latest ``` 端口 `8080` 用于 MCP proxy;端口 `9090` 用于管理 API(健康探针以及供高风险中断使用的审批-恢复 endpoint)。添加 `-d` 以后台分离模式运行,然后使用 `docker logs -f sentinelmcp` 查看日志。下面的示例配置绑定了回环地址,适合本地运行;如果绑定非回环地址(例如容器内的 `0.0.0.0`),则必须使用 TLS 或加上 `--insecure-dev-mode` 标志——请参阅[传输安全](docs/transport-security.md)。 **2. 定义您的策略 (`policies.yaml`):** ``` schema_version: "1.0" global: default_risk: low # risk for tools with no explicit entry (set medium for a stricter default) redaction_mask: "***REDACTED***" sidecar: listen_addr: "localhost:8080" # loopback bind → plaintext HTTP is safe here health_addr: "localhost:9090" # admin server (health + approval-resume); loopback enforced transport: streamable_http strict: false # DEMO ONLY — the local upstream below is plaintext. Production # MUST set strict: true and dial https:// upstreams. admin_token: "" # gates /api/v1/approval/resume — set via SENTINELMCP_ADMIN_TOKEN # tls: # opt-in inbound TLS; required when listen_addr is non-loopback # cert_file: "/etc/sentinelmcp/tls.crt" # key_file: "/etc/sentinelmcp/tls.key" upstream_servers: # your MCP tool servers — at least one is required - name: my-tools url: "http://localhost:3001/mcp" auth: api_keys: {} # inbound key → principal; enforced on every call when non-empty # e.g. { "key-abc": "agent-prod" } via Authorization: Bearer / X-API-Key tools: read_file: risk: low # allow — args and response are still DLP-scanned redact_patterns: [PASSWORD, API_KEY] write_file: risk: medium # redact — sensitive argument fields are masked before the tool runs exec_command: risk: high # interrupt — pauses for human approval, then runs or blocks require_approval: true approval_reason: "Shell commands can modify system state" "db_*": # glob patterns are supported risk: high require_approval: true dlp_patterns: # six patterns are built in (PRIVATE_KEY, PASSWORD, API_KEY, INTERNAL_HOSTNAME: # CREDIT_CARD, SSN, EMAIL); define your own here as needed regex: '\b[a-z]+\.internal\.company\.com\b' type: pii ``` **3. 将您的 MCP 客户端指向该 proxy:** ``` http://localhost:8080/mcp ``` 该 proxy 支持标准的 Streamable HTTP MCP 协议,因此任何客户端(无论是 Python、TypeScript 还是 Go)都可以无需修改 SDK 直接连接到该 URL。将 `upstream_servers` 指向您自己的 MCP 工具服务器,proxy 就会在每次调用时执行上述策略。 ### Inline SDK 模式 使用简单的构建器 API **在进程内**保护工具调用——无需 sidecar,无需网络跳转,开销为亚毫秒级。完整指南请见:[`docs/INLINE-SDK.md`](docs/INLINE-SDK.md)。 ``` package main import ( "context" "fmt" "log" "github.com/technosiveuk-ui/sentinelmcp/gateway" "github.com/technosiveuk-ui/sentinelmcp/sdk" ) func main() { // Register plain Go functions as secured tools. invoker := sdk.NewFuncInvoker(). Register("read_file", func(_ context.Context, args map[string]any) (string, error) { return fmt.Sprintf("contents of %v", args["path"]), nil }). Register("exec_command", func(_ context.Context, args map[string]any) (string, error) { return fmt.Sprintf("ran %v", args["cmd"]), nil }) // Build the pipeline. StrictDefaults routes unknown tools to redact. pipeline, err := sdk.New(invoker). WithRisk("exec_command", gateway.RiskHigh). // interrupt for approval StrictDefaults(). Build() if err != nil { log.Fatal(err) } // Every call is inspected, DLP-scanned, policy-checked, and audited. result, err := pipeline.Run(context.Background(), "read_file", map[string]any{ "path": "/etc/config.yaml", }) if err != nil { log.Fatal(err) } fmt.Println("Result:", result) } ``` 运行完整的允许 / 脱敏 / 中断示例: ``` go run ./examples/inline-sdk ``` ## 工作原理 ``` flowchart LR A[AI Agent] --> S[SentinelMCP] S --> I[Inspect Tool Call] I --> D{Policy Decision} D -->|Allow| E[Execute Tool] D -->|Redact| R[Redact PII] --> E D -->|Block| X[Return Error] D -->|Interrupt| H[Human Approval] H -->|Approved| E H -->|Denied| X E --> O[Inspect Response] O --> A E --> MCP[MCP Server] MCP --> O ``` **Pipeline 流程:** 1. **检查** — 序列化工具参数,运行 DLP 扫描,在策略数据库中查找风险等级 2. **策略决策** — 根据风险进行路由:低→允许,中→脱敏,高→中断等待审批,或者直接拦截 3. **执行** — 调用上游 MCP 工具(如适用,将传入脱敏后的参数) 4. **检查响应** — 对工具响应进行 DLP 扫描,对敏感内容进行脱敏,发出结构化的审计事件 ## 传输与认证安全 SentinelMCP 是一个*安全*网关,因此它自身的传输接口也经过了强化,不会保留为明文。它遵循**回环明文默认**模型:在回环地址(`127.0.0.1`、`::1`、`localhost`)上,sidecar 可以在无认证的情况下运行明文 HTTP——由主机边界承载信任,就像本地 socket 上的 `redis` 或 `postgres` 一样。当它绑定到非回环接口或连接到网络上游的那一刻,它会立即**故障关闭**并强制要求 TLS 和身份认证,而不是降级为明文。 - **严格模式(默认为 `true`)** 在加载配置时会拒绝明文 `http://` 和 IP 字面量上游 URL。 - **入站 TLS** 在回环地址上为可选,但在非回环地址上为**必需**(`sidecar.tls`)。 - **API key 认证**(`auth.api_keys`)以常数时间验证每一个入站调用;解析出的主体信息会传递给策略层和审计层。 - **管理员 token**(`sidecar.admin_token` / `SENTINELMCP_ADMIN_TOKEN`)始终控制着审批-恢复 endpoint,即使在回环地址上也是如此。 - **上游身份** 可以按服务器进行锁定(`ca_bundle`、`server_name`、`pinned_sha256`);绝不设置 `InsecureSkipVerify`。 - **出站凭证** 从权限为 `0600` 的 `secrets.yaml` 或环境变量中注入,且绝不记录到日志中。 - **出站白名单**(`sidecar.egress_allowlist`)限制可连接的上游主机范围。 - **静态机密**:如果 `config.yaml` 或 `secrets.yaml` 包含机密信息,但权限设置为组/全局可读,在加载时将被拒绝。 完整的原理解释、绑定策略矩阵以及完整的故障关闭契约位于 [`docs/transport-security.md`](docs/transport-security.md)。这两个开放核心的扩展切面——`gateway/auth.Authenticator` 和 `gateway/secrets.Provider`——是企业版实现(mTLS、OAuth2/OIDC、HashiCorp Vault)挂载的位置,它们同样基于这些纯净的接口。 ## 架构 ``` sentinelmcp/ ├── gateway/ # Core domain (ZERO Eino imports) │ ├── types.go # GatewayContext, AuditEvent, DLPFinding, RiskLevel, Decision │ ├── graph.go # Pipeline, ToolInvoker, GatewayConfig, MetricsRecorder │ ├── policy.go # Policy, RiskDB interfaces + DefaultPolicy, YAMLRiskDB │ ├── policy_action.go # Action-based PolicySet (allow/redact/block/interrupt by rule) │ ├── policy_reload.go # Hot policy reload (atomic swap on config change) │ ├── redact.go # DLPScanner, Redactor, ScanArgs + RegexDLPScanner, DefaultRedactor │ ├── dlp_multi.go # MultiDLPScanner (compose regex + external) │ ├── dlp_external.go # DLPEndpoint interface + ExternalDLPScanner adapter │ ├── audit.go # AuditEmitter interface + Stdout/FileAuditEmitter │ ├── audit_sink.go # AuditSink + WriterAuditSink + CompositeAuditEmitter │ ├── metrics.go # MetricsRecorder interface + NOPMetricsRecorder │ ├── webhook_approval.go # WebhookApprovalProvider + CLIApprovalProvider │ ├── auth/ # Authenticator interface + APIKeyAuthenticator (open-core seam) │ │ └── auth.go # pure — no net/http; key→principal, constant-time │ └── secrets/ # secrets.Provider interface + FileEnvProvider (open-core seam) │ └── secrets.go # pure — resolves credentials_ref; secrets.yaml (0600) or env ├── sdk/ # Inline SDK: ergonomic builder over gateway + adapter │ ├── builder.go # Builder API (New, With*, StrictDefaults, Build) │ ├── func_invoker.go # FuncInvoker — secure plain Go functions │ └── defaults.go # Convenience constructors (BuiltinDLP, StdoutAudit, …) ├── adapter/eino/ # Eino framework adapter (ONLY package with Eino imports) │ ├── graph.go # BuildGraph() → 3-node graph with interrupt/resume │ ├── otel_metrics.go # OTelMetricsRecorder (counters + histograms) │ ├── otel_audit_emitter.go # OTelAuditEmitter (audit → span events) │ └── bolt_checkpoint.go # BoltCheckPointStore ├── adapter/sidecar/ # Sidecar proxy adapters │ ├── invoker.go # SidecarInvoker (routes to upstream MCP servers) │ └── discovery.go # DiscoverTools (upstream MCP server discovery) ├── adapter/siem/ # SIEM audit sink implementations │ ├── splunk_hec.go # SplunkHECSink (batching + retry) │ └── file_rotation.go # FileRotationSink (size-based + gzip) ├── config/ # YAML config loading, validation, hot-reload │ ├── config.go # Load, Validate, ToRiskDB, ToDLPPatterns │ └── watcher.go # ConfigWatcher (fsnotify + debounce) ├── cmd/ │ ├── sentinelmcp/ # Sidecar proxy binary │ │ ├── main.go # Bootstrap: config → discover → pipeline → proxy → admin │ │ ├── proxy.go # Proxy MCP server wrapping the pipeline │ │ ├── health.go # Admin server: /healthz, /readyz, /api/v1/approval/resume │ │ └── config.go # GatewayConfig wiring (audit sinks, approval, OTel) │ ├── upstream/ # Demo upstream MCP server (3 tools) │ ├── testclient/ # Demo test client (3 enforcement flows) │ └── demo/ # In-process demo (4 MCP servers) ├── examples/ │ └── inline-sdk/ # Runnable allow/redact/interrupt SDK demo ├── Dockerfile # Multi-stage distroless build ├── docker-compose.yml # One-command demo └── config/ ├── config.yaml # Default config └── docker-config.yaml # Docker-specific config ``` ## 开源版 vs. 企业版 | 功能 | 开源版 (本代码库 — Apache 2.0) | 企业版 (商业版) | |:--------|:------------------------------|:-------------------------| | **部署方式** | Sidecar Proxy + Inline Go SDK | 集中式 Control Plane | | **策略管理** | 本地 YAML,支持热重载 | 集中式 API/DB,Web UI | | **DLP** | 基于正则表达式(6种内置 + 自定义) | 企业级 DLP 连接器 (基于 API) | | **HITL 审批** | 通用 Webhook + CLI | 企业通讯渠道集成 | | **审计** | JSON stdout + OTel | SIEM 聚合,合规性 PDF 报告 | | **认证** | API keys (常数时间比较) | SSO, RBAC, 多租户 | | **传输安全** | 回环明文默认;可选 TLS、严格模式、证书锁定、出站白名单 | mTLS, OAuth2/OIDC, Mesh egress | | **机密管理** | 文件 (0600) / 环境变量 | HashiCorp Vault, AWS/GCP Secret Manager | | **状态存储** | BoltDB (本地) | 分布式 (Postgres) | ## 性能 在 Apple M1 Pro (Go 1.26) 上的基准测试: | 路径 | 延迟 | 内存分配 | |------|---------|-------------| | Allow (关键路径) | **19μs** | 182 | | Redact | 19μs | 185 | | Interrupt | 33μs | 303 | | DLP 扫描 | 9.3μs | 0 | | RiskDB 查找 | 71ns | 0 | | Policy 决策 | 193ns | 4 | ### NFR 验证 | NFR | 目标 | 验证方式 | |-----|--------|-------------| | p99 延迟 | ≤500μs Allow | `BenchmarkPipeline_Allow` — 19μs (低于预算 26 倍) | | 内存 | <10MB heap / 1000次调用 | `TestMemory_*` — 0.73MB 并发 | | 并发 | 100+ 并发,无死锁 | `TestLoad_*` — 1000 并发,0 死锁 | | 默认拒绝 | 发生任何故障均拦截 | `TestDegradation_*` — 所有错误路径均拦截 | ## 测试覆盖率 跨越 6 个包的 90 多项测试,7 项基准测试,在开启 `-race` 的情况下全部通过: ``` # 运行所有测试 go test ./... -race -count=1 # NFR 验证 go test ./adapter/eino/ -run "TestLoad_|TestMemory_|TestDegradation_" -race -v # Sidecar E2E go test ./cmd/sentinelmcp/ -race -v # Benchmarks go test ./adapter/eino/ -bench=. -benchmem ``` ### 端到端验证 完整的执行接口也会在真实的 MCP 上游环境中进行实时验证, 而不仅仅是在单元测试中。在演示上游服务运行且配置好 sidecar 对其进行代理(参见[快速开始](#quickstart);明文 `localhost` 上游 需要设置 `sidecar.strict: false`)的情况下,内置客户端将遍历每一条执行路径: ``` go run ./cmd/upstream # demo upstream (echo_message, filesystem_read, exec_command) go run ./cmd/sentinelmcp -config ... # sidecar proxy (point upstream_servers at :3001) go run ./cmd/testclient # allow / redact / interrupt+resume — exits non-zero on any failure ``` 实时运行验证了以下内容:**允许**(低风险直通)、**脱敏**(中风险下对 响应中的 secret 进行 DLP 掩码)、**中断 + 恢复**(高风险调用 暂停等待审批,随后通过管理员恢复 API 完成)、**故障关闭认证** (恢复 endpoint 会以 `401` 拒绝缺少/无效管理员 token 的请求),以及**策略热重载** (配置保存时即应用更改,无需重启)。 ## 许可证、商标与可扩展性 **开源代码:** 本代码库中的代码,包括 `gateway/` 接口和 `adapter/` 实现,均基于 [Apache 2.0 License](LICENSE) 授权。您可以根据该许可证自由使用、修改和分发此代码。 **企业版扩展与接口实现:** 本代码库在 `gateway/` 包中定义了可扩展接口(例如 `Policy`、`DLPScanner`、`ApprovalProvider`、`AuditEmitter`),以允许进行自定义集成。虽然这些接口本身是 Apache 2.0 授权的,但**由 Technosive Ltd. 提供的这些接口的专有实现**(例如 SentinelMCP 企业版 Control Plane、企业通讯 HITL 集成、高级 DLP 连接器)是另外基于商业许可证授权的,并且不包含在本代码库中。 在您自己的代码中实现这些接口并不要求您将实现基于 Apache 2.0 授权,也不强制您开源您的自定义集成代码。 **商标:** “SentinelMCP”以及 SentinelMCP 标识是 Technosive Ltd. 的商标。Apache 2.0 许可证并未授予您在未经 Technosive Ltd. 事先书面许可的情况下,使用 SentinelMCP 商标来认可或推广衍生自本软件的产品或服务的权利。
标签:EVTX分析, Go, MCP, Ruby工具, 代理网关, 安全网关, 日志审计, 用户代理, 策略控制, 请求拦截