Commit Graph
98 Commits
Author SHA1 Message Date
Drew NewberryandPiotr Mlocek 83af7a245b feat(sandbox): inject host gateway hostAliases into sandbox pods (#306)
* feat(sandbox): inject host gateway hostAliases into sandbox pods

Sandbox pods running in the k3s cluster cannot resolve host.docker.internal
by default, preventing them from reaching services on the Docker host.

Detect the host gateway IP (default route) in the cluster entrypoint,
thread it through the Helm chart to the gateway server, and inject
hostAliases entries (host.docker.internal, host.openshell.internal)
into every sandbox pod spec. The injection is conditional -- when the
IP is empty (non-Docker deployments), no hostAliases are added.

---------

Co-authored-by: Piotr Mlocek <pmlocek@nvidia.com>
2026-03-14 16:54:03 -07:00
Drew Newberry 4b23a7e242 feat(bootstrap): restore per-gateway Docker bridge networks (#303)
PR #281 removed the shared openshell-cluster Docker network in favor of
the default bridge. This restores custom bridge networking but makes each
gateway use its own isolated network named openshell-cluster-{name},
matching the existing container/volume naming convention.

Changes:
- Add network_name() to constants.rs for per-gateway network naming
- Add ensure_network() with retry/backoff and force_remove_network()
  parameterized by network name instead of a global constant
- Attach containers to their per-gateway network via network_mode
- Disconnect and remove the network during gateway destroy
- Wire ensure_network() into the deploy flow before ensure_volume()
- Update architecture docs to reflect per-gateway network isolation
2026-03-14 13:36:06 -07:00
Piotr Mlocek 26e540d8d6 fix(cli): show startup feedback for foreground forwards (#296) 2026-03-13 21:51:08 -07:00
Piotr Mlocek 50ea7495c1 fix(sandbox): bypass proxy for localhost traffic (#290) 2026-03-13 15:30:08 -07:00
Piotr Mlocek 72e0268028 feat(sandbox): add gpu sandbox scheduling support (#257)
* feat(sandbox): add gpu sandbox scheduling support

Allow sandbox creation to request GPU resources explicitly or infer them from GPU image names. This wires GPU intent through bootstrap, validates gateway support, and adds dedicated GPU E2E coverage for follow-up cluster testing.
2026-03-13 11:32:43 -07:00
Drew Newberry fbd93a4632 refactor: rename navigator- crate prefix to openshell- (#277) 2026-03-13 02:02:18 -07:00
Drew Newberry 7b0a243304 ci: remove sandbox docker build from publish and e2e workflows (#275) 2026-03-13 00:56:07 -07:00
Drew Newberry 89d21d7852 refactor(sandbox): sandboxes are managed as separate community images (#267) 2026-03-12 22:06:52 -07:00
Piotr Mlocek b241237bde fix(sandbox): opt Node clients into proxy env support (#269) 2026-03-12 17:43:07 -07:00
Piotr Mlocek 14e296d318 fix(cli): add --no-keep for ephemeral sandbox create cleanup (#258) 2026-03-12 17:18:39 -07:00
John T. Myers 2e4c2fcc84 fix(proxy): stream inference responses instead of buffering entire body (#261)
* fix(proxy): stream inference responses instead of buffering entire body

The inference.local proxy path called response.bytes().await which
buffered the entire upstream response before sending anything to the
client. For streaming SSE responses this inflated TTFB from sub-second
to the full generation time, causing clients with TTFB timeouts to abort.

Add a streaming proxy variant that returns response headers immediately
and forwards body chunks incrementally using HTTP chunked transfer
encoding. Non-streaming responses and mock routes continue to work
through the existing buffered path.

Closes #260

* docs: update architecture docs and example for inference streaming

* test(example): add direct NVIDIA endpoint tests via L7 TLS intercept

Expand inference example to 4 test cases: inference.local and direct
endpoint, each streaming and non-streaming. The direct path exercises
the L7 REST relay (relay_chunked) to verify it already streams
correctly. NVIDIA_API_KEY is picked up from the sandbox env when
started with --provider nvidia.
2026-03-12 10:36:41 -07:00
Drew Newberry b9d10861b6 refactor(sandbox): move secrets to supervisor placeholders (#192) 2026-03-12 10:30:46 -07:00
John T. Myers 454327d890 feat(policy): add policy recommendation plumbing (#204) (#222)
* feat(policy): add policy recommendation plumbing — denial aggregation, transport, approval pipeline, and mechanistic recommendations

Implement the infrastructure layer for automated policy recommendations (#204):

- Proto: 9 new RPCs and messages for draft policy lifecycle (submit, get, approve, reject, approve-all, edit, undo, clear, history)
- Persistence: SQLite/Postgres migrations and store methods for draft_policy_chunks and denial_summaries tables
- Server: Full gRPC handler implementations with mechanistic mapper that auto-generates NetworkPolicyRule proposals from denial summaries
- Sandbox: DenialAggregator with MPSC channel, deduplication, periodic flush to gateway via SubmitPolicyAnalysis
- CLI: 'openshell draft' subcommand with get/approve/reject/approve-all/undo/clear/history operations
- TUI: Draft recommendations panel accessible from sandbox policy view
- Docs: Architecture documentation in architecture/policy-advisor.md

* feat(policy): add L7-aware mechanistic mapper and policy advisor CTF example

Add L7 rule generation to mechanistic mapper (build_l7_rules,
generalise_path, looks_like_id) with 3 new unit tests. Add
examples/policy-advisor/ with a 7-gate CTF script, restrictive
sandbox policy, and walkthrough README.

* fix(policy): use sandbox name for denial flush and add TUI draft badges

Fix denial aggregator passing sandbox UUID instead of name to
SubmitPolicyAnalysis, which caused 'sandbox not found' errors on
flush. Add notification badges to the TUI sandbox list and detail
header showing pending draft recommendation counts.

* fix(policy): deduplicate draft chunks and tolerate overlapping OPA rules

Skip draft chunk creation when a pending/approved chunk already covers
the same host:port endpoint, preventing duplicate rules across denial
aggregator flush cycles.

Rewrite three OPA complete rules (network_policy_for_request,
matched_network_policy, matched_endpoint_config) to tolerate multiple
matching policies without triggering a "complete rule conflict" error.
network_policy_for_request becomes a boolean, matched_network_policy
uses a set comprehension with min(), and matched_endpoint_config uses
an array comprehension with index-0 selection.

* feat(tui): interactive draft actions, highlight bar, and detail popup

Rework the draft recommendations panel to match the logs UX:
- Highlight bar (green accent + background) instead of arrow marker
- Viewport-aware j/k scrolling with g/G for top/bottom
- Enter opens a full-screen detail popup showing endpoints, binaries,
  rationale, security notes, and action hints

Add approve/reject/approve-all draft actions:
- [a] approve selected chunk, [x] reject, [A] approve all pending
- Actions work from both the list view and the detail popup
- gRPC calls run async; result updates status bar and refreshes data
- Nav bar shows all available keybindings

Fix draft count refresh: sandbox_draft_counts now refreshes on every
tick (not just Dashboard), so the detail header badge updates in
real time.

Improve badge labels: show 'N pending' instead of a bare number in
both the dashboard sandbox list and sandbox detail header.

* refactor(policy): DB-level draft chunk dedup with hit counter and timestamps

Replace the in-memory HashSet dedup in SubmitPolicyAnalysis with a
database-level upsert. New denormalized columns on draft_policy_chunks:
- host, port: extracted from proposed_rule at insert time
- hit_count: incremented on conflict (same sandbox + host + port)
- first_seen_ms, last_seen_ms: track when the endpoint was first and
  most recently proposed

A partial unique index (WHERE status IN ('pending','approved')) ensures
only one active chunk per endpoint per sandbox; rejected/superseded
chunks don't block new proposals.

Surface hit_count and first/last_seen in:
- CLI: 'openshell draft get' shows 'Hits: N (first ..., last ...)'
- TUI: detail popup shows hits row; list view shows 'Nx' suffix

* fix(policy): optimistic retry on policy version conflicts + structured logging

merge_chunk_into_policy and remove_chunk_from_policy now retry up to 5
times on UNIQUE constraint violations (version conflicts from concurrent
approvals). Each attempt re-reads the latest policy, re-merges the rule,
and increments the version. This eliminates the race condition where
rapid successive approvals would fail with a DB error.

Add structured tracing to all draft action handlers:
- ApproveDraftChunk: logs rule_name, host, port, hit_count before merge
  and version + policy_hash after success
- RejectDraftChunk: logs rule_name, host, port, reason
- ApproveAllDraftChunks: logs pending_count at start, per-chunk merge
  progress, and final summary with chunks_approved/skipped
- UndoDraftChunk: logs before/after with rule_name and version
- Retry attempts log as warnings with attempt number and conflicting
  version

* wip: forward proxy fix, mapper allowed_ips, TUI polish, CTF rewrite

* fix(tui): use correct --gateway flag for ssh-proxy ProxyCommand

* chore: add Docker cleanup script for stale images, volumes, and build cache

* feat(tui): approve-all confirmation modal and CTF cleanup

Add [A] confirmation popup that snapshots pending chunks, shows a
scrollable list, and approves each chunk individually on confirm.
This prevents approving chunks that arrived after the modal opened.

Remove transient issue #205 reference from CTF victory banner.

* fix(tui): correct import ordering for rustfmt

* wip: stateful toggle model, rename to network rules

Draft chunks now follow a toggle state machine:
  pending -> approved | rejected (initial decision)
  approved <-> rejected (toggle)

One row per (sandbox_id, host, port) via expanded unique index.
Rejecting an approved rule removes it from the active policy.
Re-approving a rejected rule merges it back.

Rename CLI from 'draft' to 'rule', TUI from 'Draft Recommendations'
to 'Network Rules'. State-aware keybindings: approved shows [x] Revoke,
rejected shows [a] Approve. Fix sandbox detail hiding delete confirmation
behind pending message.

* refactor(policy): move mapper sandbox-side, slim schema, per-binary granularity

Move mechanistic mapper from gateway to sandbox so all analysis runs
sandbox-side (N sandboxes = N independent pipelines). Gateway is now a
thin validate + persist + approval layer.

Architectural changes:
- Move mechanistic_mapper.rs from navigator-server to navigator-sandbox
- Sandbox flush flow: aggregator drains -> mapper runs -> proposals sent
- Gateway SubmitPolicyAnalysis: validate + persist only, no mapper
- Drop denial_summaries table (write-only, zero readers)
- Consolidate migrations 003+004+005 into single 003

Schema slimming:
- Drop 5 unused columns from draft_policy_chunks (stage, denial_refs,
  supersedes_chunk_id, analysis_mode, decided_by)
- Add per-binary granularity: binary column, widen unique index to
  (sandbox_id, host, port, binary)
- Mapper groups by (host, port, binary), one proposal per triple
- Merge appends binary to existing rule; revoke removes just that binary

CTF & UX:
- 7-gate CTF: add Gate 3 (curl -> ifconfig.me:80) for per-binary demo
- TUI shows binary short name in list, full path in detail popup
- CLI output shows binary field
- Idempotent rule names, hit_count accumulates real denial counts
- Rationale text no longer bakes in stale denial count
2026-03-12 08:28:26 -07:00
Drew Newberry 95d7ae077e refactor(cli): remove kubeconfig port, add doctor llm-help, update debug docs (#252) 2026-03-11 21:25:25 -07:00
Drew Newberry bcc6dad1d0 feat(cli): launch sandbox editors via managed ssh include (#226) 2026-03-11 20:06:38 -07:00
Piotr Mlocek 3fe445ce05 fix(cli): improve completion coverage and gateway selection (#241)
* fix(cli): improve completion coverage and gateway selection

Closes #232

Add typed completion metadata for gateway, sandbox, and path arguments, make gateway selection interactive on TTYs, and expose root subcommands in completions while keeping non-interactive behavior stable.
2026-03-11 16:57:40 -07:00
Drew NewberryandPiotr Mlocek d94d4e1166 feat(cluster): add NVIDIA GPU passthrough support for gateway start (#234)
Add --gpu flag to 'openshell gateway start' that enables NVIDIA GPU
access inside the cluster container. When enabled, the Docker container
receives GPU device requests via bollard, k3s auto-detects the NVIDIA
runtime, and the NVIDIA k8s-device-plugin is deployed via HelmChart CR
so Kubernetes workloads can request nvidia.com/gpu resources.

- Add GPU device request to Docker container creation (bollard DeviceRequest)
- Set GPU_ENABLED=true env var to gate GPU manifest deployment
- Multi-stage Dockerfile build to bundle NVIDIA Container Toolkit binaries
- Conditional GPU manifest copy in cluster entrypoint
- HelmChart CR for nvidia-device-plugin v0.18.2 with GFD and NFD enabled
- Thread gpu bool through DeployOptions, CLI flag, and run dispatch

---------

Co-authored-by: Piotr Mlocek <pmlocek@nvidia.com>
2026-03-11 16:53:24 -07:00
Drew Newberry f97270f988 refactor(docker): rename server image to gateway (#246)
* refactor(docker): rename server image to gateway

Rename Dockerfile.server to Dockerfile.gateway and update all image
references from openshell/server to openshell/gateway across Helm
charts, Kubernetes manifests, mise tasks, build/deploy scripts, CI
workflows, and documentation.

The underlying Rust binary (navigator-server) is unchanged -- this
rename only affects the Docker image name and Dockerfile.

* fix: catch remaining server->gateway references in docs and comments
2026-03-11 16:01:26 -07:00
Miyoung Choi 4e893322a1 update docs per new dev prs (#238) 2026-03-11 13:48:03 -07:00
Drew Newberry 756950140c refactor(python): rename navigator module to openshell and migrate config to gateway paths (#220) 2026-03-10 22:24:04 -07:00
Drew Newberry f4af0ee848 chore: replace all nemoclaw references with openshell (#214)
- Update registry URLs from ghcr.io/nvidia/nemoclaw to ghcr.io/nvidia/openshell
- Rename NEMOCLAW_* environment variables to OPENSHELL_*
- Update CLI command references in documentation
- Update PyPI package name references
- Update GitHub repository URLs
- Rename .agents/skills/nemoclaw-cli directory to openshell-cli
2026-03-10 16:10:57 -07:00
John T. MyersandJohn Myers e373f7c056 feat(inference): add sandbox-system inference route for platform-level inference (#209)
* feat(inference): add sandbox-system inference route for platform-level inference

Add a separate system-level inference route that the sandbox supervisor
can use in-process for platform functions (e.g., embedded agent harness
for policy analysis), distinct from the user-facing inference.local
endpoint. The system route is accessed via an in-process API on the
supervisor, ensuring userland code in the sandbox netns cannot reach it.

- Extend proto with route_name fields on Set/Get inference messages
- Add ResolvedRoute.name field to the router for route segregation
- Server resolves both user and sandbox-system routes in bundles
- Sandbox partitions routes into user/system caches on refresh
- Expose InferenceContext::system_inference() in-process API
- CLI --sandbox flag targets the system route on set/get/update
- Integration tests using mock:// routes for the full in-process path

Closes #207

* refactor(cli): rename --sandbox flag to --system for inference commands

The --sandbox flag could be misread as targeting a user-level sandbox
operation. Rename to --system to clearly indicate it configures the
platform-level system inference route.

* style(cli): collapse short if-else per rustfmt 1.94

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-10 15:30:07 -07:00
Drew Newberry d6c6e97679 chore: remove navigator references from codebase (#208) 2026-03-10 14:46:11 -07:00
Drew Newberry 984d1a6e5c chore: rename project from NemoClaw to OpenShell (#198) 2026-03-10 11:49:09 -07:00
John T. MyersandJohn Myers bffda60e92 feat(tui): auto-refresh sandbox policy view when new versions are detected (#200)
Closes #196

Piggyback on the existing 2-second sandbox list refresh to detect policy
version changes. When the user is on the sandbox screen and the listed
current_policy_version differs from the displayed version, fetch the
updated policy via GetSandboxPolicy. Scroll position is preserved across
auto-refreshes so the user is not disrupted.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-10 00:02:32 -07:00
Drew Newberry 1cf54ca055 feat(bootstrap): switch container registry from CloudFront CDN to GHCR with token auth (#167) 2026-03-09 23:46:23 -07:00
Drew Newberry a83109c359 fix(containers): remediate high-severity container vulnerabilities and remove openclaw (#191) 2026-03-09 21:39:14 -07:00
Piotr Mlocek 74ed3aff7c fix(sandbox): improve inference route refresh with conditional fetch and configurable interval (#185) 2026-03-09 19:16:46 -07:00
Drew Newberry a2de1f24e5 feat: add Cloudflare tunnel auth support (#178) 2026-03-09 18:39:42 -07:00
Piotr Mlocek 2a3bc18174 fix(sandbox): treat IPv6 ULA addresses as internal (#173) 2026-03-09 13:38:40 -07:00
John T. MyersandJohn Myers 3d0c4d17e1 fix(security): add SSH session token expiry, connection limits, and lifecycle cleanup (#182)
* fix(security): add SSH session token expiry, connection limits, and lifecycle cleanup

Closes #22

SSH session tokens previously had no TTL and remained valid indefinitely.
This adds configurable token expiry (default 24h), per-token (10) and
per-sandbox (20) concurrent connection limits, session cleanup on sandbox
deletion, and a background reaper for expired/revoked sessions.

* fix(security): lower per-token concurrent connection limit from 10 to 3

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-09 11:39:23 -07:00
Drew Newberry ed53c35d2b feat(cli): improve sandbox provisioning status messages and UX (#175) 2026-03-08 16:36:50 -07:00
Drew Newberry ba78e278a8 feat(cli): switch community sandbox registry to CloudFront CDN (#170) 2026-03-07 19:52:27 -08:00
John T. MyersandJohn Myers b8d873f68f feat(proxy): support plain HTTP forward proxy for private IP endpoints (#158)
* feat(proxy): support plain HTTP forward proxy for private IP endpoints

Add forward proxy mode to the sandbox proxy so that standard HTTP
libraries (httpx, requests, etc.) work with HTTP_PROXY for plain HTTP
calls to private IP endpoints. Previously, non-CONNECT methods were
unconditionally rejected with 403.

The forward proxy path requires all three conditions to be met:
- OPA policy explicitly allows the destination
- The matched endpoint has allowed_ips configured
- All resolved IPs are RFC 1918 private

This ensures plain HTTP never reaches the public internet while enabling
seamless access to internal services without custom CONNECT tunnel code.

Implementation:
- parse_proxy_uri(): parses absolute-form URIs into components
- rewrite_forward_request(): rewrites to origin-form, strips hop-by-hop
  headers, adds Via and Connection: close
- handle_forward_proxy(): full handler with OPA eval, SSRF checks,
  private-IP gate, upstream connect, and bidirectional relay
- Updated dispatch in handle_tcp_connection to route non-CONNECT methods

Includes 14 unit tests and 6 E2E tests (FWD-1 through FWD-6).
CONNECT path remains completely untouched.

Closes #155

* fix(proxy): remove InspectForInference match arm removed by #146

The inference routing simplification in #146 reduced NetworkAction to
Allow/Deny, removing InspectForInference. Drop the dead match arm from
handle_forward_proxy.

* fix(sandbox): restore BestEffort as default Landlock compatibility

The Landlock V2 upgrade in #151 changed the default from BestEffort to
HardRequirement. This causes all proxy-mode sandboxes to crash with
Permission denied when the policy omits the landlock field, because the
child process gets locked to only /etc/navigator-tls and /sandbox.

Restore BestEffort as the default so policies without an explicit
landlock field degrade gracefully.

Fixes #161

* fix(sandbox): inject baseline filesystem paths for proxy-mode sandboxes

Proxy-mode sandboxes need baseline filesystem paths (/usr, /lib, /etc,
/app, /var/log read-only; /sandbox, /tmp read-write) for the child
process to function under Landlock. Without these, the child can't exec
binaries, resolve DNS, or load shared libraries.

The supervisor now enriches the policy with these baseline paths at
startup, covering both standalone (file) and gateway (gRPC) modes. For
gateway mode, the enriched policy is synced back so users see the
effective policy via 'nemoclaw sandbox get'.

The gateway validation is relaxed to allow additive filesystem changes
(new paths can be added, existing paths cannot be removed) to support
the supervisor's enrichment sync-back.

Includes 2 E2E tests: BFS-1 (missing filesystem_policy) and BFS-2
(incomplete filesystem_policy).

Fixes #161

* fix(e2e): update assertion for relaxed filesystem validation message

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-06 19:31:46 -08:00
Piotr Mlocek 68525bb81a fix(build): propagate packaged version through cluster artifacts (#164)
Pass the computed cargo version through Docker and cluster packaging paths so deployed clusters report the built artifact version while keeping latest and explicit version tags aligned.
2026-03-06 18:17:03 -08:00
Drew Newberry 07b9d5d00e feat(cli): restructure CLI commands for simpler UX (#156) 2026-03-06 16:26:39 -08:00
Piotr Mlocek 31d7ca53ff refactor(inference): simplify routing — introduce inference.local, remove implicit catch-all (#146)
Remove multi-route CRUD system and replace with single managed cluster
route (inference.local). Key changes:

- Remove inference route CRUD RPCs and CLI commands
- Remove InspectForInference OPA action; policy is binary allow/deny
- Introduce AuthHeader enum and InferenceProviderProfile in navigator-core
- Router is now provider-agnostic: auth style carried on ResolvedRoute
- Replace InferenceRouteSpec with ClusterInferenceConfig (2 fields vs 8)
- Rename proto: routing_hint->name, SandboxResolvedRoute->ResolvedRoute,
  GetSandboxInferenceBundle->GetInferenceBundle, drop sandbox_id param
- Rename RouteConfig.route -> RouteConfig.name; use inference.local
- Add 'nemoclaw cluster inference update' for partial config changes
- Delete stale navigator.inference.v1.rs checked-in proto file
- Update architecture docs, agent skills, and CLI reference

Closes #133
2026-03-06 13:54:23 -08:00
Drew Newberry 338fa121c9 chore(cluster): upgrade k3s to v1.35.2 and remove K3S_VERSION from mise.toml (#152) 2026-03-06 01:35:06 -08:00
John T. MyersandJohn Myers ae9e766567 fix(server): prevent unbounded bus entry growth for sandbox IDs (#138)
Closes #27

Add remove() methods to TracingLogBus, PlatformEventBus, and
SandboxWatchBus to clean up entries when sandboxes are deleted.
Wire cleanup into both handle_deleted (K8s reconciler) and
delete_sandbox (gRPC handler). Reorder watch_sandbox to validate
sandbox existence before subscribing to buses, preventing entries
for non-existent IDs. Add one-time sandbox validation at stream
open in push_sandbox_logs.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 15:58:01 -08:00
John T. MyersandJohn Myers ff99fec819 refactor(tui): rebrand Gator to Term/NemoClaw (#134)
Closes #130

Rename the TUI from "Gator" to use generic naming:
- CLI subcommand: `nemoclaw gator` → `nemoclaw term`
- TUI title bar: "Gator" → "NemoClaw"
- Docs/skills: "Gator" → "the TUI" or "NemoClaw TUI"
- File renames: gator.toml → term.toml, gator.md → tui.md
- Remove dead link to nonexistent plans/gator-tui.md

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 15:13:13 -08:00
John T. MyersandJohn Myers 5e7a2f2dd8 fix(sandbox): verify effective UID/GID after privilege drop (#132)
Closes #32

Add post-condition checks in drop_privileges() to verify that setgid()
and setuid() actually changed the effective IDs. Also verify that
setuid(0) fails after dropping privileges, confirming root cannot be
re-acquired. This is a defense-in-depth hardening measure per CWE-250
and CERT POS37-C. All added syscalls (geteuid, getegid, setuid) are
async-signal-safe, so they are safe in the pre_exec context.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 14:27:48 -08:00
Piotr Mlocek 5befdbf307 fix(sandbox): remove control plane bypass from proxy (#128)
The sandbox proxy had a control_plane_endpoints allowlist that let
sandboxed processes reach the gateway without OPA evaluation or SSRF
checks. This is a security issue: if the sandbox can talk to the
control plane, it could modify its own policy, exfiltrate data through
provider environment endpoints, or abuse other gateway APIs.

All connections now go through OPA policy evaluation and SSRF
protection with no exceptions. Removes AllowedEndpoint, parse_endpoint_url,
the is_control_plane bypass in handle_tcp_connection, and the engine field
from ConnectDecision.
2026-03-05 12:50:26 -08:00
John T. MyersandJohn Myers e9f10719f4 fix(security): harden sandbox SSH with mandatory HMAC secret, NetworkPolicy, and nonce replay detection (#127)
* fix(security): harden sandbox SSH with mandatory HMAC secret, NetworkPolicy, and nonce replay detection

Closes #25

- Make NEMOCLAW_SSH_HANDSHAKE_SECRET mandatory: server and sandbox both
  refuse to start if the secret is empty/unset. Cluster deployments
  auto-generate it via openssl rand in the entrypoint script.
- Add Kubernetes NetworkPolicy restricting sandbox port 2222 ingress to
  the gateway pod only, preventing lateral movement from other cluster
  workloads.
- Add NSSH1 nonce replay detection with a TTL-bounded cache, rejecting
  replayed handshakes within the timestamp validity window.
- Add unit tests for verify_preface (valid, replay, expired, bad HMAC,
  malformed) and env injection.

* fix(deploy): pass sshHandshakeSecret in fast deploy helm upgrade

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-05 12:21:18 -08:00
Drew Newberry 3da64744f0 feat(cli): add --from flag to sandbox create for unified image sources (#89) 2026-03-04 21:15:13 -08:00
Drew Newberry 99bba8028a feat(sandbox): support policy discovery and restrictive defaults on sandbox containers (#84) 2026-03-04 17:17:22 -08:00
Drew Newberry 90da02a7ed chore: simplify contributing workflow and documentation (#92) 2026-03-04 13:06:32 -08:00
John T. MyersandJohn Myers 1d81fb187b feat(skills): create nemoclaw-cli agent skill (#85)
* feat(skills): create nemoclaw-cli agent skill and fix generate-sandbox-policy schema drift

Closes #83

Create a new nemoclaw-cli agent skill with tiered workflow guidance (basic
through advanced) and a supplementary CLI reference. The skill covers provider
management, sandbox lifecycle, policy iteration loops, BYOC pipelines, and
agent-assisted sandbox sessions. It cross-references generate-sandbox-policy
for policy content authoring and includes --help self-teaching as a fallback.

Fix critical schema drift in generate-sandbox-policy: rename
allowed_routing_hints to allowed_routes (matching the actual proto/serde
field name). Update default read_only paths to match dev-sandbox-policy.yaml.
Fix the same field name error in architecture/security-policy.md.

* policy updates

* fix(test): update OPA tests for renamed github_ssh_over_https policy key

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-04 09:52:55 -08:00
John T. MyersandJohn Myers 66df9f7614 feat(tui): add port forwarding support to Gator (#81)
* feat(tui): add port forwarding support to Gator (#80)

Extract forward PID management, resolve_ssh_gateway, and shell_escape from
navigator-cli into navigator-core::forward as a shared module. Add a Ports
field to the TUI create sandbox modal, a NOTES column to the sandbox table,
and a Forwards row to the sandbox detail view. On creation with ports, Gator
polls for Ready state then spawns background SSH tunnels. Forward cleanup
runs automatically on sandbox delete.

Closes #80

* fix(tui): fix Ports field invisible due to modal height overflow

The modal height calculation used content_height + 3 but block chrome
(borders + padding) is 4 rows, not 3. Combined with the 3-row Ports
field layout, the input line was clipped to zero height on standard
24-row terminals. Switch Ports to a compact single-line inline layout
and fix the chrome arithmetic.

* wip: BYOC example with port forwarding and TUI command field fix

- Move examples/bring-your-own-container.md into its own directory
- Add example Dockerfile and app.py (Python REST API with /hello endpoint)
- Rewrite README with full CLI and TUI port-forward workflows
- Change TUI Command field default from /bin/bash to empty so custom
  image entrypoints run without manual clearing

* feat(tui): wire up Command field to SSH exec after sandbox creation

When a command is specified in the create sandbox modal, the TUI now
waits for the sandbox to reach Ready (with the pacman animation),
starts any port forwards, then suspends the TUI and executes the
command via SSH — matching the CLI's post-creation flow.

- Refactor spawn_create_sandbox to poll for Ready inline when ports
  or command are set, keeping the animation visible throughout
- Add start_port_forwards() helper called within the create task
- Add handle_exec_command() to suspend TUI and run SSH exec
- Remove unused ForwardResult event variant (forwards are now started
  within the create task, not as a separate event)

* fix(tui): fix command exec, forward timeouts, and create modal UX

- Fix handle_exec_command to suspend TUI and run SSH attached (matching
  CLI behavior) so the process stays alive for the session duration
- Fix shell_escape double-quoting: escape each word individually
- Add ConnectTimeout and 20s spawn timeout to forward SSH so a stalled
  auth doesn't freeze the create flow forever
- Remove dead ForwardResult event variant
- Add cluster_name to start_port_forwards for ProxyCommand
- Add spacer between Providers and Ports in create modal
- Fix Command placeholder: 'runs /bin/bash if empty' (not image entrypoint)
- Update BYOC README: document that CMD is replaced by supervisor,
  command must be passed explicitly, remove TUI-specific sections

* fix(sandbox): demote Landlock fallback log from warn to debug

The Landlock filesystem sandbox emits a noisy warning when a policy
path does not exist (e.g. /app in custom images). Since BestEffort
mode intentionally continues without Landlock, demote to debug.

* docs: fix BYOC Dockerfile comments for supervisor CMD override

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-03 17:31:44 -08:00
John T. MyersandJohn Myers c06117eb15 fix(proxy): return 403 for non-CONNECT requests, add deny logging, and revise error messages (#79)
Closes #42

- Change non-CONNECT proxy response from 405 to 403 to align with
  how CONNECT denials are surfaced
- Add structured deny logging for non-CONNECT requests with hostname
  extraction from absolute-form URIs
- Revise 7 user-facing error messages across proxy.rs and
  dev-sandbox-policy.rego to follow consistent principle: generic
  policy-deny messages for non-inference requests, descriptive messages
  for recognized inference endpoints
- Update E2E test assertion to match new error message
- Update architecture docs to reflect new behavior

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-03 13:06:17 -08:00
Drew Newberry 9099bc3972 chore: rename Navigator to NemoClaw across user facing contracts (#73) 2026-03-03 11:41:19 -08:00