第五轮审计(J1~J16)全量修复 + 子代理 max_turns 钳制: - 子代理 max_turns 钳制(0/None 回退默认 10,负数钳制 1)+ 14 项回归测试 - J1 任务系统投毒:add_memo 入库校验(时间/间隔/类型)、调度器预检与触发事务 防御、展示路径兜底、register 移入 try、add_user_task 补 repeat_end_time 透传 - J2/J13 前端 XSS:三页面 marked 输出经 DOMPurify 净化、entry1 系统消息/任务 分隔条改 textContent、CDN 脚本补 SRI - J3 FTS 删除换序(先 fts 后内容表,索引不再残留) - J5 原子写推广:新增 common/atomic.py(mkstemp+fsync+os.replace),config/prompt/ history 全部接入;损坏配置备份后回退 - J6 单例初始化加锁(PromptManager/ConfigManager 并发首例不双重加载) - J7 broadcast 并发发送 + wait_for 超时回收僵死连接 - J4/J8/J9~J12/J14~J16:验证异常脱敏、交互输入容错、MemoDatabase WAL+busy_timeout、 memo 批量删除、/new 竞态、skills 引号转义、voice_client 清理、死代码移除、 skill_manager 同名键/help 截断
33 lines
1.1 KiB
Python
33 lines
1.1 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
common.atomic - 原子写文件工具(J5,审计 2026-08-19)
|
||
|
||
同目录 mkstemp 临时文件 + fsync + os.replace:进程中断/断电不留半截文件;
|
||
mkstemp 随机文件名避免多进程并发写同路径互踩(conversation_history 旧实现
|
||
的固定 .tmp 名在并发场景会互相覆盖)。仅依赖标准库,供 common/subsystems
|
||
各写路径复用。
|
||
"""
|
||
|
||
import os
|
||
import tempfile
|
||
|
||
|
||
def atomic_write_text(path: str, content: str) -> None:
|
||
"""原子写文本文件。失败时清理临时文件并抛出原异常。"""
|
||
path = os.path.expanduser(path)
|
||
directory = os.path.dirname(path) or "."
|
||
os.makedirs(directory, exist_ok=True)
|
||
fd, tmp_path = tempfile.mkstemp(prefix=".aibox_tmp_", dir=directory)
|
||
try:
|
||
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
||
f.write(content)
|
||
f.flush()
|
||
os.fsync(f.fileno())
|
||
os.replace(tmp_path, path)
|
||
except BaseException:
|
||
try:
|
||
os.unlink(tmp_path)
|
||
except OSError:
|
||
pass
|
||
raise
|