Skip to content

test(mesh/audit): cover read_audit_log symlink refusal + rotation-suffix filter#596

Merged
cagataycali merged 1 commit into
strands-labs:mainfrom
cagataycali:test/audit-read-walker-defenses
Jun 23, 2026
Merged

test(mesh/audit): cover read_audit_log symlink refusal + rotation-suffix filter#596
cagataycali merged 1 commit into
strands-labs:mainfrom
cagataycali:test/audit-read-walker-defenses

Conversation

@cagataycali

Copy link
Copy Markdown
Member

Problem

read_audit_log in strands_robots.mesh.audit is both the operator-facing forensic reader and the seed source for _load_seq_counters when the seq sidecar is corrupt. That dual role makes its file-handling discipline security-relevant, but three documented walker defenses had no direct regression coverage:

  • A rotated log file swapped for a symlink must be refused, not followed (mirrors the O_NOFOLLOW discipline applied to every other open in the module). Following it would let an attacker redirect the forensic read to /dev/null (fail-open seq reset) or to forged content.
  • _audit_log_files_in_order must only treat siblings whose suffix after the active-log name is purely numeric as rotated copies; an unrelated mesh_audit.jsonl.bak decoy must be excluded so its bytes never enter the forensic stream.
  • The since= cutoff must keep records at/after the timestamp and drop older ones, plus records with a missing or non-numeric ts.

Change

Adds tests/mesh/test_audit_read_walker_defenses.py with three focused tests pinning each behavior. The symlink test plants attacker bytes outside the audit dir, points a rotated-log path at them via symlink, and asserts the active record is still read while the symlinked target is refused with a warning. No source change.

Coverage

strands_robots/mesh/audit.py: the symlink-refusal branch (the refusing to read ... SYMLINK path) and the non-numeric rotation-suffix skip were previously uncovered. Mesh-suite coverage of audit.py rises from 84% to 87% (60 -> 57 missing lines).

Tests

MUJOCO_GL=egl python -m pytest tests/mesh/ -> 1690 passed, 2 skipped. ruff check + ruff format + mypy all clean.

…fix filter

read_audit_log is both the operator forensic reader and the seed source
for _load_seq_counters when the seq sidecar is corrupt, so its file
handling is security-relevant. Three documented walker defenses had no
direct regression coverage:

- a rotated log file swapped for a symlink must be refused (O_NOFOLLOW
  discipline), never followed to attacker-controlled bytes;
- only siblings whose suffix after the active-log name is purely numeric
  count as rotated copies (a mesh_audit.jsonl.bak decoy is ignored);
- since= drops records older than the cutoff and records with a missing
  or non-numeric ts.

Pins all three. No source change.

@yinsong1986 yinsong1986 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Test-only PR (+130/-0, one new file tests/mesh/test_audit_read_walker_defenses.py) that pins three previously-uncovered forensic-walker defenses in strands_robots.mesh.audit: (1) read_audit_log refuses to follow a rotated log file that has been swapped for a symlink, asserting the active record is still read and the symlinked target's bytes never enter the stream while a refusing to read ... SYMLINK warning is emitted; (2) _audit_log_files_in_order only treats <active>.<digits> siblings as rotated copies, excluding an unrelated .bak decoy; (3) the since= cutoff keeps records at/after the timestamp and drops older ones plus records with a missing or non-numeric ts. The assertions match the source behavior: the symlink-refusal branch (audit.py:1253-1259), the numeric-suffix filter (audit.py:1208, suffix.isdigit()), and the since filter (audit.py:1291). No source change.

What's good

  • Pure test addition with zero production-code change — no public API, wire format, persisted schema, CLI, or env-var surface touched, so no one-way-door risk.
  • Fixture isolates module state correctly and uses monkeypatch.setenv/delenv per AGENTS.md > Review Learnings (#86) > Testing Patterns, with full setup/teardown of _SEQ_COUNTERS and _AUDIT_STATE.
  • The symlink test plants attacker bytes outside the audit dir and asserts the active record is still returned — a true round-trip pin, not a schema-only check.
  • os.symlink test is guarded by pytest.mark.skipif(not hasattr(os, "symlink")); no host paths, no emojis in any string.

Verification suggestions

MUJOCO_GL=egl python -m pytest tests/mesh/test_audit_read_walker_defenses.py -v

@cagataycali
cagataycali merged commit 5d113aa into strands-labs:main Jun 23, 2026
6 checks passed
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