AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

越权工具与数据外泄

我的学习 →

正在读取学习记录…

订单 Agent 帮 A 租户的客服查看一张订单。租户表示一组相互隔离的客户或组织。正常情况下,客服只能读 A 租户的订单,并把商品摘要返回公司内部页面。

越权工具调用是执行了当前身份无权执行的动作;数据外泄是数据流到了无权接收的位置。两者可能同时发生,例如“读订单”工具拿到 B 租户数据,再把结果发往外部地址。也可能分别发生:工具有读取权,但输出字段或目的地超出了任务需要。

最小权限要求权限范围与任务匹配。对订单摘要而言,允许读订单、限定租户和输出位置,比给 Agent 一套能访问所有数据的共享管理员身份更容易控制。

把一次工具调用拆成四个问题

会话是应用保存当前用户状态的对象。示例 Session 的用户、租户、工具集合和目的地集合来自应用侧,而不来自模型生成的 JSON。工具参数只包含订单编号和目的地;它们仍需验证。

read_order 先看工具是否可用,再查对象是否存在、对象属于哪个租户、结果送到哪里,最后选择需要返回的字段。这样,未通过租户校验的记录不会先被格式化成邮件正文。字段投影是从记录中选择必要字段,示例仅返回商品,邮箱留在输入记录中。

权限要在每次读取与产生副作用时检查。缓存也需要带租户和用户范围:把缓存键仅写成订单编号,可能使另一个用户得到前一位用户的结果。数据库查询可将租户作为必要条件,下游服务同样保留身份上下文。

校验A 客服的正常值失败后结果
工具read_order拒绝调用
对象归属订单属于 A不返回订单字段
输出目的地internal不交给外部发送工具
字段范围商品摘要排除邮箱等无关信息
越权工具与数据外泄中输入、检查和结果分支的关系
图1:读取前核对工具、租户与输出目的地,通过后选取商品字段;跨租户或外发建议停止在检查处。

在空目录保存并运行两个文件

在 PowerShell 输入以下命令创建练习目录,并进入目录。目录创建好后,在编辑器中新建下面两份文件并保存到这个目录。

PowerShell
New-Item -ItemType Directory -Force l8-privilege
Set-Location l8-privilege

macOS 终端:

Bash
mkdir -p l8-privilege
cd l8-privilege

先保存 privilege_demo.py。它包含前面解释的目的地校验以及正常和边界输入。

Python
"""在读取数据前校验租户、工具和输出目的地。"""
from dataclasses import dataclass

@dataclass(frozen=True)
class Session:
    user: str
    tenant: str
    tools: frozenset[str]
    destinations: frozenset[str]

# 无真实客户信息;两条记录用于观察租户边界。
RECORDS = {
    "order-a": {"tenant": "A", "item": "课程笔记", "email": "a@example.test"},
    "order-b": {"tenant": "B", "item": "练习册", "email": "b@example.test"},
}
DESTINATION = "internal"

def read_order(session, order_id, destination):
    # 所有拒绝都发生在输出客户字段之前。
    if "read_order" not in session.tools:
        return "拒绝:工具权限"
    record = RECORDS.get(order_id)
    if record is None:
        return "拒绝:对象不可用"
    if record["tenant"] != session.tenant:
        return "拒绝:租户不匹配"
    if destination not in session.destinations:
        return "拒绝:输出目的地"
    # 按任务需要投影字段:摘要不需要邮箱。
    return "订单:" + record["item"]

def main():
    session = Session("reader-a", "A", frozenset({"read_order"}),
                      frozenset({"internal"}))
    print("正常读取 ->", read_order(session, "order-a", DESTINATION))
    print("另一租户 ->", read_order(session, "order-b", "internal"))
    print("外部输出 ->", read_order(session, "order-a", "external"))

if __name__ == "__main__":
    main()

再保存 test_privilege.py。测试通过程序的公开函数检查正常路径与拒绝路径;测试文件和主程序放在同一目录,才能按文件名导入。

Python
import unittest
# 固定良性输入检查正常、拒绝和边界路径,不连接外部服务。
from privilege_demo import Session, read_order

class PrivilegeTests(unittest.TestCase):
    def setUp(self):
        self.session = Session("u1", "A", frozenset({"read_order"}),
                               frozenset({"internal"}))

    def test_normal(self):
        self.assertEqual(read_order(self.session, "order-a", "internal"), "订单:课程笔记")

    def test_tenant(self):
        self.assertEqual(read_order(self.session, "order-b", "internal"), "拒绝:租户不匹配")

    def test_destination(self):
        self.assertEqual(read_order(self.session, "order-a", "external"), "拒绝:输出目的地")

    def test_tool(self):
        user = Session("u1", "A", frozenset(), frozenset({"internal"}))
        self.assertEqual(read_order(user, "order-a", "internal"), "拒绝:工具权限")

    def test_missing(self):
        self.assertEqual(read_order(self.session, "missing", "internal"), "拒绝:对象不可用")

    def test_minimize_output(self):
        self.assertNotIn("@", read_order(self.session, "order-a", "internal"))

if __name__ == "__main__":
    unittest.main()

返回同一个终端,先列出当前目录内容,确认两个文件名都以 .py 结尾,然后运行主程序。

Bash
ls
python -X utf8 privilege_demo.py

程序输出:

TXT
正常读取 -> 订单:课程笔记
另一租户 -> 拒绝:租户不匹配
外部输出 -> 拒绝:输出目的地

第一行返回 A 租户的商品摘要。第二行命中租户不匹配,未输出 B 租户的商品。第三行记录仍属于 A,但 external 不在目的地集合中,因此同样没有返回数据。允许一个工具名只回答了“能调用哪种功能”,后面的对象和目的地检查决定了具体数据能否流动。

运行测试:

Bash
python -X utf8 -m unittest -v test_privilege.py

末尾应显示 Ran 6 tests 和 OK。若提示找不到模块,先用 dir 核对目录与文件名。

改一处,观察决定怎样变化

把文件顶部 DESTINATION = "internal" 改成 DESTINATION = "external",再运行主程序。第一行变为 正常读取 -> 拒绝:输出目的地。订单与身份没有变化,变化发生在输出通道。将常量改回 "internal" 后,第一行恢复商品摘要。

Bash
python -X utf8 privilege_demo.py

恢复原值后,重新运行测试。

共享工具服务里的排查顺序

现象可能缺口具体检查
同一工具能读到其他租户只校验工具名将对象归属与会话租户比较,查询也带租户条件
摘要里出现邮箱整包序列化记录将输出改为字段白名单,确认下游日志不存原包
身份正确但外部发送成功读取与发送权限混为一组在发送入口独立校验收件人、通道和数据级别
客服读不到自己的订单会话租户或对象元数据错误查看两处可信记录,避免从模型参数补租户

生产系统需要兼顾报错与枚举风险:面对未知对象和无权对象,外部返回可以统一为“对象不可用”,详细拒绝原因留在受控审计里。示例将原因分开,便于跟做观察。权限撤销也要影响缓存和长任务,不能依赖任务开始时的一次判断。

OWASP 授权指南建议默认拒绝并对每次请求检查权限。实际服务还应在数据库、工具执行与输出侧落实相同的业务边界。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“越权工具与数据外泄”,哪种做法更稳妥?

对应知识点:L8-privilege

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L8-privilege

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L8-privilege

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L8-privilege

图片查看器
100%

文章配图 · 按 Esc 关闭