前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航
返回 AI 情报前线
All News · 全部资讯3415
  • 用视觉模型统一解析混合文档
  • Claude Code Skills 何时比提示词划算
  • Meta 发布 Muse Code 编程代理
  • 用事件队列构建可靠的智能体
  • 用交叉编码器提升 RAG 首位命中率
  • 先拆解问题再回答的认知验证提示法
  • 如何防止多智能体读取过期状态
  • 用代码图谱压缩 AI 审查上下文
  • 可组合的工程级 Agent 技能集
  • 为语音 AI 构建可控运行模式
  • 用结构化输出构建可迁移审核器
  • 四个实验验证 AI 防护栏效果
  • LFM2.5 小模型实现端侧 Agent
  • 为客服 Agent 增设风险路由层
  • 生产级 AI 系统不能只靠 RAG
  • 阿里Wan 3.0开放公测
  • GPT-5.6向ChatGPT免费用户开放
  • 用金丝雀文件审计编程Agent越界
  • 为AI生成补丁建立合并门禁
  • 用4位本地模型远程编排开发任务
  • 在Agent与工具间加入权限代理
  • 拆解 vLLM 高吞吐推理架构
  • 用 Sanitizer 检验 AI 修复的 C++ 代码
  • 蚂蚁开源多智能体协作框架Avernet
  • VS Code与Cursor接入IDEA语言能力
  • 用可复现基准筛选AI编程模型
  • 用40行测试复现Agent越权调用
  • 为Agent工具链注入故障并接入CI
  • 先构建只读Agent再开放写权限
  • 用微型聊天机器人测试指令边界
  • 用大模型把每日日志变成故障摘要
  • 可替换的文档问答检索架构
  • 用 dsctl 自动化管理 DolphinScheduler
  • TONTOU 绕过 Spectre v2 防护
  • Apple 芯片本地跑模型:MLX 对比 llama.cpp
  • AI Agent 入侵暴露凭证复用风险
  • 百万Token让模型生成三维世界
  • 用证据排序缓解RAG位置偏差
  • AI写Rust通过编译后还要审什么
  • 分清Claude Code的上下文与记忆
  • 模型切换时保护分类数据语义
  • 下一代 Qwen 或对大客户按营收抽成
  • OpenAI 发布智能体插件开放规范
  • 生产级 LLM Agent 避坑实战
  • 更换大模型前先测真实业务负载
  • 低成本弹性部署多模态推理服务
  • AI 批量造站为何几乎赚不到钱
  • 用模型路由层降低供应商锁定
  • Bot 检测应转向代理授权验证
  • AI 应用生产化的成本与扩容指南
  • DeepSeek V4 Flash 基准与架构核验
  • 已加载 51 / 3415
9.0
重磅
AI SCORE
技术实践2026-08-07 11:13

在Agent与工具间加入权限代理

dev.to · AI#Agent安全#权限控制#工具调用
Editor brief · 编辑速览

文章提出让模型只提交结构化操作意图,由能力代理解析路径、主机和接收方,再按清单决定允许、拒绝或请求审批。所有决策写入审计记录,从而把Agent安全转化为可测试的精确权限策略。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

Agent 事故很少像电影里的反派那样张扬。它们更像是一个乐于助人的助手,把两项你从未打算组合起来的权限拼到了一起:一个可以汇总文件夹内容的笔记读取器,加上一个可以向 webhook 发送消息的工具,再加上文档中一段被投毒的文字,告诉它应该把笔记发送到哪里。

在写过将 prompt 变更视为迁移之后,我开始用同样的方式对待工具权限:把它当作一份需要评审的契约,而不是 system prompt 里某种模糊的氛围。我反复采用的模式是 capability broker(能力代理器)。模型可以请求执行某项操作,但无权亲自执行。

设计原则:意图离开模型,权限留在代码中

整个流程被刻意设计得非常朴素:

  • Agent 返回一个结构化意图,例如 {'tool': 'fs.read', 'args': {'path': 'notes/trip.md'}}。

  • 在任何操作触及操作系统或网络之前,broker 会解析路径、主机、收件人和参数标志。

  • manifest 决定允许、拒绝,还是需要审批。

  • 模型收到的要么是经过清理的数据,要么是一个看起来很普通的工具错误。

  • 每一次决策都会追加到本地审计表中。

这样一来,安全问题就从“模型会一直保持规矩吗?”变成了“策略是否准许了这一次具体调用?”后一个问题是可以测试的。

# capabilities.toml
schema = 1

[tools.fs_read]
roots = ['./workspace', './public_data']
never = ['*.pem', '.env', './private']

[tools.http_fetch]
hosts = ['api.github.com', 'raw.githubusercontent.com']
max_bytes = 200000

[tools.notify]
webhooks = ['https://hooks.internal.example/*']
approval = 'human'
# broker.py
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
import fnmatch, json, sqlite3, tomllib

class Verdict(Enum):
    ALLOW = 'allow'
    DENY = 'deny'
    APPROVAL = 'approval'

@dataclass
class Decision:
    verdict: Verdict
    reason: str = ''

class Broker:
    def __init__(self, manifest_path='capabilities.toml', db='audit.sqlite3'):
        self.cfg = tomllib.loads(Path(manifest_path).read_text())
        self.db = sqlite3.connect(db)
        self.db.execute('create table if not exists audit(tool text, args text, verdict text, reason text)')

    def _record(self, tool, args, decision):
        self.db.execute('insert into audit values (?,?,?,?)', (tool, json.dumps(args), decision.verdict.value, decision.reason))
        self.db.commit()

    def _path_ok(self, raw):
        p = Path(raw).expanduser().resolve()
        root_hits = [Path(r).expanduser().resolve() for r in self.cfg['tools']['fs_read']['roots']]
        if not any(p == r or r in p.parents for r in root_hits):
            return Decision(Verdict.DENY, 'outside approved roots')
        s = str(p)
        if any(fnmatch.fnmatch(s, pat) or p.name == pat for pat in self.cfg['tools']['fs_read']['never']):
            return Decision(Verdict.DENY, 'matches never list')
        return Decision(Verdict.ALLOW)

    def review(self, tool, args):
        table = self.cfg['tools'].get(tool)
        if table is None:
            d = Decision(Verdict.DENY, 'tool is not declared')
        elif tool == 'fs_read':
            d = self._path_ok(args.get('path', ''))
        elif tool == 'http_fetch':
            host = args.get('host', '')
            d = Decision(Verdict.ALLOW) if host in table['hosts'] else Decision(Verdict.DENY, 'host not declared')
        elif tool == 'notify':
            url = args.get('url', '')
            ok = any(fnmatch.fnmatch(url, pat) for pat in table['webhooks'])
            d = Decision(Verdict.APPROVAL, 'human signoff required') if ok and table.get('approval') == 'human' else Decision(Verdict.DENY, 'webhook not declared')
        else:
            d = Decision(Verdict.DENY, 'no reviewer implemented')
        self._record(tool, args, d)
        return d

    def invoke(self, tool, args):
        d = self.review(tool, args)
        if d.verdict is not Verdict.ALLOW:
            return {'ok': False, 'error': 'tool unavailable'}  # do not leak policy detail
        return run_real_tool(tool, args)  # inject implementations here

这里最重要的行为是:拒绝必须平淡无奇。如果 Agent 请求读取 ./workspace/../../.env,它得到的仍然是同一种平淡的 tool unavailable 响应形式,看起来与临时故障没有区别。它可以选择其他操作,但无法得知究竟触发了哪条规则,也无法与执行规则的组件讨价还价。

测试应该攻击 broker,而不是称赞模型

一个小型的 pytest 测试矩阵,比一段聪明的 prompt 更有用:

import pytest
from broker import Broker, Verdict

@pytest.mark.parametrize('path,verdict', [
    ('workspace/report.md', Verdict.ALLOW),
    ('workspace/../.env', Verdict.DENY),
    ('public_data/../private/payroll.csv', Verdict.DENY),
    ('workspace/key.pem', Verdict.DENY),
])
def test_fs_boundaries(tmp_path, path, verdict):
    b = Broker()
    assert b.review('fs_read', {'path': path}).verdict is verdict

def test_undeclared_tool_is_closed():
    assert Broker().review('shell.exec', {'cmd': 'ls'}).verdict is Verdict.DENY

def test_approval_is_not_silent_allow():
    b = Broker()
    d = b.review('notify', {'url': 'https://hooks.internal.example/alerts'})
    assert d.verdict is Verdict.APPROVAL

然后再加入一套对抗性语料:恳求模型的文档、冒充用户的文档、声称策略已经变更的文档,或是在 markdown 注释中编码指令的文档。断言永远不应该是“模型拒绝了”。正确的断言是:如果模型输出了一个被禁止的意图,审计表中就会出现一条拒绝记录,并且没有发生任何真实的副作用。表现良好的模型当然很好,但真正的控制措施,是一个依然会说“不”的 broker。

一个低成本实践闭环的地方

要对这种架构进行 red team 测试,并不需要投入巨额预算。你需要的是一个可以反复输入各种奇怪内容的 endpoint,以便在 manifest 不断演进的过程中持续测试。披露声明:本文是 MonkeyCode 产品推广活动的一部分。我曾在开发期间使用 MonkeyCode 提供的免费模型访问权限和免费服务器选项,把它作为一个低门槛环境来运行这些对抗性测试闭环;请把这视为一条可用性说明,而不是 benchmark、配额承诺或生产环境建议。在围绕它构建系统之前,请先查看其当前文档。

如果它适合你的场景,可以把它用于演练:植入恶意文档、重新生成意图、对比审计输出,再收紧 manifest。对于任何面向客户的系统,都应保留同一个 broker,然后根据延迟、数据处理方式和合规需求替换背后的模型。broker 不应该关心意图由哪个模型生成,因为无论由谁生成,意图都是不可信的。

它并不是 sandbox。安全的 allowlist 无法挽救存在漏洞的 fs_read 实现、工具内部泄露的 token 或 parser bug。对于严肃的工作负载,还应将其置于容器、独立用户、seccomp/AppArmor 和网络出口规则的保护之下。

路径检查很容易定义得不够严谨。匹配前应先完成路径解析,并考虑符号链接和大小写不敏感的文件系统,还要对路径分隔符、Unicode、.. 和末尾斜杠进行 fuzz 测试。

审批疲劳确实存在。如果每一次 notify 操作都需要人工点击确认,人们最终会开始无脑批准。应该把审批保留给不可逆或外部可见的操作。

固定流水线从中获益不大。如果你的工作流从不允许模型自由选择工具,那么静态路由可能已经能以更少的机制提供同等保障。

受监管的数据删除、支付、生产环境部署,以及医疗或法律相关操作,所需的保护措施远不止 glob:它们还需要双人控制、速率限制、防篡改日志,有时甚至需要正式评审。

这个理念经得起时间考验,而且非常简单:让高度依赖判断的文本生成远离高度依赖权限的操作执行。让模型提出建议,让平淡无奇的代码作出处理。对策略进行版本化,重放审计记录,并让拒绝看起来与工具普通的倒霉一天没有任何区别。

对于后续操作,你可以考虑屏蔽此人和/或举报滥用行为。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
用4位本地模型远程编排开发任务
下一篇
拆解 vLLM 高吞吐推理架构