ADR-0008 · Security model¶
Status: Draft (locked at Phase 3) · Date: 2026-05-14
Context¶
rglob walks filesystems. The Phase 3 walker rewrite adds:
follow_symlinks=False(default) →True(opt-in) symlink traversal- a
realpath-keyed memo to terminate symlink cycles respect_gitignore(Phase 5) reading.gitignorefiles top-downon_error="ignore"|"warn"|"raise"coveringPermissionError,OSError,UnicodeDecodeError
Each of these is a small surface; together they are a security model worth writing down so we don't drift.
Threat model¶
In scope
- Symlink escape: a follow-enabled walk should never visit a path whose
realpathlies outside the user-suppliedbase. Currently we do not enforce this by default; users opt intofollow_symlinks=Trueknowing what it means. A future flag (strict_base=True) could enforce containment. - Symlink loops: a
realpathmemo terminatesa → b → aand longer cycles. The memo is scoped perfind()call (no cross-call leaks) and usesos.path.realpath(notPath.resolve(strict=True)) so dangling links don't raise. .gitignorecontainment:respect_gitignoreonly reads.gitignorefiles at or belowbase. It never traverses upward.
Out of scope
- TOCTOU between
scandirandlstat: the walker uses cachedDirEntrymetadata where possible; we do not snapshot the FS. A malicious local user racing the walk can cause it to surface or miss entries — this is inherent to filesystem walkers and not something we can fix without a kernel-side lock. - Arbitrary code execution:
rglobnever executes content it discovers. Predicate callbacks (lcount(..., func=...)) run user code by design, not by accident.
Mitigations¶
- Default
follow_symlinks=False. - Per-call
realpathmemo for cycle termination. respect_gitignoretraversal isbase-rooted, never upward.on_error="warn"(default) prints tostderrrather than terminating on unreadable entries; users opt into"raise"for strict pipelines.tests/test_symlinks.pybuilds a cycle and asserts termination.
Reporting¶
Vulnerability reports go via the project's security policy (GitHub private security advisories).