很多 Agent 项目的第一版安全设计只有一张排除表:
yaml
exclude:
- finance/**
- secrets/**
- customers/raw/**
然后开发者会自然地得出一个结论:这些目录模型看不到,所以 Agent 是安全的。
问题在于,Agent 不只通过"把文件塞进 prompt"获取信息。它还可能调用搜索、浏览器、数据库、索引和发布工具;它读取的也不只原始文件,还有摘要、类型信息、缓存和派生产物。
GitHub 9 月 2 日宣布 Copilot app 与 CLI 正式支持管理员配置的内容排除策略。官方文档也很坦诚地列出边界:部分编辑器的 Edit/Agent 模式暂不支持;IDE 可能间接提供类型、符号定义和构建配置;符号链接和远程文件系统也有当前限制。
这不是在挑 Copilot 的毛病。恰恰相反,它把一个容易被忽略的事实摆到了台面上:context exclusion 是过滤器,不是完整的 authorization system。

一个反例:文件没读,秘密还是出去了
假设运营 Agent 不允许读取 finance/budget.xlsx,但可以:
- 查询一个已经索引了预算表的知识库;
- 读取昨天由财务 Agent 生成的摘要;
- 打开项目看板里的预算卡片;
- 把当前上下文发布到公开博客。
排除规则可以百分之百命中,事故仍然会发生。因为策略只拦了原始路径,没有管数据的派生关系和输出通道。
把它抽象成一条数据流会更清楚:
text
source file
├─> embedding index
├─> generated summary
├─> task log
└─> dashboard card
└─> public publisher
如果安全标签只挂在 source file 上,任何一次转换都可能把标签洗掉。
权限请求至少要有五个维度
路径匹配器通常只接收一个字符串。真正的 Agent 权限请求应至少包含:
typescript
type AgentAccess = {
principal: string; // 谁在工作
taskId: string; // 为哪次任务工作
resource: ResourceRef; // 要访问什么
action: Action; // 要做什么
channel?: Channel; // 结果要去哪里
};
例如同一份品牌资料:
- 研究 Agent 可以
read; - 写作 Agent 可以读取并生成内部草稿;
- 发布 Agent 只能读取已审批版本;
- 任何 Agent 都不能把客户原始名单写入公开平台。
权限边界因此可以写成五层:
| 层 | 需要回答的问题 |
|---|---|
| Identity | Agent 以哪个岗位、哪个委托人的身份运行? |
| Resource | 原始数据和派生数据是否允许访问? |
| Action | read、write、delete、publish 是否分别授权? |
| Channel | 结果能否流向当前平台、Webhook 或公开页面? |
| Evidence | 能否还原实际读取、调用和提交结果? |
deny list 不够,默认值应该是 deny
仅靠黑名单的问题不是规则写少了,而是未知对象默认放行。
更稳妥的策略顺序是:
typescript
function authorize(req: AgentAccess, p: Policy): Decision {
if (!p.identities.includes(req.principal)) return DENY;
if (p.resources.deny.some(x => x.matches(req.resource))) return DENY;
if (!p.resources.allow.some(x => x.matches(req.resource))) return DENY;
if (!p.actions.allow.includes(req.action)) return DENY;
if (req.channel && !p.channels.allow.includes(req.channel)) return DENY;
if (p.actions.requireApproval.includes(req.action)) return REQUIRE_APPROVAL;
return ALLOW;
}
实际工程里还要处理优先级、继承和策略版本,但有两个原则不要变:
- 没匹配到 allow 时是拒绝,不是放行;
- "可读"不会隐式推出"可发布"。
派生产物要继承来源标签
当 Agent 生成摘要、截图或中间文件时,给产物保存 provenance:
json
{
"id": "artifact:campaign-summary",
"derivedFrom": [
"workspace://finance/budget.xlsx",
"workspace://marketing/plan.md"
],
"classification": "CONFIDENTIAL",
"allowedActions": ["read", "summarize"],
"allowedChannels": ["internal_report"]
}
最保守的合并规则是:产物继承所有来源中最高的敏感等级,除非经过一个有记录、可复核的降级流程。
这会增加元数据维护成本,但它直接堵住了"原文件禁止访问,摘要却可以随便外发"的洞。

高风险动作不要发长期钥匙
Agent 如果只负责预填文章,就不应该长期持有最终发布能力。可以在页面检查通过后签发一次性 capability:
typescript
type Capability = {
principal: "agent:publisher";
action: "platform.publish";
subject: "draft:42@sha256:7af1...";
destination: "csdn";
expiresAt: string;
maxUses: 1;
};
标题、正文、目标账号或可见范围改变后,旧 capability 立即失效。执行器还要用幂等键和页面回读避免"不知道是否成功时再点一次"。
权限控制因此不是 UI 上多一个确认框,而是执行器在最后一刻重新证明:主体、对象版本、动作和通道仍与授权一致。
一组比单元测试更有用的越权用例
正向测试只能证明允许路径可用。权限系统更需要负向测试:
yaml
cases:
- name: denied source through search index
expect: DENY
- name: confidential summary to public publisher
expect: DENY
- name: symlink points outside workspace
expect: DENY
- name: publish capability expired
expect: DENY
- name: draft hash changed after approval
expect: REQUIRE_APPROVAL
- name: audit sink unavailable during publish
expect: DENY
每次替换模型、升级 IDE、增加 MCP 或新增数据源后,都应该重新跑这些用例。因为权限边界不是只由模型决定的,工具和运行环境同样会改变可达路径。
我们做 Tipkay 时的一个取舍
Tipkay 是面向小微企业、一人公司和小团队的按需 AI 员工平台。这也是我们做产品时很在意的一个边界:平台登录态保留在本地,私有扩展留在本机,减少凭据在外部系统之间搬运。
但我们不会把"本地"当成安全结论。不同岗位助手仍要按职责拆分 Skill、MCP、工具和业务资料;从写稿继续到配图、排版和平台预填时,最终外发动作还需要检查真实页面状态。
一个人的生意,也能有一支专业团队。只是这支团队同样需要岗位权限,不能因为成员是 AI,就把所有系统的钥匙放进同一个工具箱。
最后
内容排除值得做,它能减少敏感内容进入上下文的机会。但要把 Agent 放进真实业务,还需要补上资源白名单、动作级 capability、派生数据标签、输出通道控制和审计证据。
判断一套 Agent 权限设计是否成熟,可以问一个简单问题:
它只能说明"某个文件没被放进 prompt",还是能证明"这个岗位在这次任务里,只对这个版本做了被允许的动作,并且结果去了被允许的地方"?
两者之间,就是过滤器与权限系统的距离。
参考资料:
- GitHub Changelog:《Content exclusions generally available in Copilot app and CLI》,2026-09-02
- GitHub Docs:《Content exclusion for GitHub Copilot》,2026-09-03 访问