# v0.9.0

## SSH agent forwarding, file-level masking, status bar indicators, nested-mount fix

This release lands several security-focused additions (mask sensitive files, ssh-agent forwarding, mount-order fix for rw-under-ro), a visible status bar refresh, and a batch of community PRs that improve lockdown correctness and packaging portability.

### Feature: `--mask` to hide specific files from the agent (#26)

AI agents routinely read every file in the project directory, which means `.env`, `credentials.json`, and similar files end up visible to whatever runs inside the sandbox. The old options were to move secrets outside the repo (breaks `dotenv`, docker-compose, CI helpers) or use `--lockdown` (too blunt — kills write access entirely).

`--mask PATH` replaces the target with an empty file (or empty tmpfs if the path is a directory) inside the sandbox. Relative paths resolve against the project directory, so `--mask .env` works from the project root. The flag is repeatable and can be persisted in `.ai-jail`:

```bash
ai-jail --mask .env --mask .env.local claude
```

```toml
mask = [".env", ".env.local", "credentials.json"]
```

Mechanism: a zero-byte tempfile is created at sandbox prepare time (cleaned up on drop, same pattern as the private hosts file) and bind-mounted read-only over each target path, after the project directory. On macOS, seatbelt also gets `(deny file-read* ...)` rules per mask path. Missing paths are skipped with a warning.

Thanks to @lucasrcezimbra for the feature request.

### Feature: `--ssh` for ssh-agent forwarding + `~/.ssh` read-only

`--ssh` exempts `~/.ssh` from the built-in deny list and forwards the host's `SSH_AUTH_SOCK` into the sandbox. The agent socket is bind-mounted at its original path and the env var is passed through (non-lockdown only), so `ssh`, `git push`, and anything else that talks to the agent works without exposing raw private key material to the sandbox beyond what sits on disk under `~/.ssh`.

```bash
ai-jail --ssh claude
```

```toml
ssh = true
```

An additional tmpfs is mounted over `/etc/ssh/ssh_config.d/` when `--ssh` is active. Without it, bwrap's user namespace remaps root-owned config files inside the directory to `nobody`, and ssh's strict owner/permissions check then rejects them (`Bad owner or permissions on /etc/ssh/ssh_config.d/...`). The tmpfs bypasses the check — the systemd ssh-proxy include isn't needed inside a sandbox anyway.

### Feature: `--pictures` for read-only `~/Pictures` passthrough

Mounts `~/Pictures` read-only inside the sandbox when enabled, for image-analysis workflows and similar tasks. Default off.

```bash
ai-jail --pictures claude
```

```toml
pictures = true
```

### Fix: rw-children now correctly override ro-parents

`--map ~/Projects --rw-map ~/Projects/ai-jail` previously made everything read-only. The mount builder pushed `--rw-map` first and `--map` second, and in bwrap later mounts cover earlier ones — so the ro parent landed on top of the rw child.

Fixed by applying ro-maps first and rw-maps after, so a rw-subdirectory overlays its ro-parent as expected. Landlock was already correct (hierarchical rules use the most-specific match), so no change there.

### Feature: `--no-save-config` for ephemeral runs (#23)

`--save-config` / `--no-save-config` (and the `no_save_config` config field) control whether `ai-jail` auto-writes `.ai-jail` after a run. Useful for CI, quick experimentation, and sampling different flag combinations without polluting the project config.

```bash
ai-jail --no-save-config claude
```

`--init` still writes unconditionally, and combining `--init` with `--no-save-config` now errors out with a clear message.

Thanks to @lucasrcezimbra for the contribution.

### Feature: stored project command preserved across agent switches (#20)

`ai-jail claude` followed by `ai-jail codex` previously rewrote the project's stored default from `claude` to `codex`, flipping it under multi-agent users who run different agents against the same project. The stored command is now preserved when a different CLI command is passed, and only updated on first run (no stored command yet) or explicitly via `ai-jail --init <command>`.

Thanks to @lucasrcezimbra for the report.

### Fix: `--lockdown` now always enforces `--new-session` (#30, PR #31)

The README promised `--new-session` was always on in lockdown, but `should_use_new_session()` only looked at stdin TTY state and the status bar — so `ai-jail --lockdown bash` from an interactive terminal produced bwrap args without the flag. Fixed by short-circuiting the check when lockdown is enabled.

Thanks to @pasqualtroncone for the precise root-cause analysis and fix.

### Status bar refresh

- **Pastel is now the default.** New users get a random soft pastel palette on every launch instead of the plain dark theme. `--status-bar=dark` or `--status-bar=light` to opt out.
- **Feature indicators** (right side of the bar): 🔑 when `--ssh` is active, 🖼 when `--pictures` is active, `ro:N` / `rw:N` when any extra maps are configured. Separated by `|`.
- **Palette seeding improved.** The old `subsec_nanos() % 8` tended to pick similar palettes across launches because the nanosecond range at setup time was narrow; now seeded with `(seconds ^ nanos) % N`, which gives a much better distribution between sessions.
- **⚿ icon removed** from the right section — leaves just `ai-jail <version>`, cleaner on narrow terminals.
- **Resize blank-flash on maximize fixed** (v0.8.1). Primary screen resize no longer clears the entire terminal; it preserves visible content, repositions the scroll region and status bar, and lets the child repaint after SIGWINCH. Alt screen keeps the full clear + repaint (needed for vt100 state_diff correctness).
- **Codex resize redraw** — when running `codex` through the PTY proxy, ai-jail now injects `ctrl-shift-l` (collapsing to `ctrl-l` at the wire level) a short debounce window after resize to force a repaint. Configurable via `resize_redraw_key` in `$HOME/.ai-jail` (`ctrl-l`, `ctrl-shift-l`, `disabled`, or a custom ctrl-letter).
- **README guidance** clarifies the status bar's purpose (keeping project/command visible when you run multiple agents in parallel terminals) and recommends `--no-status-bar` inside tmux/zellij to avoid nested-PTY conflicts.

### Bootstrap: `rg` (ripgrep) added to Claude Code permissions (PR #25)

AI coding agents rely heavily on ripgrep for codebase search. The generated Claude Code permission profile now includes `rg` out of the box so users don't have to edit the generated config to unblock it.

Thanks to @NathanRibeiroC for the contribution.

### Test portability for clean-chroot builders (PRs #27, #28)

Two portability fixes discovered while packaging ai-jail for the AUR:

- **`dry_run_contains_isolation_flags`** asserted `--new-session` unconditionally, but the flag is conditional on stdin-TTY state. The test passed in developer shells (where `cargo test` redirects stdin) and failed inside `systemd-nspawn` (where stdin is a pseudo-TTY). Fixed to match `should_use_new_session()` at runtime, same pattern as `new_session_when_not_interactive`.
- **`bwrap_available()` probe** was missing `--proc /proc`, so it reported success inside `nspawn` (where `unshare` works but `mount(proc, ...)` is denied). All 15 integration tests then tried to launch the real sandbox and failed with `Can't mount proc on /newroot/proc: Operation not permitted`. Now the probe matches the real sandbox's requirements, so tests skip gracefully in environments that can't run the full jail.

Thanks to @pasqualtroncone for both fixes and for packaging ai-jail on the AUR (`ai-jail`, `ai-jail-bin`).

### Backward compatibility

All new fields preserve `.ai-jail` compatibility per the project's invariant: `ssh`, `pictures`, `no_save_config`, `mask`, and `resize_redraw_key` all use `#[serde(default)]`, so old configs parse unchanged. The `v0.8.0` config format has a fresh regression test alongside existing ones for v0.1.0 through v0.7.0.

No CLI flags were renamed or removed. No behavior changes for users who don't use the new flags.

### Closed issues

- #20 — `ai-jail codex` after `ai-jail claude` rewrites stored project command
- #24 — Virtual terminal does not support resizing / masking passwords (addressed via `--no-status-bar` guidance for nested multiplexers + resize fix)
- #26 — Ability to block or mask sensitive config files (`.env`) from AI agents
- #29 — AUR packages available (acknowledged; maintainer @pasqualtroncone)
- #30 — `--lockdown` does not enforce `--new-session`
