This document provides comprehensive information for AI agents working in the EncFS codebase. It covers commands, patterns, conventions, gotchas, and project-specific context.
EncFS is an encrypted virtual filesystem that runs in userspace using FUSE. This is a Rust port of the original C++ implementation, aiming for compatibility with existing EncFS filesystems while providing memory safety and modern code practices.
- Language: Rust (Edition 2024)
- Primary Goal: Read/write compatibility with legacy EncFS filesystems
- Status: Alpha (v2.0.0-alpha.3) - functional for read/write but still maturing
- Encrypts individual files (not block devices)
- Uses FUSE for filesystem operations
- Supports multiple config formats (V4, V5, V6)
- OpenSSL for legacy cryptographic operations
- Modern cryptography (AES-GCM-SIV, Argon2) for new setups
- Internationalization support
# Build debug binaries
cargo build
# or using task runner
task build
# Build release binaries
cargo build --release
# or
task build-release
# Clean build artifacts
cargo clean
# or
task clean# Run all tests (unit + integration, excluding live mount tests)
cargo test
# or
task test
# Run live mount tests (requires FUSE, Linux, and ENCFS_LIVE_TESTS=1)
ENCFS_LIVE_TESTS=1 cargo test --test live_mount -- --ignored --test-threads=1
# or
task test-live
# Run specific test
cargo test test_name
# Run tests with output
cargo test -- --nocaptureImportant: Live mount tests (live_mount.rs) are marked with #[ignore] and require:
ENCFS_LIVE_TESTS=1environment variable- FUSE kernel module loaded (
sudo modprobe fuse) - Single-threaded execution (
--test-threads=1) - Linux or FreeBSD (macOS support via fuse-t)
# Format code
cargo fmt
# or
task fmt
# Check formatting (CI-friendly, doesn't modify files)
cargo fmt -- --check
# or
task fmt-check
# Run clippy lints (fails on warnings in CI)
cargo clippy --all-targets --all-features -- -D warnings
# or
task clippy# Mount an encrypted filesystem
./target/debug/encfs /path/to/encrypted /path/to/mountpoint
# With options
./target/debug/encfs -f -v /path/to/encrypted /path/to/mountpoint
# Control utility
./target/debug/encfsctl info /path/to/encrypted
./target/debug/encfsctl decode /path/to/encrypted encrypted_filename
./target/debug/encfsctl cat /path/to/encrypted encrypted_filename# Install to ~/.cargo/bin
cargo install --path .encfs/
├── src/ # Rust source code
│ ├── main.rs # Main encfs binary (FUSE mount)
│ ├── encfsctl.rs # Control utility binary
│ ├── lib.rs # Library entry point
│ ├── config.rs # Config file parsing (V4/V5/V6)
│ ├── config_binary.rs # Binary config format parser
│ ├── constants.rs # Global constants
│ ├── fs.rs # FUSE filesystem implementation
│ └── crypto/ # Cryptographic operations
│ ├── mod.rs # Crypto module exports
│ ├── ssl.rs # OpenSSL cipher wrapper
│ └── file.rs # File encryption/decryption
├── tests/ # Integration tests
│ ├── fixtures/ # Test data (encrypted files)
│ ├── live_mount.rs # Live FUSE mount tests (ignored by default)
│ └── *.rs # Other integration tests
├── locales/ # i18n translation files (YAML)
│ ├── main.yml # Main binary translations
│ ├── ctl.yml # encfsctl translations
│ └── help.yml # Help text translations
├── Cargo.toml # Rust dependencies
├── Taskfile.yml # Task runner config (alternative to make)
└── .github/workflows/ # CI configuration
-
lib.rs: Library entry point- Exports all public modules
- Initializes i18n system
- Contains integration tests
-
config.rs: Configuration file handlingEncfsConfig: Main config structConfigType: Enum for V3/V4/V5/V6 formatsInterface: Cipher/naming algorithm interface- Supports XML (V6) and binary (V4/V5) formats
- XML uses Boost Serialization format for compatibility
-
config_binary.rs: Binary config parserConfigReader: Reads V4/V5 binary configsConfigVar: Variable-length encoded values
-
crypto/namespace: Cryptographic operationsssl.rs: OpenSSL cipher wrapper (legacy modes)aead.rs/block.rs: Modern authenticated encryption (AES-GCM-SIV)file.rs: File-level encryption, handles block boundaries, MACs, headers
-
fs.rs: FUSE filesystem implementationEncFs: Main filesystem struct- Implements
FilesystemMTtrait fromfuse_mt - Path encryption/decryption with IV chaining
- File handle management
- All FUSE operations (read, write, readdir, etc.)
-
main.rs: Main encfs binary- CLI argument parsing with
clap - Password handling (prompt, stdin, extpass)
- Daemonization support
- FUSE mount setup
- CLI argument parsing with
-
encfsctl.rs: Control utility- Subcommands: info, passwd, decode, encode, cat, ls, showkey, export
- Standalone utility for inspecting/manipulating encrypted filesystems
- Types/Structs/Enums:
PascalCase(e.g.,EncfsConfig,SslCipher) - Functions/Variables:
snake_case(e.g.,decrypt_filename,block_size) - Constants:
SCREAMING_SNAKE_CASE(e.g.,DEFAULT_SALT_SIZE) - Modules:
snake_case(e.g.,config_binary)
- IV: Initialization Vector (used throughout crypto code)
- MAC: Message Authentication Code
- KDF: Key Derivation Function (PBKDF2)
- Volume Key: The master key used to encrypt files (encrypted by user password)
- User Key: Key derived from user password
- File IV: Per-file initialization vector (stored in file header if
unique_ivenabled) - Path IV: IV derived from path components (used with
chained_name_iv)
- Test files:
*_test.rs(e.g.,write_test.rs) - Integration tests: Top-level in
tests/directory - Fixtures:
tests/fixtures/for test data
- Unit tests:
#[cfg(test)] mod testsat bottom of source files - Integration tests: Separate files in
tests/directory - Live mount tests:
tests/live_mount.rswith#[ignore]attribute
// Integration test with fixtures
#[test]
fn test_decrypt_filenames() -> anyhow::Result<()> {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
let config_path = root.join("encfs6-std.xml");
let config = EncfsConfig::load(&config_path)?;
// ... test logic
Ok(())
}
// Live mount test (ignored by default)
#[test]
#[ignore]
fn live_smoke_mount_unmount_standard() -> Result<()> {
require_live(); // Checks ENCFS_LIVE_TESTS env var
if !live_enabled() {
return Ok(());
}
// ... test logic
Ok(())
}tests/fixtures/encfs6-std.xml: Standard mode configtests/fixtures/encfs6-paranoia.xml: Paranoia mode configtests/fixtures/MhAO8Ckgt67m1cSrFU9HHiNT: Encrypted file (decrypts to "DESIGN.md")tests/fixtures/U,-Aj0Ha7VZMhbnuv-vx1DZu/: Encrypted directory (paranoia mode)
- GitHub Actions:
.github/workflows/ci.yml - Cirrus CI:
.cirrus.yml(FreeBSD testing) - CI runs: clippy, build, test, live_mount tests
- V6 (XML): Current format,
.encfs6.xmlfile - V5 (Binary): Legacy format,
.encfs5file - READ ONLY (save not implemented) - V4 (Binary): Older format,
.encfs4file - READ ONLY - V3: Not supported (will error)
The code must maintain compatibility with all three formats for reading existing filesystems.
Two types of IV chaining affect how paths are encrypted:
-
chained_name_iv: Each directory's IV is derived from parent's IV + encrypted name- Standard mode: enabled
- Affects path encryption/decryption order
-
external_iv_chaining: File IVs are derived from path IV- Paranoia mode: enabled
- Means file headers must be decrypted with path IV, not just 0
Critical: When decrypting files in paranoia mode, you MUST use the path IV from decrypt_path(), not 0!
Encrypted files have this structure:
[Header: 8 bytes if unique_iv] [Block 0] [Block 1] ... [Block N]
Each block (if MACs enabled):
[MAC: block_mac_bytes] [Random: block_mac_rand_bytes] [Data: remaining]
Note: block_mac_rand_bytes is not yet supported (must be 0).
- Base64 encoded (URL-safe variant without padding)
- Uses stream cipher mode
- IV is derived from HMAC of plaintext name (deterministic)
- With
chained_name_iv, also depends on parent directory's IV
- Use
anyhow::Resultfor application code - Use
libc::c_interror codes for FUSE operations - Map Rust errors to errno values in
fs.rs
- Uses
rust-i18ncrate - Translations in
locales/*.ymlfiles - Macro:
t!("key.subkey", param = value) - Locale initialized from
LANGenvironment variable - Help text functions must return
String(evaluated at runtime after locale init)
- Uses
fuse_mtcrate (multi-threaded FUSE) - Implements
FilesystemMTtrait - File handles stored in
HashMap<u64, Arc<FileHandle>> - Thread-safe via
Mutexguards - Single-threaded mode available via
-sflag
Three methods (in order of precedence):
--extpass <program>: Run external program, read stdout--stdinpass: Read from stdin- Default: Interactive prompt via
rpassword
The EncfsConfig::validate() method enforces:
plain_datamust be false (not supported)unique_ivmay be true or false (filesystem supports both)block_mac_rand_bytesmust be 0 (not implemented yet)key_sizemust be positive and multiple of 8block_sizemust be positiveblock_mac_bytesmust be 0-8- Block size must be larger than MAC overhead
- Located in
legacy/directory - Uses CMake build system
- May be removed in future
- Useful for reference but not actively maintained
- Do NOT modify legacy code unless explicitly requested
- Uses
env_loggercrate - Controlled by
RUST_LOGenvironment variable -vflag sets debug level-dflag sets debug + foreground mode
- Uses
daemonizecrate - Automatic unless
-f(foreground) or-d(debug) flag - Happens after password validation, before FUSE mount
- fuse_mt (0.6.3): Multi-threaded FUSE bindings
- openssl (0.10.75): Cryptographic operations
- clap (4.5.57): CLI argument parsing
- anyhow (1.0.101): Error handling
- serde (1.0.228): Serialization/deserialization
- quick-xml (0.39.0): XML parsing for V6 configs
- base64 (0.22.1): Base64 encoding for filenames
- rust-i18n (3): Internationalization
- log (0.4.29) + env_logger (0.11.8): Logging
- rpassword (7.4.0): Password prompts
- daemonize (0.5): Background daemon support
- libc (0.2.180): POSIX system calls
- chrono (0.4): Date/time handling
- argon2 (0.5) / aes-gcm-siv (0.11.1) / sha2 (0.10): Modern cryptography
- prost (0.14.3): Protobuf serialization support
- FUSE: libfuse-dev (Linux), fusefs-libs (FreeBSD), fuse-t (macOS)
- OpenSSL: libssl-dev
- pkg-config: For finding system libraries
Runs on: ubuntu-latest
Steps:
- Install dependencies (fuse, libfuse-dev, pkg-config, libssl-dev)
- Load fuse module
- Run clippy (fails on warnings)
- Build release
- Run tests
- Run live mount tests
Runs on: FreeBSD 15.0
Steps:
- Install Rust, FUSE, OpenSSL
- Load fusefs kernel module
- Build release
- Run tests
- Run live mount tests
Status:
allow_failures: true
- Implement in appropriate module (
src/*.rs) - Add unit tests in same file
- Add integration test in
tests/if needed - Update translations in
locales/*.ymlif user-facing - Run
cargo fmtandcargo clippy - Run
cargo test - Test manually with real encrypted filesystem
- Add a failing test that reproduces the bug
- Fix the bug
- Verify test passes
- Check for similar issues elsewhere
- Run full test suite
- Edit
Cargo.toml - Run
cargo update - Run full test suite
- Check for deprecation warnings with clippy
- Add keys to
locales/main.yml,locales/ctl.yml, orlocales/help.yml - Provide translations for en, fr, de
- Use
t!("key.subkey")macro in code - For clap help text, create helper function returning
String
See ISSUES.md for detailed analysis. Key issues:
- 64-bit IVs (should be 128-bit)
- 64-bit MACs (should be 128-bit+)
- Same key for encryption and authentication
- Stream cipher for last file block
- File holes not authenticated
- Information leakage between decryption and MAC check
Note: These are protocol-level issues that can't be fixed without breaking compatibility. The Rust port inherits these limitations.
- Rust's memory safety prevents many C++ vulnerabilities
- Use
anyhow::Resultto ensure errors are handled - Avoid
unwrap()in production code paths - Use
?operator for error propagation - Validate all config values in
EncfsConfig::validate()
- Block size affects performance (default 4096 bytes for new filesystems)
- MACs/Tags add overhead per block + performance penalty (~16 bytes for AES-GCM-SIV)
- Multi-threaded FUSE by default (use
-sfor single-threaded debugging) - File buffer size: 128 KB (
FILE_BUFFER_SIZE) - Performance over NFS is known to be poor (upstream issue)
RUST_LOG=debug ./target/debug/encfs -f /encrypted /mount./target/debug/encfs -f /encrypted /mount./target/debug/encfs -s -f /encrypted /mount./target/debug/encfsctl info /encrypted./target/debug/encfsctl decode /encrypted encrypted_filename./target/debug/encfsctl cat /encrypted encrypted_filenamelsmod | grep fuse
sudo modprobe fuse # if not loadedfusermount -u /mount # Linux
umount /mount # macOS/FreeBSD- Follow Rust standard style (enforced by
cargo fmt) - Use
rustfmt.tomlif present (currently uses defaults) - Max line length: 100 characters (Rust default)
- Use
clippyrecommendations (CI fails on warnings)
- Prefer
anyhow::ResultoverResult<T, E>in application code - Use
?operator for error propagation - Add context to errors:
.context("description")? - Log errors before returning them from FUSE operations
- Use
debug!,info!,warn!,error!macros for logging - Document public APIs with
///doc comments - Use
//for implementation comments
fn some_fuse_op(&self, path: &Path) -> ResultEntry {
let (encrypted_path, iv) = self.encrypt_path(path).map_err(|e| {
error!("Failed to encrypt path: {}", e);
e // Return errno
})?;
// ... rest of implementation
}- README.md: Project overview and status
- DESIGN.md: Technical overview of EncFS encryption
- INSTALL.md: Build and installation instructions
- Cargo.toml: Dependencies and metadata
- Taskfile.yml: Available task commands
cargo build # Build
cargo test # Test
cargo clippy # Lint
cargo fmt # Format
task test-live # Live mount testssrc/fs.rs: FUSE implementationsrc/config.rs: Config parsingsrc/crypto/ssl.rs: Cryptographytests/live_mount.rs: Integration tests
- Live tests fail: Check
ENCFS_LIVE_TESTS=1and FUSE module loaded - Build fails on OpenSSL: Install libssl-dev
- Build fails on FUSE: Install libfuse-dev
Last Updated: February 21, 2026 EncFS Version: 2.0.0-alpha.3 Rust Edition: 2024