Skip to content

feat(truman_fs): FUSE-based filesystem visibility with Rust daemon#5

Open
conroywhitney wants to merge 17 commits into
mainfrom
feature/truman_fs
Open

feat(truman_fs): FUSE-based filesystem visibility with Rust daemon#5
conroywhitney wants to merge 17 commits into
mainfrom
feature/truman_fs

Conversation

@conroywhitney

@conroywhitney conroywhitney commented Jan 19, 2026

Copy link
Copy Markdown
Owner

Summary

TrumanFS provides whitelist-based filesystem visibility control via FUSE. Files outside the whitelist literally don't exist (404 principle - ENOENT, not EACCES).

What's included

  • TrumanFs.Whitelist module - ETS-backed whitelist with O(1) lookups

    • new/1, add/2, remove/2, add_all/2, remove_all/2, list/1, allowed?/2
    • Prefix matching: whitelist /projects allows /projects/app/lib/main.ex
    • 14 tests + 1 doctest passing
  • Rust FUSE daemon (native/truman_fused)

    • Uses fuser crate for FUSE operations
    • JSON protocol over stdin/stdout (Port, not NIF)
    • Message types defined with serde
  • Architecture decisions documented

    • Port over NIF (crash isolation)
    • JSON over ETF (audit-first logging must be human-readable)
    • macFUSE 5.1.3+ with FSKit (userspace on macOS 26, no kext)
    • Auditor in critical path (dead man's switch)

Architecture

Kernel (FUSE) → Rust daemon → JSON/stdout → Elixir Port
                                              ↓
                                          Auditor (log first!)
                                              ↓
                                          Handler (whitelist check)
                                              ↓
                                          JSON/stdin → Rust → Kernel

What's next

  • Elixir-side JSON protocol (TrumanFs.Protocol)
  • Port GenServer (TrumanFs.Port)
  • Auditor app (separate umbrella app for cross-cutting audit concern)
  • Wire FUSE callbacks in Rust
  • Integration tests with actual FUSE mount

Test plan

  • mix test - Whitelist module tests pass
  • cargo test - Rust protocol tests pass
  • Manual FUSE mount test (requires macFUSE installed)

🤖 Generated with Claude Code

conroywhitney and others added 6 commits January 18, 2026 21:45
Spec-driven development for the FUSE visibility layer:
- proposal.md: why FUSE, platform scope (macOS first)
- specs/whitelist: 7 requirements for ETS-backed path whitelist
- specs/fuse-visibility: 7 requirements for 404 principle
- design.md: 6 key decisions (FUSE, ETS, Rust NIF, etc.)
- tasks.md: 35 implementation tasks across 7 groups

Also updates project.md with full Reify context and
adopts "playground" terminology per established principle.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TDD implementation of core whitelist functionality:
- new/1 creates ETS-backed whitelist with initial paths
- allowed?/2 checks path with prefix matching
- Child paths automatically allowed under whitelisted directories
- Rejects paths that share prefix but aren't true children

5 tests, all passing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TDD implementation of add/remove operations:
- add/2 inserts path into ETS (idempotent)
- remove/2 deletes path from ETS (idempotent)

9 tests, all passing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TDD implementation:
- add_all/2 adds multiple paths atomically
- remove_all/2 removes multiple paths atomically
- list/1 returns all whitelisted paths

Reordered functions to satisfy Credo (callees after callers).

14 tests, all passing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@conroywhitney conroywhitney mentioned this pull request Jan 19, 2026
Architecture decisions:
- Port over NIF for crash isolation
- JSON over ETF for audit-first logging (human-readable logs)
- macFUSE 5.1.3+ with FSKit backend (userspace, no kext on macOS 26)
- Auditor in critical path (log first, parse second)

Added:
- native/truman_fused: Rust FUSE daemon skeleton with fuser + serde_json
- protocol.rs: JSON message types with tests
- Updated READMEs with architecture diagrams and API docs
- Updated design.md with Decisions 4-7
@conroywhitney conroywhitney changed the title feat: truman_fs feat(truman_fs): FUSE-based filesystem visibility with Rust daemon Jan 19, 2026
Port path validation logic from TrumanShell's Sandbox module,
renamed to Playground to match TrumanFS terminology ("playground,
not sandbox").

Uses Elixir's built-in Path.safe_relative/2 to protect against:
- Directory traversal attacks (../ sequences)
- Absolute paths outside playground
- Prefix confusion (/playground2 vs /playground)

Includes comprehensive tests covering:
- Basic path validation
- Traversal attacks
- Symlink handling
- Unicode paths
- Edge cases (empty, root, current directory)

All path operations should go through this module to ensure
consistent security behavior across the codebase.
SECURITY: Fixes path traversal vulnerability in allowed?/2.

Before: normalize_path/1 only trimmed trailing slashes
After: normalize_path/1 uses Path.expand() to resolve .. sequences

- Whitelist now delegates boundary checks to Playground module
- Updated moduledoc to document actual O(n) complexity
- Added comprehensive security tests for path traversal attacks
- Tests cover: basic traversal, nested traversal, unicode paths

Attack vector that is now blocked:
  Whitelist: /home/user/projects
  Attack: /home/user/projects/../secrets/file.txt
  Result: Rejected (was: Allowed)
- new/1 and add_all/2 now use single :ets.insert/2 call (atomic)
- remove_all/2 still sequential (ETS limitation, documented)
- Added delete/1 for ETS table cleanup (prevents leaks in tests)
- Updated docs to note atomicity guarantees
Update main.rs doc comments to correctly describe JSON protocol.
Added note explaining audit-first logging rationale.
Comprehensive protocol.rs update:

- Add base64 encoding for binary data fields (Vec<u8>)
  - Request::Write.data is now base64-encoded in JSON
  - Response::Ok.data is now base64-encoded in JSON
  - Ensures human-readable audit logs

- Add MAX_MESSAGE_SIZE bounds check in recv()
  - 16 MiB limit prevents memory exhaustion
  - Protects against MongoBleed-style attacks (CVE-2025-14847)

- Add comprehensive inline documentation
  - Wire format diagram
  - Design rationale (audit-first logging)
  - Doc comments on all types and fields

- Update dependencies
  - Add base64 = "0.22" for binary encoding
  - Keep fuser = "0.15" (0.16 has macOS build regression)
  - Commit Cargo.lock for reproducible builds

Tests added for base64 roundtrip serialization.
- Remove hello/0 boilerplate function and test
- Add async: true to test files for parallel execution
- Update TrumanFs moduledoc with actual module descriptions
- Mark completed tasks from this PR:
  - Whitelist: delete/1, path traversal fix, Playground module
  - Protocol: base64, bounds check, inline docs
  - Documentation: module docs, README

- Add future work notes from LLM review:
  - fuser 0.16 upgrade pending (macOS fix in PR)
  - FUSE integration tests (blocked on daemon impl)
  - GenServer wrapper consideration
  - O(n) optimization ideas
  - Persistent config notes

- Fix ETF → JSON in section 5.2
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