fix: reject symlinked agents directory
This commit is contained in:
@@ -18,6 +18,16 @@ type Store struct {
|
||||
}
|
||||
|
||||
func (s Store) List() ([]AgentDefinition, error) {
|
||||
agentsPath := filepath.Join(s.CodexHome, "agents")
|
||||
if info, err := os.Lstat(agentsPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return []AgentDefinition{}, nil
|
||||
}
|
||||
return nil, err
|
||||
} else if info.Mode()&os.ModeSymlink != 0 {
|
||||
return nil, codexhome.ErrForbiddenPath
|
||||
}
|
||||
|
||||
agentsDir, err := codexhome.ResolveInside(s.CodexHome, "agents")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -179,3 +179,35 @@ func TestListAgentsRejectsSymlinkToNonAgentToml(t *testing.T) {
|
||||
t.Fatalf("non-agent TOML content leaked: %#v", got[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAgentsRejectsSymlinkedAgentsDirectory(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(root, "config.toml"), []byte(`name = "project-secret"`), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(".", filepath.Join(root, "agents")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store := Store{CodexHome: root}
|
||||
got, err := store.List()
|
||||
if err == nil {
|
||||
t.Fatalf("expected symlinked agents directory to be rejected, got %#v", got)
|
||||
}
|
||||
if strings.Contains(err.Error(), "project-secret") {
|
||||
t.Fatalf("symlinked agents directory leaked sensitive content in error: %v", err)
|
||||
}
|
||||
for _, item := range got {
|
||||
if strings.Contains(item.Name, "project-secret") ||
|
||||
strings.Contains(item.Description, "project-secret") ||
|
||||
strings.Contains(item.DeveloperInstructions, "project-secret") ||
|
||||
strings.Contains(item.ParseError, "project-secret") {
|
||||
t.Fatalf("symlinked agents directory leaked sensitive content: %#v", item)
|
||||
}
|
||||
for key, value := range item.ExtraFields {
|
||||
if strings.Contains(key, "project-secret") || strings.Contains(value, "project-secret") {
|
||||
t.Fatalf("symlinked agents directory leaked sensitive extra field: %#v", item.ExtraFields)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
| 2026-05-25 | 2 | coding agent | TDD 实现 Agent TOML 只读读取和 `/api/agents` | 完成;提交 `feat: read codex agent definitions` |
|
||||
| 2026-05-25 | 2 | spec review | 规格审查未通过:重复键 TOML 可 valid;agent symlink 可读取 root `config.toml` | coding agent 按 blocking 范围修复 |
|
||||
| 2026-05-25 | 2 | coding agent | TDD 修复 agent TOML parser 和 symlink 边界 | 完成;提交 `fix: validate agent toml boundaries` |
|
||||
| 2026-05-25 | 2 | spec review | 复审未通过:`agents -> .` 目录 symlink 可读取 root `config.toml` | coding agent 按 blocking 范围修复 |
|
||||
| 2026-05-25 | 2 | coding agent | TDD 修复 symlinked `agents` 目录边界 | 完成;提交 `fix: reject symlinked agents directory` |
|
||||
|
||||
## Test Results
|
||||
|
||||
@@ -52,6 +54,11 @@
|
||||
| 2026-05-25 | `go test ./...` | PASS | Required verification |
|
||||
| 2026-05-25 | `git diff --check` | PASS | Required verification |
|
||||
| 2026-05-25 | `git status --short` | PASS | Required verification;Phase 2 review fix 文件待提交 |
|
||||
| 2026-05-25 | `go test ./internal/agents` | FAIL | TDD 红灯:`agents -> .` 目录 symlink 将 root `config.toml` 读取为 valid agent 并泄漏 `project-secret` |
|
||||
| 2026-05-25 | `go test ./internal/agents` | PASS | symlinked `agents` 目录被拒绝;leaf symlink 和 duplicate TOML 回归保持通过 |
|
||||
| 2026-05-25 | `go test ./...` | PASS | Required verification |
|
||||
| 2026-05-25 | `git diff --check` | PASS | Required verification |
|
||||
| 2026-05-25 | `git status --short` | PASS | Required verification;Phase 2 symlinked directory fix 文件待提交 |
|
||||
|
||||
## Bug Loop
|
||||
|
||||
@@ -64,3 +71,4 @@
|
||||
| 1 | `ResolveAgentTOML` 可通过 `agents/*.toml` symlink 指向 root `auth.json` 绕过 forbidden 检查 | 在 symlink 解析后对 evaluated final target 再执行 forbidden 检查 | `go test ./internal/codexhome` PASS |
|
||||
| 2 | Agent TOML parser 对重复键使用 map 覆盖,且未校验 bare key | 增加 duplicate key 和 invalid key 检测,遇到 malformed TOML 返回单条 invalid | `go test ./internal/agents` PASS |
|
||||
| 2 | Agent symlink 只校验最终路径在 Codex home 内,可读取 root `config.toml` | 在 agent store 层拒绝 `.toml` symlink,避免读取非 agent TOML 内容 | `go test ./internal/agents` PASS |
|
||||
| 2 | `agents` 目录 symlink 会让枚举逻辑读取 Codex home root 的 `.toml` 文件 | 在 `Store.List` 对 lexical `CodexHome/agents` 先 `Lstat`,发现 symlink 直接返回 forbidden error | `go test ./internal/agents` PASS |
|
||||
|
||||
@@ -33,3 +33,4 @@
|
||||
| 2026-05-25 | 1 | 代码质量审查发现 symlink 边界绕过、敏感文件大小写匹配、缺少操作域 resolver、`CODEX_HOME` 未生效 | TDD 补充失败测试后修复 `codexhome` 和 `app` | 已通过最终验证 |
|
||||
| 2026-05-25 | 1 | 规格复审发现 `ResolveAgentTOML` 可通过 `agents/*.toml` symlink 指向 root `auth.json` 绕过 forbidden 检查 | TDD 补充 symlink-to-auth 测试后检查 resolved final target | 已通过最终验证 |
|
||||
| 2026-05-25 | 2 | 规格审查发现重复键 TOML 被报告为 valid,且 agent symlink 可读取 Codex home 内非 agent TOML | TDD 补充 duplicate key、invalid key、symlink-to-config 测试后修复 parser 和 agent store 边界 | 已通过最终验证 |
|
||||
| 2026-05-25 | 2 | 复审发现 `agents -> .` 目录 symlink 可把 root `config.toml` 当作 agent 读取 | TDD 补充 symlinked agents directory 测试后在 `Store.List` 拒绝 symlinked `agents` 目录 | 已通过最终验证 |
|
||||
|
||||
Reference in New Issue
Block a user