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
> **🔧 security-fix-agent**
Closes#62
## Security Fix
### Summary
The CONNECT proxy accepted hostnames from clients and connected to whatever IP they resolved to, with no validation against internal address ranges. While the OPA policy is default-deny, a misconfigured or overly permissive policy could allow SSRF to cloud metadata (169.254.169.254), localhost, or RFC1918 services. This fix adds DNS resolution before connecting and rejects any host that resolves to an internal IP.
### Severity Assessment
- **Impact:** Medium — if exploited, could reach cloud metadata (IAM creds), cluster-internal services, or host-local services
- **Exploitability:** Very low — requires OPA policy misconfiguration or DNS rebinding attack
- **Affected components:** `crates/navigator-sandbox/src/proxy.rs` — `handle_tcp_connection`
### Changes Made
- `crates/navigator-sandbox/src/proxy.rs`: Added `is_internal_ip()` helper that checks IPv4 loopback/private/link-local, IPv6 loopback/link-local, and IPv4-mapped IPv6. Added `resolve_and_reject_internal()` that resolves DNS and rejects internal IPs. Inserted check between OPA allow and `TcpStream::connect`, with control plane endpoints exempt.
- `architecture/security-policy.md`: Added SSRF Protection section with blocked ranges table and flow diagram
- `architecture/sandbox.md`: Updated proxy connection flow diagram and added SSRF protection subsection
- `architecture/README.md`: Added internal IP rejection step to proxy description
### Tests Added
- **Unit:** 17 tests in `proxy::tests` — covers IPv4 loopback/private/link-local, IPv6 loopback/link-local, IPv4-mapped IPv6, public IPs, DNS resolution of localhost/127.0.0.1/169.254.169.254, and DNS failure handling
- **Integration/E2E:** N/A — the proxy runs inside a Linux network namespace; unit tests for IP checking and DNS resolution cover the security boundary
### Documentation Updated
- `architecture/security-policy.md`: New SSRF Protection section with blocked IP ranges and Mermaid flowchart
- `architecture/sandbox.md`: Updated proxy flow diagram and added SSRF protection subsection
- `architecture/README.md`: Added step 4 to proxy description
### Verification
All 85 sandbox tests pass including 17 new proxy SSRF tests. Pre-commit (fmt, clippy, full test suite) passes clean with zero warnings.
## Summary
- Add `GetSandboxProviderEnvironment` gRPC endpoint so the sandbox supervisor can fetch provider credentials at runtime instead of embedding them in the pod spec
- Sandbox supervisor (`navigator-sandbox`) fetches credentials on startup and injects them as environment variables into both entrypoint processes and SSH shell sessions via `Command::env()`
- Remove credential injection from sandbox creation-time pod spec in `navigator-server`
- Update `architecture/providers.md` to document the full runtime credential injection flow, discovery engine details, trait definitions, and end-to-end diagram
## Changes
- **proto**: Add `GetSandboxProviderEnvironment` RPC and request/response messages, add `providers` field to `SandboxSpec`
- **navigator-server**: Implement `GetSandboxProviderEnvironment` handler and `resolve_provider_environment()` with env var key validation and first-wins dedup; remove old creation-time credential injection from `sandbox/mod.rs`
- **navigator-sandbox**: Fetch provider env via gRPC on startup (`grpc_client.rs`), thread `provider_env` HashMap through to `process.rs` (entrypoint) and `ssh.rs` (shell sessions), inject via `cmd.env()`
- **navigator-cli**: Adjust sandbox create flow to set provider names in `SandboxSpec.providers`
- **e2e**: Add `test_sandbox_providers.py` end-to-end tests
- **docs**: Rewrite `architecture/providers.md` with implementation details
## Test Plan
- Unit tests for `resolve_provider_environment` in `grpc.rs`
- Integration tests updated in `provider_commands_integration.rs`, `mtls_integration.rs`, `multiplex_integration.rs`, `multiplex_tls_integration.rs`
- New e2e test suite `test_sandbox_providers.py`
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