Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

约束系统解析

为 Agent(智能体) 构建“牢笼“——通过权限、架构与规范三层约束实现可控的 AI 行为。

前置条件

  • 已完成 上下文工程核心,理解上下文管理的基本原理
  • 已安装 OpenCode CLI 并完成基础配置
  • 已了解基本的权限控制和安全概念

文章概述

Agent 需要牢笼才能自由发挥。约束系统是 Harness Engineering(驾驭工程) 中确保 Agent 行为可控的核心机制。没有约束的 Agent 就像没有围栏的施工现场——效率再高也无法让人放心。本章节详解约束系统的三大支柱:权限模型(能不能做)、架构护栏(怎么做)、Lint 规范(做得对)。读者将理解 OpenCode 的 3 种权限动作与三级策略(allow/ask/deny)的实际含义,掌握工具级与文件级权限控制的配置方法。

在架构护栏部分,我们讲解 AGENTS.md 如何作为架构决策的约束载体,以及如何通过规范文档约束 Agent 的代码生成方向。Lint 规则约束利用 LSP 诊断和 AST-grep 模式匹配在输出阶段进行自动校验。本章还包含威胁建模分析,涵盖越权访问、配置篡改、权限升级等典型攻击场景及其防御策略。学完本节,读者应能理解“好的约束让 Agent 更高效而不是更慢“的设计哲学,并构建适配项目需求的约束体系。

读完本文,你将能够配置三级权限策略控制 Agent 行为,通过架构护栏引导代码生成方向,以及利用 Lint 规则在输出阶段进行自动校验。

⏱ 时间有限?先读这些: 权限模型 → 架构护栏 → Lint 规则约束 → 约束的层级结构

操作系统类比:约束系统 = 操作系统安全机制

理解约束系统最直观的方式是将其类比为操作系统的安全管理机制:

操作系统概念OpenCode 对应说明
Unix 权限(rwx)+ SELinux 策略Permission Model定义 Agent 能做什么、不能做什么
OS 安全策略 / 组策略(GPO)Architecture Guardrails定义 Agent 应该怎么做,引导架构方向
内核 → 用户 → 进程权限层级Constraints Hierarchy全局约束→会话约束→任务约束的层级结构
系统调用门控工具级权限监控和限制 Agent 的每一次“系统调用“
文件系统 ACL文件级权限精确控制 Agent 对每个文件/路径的访问
审计日志(auditd)操作审计记录所有权限决策和操作行为

这个类比帮助理解几个关键设计:

  1. 层级隔离:操作系统有内核态/用户态隔离,约束系统有全局/会话/任务的分层控制
  2. 最小权限:操作系统遵循最小权限原则,约束系统的权限模型同样精确到文件级别
  3. 纵深防御:SELinux 在传统 Unix 权限之上增加安全策略,约束系统的三大支柱同样层层叠加

内容要点

  1. 约束系统总览 — Agent 需要“牢笼“的设计哲学,三大支柱:权限(能不能做)→架构(怎么做)→规范(做得对),职责分离原则。
  2. 权限模型 — 3 种权限动作概览(allow/ask/deny),三级策略的具体含义,以及工具级与文件级的粒度控制。
  3. 架构护栏 — 约束 Agent 架构决策的方法论,AGENTS.md 作为架构护栏的实现载体,实战示例(规范 Service/Repository 层生成规则,API 路径约定)。
  4. Lint 规则约束 — 利用 LSP 诊断自动约束 Agent 输出,外部工具(如 AST-grep)辅助的模式匹配代码规则检查,Code Review 作为人工约束的最后环节。
  5. 约束的层级结构 — 全局约束(项目级默认)、会话约束(当前会话生效)、任务约束(单次任务),冲突检测机制与优先级裁定规则。
  6. 约束与权限的关系 — 约束定义“行为规则“,权限定义“能力边界“,两者互补形成完整的 Agent 行为控制体系。
  7. 威胁建模分析 — 攻击者绕过约束的典型场景(越权访问、配置篡改、权限升级),约束系统如何防御这些攻击,以及纵深防御的最佳实践。

关联章节

  • ← 上下文工程核心:上下文工程为约束提供信息基础,约束反过来限制上下文的使用范围
  • → 验证护栏体系:验证护栏是约束的补充——约束管“准入“,验证管“准出“
  • → 环境搭建:权限模型在 opencode.json 中的具体配置实现

最小示例

用一个最简单的权限配置来理解约束系统:

{
  "permission": {
    "read": "allow",
    "edit": "ask",
    "bash": "deny"
  }
}

三行配置定义了 Agent 的行为边界:读取文件随便读(allow),写入要问一声且禁止执行危险命令(ask),删除等操作通过禁止 shell 执行来间接控制(deny)。这就是约束系统的核心——用 allow/ask/deny 三级策略给 Agent 画一个安全“牢笼“。

一、约束系统总览

1.1 为什么 Agent 需要“牢笼“

当 AI Agent 获得执行终端命令、读写文件、访问网络的能力后,它不再只是一个“聊天机器人“,而是一个具有实际执行能力的“数字员工“。这带来了巨大的效率提升,同时也引入了前所未有的风险。

没有约束的 Agent 会发生什么?

下图展示了缺乏约束时 Agent 可能引发的各类安全事故和连锁反应。

flowchart TB
    subgraph 无约束场景
        A1[用户:删除测试目录] --> B1[Agent 理解为:rm -rf /test]
        B1 --> C1[执行:rm -rf / test]
        C1 --> D1[❌ 删除根目录]
    end

    subgraph 有约束场景
        A2[用户:删除测试目录] --> B2[Agent 提议:rm -rf ./test]
        B2 --> C2{权限检查}
        C2 -->|路径在允许范围| D2[需要用户确认]
        C2 -->|路径在禁止范围| E2[自动拒绝]
        D2 --> F2[✅ 安全执行]
        E2 --> G2[✅ 阻止危险操作]
    end

    style D1 fill:#ffcccc
    style F2 fill:#ccffcc
    style G2 fill:#ccffcc

约束的核心价值:

价值维度无约束有约束
安全性一次误操作可能导致数据丢失、系统崩溃危险操作被拦截或需确认
可控性Agent 行为不可预测行为在预期范围内
可审计无法追溯“谁做了什么“每一步操作有记录
可信任不敢让 Agent 执行关键任务可以放心委派复杂任务

1.2 三大支柱:权限 → 架构 → 规范

约束系统由三大支柱构成,形成从“能不能做“到“怎么做“再到“做得对“的完整约束链:

graph TB
    subgraph 约束系统三大支柱
        P1[权限模型<br/>Permission Model]
        P2[架构护栏<br/>Architecture Guardrails]
        P3[Lint 规范<br/>Code Standards]
    end

    subgraph 约束层次
        L1["能不能做<br/>能力边界"]
        L2["怎么做<br/>架构方向"]
        L3["做得对<br/>代码质量"]
    end

    P1 --> L1
    P2 --> L2
    P3 --> L3

    L1 --> |约束| L2
    L2 --> |约束| L3

    style P1 fill:#4A90D9,color:#fff
    style P2 fill:#50C878,color:#fff
    style P3 fill:#FF9F43,color:#fff

三大支柱的职责分工:

支柱核心问题约束对象实现载体
权限模型Agent 能不能做这件事?工具调用、文件访问、命令执行opencode.json 权限配置
架构护栏Agent 应该怎么做?代码结构、模块划分、技术选型AGENTS.md 架构规范
Lint 规范Agent 做得对不对?代码风格、潜在错误、安全漏洞LSP + 外部工具(如 AST-grep)+ CI 门禁

1.3 约束金字塔

三大支柱形成金字塔结构,越底层的约束越基础、越严格:

graph TB
    subgraph 约束金字塔
        A[Lint 规范<br/>代码质量约束]
        B[架构护栏<br/>设计方向约束]
        C[权限模型<br/>能力边界约束]
    end

    A --> B
    B --> C

    C --> |基础层| D["最严格<br/>定义'能做什么'"]
    B --> |中间层| E["中等严格<br/>定义'应该怎么做'"]
    A --> |顶层| F["相对宽松<br/>定义'做得好不好'"]

    style C fill:#4A90D9,color:#fff
    style B fill:#50C878,color:#fff
    style A fill:#FF9F43,color:#fff

金字塔原则:

  • 底层约束决定上层边界:权限模型禁止的操作,架构护栏和 Lint 规范无需再检查
  • 上层约束补充下层不足:权限允许的操作,仍需符合架构规范和代码质量要求
  • 越底层越严格:权限约束是硬性边界,架构约束是方向指导,Lint 约束是质量要求

二、权限模型

2.1 三种权限动作

OpenCode 提供三种权限动作,覆盖从“完全信任“到“完全禁止“的全部场景:

graph LR
    subgraph 权限动作谱系
        A[allow<br/>自动允许] --> B[ask<br/>询问确认]
        B --> C[deny<br/>自动拒绝]
    end

    A --> |最宽松| D[信任度最高]
    C --> |最严格| E[信任度最低]

    style A fill:#50C878,color:#fff
    style B fill:#FF9F43,color:#fff
    style C fill:#ff6b6b,color:#fff

三种权限动作详解:

动作行为适用场景安全等级
allow自动允许,无需确认安全操作(读取非敏感文件、运行测试)低风险
ask每次询问用户确认敏感操作(写入文件、执行命令)中风险
deny自动拒绝,禁止执行危险操作(删除文件、访问密钥)高风险

2.2 三级策略:allow/ask/deny

三级策略是权限控制的核心机制,决定了 Agent 执行操作的流程:

flowchart TB
    A[Agent 请求执行操作] --> B{权限策略检查}

    B -->|allow| C[自动执行]
    C --> D[记录审计日志]
    D --> E[操作完成]

    B -->|ask| F[发送确认请求]
    F --> G{用户响应}
    G -->|批准| H[执行操作]
    G -->|拒绝| I[取消操作]
    H --> D
    I --> J[记录拒绝日志]

    B -->|deny| K[自动拒绝]
    K --> L[返回错误信息]
    L --> M[记录拒绝日志]

    style C fill:#50C878,color:#fff
    style F fill:#FF9F43,color:#fff
    style K fill:#ff6b6b,color:#fff

三级策略的配置示例:

// Requires OpenCode >= v1.17.x
{
  "permission": {
    "read": "allow",
    "edit": "ask",
    "bash": {
      "npm test": "allow",
      "npm run build": "allow",
      "rm -rf": "deny",
      "sudo *": "deny"
    }
  }
}

2.3 工具级与文件级权限控制

权限控制可以在不同粒度上实施:

工具级权限控制:

操作类别权限键推荐策略理由
文件读取readallow读取操作风险低
文件写入editask需确认修改内容
命令执行bashask需确认命令内容
网络访问webfetch/websearchdeny防止数据外泄
代码搜索codesearchallow只读操作

文件级权限控制:

{
  "permission": {
    "read": "allow",
    "edit": "ask",
    "bash": "ask"
  },
  "permissionOverrides": [
    { "path": "src/**", "read": "allow", "edit": "ask" },
    { "path": "test/**", "read": "allow", "edit": "allow" },
    { "path": ".env", "read": "deny", "edit": "deny" },
    { "path": "config/secrets/**", "read": "deny", "edit": "deny" },
    { "path": "node_modules/**", "read": "allow", "edit": "deny" }
  ]
}

2.4 只读模式的安全审查价值

只读模式是一种通过权限配置实现的运行方式,Agent 仅能读取内容,禁止所有修改操作。这在安全审查场景中极具价值:

sequenceDiagram
    participant User as 安全研究员
    participant Agent as Plan Agent<br/>(只读模式)
    participant Code as 代码库
    participant Report as 审查报告

    User->>Agent: 分析这个项目的安全漏洞
    Agent->>Code: 读取源代码
    Code-->>Agent: 返回代码内容
    Agent->>Agent: 分析安全风险
    Note over Agent: 禁止写入任何文件
    Agent->>Report: 生成审查报告(仅输出)
    Report-->>User: 展示安全发现

    Note over Agent: 整个过程零修改

只读模式的典型应用场景:

  1. 安全审计:分析代码漏洞,不修改任何文件
  2. 架构评审:评估架构设计,输出建议报告
  3. 代码审查:检查代码质量,生成审查意见
  4. 依赖分析:分析依赖关系,识别风险组件

只读模式的配置:

{
  "permission": {
    "read": "allow",
    "edit": "deny",
    "bash": "deny"
  }
}

三、架构护栏

3.1 什么是架构护栏

架构护栏(Architecture Guardrails)是一套约束 Agent 架构决策的规则体系。它不关心“代码写得对不对“(这是 Lint 的职责),而是关心“架构方向对不对“。

为什么需要架构护栏?

没有架构护栏的 Agent 可能生成“能运行但架构混乱“的代码:

// Agent 生成的代码:功能正确,架构混乱
// 所有逻辑堆在一个文件里,没有分层
// user-controller.js
export async function handleUserRequest(req, res) {
    // 直接在这里写数据库操作
    const connection = await mysql.createConnection(config);
    const [users] = await connection.execute('SELECT * FROM users');
    // 直接在这里写业务逻辑
    const processedUsers = users.map(u => ({
        ...u,
        displayName: u.first_name + ' ' + u.last_name
    }));
    // 直接在这里写响应格式化
    res.json({
        success: true,
        data: processedUsers,
        timestamp: new Date().toISOString()
    });
}

有架构护栏的 Agent 会遵循项目架构规范:

// Agent 生成的代码:功能正确,架构清晰
// controllers/user-controller.js
export class UserController {
    constructor(userService) {
        this.userService = userService;
    }
    async handleGetUsers(req, res) {
        const users = await this.userService.getAllUsers();
        res.json(UserResponseFormatter.format(users));
    }
}

// services/user-service.js
export class UserService {
    constructor(userRepository) {
        this.userRepository = userRepository;
    }
    async getAllUsers() {
        return this.userRepository.findAll();
    }
}

// repositories/user-repository.js
export class UserRepository {
    async findAll() {
        return db.query('SELECT * FROM users');
    }
}

3.2 AGENTS.md 作为架构护栏载体

AGENTS.md 是 OpenCode 的项目指令文件,它告诉 Agent 这个项目的架构规范、技术栈、约束条件。这是实现架构护栏的核心载体。

AGENTS.md 的典型结构:

# 项目架构规范

## 技术栈
- 后端:Node.js + Express + TypeScript
- 数据库:PostgreSQL + Prisma ORM
- 前端:React + TypeScript + Tailwind CSS

## 架构分层
项目采用三层架构,Agent 生成代码时必须遵循:

### Controller 层(controllers/)
- 只负责 HTTP 请求处理
- 调用 Service 层处理业务逻辑
- 不直接访问数据库

### Service 层(services/)
- 封装业务逻辑
- 调用 Repository 层访问数据
- 不直接处理 HTTP 请求/响应

### Repository 层(repositories/)
- 封装数据库操作
- 使用 Prisma Client
- 不包含业务逻辑

## API 路径约定
- RESTful 风格
- 路径前缀:/api/v1/
- 命名规范:kebab-case

## 禁止事项
- 禁止在 Controller 中直接写 SQL
- 禁止在 Service 中处理 HTTP 响应格式化
- 禁止跳过 Repository 直接访问数据库

3.3 架构护栏实战示例

示例一:规范 Service/Repository 层生成规则

当用户请求“添加用户登录功能“时,有架构护栏的 Agent 会:

flowchart TB
    A[用户请求:添加用户登录功能] --> B[Agent 读取 AGENTS.md]
    B --> C[解析架构规范]

    C --> D[生成 Controller]
    C --> E[生成 Service]
    C --> F[生成 Repository]

    D --> D1[controllers/auth-controller.ts]
    E --> E1[services/auth-service.ts]
    F --> F1[repositories/user-repository.ts]

    D1 --> G[遵循分层架构]
    E1 --> G
    F1 --> G

    G --> H[✅ 架构合规的代码]

    style H fill:#ccffcc

示例二:API 路径约定

## API 路径约定

### RESTful 规范
- GET /api/v1/users - 获取用户列表
- GET /api/v1/users/:id - 获取单个用户
- POST /api/v1/users - 创建用户
- PUT /api/v1/users/:id - 更新用户
- DELETE /api/v1/users/:id - 删除用户

### 命名规范
- 路径使用 kebab-case:/api/v1/user-profiles
- 禁止使用 camelCase:/api/v1/userProfiles ❌
- 禁止使用 snake_case:/api/v1/user_profiles ❌

3.4 架构护栏与权限模型的协作

架构护栏与权限模型形成双层约束:

flowchart TB
    A[Agent 请求生成代码] --> B{权限检查}
    B -->|允许| C{架构护栏检查}
    B -->|拒绝| D[❌ 权限不足]

    C -->|符合规范| E[生成代码]
    C -->|违反规范| F[❌ 架构违规]

    E --> G{Lint 检查}
    G -->|通过| H[✅ 代码入库]
    G -->|不通过| I[❌ 代码质量问题]

    style H fill:#ccffcc
    style D fill:#ffcccc
    style F fill:#ffcccc
    style I fill:#ffcccc

四、Lint 规则约束

4.1 LSP 诊断自动约束

LSP(Language Server Protocol) 诊断是约束 Agent 输出的第一道质量门禁。当 Agent 生成代码后,LSP 会自动检查语法错误、类型错误、潜在问题。

sequenceDiagram
    participant Agent
    participant LSP as Language Server
    participant Editor
    participant User

    Agent->>Editor: 生成代码
    Editor->>LSP: 请求诊断
    LSP->>LSP: 语法检查
    LSP->>LSP: 类型检查
    LSP->>LSP: 语义分析

    alt 存在错误
        LSP-->>Editor: 返回诊断结果(错误)
        Editor-->>Agent: 显示错误信息
        Agent->>Agent: 自动修复
        Agent->>Editor: 重新生成代码
    else 无错误
        LSP-->>Editor: 返回诊断结果(通过)
        Editor-->>User: 显示代码
    end

LSP 诊断能力矩阵:

语言LSP 实现诊断能力
TypeScripttsserver语法、类型、语义
PythonPylance/pyright语法、类型、导入
Gogopls语法、类型、格式
Rustrust-analyzer语法、类型、借用检查
Javajdtls语法、类型、风格

4.2 外部工具辅助:AST-grep 模式匹配

AST-grep 是一种基于抽象语法树(AST)的模式匹配工具(可在 OpenCode 外部配合使用),可以检测代码中的特定模式并自动修复。它比正则表达式更精确,因为它理解代码结构。

AST-grep 规则示例(需单独安装 @ast-grep/cli):

# 规则:禁止在 Controller 中直接写 SQL
id: no-direct-sql-in-controller
language: typescript
severity: error
message: "Controller 中禁止直接执行 SQL,请使用 Repository 层"

rule:
  pattern: |
    await $CONNECTION.execute($SQL)

  kind: call_expression

constraints:
  SQL:
    regex: "^['\"`].*SELECT|INSERT|UPDATE|DELETE.*['\"`]$"

files:
  include: ["controllers/**/*.ts"]
  exclude: ["repositories/**/*.ts"]

AST-grep 检测流程:

flowchart TB
    A[Agent 生成代码] --> B[AST-grep 扫描]
    B --> C{匹配规则?}

    C -->|匹配禁止模式| D[报告违规]
    D --> E[Agent 自动修复]
    E --> B

    C -->|无匹配| F[通过检查]
    F --> G[代码入库]

    style D fill:#ffcccc
    style G fill:#ccffcc

4.3 Code Review 作为人工约束

自动化约束无法覆盖所有场景,Code Review 是人工约束的最后环节:

graph TB
    subgraph 自动化约束
        A1[权限模型]
        A2[架构护栏]
        A3[LSP 诊断]
        A4[AST-grep]
    end

    subgraph 人工约束
        B1[Code Review]
        B2[架构评审]
        B3[安全审查]
    end

    A1 --> A2 --> A3 --> A4 --> B1
    B1 --> B2 --> B3

    A1 --> |"能做"| A2
    A2 --> |"该这样做"| A3
    A3 --> |"语法正确"| A4
    A4 --> |"模式合规"| B1
    B1 --> |"质量合格"| B2
    B2 --> |"架构合理"| B3
    B3 --> |"安全合规"| C[✅ 代码合并]

    style C fill:#ccffcc

Code Review 检查清单:

检查维度检查项自动化程度
功能正确性是否满足需求?部分自动化(测试覆盖)
架构合规性是否遵循分层架构?部分自动化(AST-grep)
代码可读性命名是否清晰?人工检查
性能影响是否有性能问题?部分自动化(性能测试)
安全风险是否有安全漏洞?部分自动化(安全扫描)

五、约束的层级结构

5.1 三层约束模型

约束系统采用三层结构,从全局到任务逐级细化:

graph TB
    subgraph 约束层级
        A[全局约束<br/>Global Constraints]
        B[会话约束<br/>Session Constraints]
        C[任务约束<br/>Task Constraints]
    end

    A --> B --> C

    A --> |"项目级默认"| D["opencode.json<br/>AGENTS.md"]
    B --> |"当前会话生效"| E["会话配置<br/>临时规则"]
    C --> |"单次任务"| F["任务参数<br/>Skill 约束"]

    style A fill:#4A90D9,color:#fff
    style B fill:#50C878,color:#fff
    style C fill:#FF9F43,color:#fff

三层约束详解:

层级作用范围生效时机配置载体
全局约束整个项目项目加载时opencode.json、AGENTS.md
会话约束当前会话会话启动时会话参数、环境变量
任务约束单次任务任务执行时Skill(技能) 配置、命令参数

5.2 冲突检测与优先级裁定

当不同层级的约束发生冲突时,系统按以下规则裁定:

flowchart TB
    A[约束冲突检测] --> B{冲突类型?}

    B -->|权限冲突| C[更严格的权限优先]
    B -->|架构冲突| D[任务约束优先]
    B -->|规范冲突| E[全局约束优先]

    C --> C1["allow vs deny → deny"]
    C --> C2["ask vs deny → deny"]

    D --> D1["任务指定架构 > 项目默认架构"]
    D --> D2["Skill 约束 > AGENTS.md 约束"]

    E --> E1["项目规范 > 会话临时规则"]
    E --> E2["全局 Lint > 任务 Lint"]

    style C fill:#ff6b6b,color:#fff
    style D fill:#FF9F43,color:#fff
    style E fill:#4A90D9,color:#fff

优先级规则总结:

  1. 安全优先原则:涉及安全的冲突,始终选择更严格的约束
  2. 任务优先原则:架构和规范冲突,任务级约束优先
  3. 显式优先原则:显式配置优先于隐式继承

六、威胁建模分析

约束系统是攻击者的重点目标。从 STRIDE 威胁模型(Spoofing/Tampering/Repudiation/Information Disclosure/DoS/Elevation of Privilege)来看,约束系统面临的攻击场景包括权限提升、配置篡改和信息泄露等。约束系统通过权限模型、架构护栏、Lint 规范三层纵深防御机制应对这些威胁。

详细的 STRIDE 威胁建模分析、攻击场景图解和纵深防御架构设计集中在 → 安全总览,这里不再展开。 | 否认 | 否认执行操作 | 完整审计日志、操作签名 | | 信息泄露 | 读取敏感文件 | 路径黑名单、内容过滤、访问控制 | | 拒绝服务 | 耗尽系统资源 | 资源配额、请求限流、超时控制 | | 权限提升 | 从低权限到高权限 | 权限边界隔离、配置保护 |


七、约束系统最佳实践

7.1 约束设计原则

原则一:最小权限原则(Principle of Least Privilege)

只授予 Agent 完成任务所需的最小权限,不多给一分。

// Requires OpenCode >= v1.17.x
{
  "permission": {
    "read": "allow",
    "edit": "ask",
    "bash": "ask"
  }
}

原则二:默认拒绝原则(Default Deny)

未知操作默认拒绝,只有明确允许的操作才能执行。

{
  "permission": {
    "read": "allow",
    "edit": "deny",
    "bash": "deny"
  }
}

原则三:职责分离原则(Separation of Duties)

不同职责的 Agent 使用不同的权限配置,避免权限集中。

graph TB
    subgraph Agent 权限分离
        A1[Plan Agent<br/>只读权限]
        A2[Build Agent<br/>读写权限]
        A3[Review Agent<br/>只读权限]
        A4[Deploy Agent<br/>受限权限]
    end

    A1 --> |"分析规划"| B1[无需写权限]
    A2 --> |"代码实现"| B2[需要写权限]
    A3 --> |"质量检查"| B3[无需写权限]
    A4 --> |"部署发布"| B4[仅部署权限]

    style A1 fill:#4A90D9,color:#fff
    style A2 fill:#50C878,color:#fff
    style A3 fill:#FF9F43,color:#fff
    style A4 fill:#A66CFF,color:#fff

7.2 约束配置模板

模板一:开发环境配置

{
  "permission": {
    "read": "allow",
    "edit": "ask",
    "bash": {
      "npm *": "allow",
      "git *": "ask",
      "docker *": "ask"
    }
  }
}

模板二:生产环境配置

{
  "permission": {
    "read": "ask",
    "edit": "deny",
    "bash": {
      "*": "deny"
    }
  }
}

7.3 约束系统演进建议

下图以时间线形式展示了约束系统的分阶段演进建议。

timeline
    title 约束系统演进路线
    section 初期阶段
        第1周 : 建立基础权限模型<br/>allow/ask/deny 三级策略
        第2周 : 配置路径权限<br/>敏感路径黑名单
    section 成长阶段
        第3-4周 : 引入架构护栏<br/>编写 AGENTS.md
        第5-6周 : 集成 LSP 诊断<br/>自动化代码检查
    section 成熟阶段
        第7-8周 : 集成 AST-grep<br/>外部工具模式检查
        第9-10周 : 威胁建模分析<br/>STRIDE 评估
    section 优化阶段
        持续 : 权限策略优化<br/>基于审计日志调整
        持续 : 约束性能优化<br/>减少检查开销

架构决策记录(ADR)模板

架构决策记录(Architecture Decision Record,ADR)是一种轻量级文档实践,用于记录关键架构决策的上下文、方案和合规要求。在约束系统中,ADR 是架构护栏的输入源头——先有决策记录,后有护栏规则。每次做出架构决策后,将对应的约束规则提炼到 AGENTS.md 中,既保留了“为什么这么选“,也生成了 Agent 可直接遵守的“怎么做“。

ADR 模板结构

字段说明可选值
Title(标题)简明扼要描述决策一句话概括
Status(状态)决策生命周期阶段Proposed / Accepted / Deprecated / Superseded
Context(上下文)为什么需要这个决策?驱动因素是什么?背景描述
Decision(决策)最终选择的技术方案明确的方案描述
Consequences(后果)选择此方案的收益与代价正面 / 负面影响
Compliance(合规验证)如何确保决策被遵守自动化或人工检查方式

示例:选择 MCP 协议作为工具集成标准

## ADR-001:选择 MCP 协议作为 Agent 工具集成标准

**Status(状态)**:Accepted

**Context(上下文)**:Agent 需要与外部系统(数据库、API、搜索引擎)通信。现有 Plugin 方案耦合在 OpenCode 进程内,无法被 Claude Code 等其他工具复用,且仅支持 TypeScript。

**Decision(决策)**:采用 MCP(Model Context Protocol)作为 Agent 工具集成的标准协议。外部工具通过 MCP 服务器暴露,内置工具保持原生调用。

**Consequences(后果)**:
- 正面:一次开发多工具复用;任意语言实现;进程隔离提升安全性
- 负面:每次工具调用增加 IPC/网络开销;需额外维护 MCP 进程生命周期

**Compliance(合规验证)**:CI 中运行 tools/list 检查所有必要 MCP 服务器已注册;Code Review 检查新增工具是否遵循 MCP 协议而非直接嵌入 Agent 进程

将此模板与 AGENTS.md 配合使用:ADR 记录“为什么“,AGENTS.md 定义“怎么做“,两者形成完整的架构决策闭环。


八、反面案例:没有约束系统会发生什么

理论讲得再多,不如一个事故案例让人警醒。以下是两个教学示例(基于常见错误模式整理),它们展示了约束系统缺失或配置不当的潜在后果。

8.1 案例:生产数据库误删事故

事故背景

2024 年某初创公司,团队规模 5 人,使用 AI 编程助手加速开发。项目没有配置任何权限约束,Agent 以“完全信任“模式运行。

事故经过

时间线:
14:32  开发者在聊天中说:"帮我清理一下测试数据库的旧数据"
14:33  Agent 理解为:删除测试数据库
14:34  Agent 执行命令:DROP DATABASE production;  -- 误连到生产库
14:35  生产服务全部报错,用户无法访问
14:40  运维发现数据库消失,开始紧急恢复
16:30  从备份恢复完成,损失 2 小时数据

问题分析

下图展示了事故一的根因分析流程,从事件触发到最终影响的问题链路。

flowchart TB
    A[用户请求:清理测试数据] --> B[Agent 理解意图]
    B --> C{约束检查}
    C -->|无约束| D[直接执行 DROP DATABASE]
    D --> E[❌ 连接到生产库]

    C -->|有约束| F[权限检查:deny 敏感操作]
    F --> G[路径检查:禁止访问生产配置]
    G --> H[✅ 阻止危险操作]

    style E fill:#ffcccc
    style H fill:#ccffcc

根因分析:

问题具体表现缺失的约束
环境隔离缺失Agent 能访问生产环境配置路径权限:config/prod/**: deny
危险操作无门禁DROP DATABASE 直接执行命令权限:DROP *: deny
无操作预览没有确认要执行的 SQL权限模式:ask 而非 allow
连接池共享测试和生产共用连接配置环境变量隔离

正确的约束配置:

{
  "permission": {
    "read": "allow",
    "edit": "ask",
    "bash": {
      "DROP *": "deny",
      "TRUNCATE *": "deny",
      "DELETE FROM *": "ask"
    }
  }
}

事故损失:

  • 直接损失:2 小时服务中断,约 5000 用户受影响
  • 数据损失:2 小时交易数据丢失,需人工补录
  • 信任损失:用户投诉激增,品牌形象受损
  • 人力损失:团队 2 天时间用于事故处理和复盘

8.2 案例:密钥泄露导致云账户被盗

事故背景

2024 年某 SaaS 公司,开发者让 Agent 帮忙“检查一下配置文件有没有问题“。Agent 读取了 .env 文件,发现里面有 AWS 密钥,然后在日志中输出了完整内容。日志被上传到公开的调试平台,导致 AWS 账户被盗用。

事故经过

时间线:
09:15  开发者:帮我检查配置文件有没有问题
09:16  Agent:读取 .env 文件(包含 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY)
09:17  Agent:在日志中输出配置内容以便"展示问题"
09:18  开发者将日志粘贴到公开的 Pastebin 寻求帮助
09:30  攻击者发现泄露的密钥,开始挖矿
12:00  AWS 账单告警:异常高额费用
14:00  确认账户被盗,紧急冻结密钥

问题分析

下图以时序图形式展示了事故二中凭证泄露的完整攻击链路,从开发者请求到攻击者利用的逐步分析。

sequenceDiagram
    participant Dev as 开发者
    participant Agent as AI Agent
    participant Log as 日志系统
    participant Public as 公开平台
    participant Attacker as 攻击者

    Dev->>Agent: 检查配置文件
    Agent->>Agent: 读取 .env
    Note over Agent: 无敏感信息过滤
    Agent->>Log: 输出完整配置(含密钥)
    Log-->>Dev: 显示日志
    Dev->>Public: 粘贴日志求助
    Public-->>Attacker: 攻击者发现密钥
    Attacker->>Attacker: 使用密钥挖矿

根因分析:

问题具体表现缺失的约束
敏感文件无保护.env 文件可被随意读取路径权限:.env: deny
输出无过滤日志直接输出敏感信息输出过滤:密钥模式匹配
无安全意识开发者不知道日志包含密钥安全培训 + Agent 警告

正确的约束配置:

{
  "permission": {
    "read": "allow",
    "edit": "deny",
    "bash": "deny"
  }
}

事故损失:

  • 直接损失:AWS 账单 $12,000(挖矿费用)
  • 时间损失:4 小时紧急响应 + 密钥轮换
  • 风险损失:潜在的数据泄露风险

8.3 两个事故的共同教训

下图总结了两起安全事故的共同教训,从根因到改进措施的关系映射。

graph TB
    subgraph 事故根因
        R1[权限约束缺失]
        R2[敏感路径未保护]
        R3[危险操作无门禁]
        R4[输出无安全过滤]
    end

    subgraph 防御措施
        D1[最小权限原则]
        D2[敏感路径黑名单]
        D3[危险操作 deny 策略]
        D4[输出内容过滤]
    end

    R1 --> D1
    R2 --> D2
    R3 --> D3
    R4 --> D4

    D1 --> P[✅ 事故可预防]
    D2 --> P
    D3 --> P
    D4 --> P

    style P fill:#ccffcc

约束系统的价值量化(数值为教学示例,非精确计算):

约束措施实施成本(估)预防损失(估)
敏感路径 deny约 5 分钟配置显著减少数据泄露风险
危险命令 deny约 10 分钟配置降低服务中断概率
输出过滤约 15 分钟配置减少敏感信息泄露
操作预览约 5 分钟配置减少误操作损失

8.4 约束系统的“安全带“隐喻

约束系统就像汽车的安全带:

隐喻安全带约束系统
日常感知有点麻烦,限制自由有点繁琐,需要确认
事故时刻救命的关键阻止灾难的屏障
正确态度系好安全带是习惯配置约束是基本功
错误态度“我开车技术好,不需要”“我小心使用,不需要”

记住:约束系统不是在怀疑你的能力,而是在保护你免受不可预见的错误。就像安全带不是在怀疑你的驾驶技术,而是在保护你免受意外伤害。


反向思考:使用 AI 编程时的认知陷阱

为什么有时候明知道 AI 不可靠,还是忍不住直接用了它的输出?这不是技术问题,而是认知偏差在作祟。反面案例让我们看到了事故的后果,但更值得追问的是——事故发生之前,是什么让开发者放下了警惕?

以下是 AI 编程中最常见的四个“陷阱“:

陷阱一:信任平滑 — AI 生成的代码看起来“挺专业的“,变量命名规范、注释齐全,你就会下意识觉得它是对的。漂亮的代码 ≠ 正确的代码。怎么避开:每次审查 AI 代码时,刻意怀疑写得最漂亮的那几行。

陷阱二:确认偏误 — 你心里已经有答案,让 AI 帮你实现。AI 给出的方案就算有漏洞,你也会自动忽略,因为它“符合我的想法“。怎么避开:让没参与讨论的同事或另一个 Agent 做交叉审查。

陷阱三:省力惯性 — “这个函数我自己写要 10 分钟,AI 10 秒就生成了,应该没问题吧?“省下的时间越多,你就越不愿意仔细检查。怎么避开:对 AI 生成速度越快的代码,投入等比例的审查时间——至少逐行阅读一遍。

陷阱四:责任稀释 — 出 bug 时心想“是 AI 写的“,但代码是你提交的。AI 不会为事故负责,你会。怎么避开:提交前问自己一句:“如果这是我自己一行行写的,我敢不敢上线?”

这些陷阱的共同解药只有一个:把 AI 当实习生,不要当专家。 实习生写的代码你会逐行 review,对 AI 的输出也该如此。

九、小结

约束系统是 Harness Engineering 的安全基石。通过权限模型、架构护栏、Lint 规范三大支柱,我们为 Agent 构建了一个“牢笼“——这个牢笼不是限制 Agent 的能力,而是让 Agent 在安全的边界内自由发挥。

核心要点回顾:

  1. 权限模型定义 Agent “能做什么”,是约束系统的基础层
  2. 架构护栏定义 Agent “应该怎么做”,是约束系统的方向层
  3. Lint 规范定义 Agent “做得对不对”,是约束系统的质量层
  4. 威胁建模帮助我们识别和防御约束绕过攻击
  5. 纵深防御确保任一层失效时,其他层仍能提供保护

好的约束让 Agent 更高效而不是更慢。当 Agent 清楚知道自己的行为边界时,它可以更自信地执行任务,减少不必要的确认和回退。约束系统不是 Agent 的枷锁,而是 Agent 的安全带——让 Agent 在高速行驶时依然安全可控。


常见反模式

反模式一:一刀切的权限策略

现象:团队将全部工具权限设为 allow(追求效率)或全部设为 deny(追求安全),忽略了按工具类型分级控制的可能性。

原因:缺乏对三级策略(allow/ask/deny)差异化配置的理解,或认为逐项管理过于繁琐。

对策:按工具风险等级分级——安全操作(read/glob)设为 allow,敏感操作(edit/bash)设为 ask,危险操作(rm -rf/sudo)设为 deny。从宽松起步,根据事故记录逐步收紧。

反模式二:架构护栏写成摆设

现象:AGENTS.md 中写了大量架构规范,但 Agent 生成的代码依然不符合分层架构——Controller 直接写 SQL、Service 处理 HTTP 响应,规范形同虚设。

原因:规范描述太泛(如“遵循三层架构“),没有给出具体的判断标准和边界条件。Agent 无法将抽象规范转化为可执行的判断。

对策:在 AGENTS.md 中为每层定义明确的禁止事项和允许事项,配合 AST-grep 规则在 CI 中自动校验架构合规性。

反模式三:Lint 规则过于严格

现象:团队配置了上百条 Lint 规则,Agent 生成的代码频繁触发 lint 错误,每次修改多数时间花在满足 lint 规则而非实现功能上。

原因:将 lint 规则当作质量保障的全部手段,忽略了架构层面的一致性才是更大的质量问题。

对策:区分硬性规则(语法错误、类型错误)和软性规则(风格偏好),对软性规则配置自动修复;将 Lint 检查放在 CI 阶段而非每次 Agent 输出。

常见错误与陷阱

场景一:权限配置冲突导致 Agent 行为异常

场景:开发者在 AGENTS.md 中声明了架构约束,在 opencode.json 中配置了权限规则,但二者冲突(如 AGENTS.md 禁止修改 config/ 目录但权限配置允许 edit)。Agent 有时遵守约束有时绕过,行为不稳定。

后果:开发者难以判断 Agent 的行为模式,最终选择关闭所有约束以消除不确定性。

预防:建立单一权威源——opencode.json 定义硬性权限规则,AGENTS.md 只声明架构方向而非权限边界。使用约束冲突检测工具定期检查配置一致性。

场景二:只读模式下的信息泄露

场景:安全审计场景中,Plan Agent 以只读模式运行。Agent 读取了 .env 文件中的数据库密码并在审计报告中完整输出,报告被分享到公共文档平台。

后果:敏感信息通过审计报告泄露,造成数据安全事件。

预防:在权限配置中将 .env、credentials 等敏感路径设为 deny,即使只读模式也要限制敏感文件访问;配置输出过滤规则,自动遮盖密钥模式。

场景三:约束过度导致 Agent 瘫痪

场景:新加入团队的开发者将约束配置得极其严格——所有工具操作都设为 ask,每个操作都需要确认。Agent 每次修改文件都要等待人工响应。

后果:开发效率降至手动编码的三分之一,团队放弃使用 AI 编程工具。

预防:按 80/20 原则配置约束——严格限制真正危险的操作(20%),对安全操作(80%)直接放行。从宽松起步,根据实际事故记录逐步收紧策略。

适用场景与限制

约束系统最有效的场景:多人协作的中大型项目、生产环境代码库、涉及敏感数据或金融交易的系统、合规要求严格的团队。在这些场景中,约束系统的结构和流程规范性会带来显著的质量提升和安全保障,权限模型的三级策略能精确匹配不同风险等级的操作需求。

约束系统不太适合的场景:个人原型项目、一次性脚本、探索性实验。在这些场景中,约束系统的确认流程和规则限制会带来不必要的摩擦,降低迭代速度。对于这类场景,建议使用最宽松的权限配置(allow 为主),仅对真正危险的操作设置 ask 或 deny。

有效使用约束系统需要满足的前提条件:团队对约束层级(全局/会话/任务)有共识;开发环境已正确安装并配置 LSP 服务器;定期审查和优化约束配置,淘汰过时规则;将约束配置纳入版本控制,随项目代码一起演进;至少有一次“约束救火“的亲身体验,理解约束的真实价值。


学习检查清单

完成本章学习后,请确认你能够:

  • 解释约束系统三大支柱(权限模型、架构护栏、Lint 规范)的职责分工
  • 区分三种权限动作(allow/ask/deny)的适用场景
  • 配置工具级与文件级的权限控制规则
  • 编写 AGENTS.md 作为架构护栏载体
  • 使用 STRIDE 威胁建模方法分析约束系统的安全风险
  • 从反面案例中理解约束系统缺失的严重后果

关联章节