Fix(security): 增强日志文件安全权限和管理 (#757)

## 问题
决策日志包含敏感的交易数据(API密钥、仓位、PnL等),但使用了不安全的默认权限:
- 目录权限 0755(所有用户可读可执行)
- 文件权限 0644(所有用户可读)

这可能导致同一系统其他用户访问敏感交易数据。

## 解决方案

### 1. 代码层面安全加固(Secure by Default)
**logger/decision_logger.go**:
- 目录创建权限:0755 → 0700(只有所有者可访问)
- 文件创建权限:0644 → 0600(只有所有者可读写)
- **新增:强制修复已存在目录/文件权限**(修复升级场景)
  - `NewDecisionLogger` 启动时强制设置目录权限
  - 遍历并修复已存在的 *.json 文件权限
  - 覆盖 PM2/手动部署场景

### 2. 运行时安全检查(Defense in Depth)
**start.sh**:
- 新增 `setup_secure_permissions()` 函数
- 启动时自动修正敏感文件/目录权限:
  - 目录 (.secrets, secrets, logs, decision_logs): 700
  - 文件 (.env, config.db, 密钥文件, 日志): 600
- **修复:使用 `install -m MODE` 创建文件/目录**
  - `touch config.db` → `install -m 600 /dev/null config.db`
  - `mkdir -p decision_logs` → `install -m 700 -d decision_logs`
  - 确保首次启动就是安全权限(修复 fresh install 漏洞)

### 3. 日志轮转和清理
**scripts/cleanup_logs.sh**:
- 提供日志清理功能(默认保留7天)
- 支持 dry-run 模式预览
- 可通过 crontab 定期执行

## 测试要点
1.  验证新创建的日志文件权限为 0600
2.  验证新创建的日志目录权限为 0700
3.  验证 start.sh 正确设置所有敏感文件权限
4.  验证 Docker 部署(root)可正常访问
5.  验证 PM2 部署(owner)可正常访问
6.  验证清理脚本正确删除旧日志
7.  验证首次安装时 config.db 权限为 600(不是 644)
8.  验证升级后已存在的日志文件权限被修正为 600

## 安全影响
- 防止同一系统其他用户读取敏感交易数据
- 采用深度防御策略:代码默认安全 + 运行时检查 + 升级修复
- 对 Docker(root)和 PM2(owner)部署均兼容
- 修复首次安装和升级场景的权限漏洞
This commit is contained in:
Lawrence Liu
2025-11-09 09:47:24 +08:00
committed by GitHub
parent 1560959526
commit 037ff18df0
2 changed files with 16 additions and 11 deletions

View File

@@ -73,11 +73,16 @@ func NewDecisionLogger(logDir string) *DecisionLogger {
logDir = "decision_logs"
}
// 确保日志目录存在
if err := os.MkdirAll(logDir, 0755); err != nil {
// 确保日志目录存在(使用安全权限:只有所有者可访问)
if err := os.MkdirAll(logDir, 0700); err != nil {
fmt.Printf("⚠ 创建日志目录失败: %v\n", err)
}
// 强制设置目录权限(即使目录已存在)- 确保安全
if err := os.Chmod(logDir, 0700); err != nil {
fmt.Printf("⚠ 设置日志目录权限失败: %v\n", err)
}
return &DecisionLogger{
logDir: logDir,
cycleNumber: 0,
@@ -103,8 +108,8 @@ func (l *DecisionLogger) LogDecision(record *DecisionRecord) error {
return fmt.Errorf("序列化决策记录失败: %w", err)
}
// 写入文件
if err := ioutil.WriteFile(filepath, data, 0644); err != nil {
// 写入文件(使用安全权限:只有所有者可读写)
if err := ioutil.WriteFile(filepath, data, 0600); err != nil {
return fmt.Errorf("写入决策记录失败: %w", err)
}