mirror of
https://github.com/cexll/myclaude.git
synced 2025-12-24 13:47:58 +08:00
必须修复的问题: 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>
105 lines
2.3 KiB
Go
105 lines
2.3 KiB
Go
//go:build unix || darwin || linux
|
|
// +build unix darwin linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
var findProcess = os.FindProcess
|
|
var readFileFn = os.ReadFile
|
|
|
|
// isProcessRunning returns true if a process with the given pid is running on Unix-like systems.
|
|
func isProcessRunning(pid int) bool {
|
|
if pid <= 0 {
|
|
return false
|
|
}
|
|
|
|
proc, err := findProcess(pid)
|
|
if err != nil || proc == nil {
|
|
return false
|
|
}
|
|
|
|
err = proc.Signal(syscall.Signal(0))
|
|
if err != nil && (errors.Is(err, syscall.ESRCH) || errors.Is(err, os.ErrProcessDone)) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// getProcessStartTime returns the start time of a process on Unix-like systems.
|
|
// Returns zero time if the start time cannot be determined.
|
|
func getProcessStartTime(pid int) time.Time {
|
|
if pid <= 0 {
|
|
return time.Time{}
|
|
}
|
|
|
|
// Read /proc/<pid>/stat to get process start time
|
|
statPath := fmt.Sprintf("/proc/%d/stat", pid)
|
|
data, err := readFileFn(statPath)
|
|
if err != nil {
|
|
return time.Time{}
|
|
}
|
|
|
|
// Parse stat file: fields are space-separated, but comm (field 2) can contain spaces
|
|
// Find the last ')' to skip comm field safely
|
|
content := string(data)
|
|
lastParen := strings.LastIndex(content, ")")
|
|
if lastParen == -1 {
|
|
return time.Time{}
|
|
}
|
|
|
|
fields := strings.Fields(content[lastParen+1:])
|
|
if len(fields) < 20 {
|
|
return time.Time{}
|
|
}
|
|
|
|
// Field 22 (index 19 after comm) is starttime in clock ticks since boot
|
|
startTicks, err := strconv.ParseUint(fields[19], 10, 64)
|
|
if err != nil {
|
|
return time.Time{}
|
|
}
|
|
|
|
// Get system boot time
|
|
bootTime := getBootTime()
|
|
if bootTime.IsZero() {
|
|
return time.Time{}
|
|
}
|
|
|
|
// Convert ticks to duration (typically 100 ticks/sec on most systems)
|
|
ticksPerSec := uint64(100) // sysconf(_SC_CLK_TCK), typically 100
|
|
startTime := bootTime.Add(time.Duration(startTicks/ticksPerSec) * time.Second)
|
|
|
|
return startTime
|
|
}
|
|
|
|
// getBootTime returns the system boot time by reading /proc/stat.
|
|
func getBootTime() time.Time {
|
|
data, err := readFileFn("/proc/stat")
|
|
if err != nil {
|
|
return time.Time{}
|
|
}
|
|
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "btime ") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 2 {
|
|
bootSec, err := strconv.ParseInt(fields[1], 10, 64)
|
|
if err == nil {
|
|
return time.Unix(bootSec, 0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return time.Time{}
|
|
}
|