# 安全与沙箱

DotCraft 的安全模型由四层组成：**文件黑名单**、**工作区边界**、**工具能力开关**、**沙箱隔离**。多数项目在工作区配置里调几行就够；公网部署或外部渠道接入时，建议把这一页的"严格部署"小节走一遍。

## 默认安全 baseline

工作区新建时的默认行为：

- 工作区外的文件 / Shell 操作需要审批（`Tools.File.RequireApprovalOutsideWorkspace = true` / `Tools.Shell.RequireApprovalOutsideWorkspace = true`）
- 没有黑名单（默认空），需要按本机使用环境补充
- 工具全部启用（`EnabledTools = []`）
- 沙箱关闭（`Tools.Sandbox.Enabled = false`）

适合**个人本地项目**的最小补强：

```json
{
  "Security": {
    "BlacklistedPaths": [
      "~/.ssh",
      "~/.gnupg",
      "~/.aws"
    ]
  },
  "Tools": {
    "File": {
      "RequireApprovalOutsideWorkspace": true
    },
    "Shell": {
      "RequireApprovalOutsideWorkspace": true,
      "Timeout": 300
    }
  }
}
```

## 文件访问黑名单

`Security.BlacklistedPaths` 配置禁止访问的路径列表。黑名单全局生效，CLI、Desktop、外部渠道和自动化入口都会拦截。

```json
{
  "Security": {
    "BlacklistedPaths": [
      "~/.ssh",
      "/etc/shadow",
      "C:\\Windows\\System32"
    ]
  }
}
```

黑名单行为：

- `ReadFile`、`WriteFile`、`EditFile`、`GrepFiles`、`FindFiles` 对黑名单路径的操作会被拒绝。
- Shell 命令引用黑名单路径时会被拒绝。
- 黑名单优先于工作区边界检查。
- 支持绝对路径和 `~` 展开，并检查子路径。

## 工作区边界

DotCraft 在执行 Shell 命令前会分析路径，覆盖 Unix 绝对路径、家目录路径、环境变量路径、Windows 盘符路径、UNC 路径和常见安全设备路径。

若命令中引用的任意路径解析后位于工作区之外：

- `Tools.Shell.RequireApprovalOutsideWorkspace = false` 时直接拒绝。
- `Tools.Shell.RequireApprovalOutsideWorkspace = true` 时向当前交互源发起审批。

文件工具同样会展开 `~`、`$HOME`、`${HOME}` 和 `%ENV%`，确保工作区边界检查一致。

## 工具能力开关

| 配置项 | 说明 | 默认 |
|---|---|---|
| `Tools.File.RequireApprovalOutsideWorkspace` | 工作区外文件操作是否需要审批 | `true` |
| `Tools.File.MaxFileSize` | 最大可读取文件大小（字节） | `10485760` |
| `Tools.File.RipgrepPath` | 可选的 `rg` 可执行文件路径；为空时依次尝试 `DOTCRAFT_RG_PATH`、`PATH` 和内置回退 | `""` |
| `Tools.Shell.RequireApprovalOutsideWorkspace` | 工作区外 Shell 命令是否需要审批 | `true` |
| `Tools.Shell.Timeout` | Shell 命令超时时间（秒） | `300` |
| `Tools.Shell.MaxOutputLength` | Shell 命令最大输出长度（字符） | `10000` |
| `Tools.Web.MaxChars` | Web 抓取最大字符数 | `50000` |
| `Tools.Web.Timeout` | Web 请求超时时间（秒） | `300` |
| `Tools.Web.SearchMaxResults` | 联网搜索默认返回结果数 | `5` |
| `Tools.Web.SearchProvider` | 搜索引擎提供商：`Bing` / `Exa` | `Exa` |
| `Tools.Lsp.Enabled` | 是否启用内置 LSP 工具 | `false` |
| `Tools.Lsp.MaxFileSize` | LSP 打开或同步文件时允许的最大文件大小 | `10485760` |

只想启用部分工具时使用顶层 `EnabledTools`：

```json
{
  "EnabledTools": ["ReadFile", "GrepFiles", "WebSearch"]
}
```

## Hooks

Hooks 让 DotCraft 在生命周期事件中执行外部命令。它适合做格式化、审计、通知、环境检查和安全守卫。第一次使用建议先写一个观察型 Hook，再逐步加入阻止型 Hook。

### 快速开始

在 `.craft/config.json` 中添加：

```json
{
  "Hooks": {
    "Enabled": true,
    "Events": {
      "AfterToolCall": [
        {
          "name": "log-tool-call",
          "type": "command",
          "command": "node .craft/hooks/log-tool-call.js",
          "matcher": ".*",
          "timeoutSeconds": 10
        }
      ]
    }
  }
}
```

示例脚本读取 stdin 中的 JSON，并记录工具调用：

```js
import fs from 'node:fs'

let input = ''
process.stdin.on('data', chunk => (input += chunk))
process.stdin.on('end', () => {
  fs.appendFileSync('.craft/hooks.log', input + '\n')
})
```

### 配置字段

| 字段 | 说明 |
|---|---|
| `Hooks.Enabled` | 是否启用 Hooks |
| `Hooks.Events` | 按事件名分组的 Hook 列表 |
| `name` | Hook 名称，便于日志和排查 |
| `type` | 支持 `"command"` |
| `command` | 要执行的 Shell 命令 |
| `matcher` | 匹配工具名的正则；空字符串匹配所有工具相关事件 |
| `timeoutSeconds` | Hook 超时时间 |

### 生命周期事件

| 事件 | 用途 |
|---|---|
| `BeforeToolCall` | 工具调用前检查或阻止 |
| `AfterToolCall` | 工具调用后记录、格式化或通知 |
| `BeforeTurn` | Agent 回合开始前准备上下文 |
| `AfterTurn` | Agent 回合结束后记录结果或通知 |
| `BeforeAutomationTask` | 自动化任务执行前检查 |
| `AfterAutomationTask` | 自动化任务完成后同步状态 |

### stdin 输入

Hook 进程通过 stdin 接收 JSON。字段根据事件类型变化，未使用字段不会出现。

工具相关事件通常包含：

```json
{
  "event": "BeforeToolCall",
  "workspace": "F:\\project",
  "sessionId": "thread-id",
  "toolName": "Exec",
  "arguments": {
    "command": "dotnet test"
  }
}
```

回合相关事件通常包含：

```json
{
  "event": "AfterTurn",
  "workspace": "F:\\project",
  "sessionId": "thread-id",
  "summary": "Agent completed the turn"
}
```

### 退出码语义

| 退出码 | 含义 |
|---|---|
| `0` | 成功，继续执行 |
| 非 `0` | Hook 失败；`Before*` 事件可阻止当前操作 |

Hook 的 stdout 用于日志和错误说明，不会修改工具参数或执行结果。

### Matcher

`matcher` 是正则表达式，只对工具相关事件生效。常见写法：

| matcher | 匹配 |
|---|---|
| `WriteFile|EditFile` | 文件写入和编辑 |
| `Exec` | Shell 命令 |
| `.*` | 所有工具 |

### 常见示例

**文件写入后自动格式化**

```json
{
  "name": "format-after-edit",
  "type": "command",
  "command": "npm run format -- --write",
  "matcher": "WriteFile|EditFile",
  "timeoutSeconds": 60
}
```

**Shell 命令安全守卫**

```js
let input = ''
process.stdin.on('data', chunk => (input += chunk))
process.stdin.on('end', () => {
  const payload = JSON.parse(input)
  const command = payload.arguments?.command ?? ''
  if (/rm\s+-rf|Remove-Item\s+-Recurse/i.test(command)) {
    console.error('Dangerous command blocked by workspace Hook.')
    process.exit(1)
  }
})
```

**工具调用日志**

```js
import fs from 'node:fs'

let input = ''
process.stdin.on('data', chunk => (input += chunk))
process.stdin.on('end', () => {
  const payload = JSON.parse(input)
  fs.appendFileSync('.craft/tool-calls.log', JSON.stringify({
    event: payload.event,
    toolName: payload.toolName,
    at: new Date().toISOString()
  }) + '\n')
})
```

### 最佳实践

- Hook 脚本保持短小，复杂逻辑放到项目脚本中。
- 观察型 Hook 失败时可在命令末尾加 `|| true`，避免影响主流程。
- 阻止型 Hook 要输出清楚的错误信息，方便用户知道如何修复。
- 不要把敏感 Token 写进仓库，优先使用环境变量或全局配置。
- 命令路径使用工作区相对路径，避免不同入口的 cwd 差异。

### 调试

- 先用观察型 Hook 验证 stdin payload。
- 在脚本中把原始输入写入 `.craft/hooks-debug.log`。
- 对非关键 Hook 使用 `|| true`，避免外部服务短暂失败阻塞 Agent。

## 沙箱（OpenSandbox）

通过 [OpenSandbox](https://github.com/alibaba/OpenSandbox) 可以把 Shell 和 File 工具执行环境放到隔离的 Docker 容器中。

前置条件：

```bash
pip install opensandbox-server
opensandbox-server
```

配置：

```json
{
  "Tools": {
    "Sandbox": {
      "Enabled": true,
      "Domain": "localhost:5880",
      "Image": "ubuntu:latest",
      "NetworkPolicy": "allow",
      "SyncWorkspace": true
    }
  }
}
```

| 配置项 | 说明 | 默认 |
|---|---|---|
| `Tools.Sandbox.Enabled` | 是否启用沙箱模式 | `false` |
| `Tools.Sandbox.Domain` | OpenSandbox 服务地址 | `localhost:5880` |
| `Tools.Sandbox.ApiKey` | OpenSandbox API Key | 空 |
| `Tools.Sandbox.UseHttps` | 是否使用 HTTPS | `false` |
| `Tools.Sandbox.Image` | 沙箱容器 Docker 镜像 | `ubuntu:latest` |
| `Tools.Sandbox.TimeoutSeconds` | 沙箱超时时间（秒） | `600` |
| `Tools.Sandbox.Cpu` | 容器 CPU 限制 | `1` |
| `Tools.Sandbox.Memory` | 容器内存限制 | `512Mi` |
| `Tools.Sandbox.NetworkPolicy` | 网络策略：`deny` / `allow` / `custom` | `allow` |
| `Tools.Sandbox.AllowedEgressDomains` | 自定义允许出站域名列表 | `[]` |
| `Tools.Sandbox.IdleTimeoutSeconds` | 空闲超时（秒） | `300` |
| `Tools.Sandbox.SyncWorkspace` | 是否同步 workspace 到容器 | `true` |

## 严格部署清单

把 DotCraft 暴露给外部渠道或公网时，建议同时打开：

| 配置 | 推荐值 | 作用 |
|---|---|---|
| `Tools.File.RequireApprovalOutsideWorkspace` | `true` | 工作区外文件操作必须审批 |
| `Tools.Shell.RequireApprovalOutsideWorkspace` | `true` | 工作区外 Shell 必须审批 |
| `Security.BlacklistedPaths` | 包含密钥/凭据目录 | 直接拒绝高危路径 |
| `EnabledTools` | 仅保留必要工具 | 缩小工具表面积 |
| `AppServer.WebSocket.Token` | 强随机 token | 远程 AppServer 必须认证 |
| `Tools.Sandbox.Enabled` | `true`（如需要进一步隔离） | Shell / 文件操作隔离到容器 |
| `SubAgent.MaxDepth` | `1` | 阻止 SubAgent 递归 |

## 使用场景

| 场景 | 建议 |
|---|---|
| 个人本地项目 | 保留工作区外审批；黑名单加入 SSH、云厂商凭据、密码管理目录 |
| 团队共享工作区 | 把安全策略写入项目 `.craft/config.json`，让所有入口一致执行 |
| 外部渠道或机器人 | 启用工作区外审批，限制工具与网络访问，强 token |
| 自动化任务 | 根据任务需要开启沙箱或收紧 `EnabledTools` |

## 故障排查

### 命令明明在工作区内仍被拒绝

检查命令字符串是否引用了工作区外路径，例如 `~/.ssh`、`/etc`、`C:\Users` 或环境变量展开后的路径。

### 沙箱无法启动

确认 Docker 和 `opensandbox-server` 正在运行，并检查 `Tools.Sandbox.Domain`、`ApiKey` 和网络策略。

### Web 搜索或抓取失败

检查 `Tools.Web.SearchProvider`、`Tools.Web.Timeout`、`Tools.Web.MaxChars` 和网络环境。

### Hook 没有执行

确认 `Hooks.Enabled = true`，事件名正确，`matcher` 能匹配当前工具名，并且命令路径相对当前工作区可用。

### Hook 超时

调大 `timeoutSeconds`，或把慢操作放到后台队列。Hook 适合做短任务，不适合长时间阻塞 Agent。

### Hook 修改不了工具参数

Hook 不支持修改工具参数或执行结果。Hook 只能观察或阻止当前操作。

## 相关入口

- [可观测性](./observability.md) — Dashboard 中查看审批与拦截记录
- [SubAgents](./subagents.md) — `SubAgent.MaxDepth` 与 role 工具策略
- [示例与模板](../resources/samples.md) — Hooks 示例
- [配置完整参考](../developing/configuration.md) — `Security.*` / `Tools.*` 全部字段
