fix(lib): hardening and edge-case bugfixes (FW-12, FW-16 round)
- Restored .bak generation to maintain P0-B backup invariants - Fixed stale NFS warning message to reflect SQLite DELETE fallback - Replaced vulnerable yaml.replace with os.path.splitext globally - Ensured YAML dump occurs after conn.commit() to prevent partial syncs - Re-applied chmod 0600 on SQLite -wal and -shm files
This commit is contained in:
+25
-15
@@ -112,7 +112,7 @@ resolve_tmux_server() {
|
|||||||
import os, sys, sqlite3, json, yaml
|
import os, sys, sqlite3, json, yaml
|
||||||
name = os.environ['SESSION_NAME']
|
name = os.environ['SESSION_NAME']
|
||||||
yaml_path = os.environ['YAML_PATH']
|
yaml_path = os.environ['YAML_PATH']
|
||||||
db_path = yaml_path.replace('.yaml', '.db')
|
db_path = os.path.splitext(yaml_path)[0] + '.db'
|
||||||
d = {}
|
d = {}
|
||||||
try:
|
try:
|
||||||
if os.path.exists(db_path):
|
if os.path.exists(db_path):
|
||||||
@@ -124,13 +124,13 @@ try:
|
|||||||
with open(yaml_path) as f:
|
with open(yaml_path) as f:
|
||||||
d = yaml.safe_load(f) or {}
|
d = yaml.safe_load(f) or {}
|
||||||
for s in d.get('tmux_sessions', []):
|
for s in d.get('tmux_sessions', []):
|
||||||
if s.get('name') == name:
|
if s.get('name') == name:
|
||||||
server = s.get('tmux_server')
|
server = s.get('tmux_server')
|
||||||
if server:
|
if server:
|
||||||
print(server)
|
print(server)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# Fallback
|
# Fallback
|
||||||
print(os.environ.get('TMUX_SERVER_NAME', 'default'))
|
print(os.environ.get('TMUX_SERVER_NAME', 'default'))
|
||||||
PYEOF
|
PYEOF
|
||||||
@@ -208,7 +208,7 @@ _atomic_dump_yaml_check_nfs() {
|
|||||||
if mount | grep -q "$mountpoint.*nfs\|$mountpoint.*cifs\|$mountpoint.*fuse.sshfs"; then
|
if mount | grep -q "$mountpoint.*nfs\|$mountpoint.*cifs\|$mountpoint.*fuse.sshfs"; then
|
||||||
echo "WARNING: $mountpoint appears to be a network filesystem (NFS/CIFS/SSHFS)." >&2
|
echo "WARNING: $mountpoint appears to be a network filesystem (NFS/CIFS/SSHFS)." >&2
|
||||||
echo "WARNING: fcntl.flock-based atomic writes are unreliable on network filesystems." >&2
|
echo "WARNING: fcntl.flock-based atomic writes are unreliable on network filesystems." >&2
|
||||||
echo "WARNING: Consider migrating to SQLite WAL for registry storage (see FUTURE_WORKS.md FW-02)." >&2
|
echo "WARNING: SQLite journal_mode automatically falls back to DELETE." >&2
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ from datetime import datetime, timezone
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
yaml_path = os.environ['YAML_PATH']
|
yaml_path = os.environ['YAML_PATH']
|
||||||
db_path = yaml_path.replace('.yaml', '.db')
|
db_path = os.path.splitext(yaml_path)[0] + '.db'
|
||||||
|
|
||||||
def _validate(d):
|
def _validate(d):
|
||||||
if not isinstance(d, dict):
|
if not isinstance(d, dict):
|
||||||
@@ -305,14 +305,16 @@ try:
|
|||||||
|
|
||||||
new_terminals = get_terminal_set(d)
|
new_terminals = get_terminal_set(d)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
# Write to YAML ONLY when a session transitions to a finished state
|
# Write to YAML ONLY when a session transitions to a finished state
|
||||||
|
# (Moved after conn.commit() per Claude's feedback)
|
||||||
if new_terminals != old_terminals:
|
if new_terminals != old_terminals:
|
||||||
if os.path.exists(yaml_path):
|
if os.path.exists(yaml_path):
|
||||||
try:
|
try:
|
||||||
shutil.copy2(yaml_path, yaml_path + '.bak')
|
shutil.copy2(yaml_path, yaml_path + '.bak')
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
dir_ = os.path.dirname(yaml_path) or '.'
|
dir_ = os.path.dirname(yaml_path) or '.'
|
||||||
fd, tmp = tempfile.mkstemp(dir=dir_, prefix='.agent-sessions.', suffix='.tmp')
|
fd, tmp = tempfile.mkstemp(dir=dir_, prefix='.agent-sessions.', suffix='.tmp')
|
||||||
try:
|
try:
|
||||||
@@ -324,9 +326,7 @@ try:
|
|||||||
if os.path.exists(tmp):
|
if os.path.exists(tmp):
|
||||||
os.remove(tmp)
|
os.remove(tmp)
|
||||||
raise
|
raise
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
if new_terminals != old_terminals:
|
|
||||||
try:
|
try:
|
||||||
conn.execute('PRAGMA wal_checkpoint(TRUNCATE)')
|
conn.execute('PRAGMA wal_checkpoint(TRUNCATE)')
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -336,6 +336,16 @@ except Exception:
|
|||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
# H3: Re-apply chmod 0600 after close to cover newly created -wal / -shm files
|
||||||
|
try:
|
||||||
|
os.chmod(db_path, 0o600)
|
||||||
|
wal = db_path + '-wal'
|
||||||
|
if os.path.exists(wal): os.chmod(wal, 0o600)
|
||||||
|
shm = db_path + '-shm'
|
||||||
|
if os.path.exists(shm): os.chmod(shm, 0o600)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
PYEOF
|
PYEOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,7 +374,7 @@ ws = os.environ['WS_ABS']
|
|||||||
agent = os.environ['AGENT']
|
agent = os.environ['AGENT']
|
||||||
home = os.environ['HOME_DIR']
|
home = os.environ['HOME_DIR']
|
||||||
yaml_path = os.environ['YAML_PATH']
|
yaml_path = os.environ['YAML_PATH']
|
||||||
db_path = yaml_path.replace('.yaml', '.db')
|
db_path = os.path.splitext(yaml_path)[0] + '.db'
|
||||||
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
|
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
|
||||||
|
|
||||||
d = {}
|
d = {}
|
||||||
@@ -483,7 +493,7 @@ is_already_stopped() {
|
|||||||
import os, yaml, sqlite3, json
|
import os, yaml, sqlite3, json
|
||||||
name = os.environ['SESSION_NAME']
|
name = os.environ['SESSION_NAME']
|
||||||
yaml_path = os.environ['YAML_PATH']
|
yaml_path = os.environ['YAML_PATH']
|
||||||
db_path = yaml_path.replace('.yaml', '.db')
|
db_path = os.path.splitext(yaml_path)[0] + '.db'
|
||||||
d = {}
|
d = {}
|
||||||
try:
|
try:
|
||||||
if os.path.exists(db_path):
|
if os.path.exists(db_path):
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ try:
|
|||||||
d
|
d
|
||||||
except NameError:
|
except NameError:
|
||||||
import sqlite3
|
import sqlite3
|
||||||
db_path = yaml_path.replace('.yaml', '.db')
|
db_path = os.path.splitext(yaml_path)[0] + '.db'
|
||||||
d = {}
|
d = {}
|
||||||
try:
|
try:
|
||||||
if os.path.exists(db_path):
|
if os.path.exists(db_path):
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ home = os.environ['HOME_DIR']
|
|||||||
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
|
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
|
||||||
drift = json.loads(os.environ['DRIFT_JSON'])
|
drift = json.loads(os.environ['DRIFT_JSON'])
|
||||||
|
|
||||||
db_path = yaml_path.replace('.yaml', '.db')
|
db_path = os.path.splitext(yaml_path)[0] + '.db'
|
||||||
d = {}
|
d = {}
|
||||||
import sqlite3
|
import sqlite3
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user