摘要:本文拆解 DeepSeek Harness 的三档文件权限沙箱设计,重点讲它在 macOS/Linux/Windows 上怎么用内核机制把门焊死,再深入 isolated-vm 在 Node.js 服务器端执行 AI 生成代码的实战配置、踩坑与 fail-closed 策略。


做 AI Agent 的同学迟早会撞上同一个问题:

你让 AI 干活,它要跑命令、改文件、执行代码。
那它到底能被允许碰什么?

这不是产品体验问题,是安全边界问题。
边界没焊死,后面全是坑。

我自己的 GEO Agent 场景里,AI 要写 JavaScript 做数据分析,跑在服务器上。
代码是 AI 生成的,你根本没法提前审计。

今天就把 DeepSeek 的三档沙箱设计和我自己的 isolated-vm 实践揉在一起讲透。
CSDN 上聊这个的不多,但这玩意儿是真能救命的。


DeepSeek Harness 文件沙箱三档权限详解

DeepSeek 把文件访问拆成三档,官方实测笔记里的原表我直接搬过来:

档位 能干什么 什么时候用
只读 什么都能看,一个字改不了 做分析、写方案
工作区可写(默认) 只能改你注册的那个目录 + 系统临时区 绝大多数情况
完全放开 不设限 你完全知道自己在干什么

出厂默认是中间那档。

你的桌面、下载文件夹、其他项目目录,全在圈外。
想动圈外的东西,得先问你,你可以拒绝。

看着简单对吧?
是不是觉得写个 if 判断权限就完事了?

不是。

真正拦住 agent 的不是 JavaScript 里的逻辑判断,是操作系统内核。
这才是重点。


三档权限的底层实现机制:sandbox-exec / bubblewrap / 受限令牌原理

DeepSeek 在不同平台用的内核级隔离机制不同:

平台 机制 原理
macOS sandbox-exec 进程向内核声明「我只准动这几个路径」,声明完自己都收不回
Linux bubblewrap → Landlock 优先 bubblewrap,不行退到内核的 Landlock
Windows 受限令牌 用降权的 token 启动子进程

这三个机制有一个共同性质,一句话说清楚:

这不是保安站在门口,是自己焊死的门。

哪怕 agent 后面被完全攻陷、执行了任意代码,那段代码也只能在圈里活动。
因为限制在内核里,不在进程里。

进程内的检查可以被绕,内核的隔离绕不动。
这是本质区别。

焊死的门

听我一句劝:做 agent 安全,别把希望寄托在应用层的 if 判断上。
那是纸糊的门。


服务器端 AI 代码执行沙箱:为什么不能用 Node.js 原生 vm 模块

我自己的场景跟 DeepSeek 不太一样。

我的 GEO Agent 跑在服务器上,用户说一句「帮我算一下这个月的流量趋势」,agent 就生成一段 JavaScript 去执行。
这段代码是 AI 写的,不是我写的。

问题来了:这段代码能干什么是必须管住的。

Node.js 自带的 vm 模块行不行?
官方文档自己都写了:这不是安全边界。

一个会写代码的 AI 想从 vm 里逃出去,办法多得是:

  • 原型链污染
  • constructor 逃逸
  • 直接拿 process 对象
  • 教科书级别的攻击面

vm 跑 AI 生成的代码,等于没设防。
真不行。


isolated-vm 实践教程:V8 隔离环境的安全配置与核心参数

我的方案是上 isolated-vm,一个真正的 V8 隔离环境。
它跟 vm 不一样,隔离级别在 V8 isolate 层面,不是简单包一层。

核心配置如下:

隔离环境配置:
  memoryLimit: 64(MB)
  执行超时: 10 秒
  不暴露任何宿主函数给隔离环境
  输出通过 copy 方式传出,不用 Reference

最后两行是关键,我展开讲。

早期 isolated-vm 教程会教你用 Reference 把宿主函数传进隔离环境,让里面的代码能「回调」外面。
这是巨大的安全隐患。

AI 写的代码可以通过这个引用访问到宿主环境的对象,等于你亲手给它开了一扇后门。
我的做法是零回调

  • 所有输入通过 copy 传入
  • 所有输出通过 copy 传出
  • 隔离环境里的代码对外面的世界一无所知

配置代码骨架大概长这样:

const ivm = require('isolated-vm');

// 创建隔离环境
const isolate = new ivm.Isolate({ memoryLimit: 64 });

// 创建上下文
const context = isolate.createContextSync();

// 所有输入通过 copy 传入
const sharedData = context.global;
sharedData.setSync('inputData', new ivm.ExternalCopy(JSON.stringify(input)).copyInto());

// 执行超时 10 秒
const script = isolate.compileScriptSync(userCode);
const result = script.runSync(context, { timeout: 10000 });

// 输出通过 copy 传出
const output = result.copySync();

这里有个巨坑我必须提醒:

ExternalCopy.copyInto() 返回的是一个隔离环境内的引用对象,不是宿主对象的引用。
但如果你用 new ivm.Reference(...) 传函数进去,性质就变了。

一句话:传输数据用 copy,传输能力用 Reference。
你只需要传数据,所以永远别碰 Reference。

软性封锁


isolated-vm 原生模块兼容性踩坑记录与 fail-closed 降级策略

isolated-vm 是个原生 C++ 模块。
不同平台、不同 Node 版本,兼容性都不一样。

万一它在某个环境里加载失败怎么办?

两条路:

  1. 降级到不安全的 vm
  2. 直接拒绝执行

我选了后者。

探测逻辑大概是这样:

function probeIsolatedVm() {
  try {
    require('isolated-vm');
    return { available: true };
  } catch (err) {
    return { available: false };
  }
}

// 生产环境:isolated-vm 不可用直接返回 503
// 开发环境:可以用 ALLOW_UNSAFE_NODE_VM=true 强制降级到 vm
function executeCode(code) {
  if (isolatedVmAvailable) {
    return runInIsolatedVm(code);
  }
  
  if (process.env.NODE_ENV === 'production') {
    throw new Error('安全代码执行环境当前不可用');
  }
  
  if (process.env.ALLOW_UNSAFE_NODE_VM === 'true') {
    return runInUnsafeVm(code); // 仅限开发调试
  }
  
  throw new Error('安全代码执行环境当前不可用');
}

生产环境里 isolated-vm 不可用就直接返回 503。
宁可功能挂掉,也不能开一个不设防的后门。

DeepSeek 面对 macOS 的 sandbox-exec 也是同样的选择。
那个工具被苹果标记为「已弃用」,他们的对策是加探测:万一哪天真没了,是拒绝执行,不是静默放行。

整条线的姿态一句话:沙箱不可用的时候,选择停下来,不选择降级成不设防。

这就是 fail-closed 策略。
安全系统默认关闭,比默认开放靠谱得多。


DeepSeek 软性封锁翻车复盘:沙箱提示词的时机比内容重要

DeepSeek 最早的做法很符合直觉:

把沙箱模式写进系统提示词,每个请求都带着「Bash 命令跑在只读文件沙箱下」这句话。

听起来只有好处——模型知道自己被限制了,不会白费力气去尝试。

然后线上数据把这个直觉打碎了。

带着这句话的时候,模型会拒绝去尝试那些本来会被拒、但完全可以升权拿到的工作。
第一次人工会话的 12 轮里,有 5 轮是以零次工具调用结束的。

它什么都没干,直接在回答里跟人解释「我没有权限」。

他们给这个现象起名叫:软性封锁。

沙箱本来只该拦住越界的动作,结果它把模型的尝试意愿一起拦掉了。

后来的修法是:

  • 不在开头劝退
  • 在撞墙时给梯子

模型真的越界了,内核拒绝它,然后给它一段指引——「你可以带上理由原样重试一次,我会弹窗问用户」。

限制的存在感落在「你真的越界了」那一刻,而不是均匀地摊在每一次呼吸里。

这个教训我记了很久:给 AI 立规矩,时机比内容重要。

换个角度看,这不只是 DeepSeek 的问题。
所有人做 prompt 约束都在犯同一个错:把限制前置,结果把主动性一起限制没了。


DeepSeek 审批弹窗为什么不提供「总是允许」:安全白名单的隐性代价

还有一个细节,很多人没注意到。

DeepSeek 的审批弹窗只有两个按钮:「拒绝」和「允许一次」。
没有「总是允许」。

没有记忆规则,没有白名单,同一条命令下次再来还是会问。

为什么要这么烦人?

因为他们吃过亏。

真实场景里,「总是允许」意味着你把一道安全门永久打开了,而用户根本不知道自己在为什么授权。

我自己也纠结过这个。
客户用我们的 agent 做数据导出,每次都要点一次确认,确实烦。

但后来我想通了:

烦,说明它在工作。
哪天不烦了,才是真的危险。


总结:Agent 沙箱设计的三个核心原则

整篇文章踩过的坑和验证过的结论,浓缩成三条:

  1. 安全边界必须落在内核层,不落在应用层
  2. 沙箱不可用时 fail-closed,不降级成不设防
  3. 安全提示的时机在撞墙后,不在开局前

这三条没有任何一条来自教科书,全是线上数据和生产事故堆出来的。

关于 isolated-vm 的更多细节,建议直接读官方 README,里面 Reference 和 ExternalCopy 的语义写得很清楚。
你只需要记住一件事:传数据用 copy,传能力用 Reference,而你永远不需要传能力。


本文作者来自 Adgine 团队,提供 GEO 服务(让品牌被 AI 推荐),官网 https://adgine.cn

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐