* feat(sandbox): allow egress to private IP space via allowed_ips policy field
Add an allowed_ips field to NetworkEndpoint that accepts CIDR notation and
exact IPs, enabling sandboxes to reach private IP space under policy control
while maintaining SSRF protections for loopback and link-local addresses.
Three modes are supported:
- Default (no allowed_ips): all private IPs blocked (existing behavior)
- Host + allowlist: domain must resolve to an IP in allowed_ips
- Hostless allowlist (no host): any domain allowed if resolved IP matches
Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
> **🏗️ build-from-issue-agent**
Closes#71
## Summary
Add dynamic shell completion support using `clap_complete`'s `CompleteEnv`, plus a `navigator completions <shell>` subcommand (rustup-style) that outputs completion scripts to stdout with detailed per-shell setup instructions in `--help`.
Completions are dynamic — the shell calls back into the `navigator` binary on each tab-press, so they are always in sync with the installed version. This also lays the groundwork for future runtime completers (e.g., listing sandbox names on tab, tracked in #83).
## Changes Made
- `Cargo.toml`: Added `clap_complete = { version = "4.5", features = ["unstable-dynamic"] }` to workspace dependencies
- `crates/navigator-cli/Cargo.toml`: Added `clap_complete` dependency
- `crates/navigator-cli/src/main.rs`:
- Added `CompleteEnv::with_factory(Cli::command).complete()` at the top of `main()` (before TLS/argument parsing)
- Added `Completions { shell }` subcommand that re-invokes the binary with `COMPLETE=<shell>` and pipes the output
- Added `CompletionShell` enum (bash, fish, zsh, powershell)
- Added `COMPLETIONS_HELP` with per-shell setup instructions (similar to `rustup completions --help`)
- Added 3 unit tests
- `CONTRIBUTING.md`: Added shell completions section documenting `nav` wrapper setup
## Usage
```bash
# Generate completions
navigator completions fish > ~/.config/fish/completions/navigator.fish
navigator completions bash > ~/.local/share/bash-completion/completions/navigator
navigator completions zsh > ~/.zfunc/_navigator
navigator completions powershell >> $PROFILE
# See full per-shell instructions
navigator completions --help
# For the nav wrapper (rewrite registration target)
navigator completions fish | sed 's/--command navigator/--command nav/' > ~/.config/fish/completions/nav.fish
```
## Design Decision: Why stdout-only (no auto-install)
We researched how 12 popular CLI tools handle completion installation:
| Tool | Command | Writes files? | Modifies RC? |
|------|---------|---------------|-------------|
| gh, glab, kubectl, docker, rustup, mise, starship | `completion <shell>` | stdout only | No |
| atuin | `gen-completions --shell <shell>` | stdout (`--out-dir` option) | No |
| gcloud | `install.sh` | Yes | **Yes** |
| Amazon Q | `q integrations install` | Yes | **Yes** |
**10 of 12 tools** follow the same pattern: output to stdout, never modify RC files, explicit shell selection, clear `--help` instructions. Only gcloud and Amazon Q modify RC files (and both have been criticized for it). Our approach matches the industry consensus.
## Deviations from Plan
- The original issue proposed a static `completions <shell>` subcommand using `clap_complete::generate()`. After discussion, we switched to dynamic `CompleteEnv` which is always up-to-date and enables future runtime completers.
- Added a `navigator completions <shell>` subcommand (not in original plan) for discoverability — it wraps the `COMPLETE` env var mechanism with a familiar CLI UX.
## Tests Added
- **Unit:** `cli_debug_assert` — validates CLI structure; `completions_engine_returns_candidates` — smoke test confirming the completion engine returns subcommand candidates; `completions_subcommand_appears_in_candidates` — verifies `completions` appears in tab-completion results
- **Integration:** N/A
- **E2E:** N/A
## Verification
- [x] All tests passing
- [x] Pre-commit checks passing (clippy warnings are pre-existing, not from this change)
- [x] CONTRIBUTING.md updated with nav wrapper completion setup
## Summary
- Track last deployed local changes and only redeploy new deltas in auto mode.
- Add per-component fingerprints (server/sandbox/helm) persisted to .cache/cluster-deploy-fast.state.
- Update docs for the new incremental deploy behavior.
```
mise run cluster
....
________________________________________________________
Executed in 75.21 secs fish external
usr time 578.03 millis 0.25 millis 577.78 millis
sys time 364.45 millis 1.22 millis 363.22 millis
```
with a change to `navigator-sandbox` (triggers rust recompile)
```
⟩ time mise run cluster:deploy
[cluster:deploy] $ build/scripts/cluster-deploy-fast.sh
Change detection took 0s
Fast deploy plan:
build server: 0
build sandbox: 1
helm upgrade: 1
...
________________________________________________________
Executed in 25.88 secs fish external
usr time 618.91 millis 0.24 millis 618.67 millis
sys time 557.71 millis 1.54 millis 556.18 millis
```
(most time is spent in docker build)
## Test Plan
- mise run pre-commit
## Summary
- Rewrites the README quickstart with accurate, tested setup steps (Docker prerequisite, venv/uv usage)
- Moves the "developing from source" instructions into CONTRIBUTING.md
- Removes the manual cluster deploy step from quickstart (cluster bootstraps automatically on sandbox create)
- Fixes empty bash array expansion in Docker build scripts that broke on macOS default bash 3.2 (`set -u` treats `"${EMPTY_ARRAY[@]}"` as unbound)
## Changes
- **README.md**: Replace placeholder quickstart with working install and sandbox create steps; remove manual cluster deploy step since it happens automatically
- **CONTRIBUTING.md**: Add "developing from source" section with mise setup instructions
- **build/scripts/docker-build-cluster.sh**: Use `${arr[@]+"${arr[@]}"}` pattern for safe empty array expansion
- **build/scripts/docker-build-component.sh**: Same fix
Closes#48, #52
## Summary
- Replace the envoy-gateway-based TLS setup with inline PKI generation during cluster bootstrap, generating CA, server, and client certificates directly in the `navigator-bootstrap` crate
- Remove all envoy gateway Helm templates (`gateway.yaml`, `gatewayclass.yaml`, `grpcroute.yaml`, PKI job, traffic policies) and the `Dockerfile.pki-job`
- Add native mTLS support to the navigator server with `tokio-rustls`, mounting client TLS certs as volumes into sandbox pods
- Update cluster entrypoint, healthcheck, and deploy scripts to work with the new direct-TLS architecture
- Add TLS security e2e test and fix formatting/clippy warnings
## Test Plan
- All unit tests pass (`cargo test --workspace`)
- Clippy clean (`cargo clippy --workspace --all-targets`)
- Format clean (`cargo fmt --all -- --check`)
- Python tests pass (`uv run pytest python/`)
- Full `mise run pre-commit` passes
Closes#44
## Summary
- Add `nav sandbox image push --dockerfile <path>` command to build and push custom container images into the cluster
- Fix sandbox Docker build failure caused by OpenClaw installer TTY access
- Fix sandbox proxy crash when network namespace is unavailable (fall back to loopback)
- Add e2e test for the full custom image build and sandbox creation flow
- Rename and rewrite BYOC architecture doc to reflect current state
## Changes
### `navigator-bootstrap`
- New `build` module (`src/build.rs`) with `build_and_push_image()` public API
- Builds via bollard `Docker::build_image()`, streams output to caller
- `.dockerignore` support with glob matching, 8 unit tests
### `navigator-cli`
- Added `SandboxImageCommands::Push` subcommand with `--dockerfile`, `--tag`, `--context`, `--build-arg` flags
- Added `test:e2e:custom-image` mise task
### `navigator-sandbox`
- Proxy now falls back to `127.0.0.1:3128` when no network namespace or explicit bind address is available, instead of erroring out
### Docker / Infra
- Sandbox Dockerfile: replaced OpenClaw native installer (`curl | bash`) with `npm install -g openclaw` to avoid `/dev/tty` failures in Docker build
### Documentation
- Renamed `architecture/sandbox-byoc.md` to `sandbox-custom-containers.md`
- Rewrote doc to describe current behavior (CLI usage, push pipeline, supervisor behavior, design decisions) instead of listing implementation changes
### Tests
- Added `e2e/bash/test_sandbox_custom_image.sh` covering image push + sandbox create with custom image
## Summary
- add a Docker-based macOS arm64 wheel build path so darwin wheels can be produced from Linux CI hosts
- wire macOS wheel builds into release publish workflows (`publish:main` and `publish:tag`) alongside existing Linux wheel builds
- mirror `crazymax/osxcross:latest` into `$CI_REGISTRY_IMAGE/third_party/osxcross:latest` and use that mirrored image in CI publish jobs
## Scope Notes
- this MR focuses on shipping reliable macOS wheel build support first
- temporary MR-only smoke-job wiring was removed after validation so the branch reflects final intended CI behavior
- broader cache optimization work is tracked separately in issue #43
## Test Plan
- run `mise run pre-commit`
- validate `.gitlab-ci.yml` with `uv run --with pyyaml python -c 'import yaml, pathlib; yaml.safe_load(pathlib.Path(".gitlab-ci.yml").read_text())'`
## What changed
- Added tag-driven release flow in GitLab CI:
- new `release` stage with tag-only jobs
- `publish_tag_artifacts` publishes Docker + Python artifacts on `vX.Y.Z` tags
- `create_release_notes` generates release notes from conventional commits via `git-cliff` and creates a GitLab release via `glab`
- Updated main-branch image publishing to version-aware tagging:
- `publish_ecr_images` now runs `mise run publish:main`
- main publishes `:dev`, `:latest`, and a versioned dev tag
- Added release-oriented mise tasks:
- `publish:main`
- `publish:tag`
- `python:publish:macos` (manual macOS arm64 wheel publish)
- Switched Linux Python wheel builds to buildx:
- added `deploy/docker/Dockerfile.python-wheels`
- replaced old per-arch docker-run tasks with `python:build:multiarch`
- Added macOS arm64 wheel build path for local publishing:
- `python:build:macos` builds `aarch64-apple-darwin`
- intended to run locally on macOS after tag CI finishes
- Made Docker multiarch publish script tag-flexible:
- `TAG_LATEST` is no longer hardcoded in ECR mode
- supports `EXTRA_DOCKER_TAGS`
- applies extra tags to sandbox/server/pki-job/cluster images
- Moved release tooling to `build/scripts/release.py` and updated all mise references
- Removed obsolete Docker Artifactory env vars from `mise.toml`
## Release behavior
- **Main branch CI**
- Docker: `:dev`, `:latest`, and versioned dev tag
- **Tag CI (`vX.Y.Z`)**
- Docker: `:X.Y.Z` only (no `:latest`)
- Python: Linux wheels published from CI
- GitLab release notes created from conventional commits
- **Manual macOS step (after tagging)**
1. Checkout the tag locally on macOS
2. Run `mise run python:publish:macos`
## Validation
- `mise run python:lint`
- `mise run version:print`
- `mise run python:build:macos`
- `uv run python build/scripts/release.py --help`
Closes#13
## Summary
- add Python sandbox execution APIs for command and callable workflows
- consolidate sandbox policy fixtures and expand e2e test coverage for policy and Python exec paths
- update CI/build config and images for sandbox e2e execution dependencies
## Test Plan
- mise run pre-commit