Repository navigation
fix(cli): harden calculate_dir_size against silent errors and symlink cycles - #1862
Conversation
… cycles WalkDir sizing previously used filter_map(ok) which silently dropped permission-denied / I/O errors and left symlink-following at the default. This undercounted audit/rollback disk usage, so audit cleanup --max-total-size could keep more than the operator's budget, and a planted symlink loop could cause WalkDir traversal until OS limits before being discarded. Harden both audit_session::calculate_dir_size and rollback_session::calculate_dir_size: disable symlink following, cap open FDs at 128, and log skips via tracing::warn instead of dropping. Use saturating_add for total. Add 8 tests covering regular files, empty dir, broken symlink, symlinked dir not followed, and symlink cycle termination. Closes nolabs-ai#1858 Signed-off-by: Ayush7614 <ayushknj3@gmail.com>
PR Review SummarySize
Affected crates
Blast radius — ContainedThis PR touches: source code Updated automatically on each push to this PR. |
| continue; | ||
| } | ||
| }; | ||
| let metadata = match entry.metadata() { |
There was a problem hiding this comment.
Warning
🔒 [MEDIUM · security] The helper calculate_dir_size uses entry.metadata() which resolves symbolic links to their targets, even when WalkDir is configured with follow_links(false). If a symlink points to a file, metadata.is_file() returns true and its target length is added to total. This can lead to double-counting of symlinked files or incorrect size attribution for files located outside the session directory. To fix this, skip non-files by checking if !entry.file_type().is_file() { continue; } before retrieving metadata.
| continue; | ||
| } | ||
| }; | ||
| let metadata = match entry.metadata() { |
There was a problem hiding this comment.
Warning
🔒 [MEDIUM · security] The helper calculate_dir_size uses entry.metadata() which resolves symbolic links to their targets, even when WalkDir is configured with follow_links(false). If a symlink points to a file, metadata.is_file() returns true and its target length is added to total. This can lead to double-counting of symlinked files or incorrect size attribution for files located outside the session directory. To fix this, skip non-files by checking if !entry.file_type().is_file() { continue; } before retrieving metadata.
| @@ -194,13 +194,36 @@ fn is_process_alive(pid: u32) -> bool { | |||
|
|
|||
| /// Calculate the total size of all files in a directory tree. | |||
| fn calculate_dir_size(dir: &Path) -> u64 { | |||
There was a problem hiding this comment.
Note
🏗️ [LOW · design] The calculate_dir_size helper logic is duplicated between crates/nono-cli/src/audit_session.rs and crates/nono-cli/src/rollback_session.rs. Consider consolidating this helper into a single shared function under a common utility module such as crates/nono-cli/src/state_paths.rs to avoid duplication.
- Check file_type().is_file() before metadata() so symlink-to-file entries are not counted via metadata() follow (which resolves external targets and double-counts). With follow_links(false), file_type correctly distinguishes symlink vs regular file. - Extract shared helper state_paths::calculate_dir_size and have audit_session and rollback_session delegate to it, removing duplication (LOW design). - Add tests for symlink-to-file outside dir not counted in both modules; now 11 calculate_dir_size tests pass. Fixes nogent MEDIUM security (metadata follows symlink) and LOW design (duplication) on nolabs-ai#1862. Signed-off-by: Ayush7614 <ayushknj3@gmail.com>
|
Addressed nogent review on b2d60d6:
New commit 94e250e pushed; clippy clean, 2086 tests pass. |
| @@ -194,13 +193,7 @@ fn is_process_alive(pid: u32) -> bool { | |||
|
|
|||
| /// Calculate the total size of all files in a directory tree. | |||
| fn calculate_dir_size(dir: &Path) -> u64 { | |||
There was a problem hiding this comment.
Why do we have calculate_dir_size in rollback_session if you already created a shared helper in state_paths ??
There was a problem hiding this comment.
Good catch — that wrapper was leftover from the dedup commit. Removed the thin calculate_dir_size wrappers from both rollback_session.rs and audit_session.rs; call sites now use state_paths::calculate_dir_size directly, and the duplicated tests are consolidated into a single suite in state_paths. Also merged upstream/main so the branch is up to date. All checks pass (2093 tests, clippy, fmt).
Remove thin calculate_dir_size wrappers in audit_session and rollback_session that only delegated to the shared helper. Call sites now use state_paths::calculate_dir_size directly. Consolidate the duplicated size tests into a single suite in state_paths, removing the per-module copies. Addresses SequeI review on nolabs-ai#1862. Signed-off-by: Ayush7614 <ayushknj3@gmail.com>
Signed-off-by: Ayush7614 <ayushknj3@gmail.com>
Linked Issue
Closes #1858
Summary
Both
crates/nono-cli/src/audit_session.rs:273andcrates/nono-cli/src/rollback_session.rs:195sized session directories via:filter_map(ok)silently dropped permission-denied / I/O errors → size undercount, soaudit cleanup --max-total-sizecould keep more than the operator's budget.follow_links(false)ormax_opencap — a symlink loop (potentially planted via sandbox) could traverse until OS limits before being discarded, consuming CPU on everydiscover_sessions.Fix hardens both helpers:
WalkDir::new(dir).follow_links(false).max_open(128), logs skips viatracing::warn!instead of dropping, and usessaturating_addfor total. Symlinked dirs are not descended; broken symlinks and loops are logged and skipped without double-counting.Files:
crates/nono-cli/src/audit_session.rs:273-304— hardened + 5 new testscrates/nono-cli/src/rollback_session.rs:195-226— hardened + 3 new testsAgent Disclosure
Generated by AI assistant (Muse Spark via OpenCode). Audited
calculate_dir_sizein both modules, compared withcollect_symlink_hopsMAX_SYMLINKS handling, verified no duplicate open issue (#1819 is the feature, not its budget bug). No NEP required — CLI-only behavior fix.Test Plan
cargo test -p nono-cli --bin nono -- calculate_dir_size— 9 passed:calculate_dir_size_worksstill passescargo test -p nono-cli --bin nono— 2084 passed, 0 failed, 11 ignoredcargo clippy -p nono-cli -- -D warnings -D clippy::unwrap_used— cleancargo fmt --check— cleanChecklist