Goal
Ensure basectl clean can never delete files or directories outside the resolved Base cache root, including when a runtime owner or category path contains a symlink.
Background
The cleanup scanner accepts owner roots through Path.is_dir(), which follows symlinks. remove_path checks only whether the final candidate is a symlink before calling shutil.rmtree. A child beneath a symlinked owner root is therefore treated as an ordinary directory.
A focused reproduction created a projects// owner symlink pointing at an unrelated temporary directory. basectl clean --older-than 1s deleted the external run directory and returned success.
Relevant code:
Scope
- Reject symlinked runtime owner roots and category roots.
- Resolve every deletion candidate and verify it remains beneath the resolved Base cache root.
- Fail closed if containment or filesystem metadata cannot be verified.
- Add focused parent-symlink and candidate-symlink regression tests.
- Document the cleanup containment guarantee if the public contract needs clarification.
Acceptance Criteria
- A symlinked owner root cannot cause cleanup outside the cache.
- A symlinked runs or component-cache directory cannot cause cleanup outside the cache.
- Normal in-cache cleanup still works for both --older-than and --keep-last.
- Unsafe candidates are skipped or rejected with an actionable diagnostic.
- Tests cover symlinks at every relevant parent level.
Validation
- Focused base_clean pytest coverage.
- basectl clean dry-run and real deletion against isolated temporary caches.
- env -u BASE_HOME ./bin/base-test
- git diff --check
Non-Goals
- Do not broaden cleanup to user-selected arbitrary paths.
- Do not change history-index retention.
- Do not follow symlinks in order to clean their targets.
Project Fields
- Status: Ready
- Priority: P1
- Area: Security
- Initiative: Contract Hardening
- Size: M
Agent Assignment
Goal
Ensure basectl clean can never delete files or directories outside the resolved Base cache root, including when a runtime owner or category path contains a symlink.
Background
The cleanup scanner accepts owner roots through Path.is_dir(), which follows symlinks. remove_path checks only whether the final candidate is a symlink before calling shutil.rmtree. A child beneath a symlinked owner root is therefore treated as an ordinary directory.
A focused reproduction created a projects// owner symlink pointing at an unrelated temporary directory. basectl clean --older-than 1s deleted the external run directory and returned success.
Relevant code:
Scope
Acceptance Criteria
Validation
Non-Goals
Project Fields
Agent Assignment