fix: 增强日志清理的安全性和可靠性

必须修复的问题:
1. PID重用防护 - 添加进程启动时间检查,对比文件修改时间避免误删活动进程的日志
   - Unix: 通过 /proc/<pid>/stat 读取进程启动时间
   - Windows: 使用 GetProcessTimes API 获取创建时间
   - 7天策略: 无法获取进程启动时间时,超过7天的日志视为孤儿

2. 符号链接攻击防护 - 新增安全检查避免删除恶意符号链接
   - 使用 os.Lstat 检测符号链接
   - 使用 filepath.EvalSymlinks 解析真实路径
   - 确保所有文件在 TempDir 内(防止路径遍历)

强烈建议的改进:
3. 异步启动清理 - 通过 goroutine 运行清理避免阻塞主流程启动

4. NotExist错误语义修正 - 文件已被其他进程删除时计入 Kept 而非 Deleted
   - 更准确反映实际清理行为
   - 避免并发清理时的统计误导

5. Windows兼容性验证 - 完善Windows平台的进程时间获取

测试覆盖:
- 更新所有测试以适配新的安全检查逻辑
- 添加 stubProcessStartTime 支持PID重用测试
- 修复 setTempDirEnv 解析符号链接避免安全检查失败
- 所有测试通过(codex-wrapper: ok 6.183s)

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
swe-agent[bot]
2025-12-08 10:53:52 +08:00
parent 9452b77307
commit da257b860b
6 changed files with 317 additions and 46 deletions

View File

@@ -403,8 +403,7 @@ func TestRunConcurrentSpeedupBenchmark(t *testing.T) {
func TestRunStartupCleanupRemovesOrphansEndToEnd(t *testing.T) {
defer resetTestHooks()
tempDir := t.TempDir()
setTempDirEnv(t, tempDir)
tempDir := setTempDirEnv(t, t.TempDir())
orphanA := createTempLog(t, tempDir, "codex-wrapper-5001.log")
orphanB := createTempLog(t, tempDir, "codex-wrapper-5002-extra.log")
@@ -416,6 +415,12 @@ func TestRunStartupCleanupRemovesOrphansEndToEnd(t *testing.T) {
stubProcessRunning(t, func(pid int) bool {
return pid == runningPID || pid == os.Getpid()
})
stubProcessStartTime(t, func(pid int) time.Time {
if pid == runningPID || pid == os.Getpid() {
return time.Now().Add(-1 * time.Hour)
}
return time.Time{}
})
codexCommand = createFakeCodexScript(t, "tid-startup", "ok")
stdinReader = strings.NewReader("")
@@ -442,8 +447,7 @@ func TestRunStartupCleanupRemovesOrphansEndToEnd(t *testing.T) {
func TestRunStartupCleanupConcurrentWrappers(t *testing.T) {
defer resetTestHooks()
tempDir := t.TempDir()
setTempDirEnv(t, tempDir)
tempDir := setTempDirEnv(t, t.TempDir())
const totalLogs = 40
for i := 0; i < totalLogs; i++ {
@@ -453,6 +457,7 @@ func TestRunStartupCleanupConcurrentWrappers(t *testing.T) {
stubProcessRunning(t, func(pid int) bool {
return false
})
stubProcessStartTime(t, func(int) time.Time { return time.Time{} })
var wg sync.WaitGroup
const instances = 5
@@ -482,8 +487,7 @@ func TestRunStartupCleanupConcurrentWrappers(t *testing.T) {
func TestRunCleanupFlagEndToEnd_Success(t *testing.T) {
defer resetTestHooks()
tempDir := t.TempDir()
setTempDirEnv(t, tempDir)
tempDir := setTempDirEnv(t, t.TempDir())
staleA := createTempLog(t, tempDir, "codex-wrapper-2100.log")
staleB := createTempLog(t, tempDir, "codex-wrapper-2200-extra.log")
@@ -492,6 +496,12 @@ func TestRunCleanupFlagEndToEnd_Success(t *testing.T) {
stubProcessRunning(t, func(pid int) bool {
return pid == 2300 || pid == os.Getpid()
})
stubProcessStartTime(t, func(pid int) time.Time {
if pid == 2300 || pid == os.Getpid() {
return time.Now().Add(-1 * time.Hour)
}
return time.Time{}
})
os.Args = []string{"codex-wrapper", "--cleanup"}
@@ -544,8 +554,7 @@ func TestRunCleanupFlagEndToEnd_Success(t *testing.T) {
func TestRunCleanupFlagEndToEnd_FailureDoesNotAffectStartup(t *testing.T) {
defer resetTestHooks()
tempDir := t.TempDir()
setTempDirEnv(t, tempDir)
tempDir := setTempDirEnv(t, t.TempDir())
calls := 0
cleanupLogsFn = func() (CleanupStats, error) {