PR #91 added a string-equality registered-vs-spawned root check, which doesn't
catch `..`, symlinks, or a root pointing outside the jails tree. Add a real
containment guard in colibri-vault::provision, the layer that writes the .env:
- Before create_dir_all, canonicalize the target (resolving `..`/symlinks) and
assert it is STRICTLY under the allowed jail-root base; refuse otherwise.
Running before create_dir_all means a traversal/symlink target can't even
create a directory outside the tree, let alone an .env.
- Allowed base defaults to /usr/local/bastille/jails (FreeBSD/Bastille),
overridable via COLIBRI_JAIL_ROOT_BASE for Linux/Docker volume roots.
- Fail-closed: returns VaultError::TargetEscapesRoot; the daemon spawn hook
already treats provision errors as fail-soft (no .env written).
- Tests: child accepted; base-itself / nonexistent / `..`-escape / symlink-escape
all refused (no tempfile dep — uses std temp_dir).
Acceptance (#92): a target with `..`, a symlink, or resolving outside the jail
root is refused, no .env written. fmt + clippy --all-targets clean;
cargo test --workspace 230 passed / 0 failed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>