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 f656a9c0fe
commit 9d41d5ab39
2 changed files with 16 additions and 11 deletions
+9 -4
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)
}
+7 -7
View File
@@ -219,14 +219,14 @@ check_database() {
print_warning "config.db 是目录而非文件,正在删除目录..."
rm -rf config.db
print_info "✓ 已删除目录,现在创建文件..."
touch config.db
print_success "✓ 已创建空数据库文件,系统将在启动时初始化"
install -m 600 /dev/null config.db
print_success "✓ 已创建空数据库文件(权限: 600,系统将在启动时初始化"
elif [ ! -f "config.db" ]; then
# 如果不存在文件,创建它
print_warning "数据库文件不存在,创建空数据库文件..."
# 创建空文件以避免Docker创建目录
touch config.db
print_info "✓ 已创建空数据库文件,系统将在启动时初始化"
# 创建空文件以避免Docker创建目录(使用安全权限600
install -m 600 /dev/null config.db
print_info "✓ 已创建空数据库文件(权限: 600,系统将在启动时初始化"
else
# 文件存在
print_success "数据库文件存在"
@@ -274,11 +274,11 @@ start() {
# 确保必要的文件和目录存在(修复 Docker volume 挂载问题)
if [ ! -f "config.db" ]; then
print_info "创建数据库文件..."
touch config.db
install -m 600 /dev/null config.db
fi
if [ ! -d "decision_logs" ]; then
print_info "创建日志目录..."
mkdir -p decision_logs
install -m 700 -d decision_logs
fi
# Auto-build frontend if missing or forced