Skip to content

feat(infra): implement filesystem bootstrap with domain path-containment primitives#20

Merged
mpiton merged 3 commits intomainfrom
feat/task-T-104-ensure-dirs
May 4, 2026
Merged

feat(infra): implement filesystem bootstrap with domain path-containment primitives#20
mpiton merged 3 commits intomainfrom
feat/task-T-104-ensure-dirs

Conversation

@mpiton
Copy link
Copy Markdown
Owner

@mpiton mpiton commented May 4, 2026

Summary

Implements task T-104: filesystem bootstrap with path-containment isolation. ensure_dirs() creates Forgent's 12-directory layout under two allowed roots (<project>/.claude/forgent/ and ~/.forgent/). Path containment is enforced before any syscall via lexical check that rejects .. traversal and validates against allowed roots—TOCTOU-immune by construction.

Secondary refactor extracts reusable path primitives into pure domain helpers (has_traversal, is_within) for use by other adapters (prompts_fs, worktrees, etc.).

Closes T-104. PRD §0.3, ARCHI §5.5 / §9.2.

Why

Forgent's path-isolation guarantee (PRD §0.3) requires the app to never write outside the two allowed roots. Boot must create the full directory structure idempotently before any I/O. Extracting primitives to domain layer makes containment checks reusable and encourages composition over duplication across future filesystem consumers.

Changes

  • Add pub async fn ensure_dirs(cfg: &AppConfig) in infrastructure/filesystem/paths.rs that creates 12 directories across two allowed roots, enforcing path containment before each create_dir_all syscall.
  • Populate domain/security/path_containment.rs stub with has_traversal() (lexical rejection of ParentDir components) and is_within() (component-aware prefix matching).
  • Refactor ensure_dirs to delegate to domain helpers, hoist root derivation (eliminates ~24 redundant allocations per boot), and inline PROJECT_FORGENT_SUBDIR constant.
  • Add 7 unit tests to path_containment (100% coverage, security target) and 5 tokio tests to paths.rs covering creation, idempotency, traversal rejection, escape-root refusal, and enumeration.
  • Update CHANGELOG.md with both commits.

Testing

cargo test --workspace
# 34 tests pass (27 → 34), paths.rs 99.29% lines, path_containment 100% lines

cargo llvm-cov --workspace --fail-under-lines 75
# Workspace coverage ≥75% ✓

cargo clippy --workspace -- -D warnings
# No warnings

pnpm exec oxlint .
# No violations

All tests pass, linting green. tempfile-based integration tests verify behavior on temp filesystem; lexical checks immune to TOCTOU races.

Related Issues

Closes #T-104

Checklist

  • Tests added (12 total, 100% domain/security + 99.29% infra/filesystem)
  • Docs updated (CHANGELOG.md, inline comments for traversal rationale)
  • No secrets committed
  • Self-reviewed diff
  • Clippy + oxlint green

Summary by CodeRabbit

  • Security Improvements

    • Strengthened path traversal prevention and containment checks to ensure directories stay within allowed roots.
  • New Features

    • Added a boot-time directory layout creation routine that validates targets before creating directories.
  • Refactor

    • Moved path containment logic into a dedicated security utilities component for clearer responsibilities.
  • Tests

    • Added comprehensive unit and async tests covering containment, traversal rejection, idempotency, and safe directory creation.

mpiton added 2 commits May 4, 2026 15:05
Boot-time bootstrap that creates Forgent's 12-directory layout under the two allowed roots: <project>/.claude/forgent/ and ~/.forgent/. Path containment is enforced before any filesystem syscall via a lexical check that rejects '..' traversal components and refuses any candidate dir that does not start with one of the two allowed roots derived from AppConfig.

5 tokio tests in tempfile sandboxes covering full creation, idempotency, traversal refusal, escape-root refusal, and enumeration of created entries. Coverage 99.28% lines.

PRD §0.3, ARCHI §5.5 / §9.2.
…urity

/simplify follow-up on T-104. Two changes:

- Populate the previously-empty domain/security/path_containment.rs stub
  with pure helpers has_traversal(path) and is_within(path, root). 7 unit
  tests, 100% coverage (security target). Pure std, no I/O, no canonicalize
  — TOCTOU-immune by construction. Available now for prompts_fs, worktrees
  and other adapters that will need containment checks.

- Refactor infrastructure/filesystem/paths.rs::ensure_dirs to delegate to
  the new domain helpers, hoist project_forgent + global_root derivation
  out of the per-iteration helper (eliminates ~24 redundant PathBuf
  allocations per boot), inline a PROJECT_FORGENT_SUBDIR constant, and
  drop the needless borrow on starts_with.

Behavior unchanged. cargo test --workspace 27 → 34, paths.rs coverage
99.28 → 99.29%, no clippy warnings. Skipped: phase enum (T-109 stub),
try_join_all (boot-only path), AppConfig::global_root field (out of T-103
scope).
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 405a4b88-fdf4-41e2-870f-e2f007a6ff7c

📥 Commits

Reviewing files that changed from the base of the PR and between ae8286f and 1cfd0b2.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • src-tauri/src/infrastructure/filesystem/paths.rs
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src-tauri/src/infrastructure/filesystem/paths.rs

📝 Walkthrough

Walkthrough

Extracts pure path-containment primitives (has_traversal, is_within) and adds a boot-time directory bootstrap (ensure_dirs) that lexically validates candidate paths against two allowed roots before creating directories; wiring exposes paths as a public filesystem submodule and tests cover creation, idempotency, and rejection cases.

Changes

Path Security & Directory Bootstrap

Layer / File(s) Summary
Primitives (pure, no I/O)
src-tauri/src/domain/security/path_containment.rs
Adds pub fn has_traversal(path: &Path) -> bool (detects Component::ParentDir) and pub fn is_within(path: &Path, root: &Path) -> bool (delegates to Path::starts_with). Includes unit tests for traversal detection and component-aware containment.
Core bootstrap logic
src-tauri/src/infrastructure/filesystem/paths.rs
Adds pub async fn ensure_dirs(cfg: &AppConfig) -> Result<(), AppError> which derives project/global allowed roots, computes required_dirs, validates each candidate with has_traversal and is_within (returns AppError::Security on failure; AppError::Internal if logs_dir.parent() missing), then calls tokio::fs::create_dir_all for all validated targets.
Helper decomposition
src-tauri/src/infrastructure/filesystem/paths.rs
Extracts required_dirs(cfg, project_forgent) and ensure_under_allowed_root(...) to structure validation before creation; hoists root derivation to reduce redundant allocations.
Module wiring
src-tauri/src/infrastructure/filesystem/mod.rs
Adds pub mod paths; to expose the new paths bootstrap and helpers.
Tests / Coverage
src-tauri/src/infrastructure/filesystem/paths.rs tests, src-tauri/src/domain/security/path_containment.rs tests, CHANGELOG.md
Async tests verify directory creation, idempotency, refusal on traversal and outside-root candidates, and ensure no writes occur on validation failure. CHANGELOG updated to document changes and added components.

Sequence Diagram

sequenceDiagram
    participant Caller as Caller
    participant ENS as ensure_dirs
    participant PC as path_containment
    participant FS as Filesystem

    Caller->>ENS: ensure_dirs(cfg)
    ENS->>ENS: derive project_root & global_root
    ENS->>ENS: build required_dirs list
    loop each target
        ENS->>PC: has_traversal(path)?
        PC-->>ENS: bool
        alt traversal found
            ENS-->>Caller: AppError::Security
        else
            ENS->>PC: is_within(path, project_root/global_root)?
            PC-->>ENS: bool
            alt within allowed root
                ENS->>FS: tokio::fs::create_dir_all(path)
                FS-->>ENS: Result
            else outside roots
                ENS-->>Caller: AppError::Security
            end
        end
    end
    ENS-->>Caller: Result<(), AppError>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through paths both near and far,
Found dots and double-dots that mar.
I sniffed the roots, kept writes in line,
Now bootstraps bloom where fences sign.
A thump, a twitch — safe folders shine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(infra): implement filesystem bootstrap with domain path-containment primitives' accurately and specifically summarizes the main changes: adding filesystem bootstrap functionality (ensure_dirs) with domain-level path-containment helpers, matching the primary objectives of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/task-T-104-ensure-dirs

Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 4, 2026

Merging this PR will not alter performance

✅ 7 untouched benchmarks


Comparing feat/task-T-104-ensure-dirs (1cfd0b2) with main (ccb4870)

Open in CodSpeed

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/src/infrastructure/filesystem/paths.rs`:
- Around line 30-33: The loop currently interleaves validation and creation
causing partial side effects; first collect all directories from
required_dirs(cfg, &project_forgent), run ensure_under_allowed_root for each
(using the same project_forgent and global_root) and fail early if any
validation errors occur, and only after all validations succeed iterate and call
tokio::fs::create_dir_all for each dir; update the code around the
required_dirs/ensure_under_allowed_root/create_dir_all usage to implement this
two‑phase validate-then-create flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c29238ab-f8dd-4689-99c5-e1d4e662b3da

📥 Commits

Reviewing files that changed from the base of the PR and between ccb4870 and ae8286f.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src-tauri/src/domain/security/path_containment.rs
  • src-tauri/src/infrastructure/filesystem/mod.rs
  • src-tauri/src/infrastructure/filesystem/paths.rs

Comment thread src-tauri/src/infrastructure/filesystem/paths.rs Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

Addresses CodeRabbit review on PR #20. The previous loop interleaved
ensure_under_allowed_root with tokio::fs::create_dir_all, so a malformed
cfg.prompts_overrides_dir (outside both allowed roots) could create the
project's .claude/forgent subtree before failing on the prompts loop.

First iteration runs containment validation over all 12 candidates;
second iteration calls create_dir_all only after every path passes.
Strengthened ensure_dirs_refuses_dir_outside_two_allowed_roots with
negative assertions on <project>/.claude and <home>/.forgent
non-existence after a validation failure.
@mpiton mpiton merged commit bdc1af4 into main May 4, 2026
16 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.

1 participant