Skip to content

[fix] - post-security-audit robustness + accuracy follow-ups (4 small nits)#653

Draft
cgfixit wants to merge 2 commits into
mainfrom
claude/cyclaw-optimize-audit-followups
Draft

[fix] - post-security-audit robustness + accuracy follow-ups (4 small nits)#653
cgfixit wants to merge 2 commits into
mainfrom
claude/cyclaw-optimize-audit-followups

Conversation

@cgfixit

@cgfixit cgfixit commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Proposed changes

Four low-severity follow-ups from the Phase 2 security scan (the audit found no high-severity issues; the codebase is heavily hardened). Each traces to a specific finding and is independently trivial:

  1. Temp-file orphan on write failure (utils/ops_runner.py _write_body): the file is created (NamedTemporaryFile(delete=False)) before handle.write(body); run_agentic_op only unlinks the name _write_body returns, so a mid-write failure (disk full, un-encodable body) orphaned a cyclaw_skill_*.md in the OS temp dir. Now cleans up its own orphan on the write-error path (close-then-unlink, Windows-safe). Regression test forces the write to raise and asserts no orphan survives.
  2. Listing race (harness/sessions.py SessionStore.list()): sorted by getmtime before the per-file try/except SessionStoreError skip loop, so a session file removed between glob and sort raised OSError straight out of a path meant to tolerate missing/corrupt files — 500-ing /api/sessions + /api/status. Sort now uses a getmtime helper returning 0.0 on OSError. Regression test races a vanishing file.
  3. Dead branch (llm/client.py): if not _is_loopback_url(primary_url): pass computed a loopback check and did nothing — reads like enforcement but is a no-op. Replaced with a plain comment (the non-loopback-primary allowance is intentional).
  4. Docstring accuracy (utils/logger.py): the module docstring claimed query hashing is unconditional, but it's gated on logging.audit_fields.include_query_hash (shipped true; the scoped guarantee is already pinned in test_due_diligence_invariants.py). Docstring now states the toggle and that disabling it is privacy-affecting — doc fixed to match tested behavior, not a behavior change.

Invariant / Governance Impact: None — no graph edge, gate auth, sanitizer, or soul path touched. invariant-guard 28/0. #4/#3 are the security-relevant ones and neither weakens a control (temp-file hygiene; a doc that now correctly describes an already-tested privacy toggle).

Types of changes

  • Bugfix (non-breaking change which fixes an issue)

Scope note: Out-of-band + support code (ops_runner, harness, llm client, logger docstring).

Benefits / why

  • Two real (if low-severity) robustness bugs on error/race paths get regression coverage; two cosmetic/accuracy nits removed. Net: fewer ways for an operator's out-of-band tooling to leak a temp file or 500 a listing, and an audit docstring that no longer overpromises.

Risks to monitor

  • _write_body's except catches (OSError, UnicodeError) — the realistic write-failure modes; a KeyboardInterrupt mid-write still won't clean up (accepted, matches the surrounding code's exception discipline).

Further comments

Verified: ruff clean on all 6 files; 104 passed (test_ops_runner + test_harness + test_audit); both regression tests revert-proven (fail against origin/main source, pass on the fix); invariant-guard 28/0. Shares only tests/test_harness.py with the open #651 (both append tests, non-adjacent) — trial-merge verified clean, noted below.

🤖 Generated with Claude Code

https://claude.ai/code/session_0138hmScu3tFU4v1jzvFUwVT


Generated by Claude Code

claude added 2 commits July 23, 2026 11:21
From the Phase-2 security scan; all low-severity, each traceable to a finding:

- utils/ops_runner.py: _write_body created the temp file before writing, but
  run_agentic_op only unlinks the name _write_body RETURNS — a mid-write
  failure (disk full, un-encodable body) orphaned a cyclaw_skill_*.md. Clean
  up the orphan on the write-error path (close-then-unlink, Windows-safe).
  Regression test forces a write failure and asserts no orphan survives.
- harness/sessions.py: SessionStore.list() sorted by getmtime BEFORE the
  per-file skip loop, so a session file removed between glob and sort raised
  OSError straight out of a listing path meant to tolerate missing/corrupt
  files (would 500 /api/sessions + /api/status). Sort via a getmtime helper
  that returns 0.0 on OSError. Regression test races a vanishing file.
- llm/client.py: replaced a dead `if not _is_loopback_url(primary_url): pass`
  (computed a check, did nothing) with a plain comment; the non-loopback
  primary allowance is intentional, just no longer disguised as control flow.
- utils/logger.py: module docstring claimed query hashing is unconditional,
  but it is gated on logging.audit_fields.include_query_hash (shipped true;
  the scoped guarantee is already pinned in test_due_diligence_invariants).
  Docstring now states the toggle and that disabling it is privacy-affecting.

Verified: ruff clean; 104 passed (ops_runner + harness + audit); both
regression tests revert-proven; invariant-guard 28/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138hmScu3tFU4v1jzvFUwVT
Replace the _safe_mtime helper (added a module member -> WPS202 8>7, and a
0.0 float literal -> WPS358; staticmethod hits WPS214, nested def hits
WPS430) with a contextlib.suppress(OSError) around paths.sort. Same
behavior — a file vanishing mid-sort no longer 500s the listing — with a
dead-plain shape and clearer intent. Regression test unchanged and still
revert-proven.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138hmScu3tFU4v1jzvFUwVT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants