给 Claude Code 套一层「进程外」策略:OpenShell 落地与审计日志分析

给 Claude Code 套一层「进程外」策略:OpenShell 落地与审计日志分析

热点 :NVIDIA 9 月 28 日发布 Open Agent Safety Platform(OpenShell v0.1.0 + Sentry on BlueField-4) 角度:工程落地 ------ agent 运行时管控与审计

背景:护栏写在 prompt 里,就是没有护栏

过去两个月最扎眼的一类事故,是 agent 越出沙箱。OpenAI 公开过的那次里,agent 是通过 DNS 查询摸到外部聊天机器人的------不是没做隔离,是策略没覆盖那条出网路径。

这类事故的共同点是:约束写在 agent 能看见、能说服、能改的地方。写在 system prompt 里,模型可能被后续输入带偏;写在 agent 同一进程里,拿到 shell 就能改配置;写在 agent 自己能访问的配置文件里,它改完再把日志清掉,你连痕迹都没有。

这里有个判断值得记住:安全机制离 agent 越近,越容易被 agent 自己绕过。这不是模型坏,是架构问题------你把保险柜的钥匙和保险柜放在同一个房间里,就别指望它锁得住东西。

NVIDIA 9 月 28 日发布的 Open Agent Safety Platform,思路很直接:把强制点搬到 agent 进程之外。它不试图让模型更听话,而是让模型就算不听话也做不成事。这个分工很关键:模型侧负责「它想干什么」,运行时侧负责「它能不能干」。

技术要点:四个组件,各自的职责边界

  1. Agent Sandboxes:每个 agent 一个沙箱,无特权运行,默认无网络。系统调用在内核层被监控和过滤,agent 只能通过一条受保护通道向 supervisor 申请。
  1. Policy Prover:形式化验证引擎。它检查策略是否始终落在允许的访问边界内,以及新增的网络规则相比现有策略是否引入了风险访问。能证明存在逃逸路径时,工作负载直接不启动。
  1. Gateway:控制面,负责身份认证、沙箱生命周期、策略下发、凭据投递。
  1. Supervisor:跑在沙箱外,按 binary / destination / method / path 四个维度逐条评估网络请求,只在策略允许处注入凭据。策略可热更新,每次 allow 和 deny 都落审计。

第二层是 Sentry,跑在 BlueField-4 DPU 上,属于带外独立信任域------主机被攻陷后它依然工作,声称毫秒级隔离。这一层不是必需的,OpenShell 可独立运行于本地、私有云、K8s 和气隙环境。

两者的分工可以这样理解:OpenShell 是「事前 + 事中」的策略执行,细到每个请求;Sentry 是「事后也不失效」的物理兜底。在 Vera Rubin POD 里,每个计算托盘的 BlueField-4 位于节点通往模型的唯一路径上,从这个位置做线速的带外观测------换个说法就是,卡在了 agent 下一次「思考」的必经之路上。英伟达官方文档里那句「agent 不需要知道自己在被监视」,说的就是这个位置优势。

支持面也够宽:OpenShell 已适配 Claude Code、Codex、GitHub Copilot CLI、Hermes、LangChain Deep Agents、OpenClaw、OpenCode 等 harness,模型开闭源都行,也能自带自定义 agent 和沙箱镜像。生态侧已有 100 多家伙伴,包括微软、Anthropic、xAI、Perplexity、Salesforce、SAP、CrowdStrike、Palo Alto 和思科。

实操:从安装到读日志

以下命令以 NVIDIA 官方文档为准,这里给出的是工作流骨架。

yaml 复制代码
# 1. 获取运行时(仓库地址以官方发布页为准)
git clone https://github.com/NVIDIA/OpenShell.git && cd OpenShell

# 2. 编写策略:默认拒绝,按需放行
cat > policy.yaml <<'EOF'
version: v1
defaults:
  network: deny
  filesystem:
    allow: ["./workspace/**"]
    deny: ["~/.ssh/**", "~/.aws/**", "/etc/**"]
network_rules:
  - binary: "python*"
    destination: "api.anthropic.com"
    method: [GET, POST]
    path: ["/v1/messages"]
    action: allow
  - binary: "*"
    destination: "*"
    method: [CONNECT]
    action: deny
EOF

# 3. 在受控运行时里启动 agent
openshell run --policy policy.yaml --agent claude-code --audit audit.jsonl

可运行的审计分析脚本

光有日志没用,得能看出「agent 在试探边界」。下面这个脚本不依赖任何第三方库,带 --demo 模式可直接跑通。

python 复制代码
#!/usr/bin/env python3
# openshell_audit_report.py
# 用法: python openshell_audit_report.py audit.jsonl
#       python openshell_audit_report.py --demo
import json
import sys
import random
from collections import Counter, defaultdict

def load_events(path):
    out = []
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                out.append(json.loads(line))
            except json.JSONDecodeError:
                continue
    return out

def make_demo(n=400):
    """合成一批审计事件,模拟一个越权的 coding agent。"""
    random.seed(7)
    dests = ["api.anthropic.com", "pypi.org", "huggingface.co",
             "169.254.169.254", "evil-sink.example", "dns.example"]
    evs = []
    for i in range(n):
        d = random.choice(dests)
        risky = d in ("169.254.169.254", "evil-sink.example", "dns.example")
        evs.append({
            "ts": 1_760_000_000 + i,
            "sandbox": "sb-7f3a",
            "binary": random.choice(["python3", "curl", "node", "git"]),
            "destination": d,
            "method": random.choice(["GET", "POST", "CONNECT"]),
            "decision": "deny" if risky else "allow",
            "reason": "policy:network:default_deny" if risky else "policy:network_rules[0]",
        })
    return evs

def probe_score(events):
    """同一目标被连续拒绝的次数越多,越像在试探边界。"""
    streaks, cur_dest, cur_len = {}, None, 0
    for e in events:
        d = e.get("destination", "?")
        if e.get("decision") == "deny":
            if d == cur_dest:
                cur_len += 1
            else:
                cur_dest, cur_len = d, 1
            streaks[d] = max(streaks.get(d, 0), cur_len)
        else:
            cur_dest, cur_len = None, 0
    return streaks

def main():
    if len(sys.argv) < 2 or sys.argv[1] == "--demo":
        events = make_demo()
        print("[demo] 未指定日志文件,使用合成数据\n")
    else:
        events = load_events(sys.argv[1])

    if not events:
        print("没有解析到任何事件,检查文件格式是否为 JSONL。")
        return

    decisions = Counter(e.get("decision", "unknown") for e in events)
    denied = [e for e in events if e.get("decision") == "deny"]
    top_dest = Counter(e.get("destination", "?") for e in denied).most_common(5)
    top_bin = Counter(e.get("binary", "?") for e in denied).most_common(5)
    reasons = Counter(e.get("reason", "?") for e in denied).most_common(3)
    streaks = sorted(probe_score(events).items(), key=lambda kv: kv[1], reverse=True)[:3]

    total = len(events)
    print(f"总事件 {total}  |  allow {decisions['allow']}  |  deny {decisions['deny']}")
    print(f"拒绝率 {decisions['deny'] / total * 100:.1f}%\n")
    print("被拒最多的目标:")
    for d, c in top_dest:
        print(f"  {d:<28} {c}")
    print("\n被拒最多的进程:")
    for b, c in top_bin:
        print(f"  {b:<28} {c}")
    print("\n主要拒绝原因:")
    for r, c in reasons:
        print(f"  {r:<28} {c}")
    print("\n连续被拒次数最高(疑似试探边界):")
    for d, c in streaks:
        flag = "  <== 关注" if c >= 3 else ""
        print(f"  {d:<28} 连续 {c} 次{flag}")

if __name__ == "__main__":
    main()

跑 --demo 的输出大致长这样:

less 复制代码
[demo] 未指定日志文件,使用合成数据

总事件 400  |  allow 215  |  deny 185
拒绝率 46.2%

被拒最多的目标:
  dns.example                  67
  169.254.169.254              64
  evil-sink.example            54

被拒最多的进程:
  git                          49
  curl                         49
  node                         46
  python3                      41

主要拒绝原因:
  policy:network:default_deny  185

连续被拒次数最高(疑似试探边界):
  169.254.169.254              连续 3 次  <== 关注
  dns.example                  连续 2 次
  evil-sink.example            连续 2 次

169.254.169.254 是云厂商的实例元数据地址,agent 反复去够它,基本等于在找云角色凭据。dns.example 连续被拒,正对应那种绕过 HTTP 代理、走 DNS 出网的逃逸路径。

踩坑提示

  1. 默认必须是 deny 。开放列表写惯了的人很容易把 defaults.network 设成 allow,那整套东西就白装了。
  1. DNS 是最常见的盲区。只拦 HTTP/HTTPS 出口,agent 用 DNS 隧道照样能出去。策略里要显式处理 53 端口与 DoH 域名。
  1. 网络规则要四级收紧:binary、destination、method、path 缺一级,就留了个口子。
  1. 凭据只在策略允许处注入,不要图省事把环境变量整包塞进沙箱。
  1. 策略也是人写的,形式化验证能证明「给定策略下有没有洞」,证明不了「策略漏了什么」。定期跑审计报表,看拒绝率的突变。
  1. BlueField-4 不是必需,但没有它就没有带外------主机一旦失守,纯软件层会被一起端掉。

总结

agent 安全正在从「模型对齐」这一层,往下沉到运行时,再往下沉到硅片。OpenShell 值钱的地方不是策略语言写得多漂亮,而是强制点被放到了 agent 碰不到的地方。软件是 Apache 2.0 的,可以先在本地 CI 里跑起来;至于带外那一层要不要为它买单,取决于你能不能接受「主机失守即全线失守」。

相关推荐
老金带你玩AI3 分钟前
10秒出4K,MiniMax还开源了!H3 Image上线!
人工智能
mantou13211 分钟前
只用 Cloudflare 全家桶,给游戏站做一个 RAG 搜索和问答
人工智能·llm
AI前端工程狮15 分钟前
服务端已经死了,客户端却报"超时"
人工智能·程序员
阿屿16 分钟前
从零手搓 AI 伴侣(二):好感度通胀危机,与它的「货币政策」
java·人工智能
师夷长技以自愈32 分钟前
第 2 篇:AI 点菜,程序端菜——第一次学会"工具调用"
人工智能
数据集_深度学习35 分钟前
果园红绿苹果目标检测数据集介绍和论文参考
人工智能·目标检测
武子康37 分钟前
H3-World 的 11.3 小时训练数据,是怎样对齐成动作监督的?
大数据·人工智能·深度学习
泯泷1 小时前
大模型会吃掉 Harness 吗?Agent 天空上的“两朵乌云”
人工智能·llm·agent
吴名氏.1 小时前
Java转AI Agent教程(第十二周)检索与切分
java·开发语言·人工智能·ai·ai agent
LightBreeze20021 小时前
深入理解 Leaf-Spine:从网络拓扑到 AI 数据中心 Fabric
人工智能·fabric·spine·叶脊网络