Dual-point policy enforcement and secret stand-ins for MCP tools, in one MoonBit core.
| 项目 | 已有能力 |
|---|---|
| MCP Shield | 策略、密钥脱敏、跨工具外泄链、人工审批、哈希链审计 |
| MCPKernel / MCPGuard | 策略、污点扫描、沙箱、签名审计 |
| mcp-firewall | 策略、审批、哈希链、仪表盘 |
| taintmcp | 输出污点标记、来源追踪、首匹配策略 |
| AgentPerms | 录制真实会话后推断最小权限,再代理执行 |
| Authsome / OneCLI | 凭据代理:Agent 只拿到占位符,代理在出口替换 |
Agent ──stdio──▶ MoonGate 网关 ──stdio──▶ MCP Server ──▶ 文件 / 网络 / 数据库
│ │
│ 判定 1 │ 判定 2(内嵌 guard)
│ 策略 + 血缘 + 审批 │ 同一份策略,硬拒绝
└────────────────────────┘工具结果: API_KEY=sk-live-9f3a2b7c1d4e
│ 网关提取、指纹、存入会话 vault
▼
模型看到: API_KEY=«mg:8b201fda70f4dd1b»
│
├─ 发往未授权地址 → 拒绝(secret-reveal),明文不离开网关
└─ 发往授权地址 → 转发前替换回真值,上游收到真实密钥git clone https://github.com/zhangbowen2006/moongate.git
cd moongate
node demo/run.mjs| 场景 | 可观测证据 |
|---|---|
| 读取凭据 | 模型只看到 «mg:…»,明文没有进入上下文 |
| 替身发往未授权地址 | secret-reveal 拒绝;接收端 0 次 |
| 替身发往已授权地址 | 网关替换回真值;接收端收到真实密钥,1 次 |
| reports/../../etc/passwd | 规范化后命中 deny-escape,上游未收到请求 |
| 写报告 | 审批台出现请求;批准后文件才落盘 |
| 换内容再写 | 参数哈希变化,必须重新审批;拒绝后文件不变 |
| 删除文件 | 工具未出现在 tools/list,强行调用也被拒绝 |
| 绕过网关直连服务端 | 服务端 guard 拒绝删除与路径穿越,文件仍在 |
| 账本 | verify 逐条重算哈希链 |
node bin/moongate.mjs verify --ledger runs/demo.jsonl
node bin/moongate.mjs report --ledger runs/demo.jsonlmoon check --deny-warn
moon test --deny-warn # 47 个单元测试
node scripts/build.mjs
node --test test/*.test.mjs # 21 个 Node 测试moon add zhangbowen2006/moongate@0.7.1import {
"zhangbowen2006/moongate" @moongate,
}
fn main {
let policy = @moongate.parse_policy(policy_json) catch {
@moongate.PolicyError(message) => abort(message)
}
// 服务端第二执行点:只做硬拒绝,approval 交给网关
let verdict = @moongate.guard_decision(policy, "fs.delete", args_json)
println(@moongate.json_render(verdict))
}// examples/servers.demo.json
{ "servers": [
{ "name": "fs", "command": "node", "args": ["demo/server.mjs"], "env": { "MOONGATE_ROLE": "fs" } },
{ "name": "net", "command": "node", "args": ["demo/server.mjs"], "env": { "MOONGATE_ROLE": "net" } }
] }node bin/moongate.mjs run --policy examples/policy.workspace.json \
--servers examples/servers.demo.json --ledger runs/multi.jsonlnode bin/moongate.mjs import-policy --format moonpod \
--in examples/policy.moonpod.json --out runs/imported.json
node bin/moongate.mjs check --policy runs/imported.json{
"mcpServers": {
"workspace": {
"command": "node",
"args": [
"/abs/path/moongate/bin/moongate.mjs", "run",
"--policy", "/abs/path/policy.json",
"--ledger", "/abs/path/runs/session.jsonl",
"--",
"npx", "-y", "@modelcontextprotocol/server-filesystem", "/abs/path/workspace"
]
}
}
}import * as gate from 'moongate/dist/engine.js';
import { readFileSync } from 'node:fs';
const guard = JSON.parse(gate.guard_create(readFileSync('policy.json', 'utf8'))).handle;
const verdict = JSON.parse(gate.guard_check(guard, 'fs.delete', JSON.stringify(args)));
if (verdict.decision === 'deny') {
return { isError: true, content: [{ type: 'text', text: `blocked by ${verdict.rule}` }] };
}{ "id": "deny-escape", "tool": "fs.*", "decision": "deny",
"when": [{ "arg": "path", "normalize": "path", "prefix": ["..", "/", "C:/", "~"] }] }{
"mask_secrets": true,
"sensitive": [{ "tool": "fs.read", "when": [{ "arg": "path", "glob": ["**/*.env"] }] }],
"reveal": [{ "id": "internal", "tool": "net.post",
"when": [{ "arg": "url", "glob": ["**/internal/**"] }] }]
}{ "budget": { "max_calls": 80, "max_arg_bytes": 262144, "max_reveals": 5 } }hash_n = SHA-256(prev_hash + "\n" + canonical(event_without_hash))pub struct Budget {
max_calls : Int
max_arg_bytes : Int
max_single_bytes : Int
max_reveals : Int
}type Inflightpub struct Policy {
name : String
digest : String
default_decision : String
rules : Array[Rule]
sensitive : Array[SensitiveRule]
reveal : Array[RevealRule]
mask_secrets : Bool
egress_tools : Array[String]
on_taint : String
rate : RateLimit
budget : Budget
approval_ttl_ms : Int
min_secret_len : Int
hide_denied : Bool
}pub struct RawToken {
value : String
fingerprint : String
preview : String
}type SecretEntrypub struct SecretToken {
fingerprint : String
preview : String
}pub struct Session {
id : String
policy : Policy
seq : Int
prev_hash : String
call_seq : Int
inflight : Map[String, Inflight]
lists : Map[String, Bool]
pending : Map[String, PendingApproval]
taint : Map[String, TaintSource]
vault : Map[String, SecretEntry]
attempts : Array[Int64]
allowed : Int
denied : Int
approval_requests : Int
forwarded : Int
tainted_values : Int
total_calls : Int
arg_bytes : Int64
reveal_count : Int
}pub struct TaintSource {
call : String
tool : String
preview : String
}fn base64_encode(text : String) -> Stringfn capability_name(verb : String) -> Stringfn collapse_separators(text : String) -> Stringfn find_taint(taint : Map[String, TaintSource], texts : Array[String], min_len : Int) -> Array[TaintHit]fn fingerprint_of(text : String) -> Stringfn glob_match(pattern : String, text : String) -> Boolfn hex_uint(value : UInt, width? : Int) -> Stringfn is_passthrough_method(verb : String) -> Boolfn placeholder_for(fingerprint : String) -> Stringfn sha256_hex_bytes(data : BytesView) -> StringInstall
Download zipDual-point policy enforcement and secret stand-ins for MCP tools, in one MoonBit core.