Skip to content

test(send_allowlist): 16 behavioral tests for is_path_sendable - #1137

Closed
bassilkhilo-ag2 wants to merge 3 commits into
sonichi:mainfrom
bassilkhilo-ag2:test/send-allowlist-behavioral-1029
Closed

bassilkhilo-ag2 wants to merge 3 commits into
sonichi:mainfrom
bassilkhilo-ag2:test/send-allowlist-behavioral-1029

Conversation

@bassilkhilo-ag2

Copy link
Copy Markdown
Collaborator

Summary

Security properties tested (16 tests)

Test What it guards
T1–T2 Fail-closed: nonexistent path, empty string → False
T3 Directories never sendable (even with sutando- name)
T4–T5 Prefix allow: /tmp/sutando-* and /tmp/echo-* accepted
T6 Prefix reject: /tmp/notallowed-* rejected
T7 Symlink to /etc/passwd rejected via realpath collapse
T8 /tmp/sutando-<link> → outside area: name passes, realpath defeats it
T9 Path traversal ../../../etc/passwd neutralised by realpath
T10–T11 Root-based: file in root and subdirectory accepted
T12 Root directory itself (not a file) rejected
T13 Substring guard: file in /tmp/notallowed-*/ with "sutando" in name
T14 Trailing-sep guard: results-extra/ not matched by results/ root
T15–T16 Intra-root symlink→same root accepted; symlink→outside root rejected

Test plan

  • python3 tests/send-allowlist-is-path-sendable.test.py → 16/16 PASS
  • No discord.py dependency — importlib loads module directly
  • Root-based tests use monkeypatching of SEND_ALLOWED_ROOTS with tempdir fixtures; no real workspace mutations

🤖 Generated with Claude Code

bassilkhilo-ag2 and others added 3 commits May 26, 2026 04:28
Covers: missing results/ no-op, mtime cutoff (archive old, skip fresh),
non-.txt skip, archive-* subdir immunity, DRY_RUN=1 print-only mode,
DRY_RUN case-insensitivity ('No'/'FALSE' as falsy), archive-YYYY-MM-DD
naming, file placement inside archive dir, multi-file batch.

Motivated by the 2026-04-15 DM-flood incident (142 stale files fired as DMs
on service restart) — this archiver was the prevention mechanism; tests
ensure it remains correct.

12/12 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- T13: mixed fresh+stale — only stale archived, fresh untouched
- T14: RETENTION_HOURS=0 archives everything regardless of age
- T15: .sending files (orphan proactive-delivery claims) are not
       archived — only .txt suffix is in scope

Total: 15/15 passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Companion to send-allowlist-shared.test.py (architectural invariants).
This suite exercises the runtime security properties that matter most:

- Symlink-traversal: /tmp/sutando-<link>→outside is rejected via realpath
- Path traversal: ../../../etc/passwd neutralised by realpath collapse
- Intra-root escape: symlink within results/→outside results/ rejected
- Fail-closed: nonexistent paths, directories, empty string all False
- Trailing-sep guard: results-extra/ not matched by results/ root
- Prefix correctness: /tmp/sutando-* and /tmp/echo-* accepted; others not

No discord.py or workspace mutation — all root-based tests use
monkeypatching of SEND_ALLOWED_ROOTS with tempdir fixtures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@liususan091219 liususan091219 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

NON-BLOCKING — LGTM (bottom of stack)

Clean PR — adds tests/send-allowlist-is-path-sendable.test.py (+355, 16 tests for is_path_sendable). The other test file in the diff (archive-stale-results.test.py) is base from #1136. No build_log.md leak, no source edits.

This is the bottom of a 6-PR stack (#1137 → #1142). Each adds exactly one new test file on top of the previous. Stack pattern looks correct.

Deferring the merge call to Susan.

@VasiliyRad VasiliyRad 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.

All 16 assertions verified against the is_path_sendable source. Test-by-test:

  • T1-T3: fail-closed (nonexistent, empty, directory) — correct, all hit os.path.isfile guard.
  • T4-T6: prefix allow/reject — correct, realpath on macOS resolves /tmp → /private/tmp and the prefixes list covers both forms.
  • T7-T9: symlink traversal + path-traversal .. — correct, realpath collapses before the check runs.
  • T10-T12: root-based matching via _with_patched_roots monkeypatch — clean isolation, assertions match startswith(root_real + os.sep) logic.
  • T13-T14: substring/sibling false-prefix guards — correct, validates the trailing-sep invariant.
  • T15-T16: intra-root symlink accept vs escape-symlink reject — correct.

No non-test files modified. No false-positive risks. LGTM.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

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.

3 participants