首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >给 Agent 开个目录白名单,43 条绕过用例打穿了 21 条

给 Agent 开个目录白名单,43 条绕过用例打穿了 21 条

原创
作者头像
用户12722577
发布于 2026-11-06 13:41:35
发布于 2026-11-06 13:41:35
1340
举报

最近半年在做一件具体的事:让一个桌面端 Agent 能真正操作本机文件、跑命令、改表格。功能跑通花了两周,把权限收住花了两个月。

这篇只讲一件事,目录白名单到底该怎么写。那些「加个 startswith 判断就行了」的方案,我全部试过,全部被绕过。

第一版:路径前缀匹配

最直觉的写法:

代码语言:python
复制
ALLOWED = [r"D:\workspace", r"C:\Users\me\Documents\proj"]

def check(path: str) -> bool:
    p = os.path.abspath(path)
    return any(p.startswith(root) for root in ALLOWED)

三条安全边界,全错。

坑 1:abspath 不解析符号链接

os.path.abspath 只做 .. 和 . 的字符串折叠,不碰软链接。在 D:\workspace 下建一个指向 C:\Windows 的 junction,一条命令就够:mklink /J D:\workspace\sys C:\Windows。

此时 os.path.abspath(r"D:\workspace\sys\System32\config\SAM") 返回的仍然是 D:\workspace\...,startswith 通过,文件读到了。攻击面不需要多高的技术,一个被 prompt 注入的模型自己建这个 junction 就够。

坑 2:前缀匹配没有目录边界

D:\workspace 和 D:\workspace_evil 都能通过 startswith。目标路径写成 D:\workspace_evil\x.txt 就出去了。修法是补分隔符:

代码语言:python
复制
return any(p == root or p.startswith(root + os.sep) for root in roots)

坑 3:Windows 路径等价类

同一个文件至少有六种写法:

  • C:\Users\me\a.txt
  • c:\users\ME\A.TXT
  • C:\Users\me\a.txt.
  • C:\Users\me\A~1.TXT
  • \\?\C:\Users\me\a.txt
  • \\localhost\C$\Users\me\a.txt

前三个用 normcase 加 realpath 能处理,后三个不行。\\?\ 前缀会跳过所有路径规范化,\\localhost\C$ 是 UNC 形式的管理共享,两个都是独立通道。

第二版:三处改动

代码语言:python
复制
import os
from pathlib import Path

ROOTS = [
    Path(r"D:\workspace").resolve(),
    Path.home() / "Documents" / "proj",
]

def is_allowed(target: str, write: bool = False) -> bool:
    # 1. 规范化,strict=False 保证路径不存在时也不抛异常
    p = Path(target).resolve(strict=False)

    # 2. 任何 UNC / 设备路径直接拒绝
    if str(p).startswith("\\\\"):
        return False

    # 3. 带边界的前缀判断
    for root in ROOTS:
        r = root.resolve(strict=False)
        if p == r:
            return not write      # 根目录本身只读
        if str(p).lower().startswith(str(r).lower() + os.sep):
            return True
    return False

resolve(strict=False) 是这版的核心,它会解析符号链接和 junction,同时容忍不存在的路径。旧版 os.path.realpath 在 Python 3.8 之后行为也有变化,跨版本部署要注意。

实测数据

我写了 43 条绕过用例,分五组跑:

组别

用例数

修复前通过

修复后通过

路径穿越 ../

9

0

0

符号链接 / junction

8

5

0

前缀同名目录

6

6

0

Windows 等价路径

12

6

0

UNC / 管理共享

8

4

0

合计 43 条,修复前 21 条能出去,修复后 0 条。../ 那组一开始就是 0,因为 abspath 确实处理了它,这也是最容易被误判为「方案没问题」的原因。

白名单解决不了的三个问题

第一,白名单只防路径选错,防不住路径选对。

模型在 D:\workspace 里删掉 300 个文件,白名单全程放行。这是权限模型的固有边界,不是实现 bug。要挡这一层,只能靠写前 diff、操作回放、可撤销。

第二,读写必须分成两套白名单。

我最初读写共用一个根目录,结果一个「把这份 Excel 汇总一下」的任务,顺手改掉了同目录下 40 个源文件。现在的规则是所有写操作走一个更窄的根目录,且写之前必须落一份 diff 给人看。

第三,操作回放不做,前面两条都白搭。

每次工具调用记下完整 payload:路径、模式、参数、返回值摘要。出问题时能倒查是哪一步开始的。这个存储成本很低,几千次调用也就几 MB。

这套设计后来固化进了 PRIVO 的桌面端,它默认就是窄权限加写前 diff,每个写操作要人点一次确认。代价很明显,批量任务要多点几十次鼠标,赶工期的时候确实烦。

但四十五分钟的返工和几十次点击之间,我现在会选后者。

你们给 Agent 提权的时候,是直接放开整个用户目录,还是也做了这种收窄?中间那层边界是怎么划的?

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 第一版:路径前缀匹配
    • 坑 1:abspath 不解析符号链接
    • 坑 2:前缀匹配没有目录边界
    • 坑 3:Windows 路径等价类
  • 第二版:三处改动
  • 实测数据
  • 白名单解决不了的三个问题
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档