rappct 0.13.3

Rust AppContainer / LPAC toolkit for Windows (profiles, capabilities, process launch, diagnostics).
Documentation
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

rappct is a Rust toolkit for working with Windows AppContainer (AC) and Low Privilege AppContainer (LPAC) security boundaries. It wraps Windows APIs to enable creating, managing, and launching AppContainer-aware workloads with minimal boilerplate.

**Platform**: Windows-only (non-Windows hosts return `UnsupportedPlatform`)
**MSRV**: Rust 1.90 (stable)
**Edition**: 2024

## Build & Development Commands

```bash
# Build the library
cargo build

# Build with all features
cargo build --all-features

# Run tests (requires Windows, some tests need elevation)
cargo test --all-targets --all-features

# Run a specific test
cargo test <test_name>

# Run tests for a specific module
cargo test --test windows_launch

# Lint
cargo clippy --all-targets --all-features

# Format
cargo fmt

# Run example CLI
cargo run --example acrun -- --help
```

## Local Quality Gates & Hooks (required before commit/push/merge)

Run the same checks that CI enforces locally, every time:

- `cargo fmt --all -- --check`
- `cargo clippy --all-targets --all-features -- -D warnings`
- `cargo test --all-targets` (repeat with feature sets as needed: `--features net`, `--features introspection`, or both)

Repository-provided hooks and scripts:

- Enable hooks once: `git config core.hooksPath .githooks`
- `.githooks/pre-commit` runs fmt, clippy, and tests.
- `.githooks/pre-push` runs the full local CI matrix via:
  - `scripts/ci-local.sh` (Bash) or `scripts/ci-local.ps1` (PowerShell)

Do not bypass hooks except in emergencies (`git push --no-verify`). Keeping local gates green will keep CI green.

**Note**: Some tests require elevated PowerShell when they involve loopback exemptions or ACL adjustments.

## Architecture Overview

### Core Module Structure

The crate is organized into focused modules that compose together:

1. **profile** (`src/profile.rs`): AppContainer profile lifecycle
   - Create/open/delete profiles via `AppContainerProfile::ensure()`
   - Derives package SIDs from profile names
   - Resolves folder paths and named-object paths

2. **capability** (`src/capability.rs`): Capability SID derivation
   - Maps `KnownCapability` enum to Windows capability names
   - Calls `DeriveCapabilitySidsFromName` (manually bound FFI)
   - Builder pattern via `SecurityCapabilitiesBuilder` to compose capabilities + LPAC flag
   - **Important**: LPAC capabilities are opt-in via `with_lpac_defaults()`

3. **launch** (`src/launch/mod.rs`, `src/launch/attr.rs`): Process launch in AC/LPAC context
   - Constructs `STARTUPINFOEX` with `PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES`
   - Handles stdio redirection (Inherit/Null/Pipe) with proper handle inheritance
   - Optional job object integration (memory limits, CPU caps, kill-on-close)
   - `AttributeContext` manages lifetime of SID guards, SECURITY_CAPABILITIES struct, and attribute lists
   - `launch_in_container()` returns basic `Launched` with PID
   - `launch_in_container_with_io()` returns `LaunchedIo` with stdio handles and optional `JobGuard`

4. **token** (`src/token.rs`): Token introspection
   - Queries current process token for AppContainer/LPAC status
   - Extracts package SID and capability SIDs from token

5. **acl** (`src/acl.rs`): DACL grant helpers
   - Grant filesystem or registry access to package SIDs or capability SIDs
   - Supports `File`, `Directory`, `RegistryKey` targets
   - Registry keys support `HKCU\` and `HKLM\` roots (case-insensitive)

6. **sid** (`src/sid.rs`): SID wrappers
   - `AppContainerSid` wraps SDDL strings (e.g., "S-1-15-2-...")
   - `SidAndAttributes` pairs SID SDDL with attribute flags

7. **net** (`src/net.rs`, feature-gated): Firewall loopback exemptions
   - **WARNING**: Changes global firewall state; debug-only
   - Must call `LoopbackAdd::confirm_debug_only()` before `add_loopback_exemption()`
   - Always restore with `remove_loopback_exemption()` when done

8. **diag** (`src/diag.rs`, feature-gated): Diagnostics and validation

9. **ffi** (`src/ffi/*`): crate-private FFI RAII helpers
   - `handles::Handle`, `mem::{LocalAllocGuard, CoTaskMem}`, `sid::OwnedSid`, `wstr::WideString`, `sec_caps::OwnedSecurityCapabilities`, `attr_list::AttrList`
   - Prefer these over legacy `util` guards; `src/util.rs` remains for compatibility but should not be used in new code

### Key Architectural Patterns

**Lifetime Management via Guards**: All Windows API memory (SIDs, ACLs, handles) is wrapped in RAII guards that call appropriate cleanup functions (`LocalFree`, `FreeSid`, `CloseHandle`) on drop. The `AttributeContext` struct in `launch/mod.rs` is a critical example—it holds all the SID guards and keeps them alive while `CreateProcessW` executes.

**Builder Pattern for Capabilities**: `SecurityCapabilitiesBuilder` accumulates named capabilities and LPAC flag, then calls `derive_named_capability_sids()` in `build()`. This separates the ergonomic API from the unsafe FFI.

**FFI Boundary Isolation**: Windows APIs not exposed by the `windows` crate are manually bound (e.g., `DeriveCapabilitySidsFromName`, `CreateAppContainerProfile`) in `extern "system"` blocks. All FFI calls are `unsafe` and isolated to platform-specific `#[cfg(windows)]` sections.

**Error Handling**: `AcError` enum provides context-rich variants:
- `LaunchFailed { stage, hint, source }` for launch failures
- `UnknownCapability { name, suggestion }` with optional fuzzy suggestions (when `introspection` feature enabled)
- `UnsupportedLpac` vs `UnsupportedPlatform` for OS/platform checks

**LPAC Detection**: `supports_lpac()` queries OS build via `ntdll!RtlGetVersion` (Windows 10 build 15063+). Can be overridden in tests via `RAPPCT_TEST_LPAC_STATUS` env var.

## Feature Flags

- `net`: Enable loopback exemption helpers (requires `Win32_NetworkManagement_WindowsFirewall`)
- `introspection`: Enable diagnostics and capability name suggestions (adds `strsim` dependency)
- `tracing`: Emit structured logs via `tracing` crate
- `serde`: Enable Serialize/Deserialize derives on SecurityCapabilities, AppContainerSid, and SidAndAttributes

## Testing Conventions

- Integration tests in `tests/` are prefixed by platform: `windows_*.rs` for Windows-only, `api_surface.rs` for cross-platform API checks
- Tests that modify global state (firewall, registry) should clean up in `Drop` or use `tempfile`
- Use `#[cfg_attr(not(windows), ignore)]` for Windows-only tests
- CI sets `RAPPCT_TEST_LPAC_STATUS=ok` to bypass LPAC detection on older CI images

## Important Constraints

1. **LPAC requires Windows 10 1703+ (build 15063)**: Call `supports_lpac()` before using LPAC features
2. **Security capabilities must outlive `CreateProcessW`**: `AttributeContext` ensures this via lifetimes
3. **Handle inheritance requires explicit handle list**: When using `StdioConfig::Pipe`, pass child ends in `PROC_THREAD_ATTRIBUTE_HANDLE_LIST`
4. **Registry ACL grants only support HKCU/HKLM**: Other roots return error
5. **Loopback exemptions are debug-only**: Never use in production

## Common Gotchas

- **Forgetting `with_lpac_defaults()`**: LPAC capabilities are opt-in; without them, the process won't have `registryRead` or `lpacCom`
- **Not waiting for child process**: `LaunchedIo` has a `wait()` method; dropping it without waiting may leave orphaned processes if `kill_on_job_close` is false
- **ACL grant failures on non-existent paths**: Ensure target file/directory/registry key exists before calling `grant_to_package()`
- **Mixing `&str` and `&OsStr` UTF-16 conversions**: Use `util::to_utf16()` for `&str`, `util::to_utf16_os()` for `&OsStr`
- **Custom environment blocks (Error 203)**: When passing `LaunchOptions::env`, it **completely replaces** the parent environment. Windows processes require essential system variables (SystemRoot, ComSpec, PATHEXT, TEMP, TMP) to function. Always copy these from the parent environment before adding custom variables. See `advanced_features.rs` Demo 5 for the pattern.
- **PowerShell console buffer errors in AppContainer (Error 0x5)**: PowerShell tries to access the console output buffer for formatting, which AppContainers restrict. Redirect PowerShell output to temporary files using `Out-File -FilePath`, read back with `type`, and clean up with `del`. Must grant ACL access to temp directory for the AppContainer. See `network_demo.rs` and `comprehensive_demo.rs` Demo 4 for examples.

## Debug Flags

- `RAPPCT_DEBUG_LAUNCH=1`: Print CreateProcessW failure details to stderr (no tracing subscriber required)
- `RAPPCT_TEST_LPAC_STATUS=ok|unsupported`: Override LPAC detection for tests

## External API Bindings

These Windows APIs are manually bound because they're not fully exposed in `windows-rs`:

- `Userenv.dll`: `CreateAppContainerProfile`, `DeleteAppContainerProfile`, `DeriveAppContainerSidFromAppContainerName`, `DeriveCapabilitySidsFromName`, `GetAppContainerFolderPath`, `GetAppContainerNamedObjectPath`
- `ntdll.dll`: `RtlGetVersion` (for LPAC OS version check)
- `Advapi32.dll`: `OpenProcessToken`