Commit Graph
494 Commits
Author SHA1 Message Date
Adel Zaalouk bec39d9594 fix(sandbox): undo tmpfs overlay when setns fails during SPIFFE mount namespace setup
On OpenShift and other container runtimes that do not grant
CAP_SYS_ADMIN, setns(CLONE_NEWNS) fails with EPERM after
create_supervisor_identity_mount_namespace has already mounted
an empty tmpfs over the SPIFFE workload API socket directory.
PID 1 remains stuck in the new mount namespace where the tmpfs
hides the socket, making it invisible to all processes.

Fix: when setns() fails, call umount2(MNT_DETACH) to remove
the tmpfs overlay before returning the error, so the SPIFFE
socket stays accessible even without namespace isolation.

Also wire up prepare_supervisor_identity_mount_namespace_from_env
in main.rs and make it gracefully degrade on error instead of
aborting the supervisor.

Signed-off-by: Adel Zaalouk <azaalouk@redhat.com>
2026-07-08 15:27:55 +02:00
Seth Jennings 6461677c32 feat(policy): accept numeric UIDs for sandbox process identity (#1973)
* feat(policy): accept numeric UIDs in sandbox process identity validation

Allow run_as_user and run_as_group to be either the literal 'sandbox'
or a numeric UID/GID within [1000, 2_000_000_000]. This removes the
hard dependency on a baked-in 'sandbox' user in container images,
enabling compute drivers to inject resolved UIDs at sandbox creation.

Phase 1 of #1959.

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* feat(supervisor): accept numeric UIDs for process identity dropping

Allow run_as_user and run_as_group to be numeric UIDs/GIDs, removing
the hard dependency on a baked-in 'sandbox' user in container images.

Changes:
- validate_sandbox_user(): accepts numeric UIDs without passwd lookup
  (logs OCSF event); keeps passwd check for "sandbox" name; rejects
  non-numeric non-sandbox strings that fail passwd lookup
- prepare_filesystem(): passes numeric UIDs/GIDs directly to chown()
  instead of requiring a passwd entry
- drop_privileges(): resolves numeric UIDs/GIDs directly via UID::from_raw
  / Gid::from_raw; skips initgroups when target uid matches current euid;
  uses guard conditions before setgid/setuid calls
- session_user_and_home(): falls back to ("{uid}", "/sandbox") for
  numeric UIDs, avoiding a passwd lookup that will fail

Re-exports MIN_SANDBOX_UID and MAX_SANDBOX_UID from openshell-policy
so callers have consistent range constants.

Phase 2 of #1959.

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* feat(driver-kubernetes): resolve sandbox UID/GID from config or OpenShift SCC annotations

Phase 3 of the numeric-UID plan: allow operators to specify explicit
sandbox_uid/sandbox_gid in Kubernetes driver config, auto-detect from
OpenShift SCC namespace annotations, and propagate resolved values to
supervisor container env vars and PVC init container securityContext.

Changes:
- Add sandbox_uid/sandbox_gid fields to KubernetesComputeConfig
- Add SANDBOX_UID/SANDBOX_GID env var constants to openshell-core
- Implement resolve_sandbox_identity() to fetch namespace annotations
  and auto-detect OpenShift SCC UID ranges (sa.scc.uid-range)
- Pass resolved UID/GID through SandboxPodParams to pod spec builder
- Inject SANDBOX_UID/SANDBOX_GID env vars into supervisor container
- Update PVC init container securityContext with resolved UID/GID
  instead of hard-coded root
- Add comprehensive unit tests for resolution logic and annotation
  parsing (resolve_sandbox_uid, resolve_sandbox_gid, OpenShift SCC
  annotation parsing)

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* feat(driver-vm): add configurable sandbox UID/GID and update docs/examples

Phase 4 of the numeric-UID plan: replace hardcoded SANDBOX_UID (10001)
in VM rootfs preparation with configurable sandbox_uid/sandbox_gid fields.

Changes:
- Add sandbox_uid/sandbox_gid to VmDriverConfig with serde derives
- Pass resolved UID/GID through prepare_sandbox_rootfs_from_image_root
  to ensure_sandbox_guest_user which writes /etc/passwd/group/gshadow
- Update BYOC Dockerfile: remove groupadd/useradd, document runtime UID
  injection and the ability to skip baked-in sandbox user
- Update gateway-config.mdx: document sandbox_uid/sandbox_gid for both
  Kubernetes (with OpenShift SCC autodetection) and VM drivers
- Update sandbox-compute-drivers.mdx: add Sandbox User Identity section
  explaining numeric UID support across all compute drivers
- Update rootfs tests to use non-default UIDs, verify config passthrough

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* code review changes

* fix(supervisor): harden tests for restricted CI container environments

Guard tests against CI-specific constraints: root without CAP_SETPCAP,
UIDs with no /etc/passwd entry, and restricted /proc access.

Signed-off-by: Seth Jennings <sjennings@nvidia.com>
Signed-off-by: Seth Jennings <sjenning@redhat.com>

---------

Signed-off-by: Seth Jennings <sjenning@redhat.com>
Signed-off-by: Seth Jennings <sjennings@nvidia.com>
2026-07-02 13:31:06 -07:00
Florian Bergmann 43bb030266 feat(docker,podman): add SELinux label support for bind mounts (#2092)
* feat(docker,podman): add SELinux label support for bind mounts

The Docker Engine structured Mount API does not support SELinux
relabelling (:z / :Z). Move user-supplied bind mounts from the
structured `mounts` field to the legacy string-format `binds` field,
which does support these options.

Add a shared `SelinuxLabel` enum (shared/private) to openshell-core so
both Docker and Podman drivers accept an optional `selinux_label` field
on bind mount configs. For Docker, labels are appended to the bind
string; for Podman, they are pushed to the mount options vec.

Signed-off-by: Florian Bergmann <fbergman@redhat.com>

* fix(docker): reject missing bind source paths on legacy binds

Moving user bind mounts from the structured Mount API to the legacy
Binds field changed Docker's behavior for missing source directories:
the legacy path silently creates them as empty root-owned dirs instead
of erroring. Add an explicit Path::exists() check to preserve the
fail-fast behavior operators expect.

Signed-off-by: Florian Bergmann <fbergman@redhat.com>

---------

Signed-off-by: Florian Bergmann <fbergman@redhat.com>
2026-07-02 08:16:52 -07:00
Evan Lezar 45614a3fb6 refactor(api): remove SandboxTemplate.volume_claim_templates (#2088)
The field was added during the Kubernetes driver extraction refactor
(#817) as a pass-through mechanism, but was never wired up to a CLI
flag, Python SDK helper, or any documentation. The only reachable
user path was raw gRPC construction.

The Kubernetes driver now always injects the default workspace PVC,
removing the branching logic that checked for a user-supplied VCT.
Field number 9 is reserved in the proto to prevent reuse.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-07-01 16:04:47 +02:00
Evan Lezar 450685c755 fix(drivers): reject whitespace in mount fields (#2086)
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-07-01 16:04:07 +02:00
Taylor Mutch 914da339b4 feat(kubernetes): add combined topology config surface (#2074)
* feat(kubernetes): add combined topology config surface

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* docs(kubernetes): clarify topology defaults

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-06-30 15:07:55 -07:00
Evan Lezar 0a25fdf525 refactor(core): remove unused extra bind addresses (#2059)
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-30 10:37:10 -07:00
John T. MyersandJohn Myers f27ff1507c fix(providers): reserve credential placeholder revisions (#2049)
* fix(providers): reserve credential placeholder revisions

Signed-off-by: John Myers <johntmyers@users.noreply.github.com>

* fix(providers): share placeholder namespace parser

Signed-off-by: John Myers <johntmyers@users.noreply.github.com>

* test(providers): cover non-revision env key

Signed-off-by: John Myers <johntmyers@users.noreply.github.com>

---------

Signed-off-by: John Myers <johntmyers@users.noreply.github.com>
Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-06-30 10:57:10 +02:00
Evan Lezar a5161d0bc3 refactor(server): normalize compute driver config acquisition (#1974)
* refactor(server): remove unused compute runtime constructor parameter

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(server): normalize compute driver type imports

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(server): key driver config tables by name

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(server): normalize compute driver config acquisition

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-29 18:59:08 +02:00
alangou afc06dd2a4 fix(supervisor): drop sandbox child capability bounding set (#2001)
Reduce the Linux capability bounding set in the common privilege-drop path before executing sandbox workloads or connect shells and use capctl

Signed-off-by: Adrien Langou <alangou@nvidia.com>
2026-06-29 17:10:15 +02:00
krishicksandddurst 7bce1223dc feat(policy): add JSON-RPC and MCP L7 policies (#1865)
Add policy schema, proto, provider profile, OPA, and L7 proxy support for
`protocol: json-rpc` and `protocol: mcp`. Generic JSON-RPC endpoints match
exact method names only, with `method: "*"` as the all-method sentinel;
wildcard/glob methods and params matchers are rejected.

Parse JSON-RPC request bodies and batches in the forward proxy, deny
response-shaped client frames, limit receive-stream GET allowance to MCP
endpoints, and redact params in decision logs. Preserve L7 rule params on the
proto load path so MCP `tools/call` tool filters behave like YAML-loaded
policies.

Add MCP conformance coverage, JSON-RPC L7 e2e coverage, and docs for the new
protocols and current matcher limitations.

Signed-off-by: Kris Hicks <khicks@nvidia.com>
Co-authored-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
2026-06-26 15:52:16 -07:00
Gal Zaidman 45e5a5d128 fix(server): prevent exec relays from hanging on idle connections (#1992)
Add HTTP/2 keepalive on supervisor multiplex connections so half-dead
sessions cannot leave in-flight exec relays parked indefinitely. Configure
SSH keepalive on exec relay clients so long silent commands are not timed
out on stdout idle alone; wedged or orphaned relays fail after missed
keepalives instead.

After a command reports exit status, bound how long the gateway waits for
the trailing channel close. Return UNAVAILABLE when a relay closes before
reporting exit status rather than defaulting to exit code 1.

Signed-off-by: Gal Zaidman <gzaidman@nvidia.com>
2026-06-26 13:33:32 -07:00
Taylor Mutch ba21bb32a2 feat(kubernetes): support agent-sandbox v1beta1 (#2009)
* feat(kubernetes): support agent-sandbox v1beta1

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* ci(kubernetes): test agent-sandbox api versions

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): retry agent-sandbox raw 404s

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): harden agent-sandbox api setup

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): cache agent-sandbox api version

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* docs(kubernetes): document agent-sandbox upgrade behavior

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* refactor(kubernetes): collapse agent-sandbox api selection

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-06-26 11:44:52 -07:00
Jesse JaggarsandRussell Bryant f569a0ade6 feat(sandbox): proxy-side AWS SigV4 credential signing for CONNECT tunnels (#1638)
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Co-authored-by: Russell Bryant <russell.bryant@gmail.com>
2026-06-26 10:52:24 -07:00
Calum Murray 4b78b442e6 fix(openshell-network-supervisor): gate proxy accept on symlink resolution readiness (#1968)
* fix(openshell-network-supervisor): gate proxy accept on symlink resolution readiness

Signed-off-by: Calum Murray <cmurray@redhat.com>

* fix(openshell-network-supervisor): skip symlink resolution gate when process supervisor is disabled

Signed-off-by: Calum Murray <cmurray@redhat.com>

---------

Signed-off-by: Calum Murray <cmurray@redhat.com>
2026-06-26 09:17:07 -07:00
John T. Myers b855d8d872 fix(policy): reserve provider rule namespace (#1991)
Closes #1982

Reject user-authored _provider_* network policy keys at gateway boundaries, strip provider-derived rules from sandbox sync, and document the round-trip workflow.

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-06-26 08:28:35 -07:00
Evan Lezar 7ea471cd5a chore(deps): remove unused regorus yaml feature (#2021)
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-26 16:14:08 +02:00
Evan Lezar e3382cb447 fix(server): update driver spec test argument (#2022)
* fix(server): update driver spec test argument

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* test(server): stabilize concurrent lease steal test

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-26 06:59:15 -07:00
st-grandEvan Lezar 75a317ea45 feat(server): support out-of-tree compute drivers via --compute-driver-socket (#1703)
* feat(server): support remote compute driver endpoints

Add named remote compute driver endpoint support to the gateway.

Remote drivers are selected by a non-reserved compute driver name and either a CLI/env socket endpoint or [openshell.drivers.<name>].socket_path. The VM driver now enters ComputeRuntime through the same acquired remote endpoint path, while Docker, Podman, and Kubernetes retain their in-process drivers.

Require --drivers/OPENSHELL_DRIVERS when pairing an ad-hoc socket endpoint so the socket does not imply a magic driver name, and keep reserved in-tree names unavailable for unmanaged socket endpoints.

Co-authored-by: Evan Lezar <elezar@nvidia.com>

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>
Signed-off-by: Evan Lezar <elezar@nvidia.com>

* test(server): cover remote compute driver UDS lifecycle

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>
Signed-off-by: Evan Lezar <elezar@nvidia.com>
Co-authored-by: Evan Lezar <elezar@nvidia.com>
2026-06-26 12:32:20 +02:00
Yuedong Wu f2ecadf657 refactor(cli): replace sandbox_create positional args with SandboxCreateConfig struct (#1997)
Signed-off-by: Yuedong Wu <dwcn22@outlook.com>
2026-06-26 07:30:25 +00:00
Evan Lezar d93293ad12 fix(e2e): stabilize local Docker smoke test (#1935)
* fix(docker): honor configured supervisor image

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(cli): isolate ssh from host linker environment

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-25 16:51:27 +02:00
Simon Scatton c7879a0a1d test(e2e): stop using custom e2e binary builds (#2000)
Remove the production-crate dev-settings feature so e2e tests build the same gateway, CLI, and supervisor binaries that we ship. Move settings coverage from dummy test-only keys to the production ocsf_json_enabled setting.

Signed-off-by: Simon Scatton <sscatton@nvidia.com>
2026-06-25 13:08:04 +00:00
Evan Lezar 2c545893ed feat(cli): add GPU count requests (#1812)
* feat(gpu)!: add resource requirements

BREAKING CHANGE: SandboxSpec.gpu and DriverSandboxSpec.gpu were replaced with resource_requirements.gpu, changing protobuf field 9 from a bool to a message for both public and driver APIs.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(gpu): pass requirements through sandbox create

Pass the coupled GPU requirement object through the CLI sandbox_create boundary instead of splitting presence and count into separate arguments.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(gpu): pass requirements to timeout message

Pass ResourceRequirements into the provisioning timeout message helper so GPU hints are derived from the same nested request object used to create the sandbox.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(gpu): pass driver requirements through helpers

Thread Option<GpuResourceRequirements> through driver validation and rendering helpers instead of splitting GPU presence and count into separate arguments.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(gpu): validate exact device requests

Require exact driver GPU device lists to be tied to a GPU request, allow a single exact device to use the default countless request, and require explicit matching counts for multi-device lists.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-23 21:49:02 -07:00
Taylor Mutch 8e831f3a52 fix(ci): fix linting issues (#1985) 2026-06-23 20:05:34 -07:00
st-grandClaude Opus 4.7 4ee27d9955 feat(sandbox,providers): add aws-bedrock as a recognized inference provider (#1704)
* feat(sandbox): allow AWS Bedrock InvokeModel paths through the L7 router

Adds two patterns to `default_patterns()` so the supervisor's L7
inference router recognizes the Bedrock InvokeModel URL shape and
forwards matched requests to the registered upstream:

- `POST /model/{modelId}/invoke`                       → aws_bedrock_invoke
- `POST /model/{modelId}/invoke-with-response-stream`  → aws_bedrock_invoke_stream

The `{modelId}` segment is wildcarded by extending `detect_inference_pattern`
to handle one middle `/*/` segment in addition to the existing trailing
`/*`. The wildcard is constrained to a single non-empty path segment to
avoid path-traversal liabilities — `/model//invoke` and `/model/a/b/invoke`
both no-match.

Without this, sandboxes running Claude Code in its native Bedrock mode
(`CLAUDE_CODE_USE_BEDROCK=1`, `ANTHROPIC_BEDROCK_BASE_URL`, AWS-style
auth) hit the supervisor with `403 connection not allowed by policy`
because their URL doesn't match `/v1/*` shapes. The fix unblocks
operators wanting to register direct AWS Bedrock, an in-cluster
Bedrock-compatible bridge, or a Bedrock-emulating LiteLLM as
`--type aws-bedrock` providers.

Tests cover: positive matches for invoke + invoke-with-response-stream,
query-string handling, GET rejection, empty-segment rejection,
multi-segment rejection, and unknown-action rejection.

Companion changes (provider discovery spec + YAML profile) follow in
the next commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* feat(providers): add aws-bedrock provider profile + discovery spec

Adds `aws-bedrock` to the built-in provider catalog so operators can
run `openshell provider create --type aws-bedrock --credential ...`
and have the gateway treat it as a first-class inference provider
alongside `anthropic`, `openai`, etc.

- `providers/aws-bedrock.yaml`: YAML profile declaring four credentials
  (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_REGION).
  Default endpoint is `bedrock-runtime.us-east-1.amazonaws.com:443`;
  operators in other regions or running against a Bedrock-compatible
  proxy override via the operator-supplied `BEDROCK_BASE_URL` config-key
  (mirrors `ANTHROPIC_BASE_URL` for the `anthropic` provider).

- `crates/openshell-providers/src/providers/aws_bedrock.rs`: the
  `ProviderDiscoverySpec` so `openshell provider create --auto-providers`
  picks up AWS_* env vars from local credentials.

- `crates/openshell-providers/src/providers/mod.rs`: register the module.

- `crates/openshell-providers/src/lib.rs`: register the SPEC in the
  default registry alongside the other providers.

- `crates/openshell-providers/src/profiles.rs`: include the new YAML in
  `BUILT_IN_PROFILE_YAMLS`.

What this PR explicitly does NOT add (intentionally separated for
review-size reasons; will follow up):

- A SigV4 signer in `openshell-router`. The current change simply
  declares the protocol; a follow-up PR adds outbound SigV4 signing
  using the `aws-sigv4` crate and a new `auth_style: sigv4` validator
  branch in profiles.rs. Operators who don't need SigV4 (e.g. an
  in-cluster bridge that ignores it and authenticates separately to
  the upstream) can use this PR today.

- Body translation between Bedrock InvokeModel shape and other
  inference shapes. The router treats Bedrock requests as opaque
  pass-through; if the operator's upstream is real AWS Bedrock it
  speaks Bedrock natively, if it's a translating bridge the bridge
  does any conversion server-side.

- `BEDROCK_BASE_URL` placeholder substitution in the YAML loader.
  Today the YAML's `host` is a literal default; operators override
  with the config-key the same way `ANTHROPIC_BASE_URL` works.

Tested: `cargo test -p openshell-providers` (35 tests green) and
`cargo test -p openshell-sandbox --lib l7::inference` (40 tests green
including the seven new aws_bedrock cases from the previous commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* revert(providers): drop legacy aws-bedrock SPEC, rely on v2 YAML profile

Addresses johntmyers's review on NVIDIA/OpenShell#1704: net-new
providers should land via the v2 YAML profile only and should NOT
require changes to the legacy `ProviderDiscoverySpec` registry.

- Delete `crates/openshell-providers/src/providers/aws_bedrock.rs`
  (the legacy SPEC + `test_discovers_env_credential!` invocation).
- Drop `pub mod aws_bedrock;` from `crates/openshell-providers/src/providers/mod.rs`.
- Drop `registry.register(providers::aws_bedrock::SPEC)` from
  `crates/openshell-providers/src/lib.rs`.

Kept:

- `providers/aws-bedrock.yaml` and the `include_str!` in
  `BUILT_IN_PROFILE_YAMLS` (`profiles.rs`) — the v2 path.
  `discover_from_profile()` (`crates/openshell-providers/src/discovery.rs`)
  picks up AWS_* env vars via `discovery.credentials` in the YAML.
- L7 router patterns in `crates/openshell-sandbox/src/l7/inference.rs`
  — orthogonal to the provider registry.

The discovery test in the deleted file goes with it; v2 doesn't have
an established per-provider env-var-pickup unit test pattern, and
other YAML-only registrations (none today, but this is the new
direction) won't carry one either.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* fix(providers): include aws_session_token in discovery + update profile assertion

Two fixes from johntmyers's gator-agent re-check on NVIDIA/OpenShell#1704:

1. `providers/aws-bedrock.yaml`: add `aws_session_token` to
   `discovery.credentials`. The credential is declared in the profile
   but was missing from the discovery scan list, so Providers v2
   `--from-existing` would silently drop temporary AWS credentials
   (STS / IRSA scenarios).

2. `crates/openshell-server/src/grpc/provider.rs`: update the static
   `list_provider_profiles_returns_built_in_profile_categories`
   assertion to include `aws-bedrock` at alphabetical position 0.
   Adding `providers/aws-bedrock.yaml` to BUILT_IN_PROFILE_YAMLS made
   the prior `["claude-code", "github", "nvidia"]` expectation stale.

Remaining blockers from the same review (deferred to follow-up
commits): `inference::profile_for` registration for aws-bedrock,
user-facing provider + inference-routing docs, and an
`upsert_cluster_inference_route` integration test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* feat(inference): register aws-bedrock profile (bridge-fronted) + docs

Addresses johntmyers's blocking review feedback on PR #1704:
"aws-bedrock still is not wired into the managed inference.local route
registry. profile_for only registers openai, anthropic, and nvidia, so
inference set --provider <aws-bedrock-provider> will reject this
provider before the new sandbox L7 patterns can be used."

Approach: register aws-bedrock as a *bridge-fronted* upstream — the
router does not inject any auth header on outbound requests; the
configured BEDROCK_BASE_URL is expected to point at a translating
bridge / Bedrock-compatible proxy that handles auth in its own pod.
This is the shape the L7 patterns commit (8b30211a) and the YAML
profile (6b51e1a6) were designed for. SigV4 signing for direct AWS
Bedrock is a separate follow-up; see PR thread.

Changes:

- core::inference::AuthHeader: add `None` variant for upstreams that
  authenticate themselves.
- core::inference: add AWS_BEDROCK_PROFILE static + register in
  profile_for. Default base URL is bedrock-runtime.us-east-1, override
  via BEDROCK_BASE_URL config-key (mirrors ANTHROPIC_BASE_URL pattern).
  Empty credential_key_names + auth: None means no router-side
  credential lookup at route time.
- router::backend: handle AuthHeader::None as a no-op (skip auth
  injection).
- server::inference::resolve_provider_route: gate find_provider_api_key
  on auth != None. aws-bedrock providers with empty credentials now
  resolve cleanly. Updated the unsupported-type error message to
  include aws-bedrock in the supported list.
- server::inference tests: add positive
  upsert_cluster_route_succeeds_for_aws_bedrock_without_api_key test
  covering the new code path end-to-end (provider with empty creds +
  BEDROCK_BASE_URL config → upsert succeeds → resolved route has
  empty api_key + provider_type aws-bedrock + bridge URL).
- core::inference tests: profile_for_known_types covers aws-bedrock,
  case-insensitive lookup, plus three new aws-bedrock-specific tests
  (auth: None, no credential keys, bedrock-specific protocols).
- docs/sandboxes/inference-routing.mdx: header forwarding row
  mentions aws-bedrock has no passthrough headers; new tabs in
  Supported API Patterns (InvokeModel + InvokeModelWithResponseStream)
  and Create a Provider (with the bridge-fronted shape note + SigV4
  deferral).
- docs/sandboxes/manage-providers.mdx: new row in Supported Provider
  Types table; new row in Supported Inference Providers table.

Verification (in dev container):
- cargo check -p openshell-core -p openshell-router -p openshell-server: clean
- cargo test -p openshell-core --lib inference: 14/14 pass (incl. 3 new)
- cargo test -p openshell-server --lib inference::tests::upsert: 6/6 pass
  (incl. new aws-bedrock test)
- cargo fmt --check: clean
- cargo clippy --all-targets -D warnings: clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* fix(aws-bedrock): bridge-only YAML, doc actual cmd shape, neg test

Addresses four findings from gator-agent's #1704 re-check on 4ab587f1:

- **Item 5** (YAML collects unused AWS creds): mark all four AWS
  credentials `required: false` and clear `discovery.credentials`.
  Bridge-fronted routing intentionally does not consume AWS
  credentials, so `--from-existing` no longer scans for them. The
  credentials remain in the schema (not deleted) so the SigV4
  follow-up can flip them back without a schema migration. Added a
  multi-line description that names the bridge-fronted shape and the
  SigV4 deferral so readers don't have to cross-reference the PR
  thread.

- **Item 3** (docs show command that the CLI rejects): rewrite the
  Create-a-Provider example for AWS Bedrock to use the actual
  required shape — placeholder `--credential AWS_ACCESS_KEY_ID=
  unused-bridge-fronted-shape` plus the `--config BEDROCK_BASE_URL`.
  The placeholder satisfies the gRPC handler's
  `provider.credentials.is_empty()` rejection without expanding
  server-side validation; the router ignores it on the outbound path
  because `auth: AuthHeader::None` skips header injection. Operators
  see a clearly-labeled placeholder in `provider get` output.

- **Item 1** (validator probe): document `--no-verify` as required
  for `openshell inference set --provider <aws-bedrock>` since the
  default validation probe doesn't recognize the
  `aws_bedrock_invoke` / `aws_bedrock_invoke_stream` protocols. Doc
  now shows the full `provider create` + `inference set --no-verify`
  flow with rationale for both decisions inline.

- **Item 6** (docs polish): `inference-routing.mdx` summary row now
  lists AWS Bedrock alongside NVIDIA, Anthropic, Vertex AI, and
  OpenAI-compatible providers, with the bridge-fronted caveat
  inline.

Test additions in `crates/openshell-server/src/inference.rs`:

- Renamed the existing aws-bedrock test from
  `..._without_api_key` to `..._with_bridge_url` and updated it to
  use a placeholder credential (mirroring the doc-recommended
  pattern operators will copy-paste). The `auth: None` path still
  produces an empty `api_key` on the resolved route — the test now
  documents that the credential is *stored* but not *used*.
- Added `upsert_cluster_route_rejects_aws_bedrock_without_bedrock_base_url`:
  the negative half of johntmyers' "successfully used by
  upsert_cluster_inference_route or intentionally rejected with a
  clear documented error" ask. With
  `default_base_url: ""` and no `BEDROCK_BASE_URL` config, route
  resolution returns `InvalidArgument` naming the missing base_url
  rather than silently forwarding prompts to AWS Bedrock with no
  usable auth.

Verification (in dev container):
- cargo test -p openshell-core --lib inference: 18/18 (incl. 3 new)
- cargo test -p openshell-server --lib inference::tests::upsert: 8/8
  (incl. 2 new aws-bedrock cases — positive + negative)
- cargo fmt --check: clean
- cargo clippy --all-targets -D warnings: clean

Item 2 (router-side enforcement of operator-configured Bedrock model
path, replacing the current verbatim path forwarding + body-only
model rewrite) is the remaining blocker and is genuinely separable —
it touches the L7 router with streaming-aware test coverage.
Deferring to its own commit so the security-critical change gets the
review attention it deserves.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* fix(router): enforce operator-configured Bedrock model in request path

Closes the security-blocking item from gator-agent's #1704 re-check
on 4ab587f1: "Bedrock carries the model id in /model/{modelId}/invoke,
but the router currently forwards the caller's original path and only
rewrites JSON body model. That lets sandbox code choose a different
upstream model than the operator-configured route model, and may also
mutate native Bedrock request bodies incorrectly."

Two changes in `prepare_backend_request`:

1. **Path rewrite for Bedrock routes.** Before computing the upstream
   URL, parse the inbound path's `/model/<id>/invoke[-with-response-stream]`
   shape and substitute the operator-configured `route.model` for the
   caller-supplied model segment. Sandbox code that hardcodes a
   different model still works (we don't reject on mismatch), but the
   operator's configured model is what reaches the upstream / bridge.
   If the inbound path is somehow not a recognized Bedrock shape on a
   Bedrock route (the L7 pattern detector upstream of the router
   should never produce this combination), reject with
   RouterError::Internal naming the offending path rather than
   forwarding verbatim.

2. **Skip body-model injection for Bedrock routes.** The existing body
   rewriter unconditionally inserts `route.model` into the JSON body
   for non-Vertex routes. AWS Bedrock InvokeModel encodes the model
   in the URL path; the body is the raw provider-specific payload
   (Anthropic Messages for Claude, Mistral payload for Mistral, etc.)
   and must not be mutated. The branch ordering is now:
   needs_vertex_anthropic_version → strip body model + inject
   anthropic_version; route_is_bedrock → leave body alone; else →
   inject route.model (existing default).

New helpers, all in `crates/openshell-router/src/backend.rs`:

- `route_is_bedrock(route)` — true when route.protocols contains
  aws_bedrock_invoke or aws_bedrock_invoke_stream.
- `parse_bedrock_invocation_path(path)` — returns
  Some((model_id, "/invoke" | "/invoke-with-response-stream")) for
  paths matching the recognized Bedrock shapes. Strips query strings.
  Rejects empty model ids and multi-segment ids (defense-in-depth
  matching the L7 pattern detector's existing guards).
- `rewrite_bedrock_path(route, path)` — returns the path with the
  caller's model segment replaced by route.model.

Test coverage in the same file (9 new tests):

- parse_bedrock_invocation_path: positive cases for both invoke
  variants, query-string stripping; negative cases for empty model id,
  multi-segment id, unknown action, wrong prefix, missing slash.
- route_is_bedrock: matches both protocol variants singly and
  combined; rejects openai_chat_completions.
- rewrite_bedrock_path: substitutes operator model on both invoke
  variants; returns None for non-Bedrock paths.
- bedrock_route_rewrites_model_in_path_and_preserves_body
  (wiremock end-to-end): caller sends /model/some-other-model/invoke
  with a body containing model: "caller-supplied-model-name". Mock
  asserts the upstream receives /model/<operator-model>/invoke and the
  body's model field is the caller's value (NOT route.model) — proves
  both the path rewrite and the body preservation.
- bedrock_route_streaming_rewrites_model_in_path: same contract for
  invoke-with-response-stream.
- bedrock_route_rejects_non_bedrock_path: defense-in-depth coverage of
  the Internal-error path when a Bedrock route receives a path that
  doesn't match Bedrock shape.

Verification (in dev container):
- cargo test -p openshell-router --lib: 53/53 (incl. 9 new)
- cargo fmt --check: clean
- cargo clippy -p openshell-core -p openshell-router -p openshell-server
  --all-targets -- -D warnings: clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* fix(sandbox/l7): declare framing for Bedrock patterns

Closes the buffered-vs-streaming framing warning from gator-agent's
re-check on 4ab587f1: "Bedrock InvokeModel should be buffered while
InvokeModelWithResponseStream is streaming. Please add framing/coverage
so /model/{id}/invoke cannot be corrupted by the streaming proxy's
truncation/error-frame behavior."

The InferenceApiPattern struct gained a `framing: ResponseFraming`
field upstream after the original Bedrock-patterns commit (#22b78cff)
landed; the cherry-pick onto current upstream/main left the two
Bedrock entries without the new field. Fixed here:

- aws_bedrock_invoke (POST /model/{id}/invoke):
    framing = ResponseFraming::Buffered
  InvokeModel returns one JSON object the caller decodes whole. Sending
  it through the streaming proxy would risk a mid-body size-cap
  truncation or idle-timeout failure appending an SSE error event onto
  bytes the caller decodes as one JSON body — the same corruption mode
  that drove the existing embeddings + model-discovery to Buffered.
- aws_bedrock_invoke_stream (POST /model/{id}/invoke-with-response-stream):
    framing = ResponseFraming::Streaming
  InvokeModelWithResponseStream returns an AWS event-stream of binary
  chunks; the caller wants chunks incrementally, so the streaming proxy
  path is correct.

Two new tests in `crates/openshell-sandbox/src/l7/inference.rs` pin
down the contract:

- aws_bedrock_invoke_is_buffered — detect_inference_pattern returns a
  Buffered pattern for /model/<id>/invoke, with explanatory message
  naming the corruption mode being prevented.
- aws_bedrock_invoke_stream_is_streaming — same shape, asserting
  Streaming for /model/<id>/invoke-with-response-stream.

Verification (in dev container):
- cargo check -p openshell-sandbox: clean (was failing on missing
  `framing` field before this commit)
- cargo test -p openshell-sandbox --lib l7::inference::tests::aws_bedrock:
  7/7 (incl. 2 new framing tests)
- cargo fmt --check: clean
- cargo clippy --all-targets -- -D warnings: clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* fix(inference): drop aws_bedrock_invoke_stream until protocol-aware errors land

Per PR #1704 review (johntmyers): defer Bedrock streaming to a follow-up
that also wires protocol-aware error framing for the AWS event-stream
shape. Until then, surfacing `/model/{id}/invoke-with-response-stream`
risks shipping responses the sandbox cannot interpret on failure.

- AWS_BEDROCK_PROTOCOLS no longer advertises `aws_bedrock_invoke_stream`.
- The L7 inference pattern table drops the streaming entry; only
  `aws_bedrock_invoke` (buffered) is recognized.
- Test `aws_bedrock_invoke_stream_pattern_is_deferred` asserts no
  pattern claims that protocol so the gap is visible.

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* fix(inference): validate AWS Bedrock model_id at route + rewrite, preserve query

Per PR #1704 review (johntmyers, gator agent): the operator-configured
Bedrock model_id flows verbatim into the upstream URL path; the previous
plumbing left no enforcement that the value was a single benign path
segment, opening a path-injection vector.

Defense in depth, both layers:

* `openshell-server::inference`: new `validate_aws_bedrock_model_id`
  rejects empty, leading/trailing whitespace, `/`, `\\`, `?`, `#`, `%`,
  `..`, control/whitespace characters. Wired into `resolve_provider_route`
  ahead of base_url resolution so the route store cannot persist a
  malformed model_id. Mirrors `validate_vertex_model_id` exactly.

* `openshell-router::backend`: `rewrite_bedrock_path` now refuses to
  construct the upstream URL unless `route.model` passes
  `is_valid_bedrock_model_id`, so even a stale or hand-edited route
  cannot reach the wire. The parser also drops the
  `/invoke-with-response-stream` arm to match the protocol catalog.

* `parse_bedrock_invocation_path` returns the `?`-prefixed query tail
  as a third element; `rewrite_bedrock_path` re-attaches it so any
  caller-supplied query string is preserved through the model rewrite.

Tests: 5 unit tests for the validator, 1 integration test that
exercises every unsafe-model_id reject path through
`upsert_cluster_inference_route`, plus a router-side rewrite-rejects
test covering 11 unsafe `route.model` values. All 72 server inference
tests + router tests pass.

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* fix(aws-bedrock): make built-in profile non-egress-granting; tighten docs

Per PR #1704 review (johntmyers): a single profile that is "usable as
is" — not a profile that auto-grants direct AWS Bedrock egress to any
sandbox that selects it. The bridge IS the egress point and is
operator-managed; the profile must not implicitly punch a hole through
the cluster's network policy on its behalf.

* `providers/aws-bedrock.yaml`: clear `endpoints` and `binaries` to
  empty arrays. Rewrite the description to spell out that the profile
  is intentionally non-egress-granting, that operators are responsible
  for declaring their bridge's egress endpoint and binary attribution,
  and that the SigV4 follow-up will repopulate these fields once
  router-side signing exists.

* `docs/sandboxes/inference-routing.mdx`:
  - Drop the `InvokeModelWithResponseStream` row from the supported
    patterns table (matches the protocol-catalog + L7-pattern drop).
  - Update the `--no-verify` paragraph to reference only
    `aws_bedrock_invoke`.
  - Generalise the placeholder-credential rationale: any
    standalone-router profile registering `AuthHeader::None` will hit
    the same non-empty-credentials structural requirement.

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* style(router): satisfy cargo fmt for Bedrock query-tail extraction

CI's rustfmt rejected the one-line `let (path_only, query_tail) = path.find('?').map_or(...)`
shape from commit c51160e4 and required the chained-method layout
instead. Functional behaviour is unchanged; tests still pass.

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* style(router): drop unnecessary `crate::` prefix on RouterError matcher

`RouterError` is already imported at the top of the file, so the
two test-side `matches!(result, Err(crate::RouterError::UpstreamProtocol(_)))`
uses trip clippy's `unused_qualifications` under `-D warnings`. Drop
the prefix on both sites; functional behaviour is unchanged.

These warnings predate this PR (originated in 25abc9e3c on 2026-06-07)
but NVIDIA's `rust:lint` re-runs because this PR touches
`backend.rs`, so the lint regression surfaces here.

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

* style(server): silence unused-variable lint on first shutdown_tx

`crates/openshell-server/src/lib.rs` declares `(shutdown_tx, shutdown_rx)`
twice in `run_server` — the first pair (introduced by upstream #1577
reconciler-lease work) only consumes `shutdown_rx`, leaving the first
`shutdown_tx` unused. Clippy's `-D warnings` flags it on workspace
lint. Rebasing this Bedrock-scoped PR onto current upstream/main
exposes the regression because we re-touch the file and the lint
re-runs.

Prefix the unused half with an underscore so the workspace lint is
clean. Behaviour is unchanged — only the second shutdown_tx (line
~425) ever sends a shutdown signal today.

This is a drive-by upstream fix unrelated to the Bedrock provider
work; keeping it separate so it can be cherry-picked or reverted
independently of the Bedrock commits.

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>

---------

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-23 13:10:37 -07:00
Robert Sturla 48545cfb59 feat(sandbox): add GCE metadata emulator for Google Cloud (#1763)
* feat(core): add shared GCP constants module

Single source of truth for GCP naming: env var aliases, provider config
keys, token search order, and Vertex-specific env vars. Consumed by
openshell-server, openshell-providers, and openshell-sandbox.

- Add google_cloud.rs with metadata emulator host and loopback address
- Define PROJECT_ID, REGION, and SERVICE_ACCOUNT_EMAIL env var aliases
- Add provider config key constants for gcp provider implementations
- Define TOKEN_ENV_KEYS search order (SA token takes priority over ADC)
- Add Vertex-specific env vars for Goose and Claude Code SDK integration
- Add STATIC_CONFIG_KEYS as union of all alias arrays for env resolution
- Export module via openshell-core lib.rs

Signed-off-by: Robert Sturla <rsturla@redhat.com>

* feat(providers): add google-cloud and vertex provider plugins

Add GoogleCloudProvider and VertexProvider implementing inject_env to
project GCP config (project ID, region, SA email, metadata host) into
sandbox environment variables. Replace the inline Vertex AI env
injection in the server with the registry-based inject_env dispatch.

Also adds the google-cloud.yaml provider profile with SA JWT and ADC
OAuth2 credential refresh flows.

Signed-off-by: Robert Sturla <rsturla@redhat.com>

* feat(sandbox): add GCE metadata emulator for GCP

Add a loopback HTTP server on 127.0.0.1:8174 inside the sandbox
network namespace that emulates the GCE instance metadata API.
GCP client SDKs discover it via GCE_METADATA_HOST and obtain
credential placeholders that the proxy resolves to real tokens
at egress.

Add metadata_server module with MetadataHandler trait and
netns-aware TCP binding via std::thread (not spawn_blocking)
to avoid tokio pool namespace contamination
Add google_cloud_metadata module implementing the GCE metadata
API subset (token, project-id, email, scopes, service-accounts)
Add child_env_resolved() and gcp_token_response() to
ProviderCredentialState for GCP-aware credential projection
Wire metadata server into sandbox lifecycle before SSH handler
Collapse multi-line HTTP response format string into single line

Signed-off-by: Robert Sturla <rsturla@redhat.com>

* docs(sandbox): add GCP credentials documentation

Document the google-cloud provider setup for ADC and service account
flows, injected environment variables, metadata emulator behavior, and
network policy configuration for GCP APIs.

Signed-off-by: Robert Sturla <rsturla@redhat.com>

* feat(cli): support --from-gcloud-adc for google-cloud providers

Widen --from-gcloud-adc to accept google-cloud providers. The ADC
credential key is derived from the provider profile rather than
hardcoded per type, so future GCP provider types get ADC support by
declaring the right refresh metadata in their profile YAML.

Add ProviderTypeProfile::adc_credential() to find the ADC-compatible
credential from a profile's refresh metadata. Remove unused
VERTEX_AI_ADC_TOKEN_KEY and GCP_ADC_TOKEN_KEY constants.

Signed-off-by: Robert Sturla <rsturla@redhat.com>

---------

Signed-off-by: Robert Sturla <rsturla@redhat.com>
2026-06-23 15:05:07 -05:00
John T. Myers 48a7d09e82 feat(providers): support profile updates (#1914)
* feat(providers): support profile updates

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(providers): require CAS for profile updates

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(providers): serialize profile update validation

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(providers): serialize profile import invariants

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(providers): require profile update target id

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

---------

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-06-23 11:34:46 -07:00
ddurst-nvidia d64542f69d fix(supervisor-network): block h2c L7 tunnel escape (#1967)
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
2026-06-22 15:48:55 -07:00
Shiju ffc102a02d fix(cli): verify forward listener before success (#1880)
* fix(cli): verify forward listener before success

Wait for a connectable local forward listener before reporting
foreground forwarding success.

Fail background forwarding when the forked SSH process cannot be
tracked, probe the listener before writing the PID file, and
terminate the tracked SSH process if the listener never opens.

Document that forwarded URLs are printed only after listener
health is proven.

Signed-off-by: Shiju <shiju@nvidia.com>

* fix(cli): harden background forward cleanup

Signed-off-by: Shiju <shiju@nvidia.com>

* fix(cli): validate background forward cleanup pid

Signed-off-by: Shiju <shiju@nvidia.com>

* fix(cli): harden forward PID matching and teardown

Require background forward cleanup to prove a candidate PID is the
OpenShell-generated SSH forwarding process before reporting it active
or signaling it. Match the exact -L forward argument and sandbox ID
instead of by substring, so a request for port 80 cannot collide with
an existing 8080 forward and terminate the wrong process. Reject legacy
PID records as non-authoritative and route port-only lookup through
validated live forward state.

Recover the exact process argv from /proc/<pid>/cmdline on Linux so a
ProxyCommand whose executable path contains whitespace is parsed
correctly; other platforms keep the best-effort ps command-line parse.

After listener readiness succeeds, terminate the revalidated background
ssh process if the PID-file write fails, instead of returning early and
leaving a reachable but untracked forward that forward stop/list cannot
manage.

Cover adversarial matcher cases (port and sandbox-id collisions,
command spoofing, remote-command token confusion, whitespace executable
paths) and the PID-file write-failure teardown path with focused tests.

Signed-off-by: Shiju <shiju@nvidia.com>

---------

Signed-off-by: Shiju <shiju@nvidia.com>
2026-06-22 12:20:28 -07:00
Evan Lezar 4c75b8542b fix(server): share gateway shutdown channel (#1945)
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-17 10:48:54 +02:00
mmilutinovic371 36bb9e3ec2 feat(providers): add DeepInfra as a built-in inference provider (#1902)
* feat(providers): add DeepInfra as a built-in inference provider (v2 only)

- Adds `deepinfra` as a built-in Providers v2 profile (`providers/deepinfra.yaml`)
  with inference category, Bearer auth, and `DEEPINFRA_API_KEY` discovery
- Adds `DEEPINFRA_PROFILE` to inference routing so `inference.local` works
  with the `deepinfra` provider type
- Fixes `build_backend_url` to strip `/v1` from request paths when the base
  URL contains `/v1/` as an internal segment (e.g. `api.deepinfra.com/v1/openai`),
  preventing double-versioned paths like `.../v1/openai/v1/chat/completions`
- Updates `docs/sandboxes/providers-v2.mdx` and `docs/sandboxes/manage-providers.mdx`
  with DeepInfra entries; removes the old v1 workaround row that used `openai`
  type with `OPENAI_API_KEY`

Signed-off-by: Milos Milutinovic <codemastermilos@gmail.com>

* fix(providers): address gator review findings for DeepInfra provider

- Narrow build_backend_url /v1 dedupe to URLs whose path component is
  exactly /v1 or starts with /v1/ — prevents regression on proxy
  endpoints where /v1 is buried deeper (e.g. /api/v1/openai); add
  regression test for the nested proxy path case
- Add deepinfra provider plugin with DEEPINFRA_API_KEY discovery,
  registered in ProviderRegistry so known_types() and TUI include it
- Add deepinfra to unsupported-inference-provider error message in
  openshell-server for accurate user-facing debugging guidance
- Add deepinfra to openai_compatible_profiles_include_embeddings test
  to lock in the OpenAI-compatible protocol contract

Signed-off-by: Milos Milutinovic <codemastermilos@gmail.com>

* fix(router): handle /v1 as final path segment in build_backend_url dedup

Extends the /v1 deduplication logic to also strip /v1 from request paths
when the base URL's path ends with /v1 (e.g. https://api.groq.com/openai/v1).
The previous fix only matched paths starting with /v1/, which regressed
providers like Groq whose base path has /v1 as the last segment rather than
the first. The nested-proxy exclusion (e.g. /api/v1/openai) is preserved
since /v1 appears in the middle — neither first nor last segment. Adds a
regression test for the Groq-style base URL.

Signed-off-by: Milos Milutinovic <codemastermilos@gmail.com>

* fix(providers): add deepinfra telemetry bucket and update profile list test

- Add DeepInfra variant to ProviderProfile telemetry enum and from_raw()
  mapping so deepinfra providers are tracked in their own bucket rather
  than falling through to Custom
- Map deepinfra in telemetry_provider_profile() in openshell-server
- Add deepinfra to list_provider_profiles_returns_built_in_profile_categories
  test (sorted between cursor and github)
- Update architecture/gateway.md inference provider list to include deepinfra

Signed-off-by: Milos Milutinovic <codemastermilos@gmail.com>

* style(router): apply cargo fmt to backend.rs

Signed-off-by: Milos Milutinovic <codemastermilos@gmail.com>

---------

Signed-off-by: Milos Milutinovic <codemastermilos@gmail.com>
2026-06-16 16:39:03 -07:00
Yuedong Wu ff028ce0df feat(server): support TLS certificate hot-reload (#1870)
* feat(server): support TLS certificate hot-reload

Signed-off-by: Yuedong Wu <dwcn22@outlook.com>

* refactor(server): extract shared TLS test utilities

Signed-off-by: Yuedong Wu <dwcn22@outlook.com>

---------

Signed-off-by: Yuedong Wu <dwcn22@outlook.com>
2026-06-16 16:16:13 -07:00
Taylor Mutch fd6cbf6b6c fix(server): retry sandbox delete phase conflicts (#1905)
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-06-16 10:42:03 -07:00
Evan Lezar 294c64eed1 fix(gpu): prefer single CDI devices for local runtimes (#1675)
Prefer a single CDI-qualified device when Docker or Podman resolves the default GPU request to one GPU.

Allow nvidia.com/gpu=all only as a WSL2 all-only compatibility fallback, using Docker daemon info and Podman's /dev/dxg probe to identify that case.

Update driver docs, architecture notes, and GPU e2e coverage for the default selection behavior.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-16 10:14:49 +02:00
Taylor Mutch ec71b1ac73 fix(sandbox): apply initial OCSF JSON setting (#1921) 2026-06-15 22:20:38 -07:00
Jeff MAURY ed65bfd86d feat(cli): add JSON/YAML output format to provider list command (#1830)
* feat(cli): add JSON/YAML output format to provider list command

Closes #1745

Add -o/--output flag support (json, yaml, table) to openshell provider list
by migrating it to use the existing generic output formatter from #1750.
This follows the established pattern used by sandbox list, gateway list,
and provider list-profiles commands.

Changes:
- crates/openshell-cli/src/run.rs: Add provider_to_json() helper that maps
  Provider proto fields to JSON, including only credential keys (not values)
  for security. Add output parameter to provider_list() and insert early-return
  for structured formats.
- crates/openshell-cli/src/main.rs: Add output field to ProviderCommands::List
  with conflicts_with constraints between names and output flags. Update
  invocation to pass output parameter.
- crates/openshell-cli/tests/provider_commands_integration.rs: Update
  provider_list() call to include output parameter.

Security: The provider_to_json() helper only exposes credential keys, never
credential values, preventing CWE-532 (insertion of sensitive information
into output).

* refactor(cli): expose only config keys in provider list JSON/YAML output

Update provider_to_json() to only include config keys (not values) in
structured output, matching the security model used for credentials.
This prevents potential exposure of sensitive configuration values.

Changed field name from "config" to "config_keys" to clarify that only
keys are included, consistent with "credential_keys" field naming.

* test(cli): add comprehensive tests for provider list JSON/YAML output

Add three layers of test coverage for provider list structured output:

1. Unit tests in run.rs for provider_to_json() (8 tests):
   - Core field validation (id, name, type)
   - Security: Credential keys exposed, values hidden
   - Security: Config keys exposed, values hidden
   - Metadata field inclusion/omission logic
   - Empty field omission behavior
   - Credential expiration times

2. CLI parsing tests in main.rs (4 tests):
   - JSON/YAML output format acceptance
   - Conflicts between --names and -o flags

3. Integration tests in provider_commands_integration.rs (3 tests):
   - JSON output execution with populated provider
   - YAML output execution with populated provider
   - Empty provider list edge case

Security focus: Multiple tests verify that credential and config VALUES
are never exposed in JSON/YAML output, only keys. This prevents CWE-532
(insertion of sensitive information into output).

All 778 existing unit tests continue to pass, plus 15 new tests added.

* feat(cli): format created_at as human-readable datetime in provider list JSON/YAML

Update provider_to_json() to format created_at_ms as a human-readable
datetime string using format_epoch_ms(), matching the pattern used by
sandbox_to_json().

Changes:
- Field name: created_at_ms → created_at
- Value format: Raw milliseconds → "YYYY-MM-DD HH:MM:SS"
- Implementation: Use format_epoch_ms(meta.created_at_ms)

This change:
- Aligns provider list output with sandbox list output for consistency
- Makes JSON/YAML output more human-readable for interactive use
- Follows the established pattern across all *_to_json() functions

Breaking change: Field renamed and type changed (number → string).
However, this feature was just added in PR #1830 and hasn't been
released yet, so no users are affected.

Example output:
Before: {"created_at_ms": 1234567890000}
After:  {"created_at": "2009-02-13 23:31:30"}

Tests updated:
- provider_to_json_includes_metadata_fields_when_present: Assert formatted string
- provider_to_json_omits_zero_metadata_fields: Update field name
- provider_to_json_formats_created_at_as_human_readable: New test validating format

* docs(cli): document JSON/YAML output format for provider list

Add documentation for the -o/--output flag on provider list command,
following the established pattern from sandbox list documentation.

Changes:
- Add examples showing -o json and -o yaml usage
- Explain output structure (metadata, credential keys, config keys)
- Emphasize security design (keys-only exposure, never values)
- Cross-reference existing Credential Injection section

The documentation matches the feature added in a26a6341 and refined
in subsequent commits (509db497, a5f59ce9).

Signed-off-by: Jeff MAURY <jmaury@redhat.com>

---------

Signed-off-by: Jeff MAURY <jmaury@redhat.com>
2026-06-15 14:52:50 -07:00
rhubenovandEvan Lezar 1ca23bc5c8 refactor(openshell-sandbox): Split sandbox into process and network subcrates. (#1650)
* refactor(sandbox): extract run_networking from run_sandbox

Lifts TLS state generation, network namespace setup, proxy startup,
bypass monitor spawn, and SSH-side proxy URL / netns FD computation
out of run_sandbox into a sibling async fn `run_networking` that
returns a Networking struct. The identity cache moves with it (only
consumed by the proxy). Entrypoint PID allocation moves just above
the call site so it can be passed in.

No behavior changes — same OCSF emits, same async order, same RAII
lifetimes for the proxy and bypass-monitor handles, now held by the
returned Networking value in run_sandbox's frame.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(sandbox): extract run_process and lift netns to run_sandbox

Lifts the post-networking tail of `run_sandbox` (zombie reaper, SSH
server, supervisor session, process spawn, OPA probe, policy poll loop,
denial aggregator, wait/exit) into a sibling async fn `run_process`.

Also moves network namespace creation out of `run_networking` into a new
`create_netns_for_proxy` helper invoked from `run_sandbox`, so
`run_networking` is purely the proxy component (OPA evaluation, TLS
interception, credential injection, inference routing, gRPC control
API). The netns is then borrowed into both `run_networking` and
`run_process`.

No behavior change.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* chore(workspace): scaffold openshell-supervisor-networking and openshell-supervisor-process crates

Add empty placeholder crates that subsequent commits will populate as the
sandbox decomposition proceeds. Both crates compile clean as part of the
workspace and are picked up automatically by the existing
`members = ["crates/*"]` glob.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(core): lift DenialEvent to openshell-core

The DenialEvent struct is emitted by both the proxy/L7 layer (networking-side)
and the bypass monitor (process-side), and crosses the run_networking ->
run_process API boundary. Move it to openshell-core so the eventual
supervisor-networking and supervisor-process crates can both reference it
without depending on each other. DenialAggregator and the channel/flush
helpers stay in openshell-sandbox for now.

A thin `pub use openshell_core::DenialEvent;` re-export from
denial_aggregator.rs keeps every existing `crate::denial_aggregator::DenialEvent`
call site resolving without further edits.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(core): lift normalize_path to openshell-core

Move the lexical path-normalization helper from openshell-policy to
openshell-core::paths so it can be reached from crates that sit below
openshell-policy in the dependency graph. openshell-policy keeps its
existing public API via a `pub use` re-export, so all current call sites
(e.g. openshell-sandbox/src/policy.rs) continue to resolve unchanged.

This is a prerequisite for lifting openshell-sandbox/src/policy.rs into
openshell-core: that file's `From<ProtoFilesystemPolicy>` impl calls
normalize_path, and lifting it as-is would cycle through openshell-policy.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(core): lift SandboxPolicy and friends to openshell-core

Move openshell-sandbox/src/policy.rs (SandboxPolicy, NetworkPolicy,
ProxyPolicy, FilesystemPolicy, LandlockPolicy, ProcessPolicy, NetworkMode,
LandlockCompatibility, plus their Proto* TryFrom/From impls) to
openshell-core/src/policy.rs.

Both prospective supervisor leaves (networking and process) dispatch on
SandboxPolicy. Hosting it in openshell-core lets either leaf reach for it
without depending on the other (or on the future orchestrator).

The From<ProtoFilesystemPolicy> impl now calls the in-crate
openshell_core::paths::normalize_path lifted in the previous commit, which
is what made this move cycle-free.

Update all crate::policy::* call sites in openshell-sandbox to
openshell_core::policy::*.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move child_env from openshell-sandbox

child_env (proxy_env_vars, tls_env_vars) is process-side only — consumed
by process.rs and ssh.rs. With the orchestrator staying in
openshell-sandbox (Shape A), openshell-sandbox depends on the new leaf
crates, so process-only modules can land in
openshell-supervisor-process directly.

Add openshell-supervisor-process as a path dependency of
openshell-sandbox. Update process.rs and ssh.rs to import from
openshell_supervisor_process::child_env.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move skills from openshell-sandbox

Move the static skills installer (and its embedded resource directory)
out of openshell-sandbox into openshell-supervisor-process. The module
is process-side only — invoked once during sandbox start to drop
agent skill files into the workspace — and has no cross-leaf consumers.

Adds miette as a dependency and tempfile as a dev-dependency on
openshell-supervisor-process.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-networking): move mechanistic_mapper from openshell-sandbox

Move the mechanistic mapper (HTTP method/path → operation classifier
that derives policy proposals from connection summaries) out of
openshell-sandbox into openshell-supervisor-networking. Single internal
caller (run_policy_poll_loop in lib.rs) and only depends on
openshell-core + tracing — no cross-leaf entanglement.

First population of the openshell-supervisor-networking crate; adds
openshell-core and tracing as dependencies.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(core): lift procfs to openshell-core

Move procfs (PID lookups, ancestor walking, /proc/net/tcp socket-owner
resolution, file SHA256 hashing) from openshell-sandbox into
openshell-core. The module is consumed cross-leaf — by bypass_monitor
on the process side and by identity / proxy on the networking side —
so it has to sit below both leaves.

Adds tracing, sha2, and hex as dependencies on openshell-core.
Updates the three call sites in openshell-sandbox to import from
openshell_core::procfs.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-networking): move identity from openshell-sandbox

Move BinaryIdentityCache (path → SHA256 cache used to identify the
process behind an outbound connection) from openshell-sandbox into
openshell-supervisor-networking. The cache is consumed only by the
networking-side proxy and the orchestrator; with procfs already in
openshell-core there are no remaining cross-leaf dependencies.

Adds miette as a dependency and tempfile as a dev-dependency on
openshell-supervisor-networking. Adds a Default impl for
BinaryIdentityCache to satisfy clippy::new_without_default now that
the type is publicly exposed across crates.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move agent-proposals flag from openshell-sandbox

Move AGENT_PROPOSALS_ENABLED, agent_proposals_enabled(), and the
test-only ProposalsFlagGuard out of openshell-sandbox into
openshell-supervisor-process::proposals. The flag is read only by the
process-side policy_local route handler and the orchestrator; lifting
it to openshell-core would have made core carry sandbox-owned runtime
state without buying anything.

The test-only ProposalsFlagGuard is still consumed from networking-side
l7/rest tests today (until the wider Q2 OCSF/gRPC injection work lands).
Expose it via a new optional `test-helpers` feature on
openshell-supervisor-process so test crates opt in explicitly without
pulling tokio sync primitives into production builds.

openshell-sandbox keeps its existing crate-private path
(`crate::AGENT_PROPOSALS_ENABLED`, `crate::test_helpers`) via re-exports
so call sites and tests are unchanged.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(core): lift secrets to openshell-core

Move crates/openshell-sandbox/src/secrets.rs to crates/openshell-core/src/secrets.rs so both supervisor leaves can reach SecretResolver and the placeholder helpers without depending on openshell-sandbox.

Add base64 to openshell-core deps (only stdlib + base64 are used). Promote previously pub(crate) constructors and methods on SecretResolver to pub since cross-crate callers (provider_credentials, proxy/L7 tests) now name them across the crate boundary. Update import paths in proxy.rs, l7/{rest,relay,websocket}.rs, and provider_credentials.rs from crate::secrets to openshell_core::secrets.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(core): lift provider_credentials to openshell-core

Move crates/openshell-sandbox/src/provider_credentials.rs to crates/openshell-core/src/provider_credentials.rs. Both supervisor leaves now name ProviderCredentialState in their function signatures (run_networking takes &ProviderCredentialState, run_process takes ProviderCredentialState by value), and under Shape A leaves can't depend on openshell-sandbox, so the type must live in openshell-core.

The orchestrator (run_sandbox in openshell-sandbox) remains the only writer: it constructs ProviderCredentialState::from_environment and the policy poll loop calls install_environment on credential rotation. Both leaves stay pure readers via snapshot()/resolver().

Update import paths in proxy.rs, ssh.rs, and lib.rs from crate::provider_credentials to openshell_core::provider_credentials.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* style: rustfmt import ordering

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(ocsf): move SandboxContext singleton from openshell-sandbox

Move the process-wide OCSF SandboxContext OnceLock + LazyLock fallback + getter from openshell-sandbox/src/lib.rs into a new openshell-ocsf::ctx module. The type already lives in openshell-ocsf, so its singleton lives next to it.

Add openshell_ocsf::ctx::set_ctx() and openshell_ocsf::ctx::ctx(). The orchestrator (run_sandbox) now calls set_ctx during startup. Sandbox keeps a pub(crate) use openshell_ocsf::ctx::ctx as ocsf_ctx; re-export so the 138 existing crate::ocsf_ctx() call sites resolve unchanged.

When the sandbox modules themselves migrate into the leaf crates, they'll import openshell_ocsf::ctx directly and the re-export goes away.

Under Shape A neither leaf can depend on openshell-sandbox; both already depend on openshell-ocsf to construct events, so this adds no new dep edge.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(core): lift grpc_client to openshell-core

Both prospective leaves (supervisor-networking and supervisor-process)
need CachedOpenShellClient, AuthedChannel, and the connect/fetch
helpers. Under Shape A the leaves cannot depend on openshell-sandbox,
so the type has to live below them. openshell-core already pulls in
tonic and miette; this enables tonic's channel/tls features and adds
tokio as a direct dep.

Updates all crate::grpc_client::* call sites in openshell-sandbox to
openshell_core::grpc_client::*. No re-export shim — the call-site
count was small enough to update directly.

See architecture/plans/sandbox-split-design-choices.md for the full
rationale and trade-offs.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-networking): move denial_aggregator from openshell-sandbox

DenialAggregator and FlushableDenialSummary belong with the proxy and L7
layer that emit denials. Moves the file into openshell-supervisor-networking;
adds tokio as a regular dep there since DenialAggregator uses
tokio::sync::mpsc.

Drops the pub use openshell_core::DenialEvent re-export inside the moved
file (no longer needed cross-crate). Updates bypass_monitor.rs, proxy.rs,
and lib.rs to import openshell_core::DenialEvent directly.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move log_push from openshell-sandbox

LogPushLayer is a process-side tracing layer that streams sandbox logs
to the gateway via gRPC. Moves into openshell-supervisor-process; adds
openshell-core, openshell-ocsf, tokio-stream, tracing, and
tracing-subscriber as direct deps there.

Updates the only external call site (openshell-sandbox/src/main.rs) to
import from openshell_supervisor_process::log_push.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move bypass_monitor from openshell-sandbox

bypass_monitor reads /dev/kmsg for nftables drop log lines and emits
denial events. Pure process-side concern, called only from
run_networking which spawns it on the netns. Moves into
openshell-supervisor-process; all deps (openshell-core, openshell-ocsf,
tokio, tracing) were already declared there.

Replaces crate::ocsf_ctx() shim calls inside the moved file with
openshell_ocsf::ctx::ctx() — first leaf-side caller to import the OCSF
context singleton directly instead of going through openshell-sandbox's
re-export.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move debug_rpc from openshell-sandbox

debug_rpc is the CLI subcommand handler that exercises authenticated
gRPC calls (issue-token, refresh-token, get-config, etc.). Pure
process-side concern, called only from openshell-sandbox/main.rs.

Adds base64, hex, serde_json, sha2, and tonic (with channel/tls
features) as direct deps on openshell-supervisor-process. Updates the
single call site in main.rs.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move supervisor_session from openshell-sandbox

supervisor_session opens a bidirectional gRPC stream that lets the
gateway initiate shells inside the sandbox. Pure process-side concern,
called only from run_process. Adds uuid as a direct dep on
openshell-supervisor-process.

Replaces crate::ocsf_ctx() shim calls inside the moved file with
openshell_ocsf::ctx::ctx() — same pattern as bypass_monitor.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): lift managed_children tracker from openshell-sandbox

The MANAGED_CHILDREN set tracks PIDs of supervisor-spawned children
(entrypoint + SSH sessions) so the orchestrator's SIGCHLD reaper can
distinguish them from incidental zombies. Pure process-side concern,
moves to openshell_supervisor_process::managed_children with three
public fns: register, unregister, is_managed.

Updates lib.rs reaper, process.rs, and ssh.rs to call through the new
module path. Drops the now-unused HashSet import from lib.rs.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move sandbox hardening from openshell-sandbox

Lift the process-only hardening pieces (landlock, seccomp, PreparedSandbox,
prepare/enforce, log_sandbox_readiness, top-level apply, and
apply_supervisor_startup_hardening) from crates/openshell-sandbox/src/sandbox/
to crates/openshell-supervisor-process/src/sandbox/.

Leave netns.rs and nft_ruleset.rs in openshell-sandbox for now, since both
eventual leaf crates (supervisor-networking and supervisor-process) read from
NetworkNamespace and its final home is decided when run_networking and
run_process are extracted.

Replace crate::ocsf_ctx() shims in landlock.rs and the new linux/mod.rs with
direct openshell_ocsf::ctx::ctx() calls. Update call sites in lib.rs,
process.rs, and ssh.rs to import sandbox from openshell_supervisor_process
while keeping the netns import unchanged.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(core): lift proposals flag from openshell-supervisor-process

Move proposals.rs (AGENT_PROPOSALS_ENABLED OnceLock + agent_proposals_enabled
reader + test_helpers::ProposalsFlagGuard) from openshell-supervisor-process
to openshell-core so both eventual leaf crates can read it without depending
on each other.

The flag is process-wide singleton state initialised once during sandbox
startup and read by both the policy.local route (networking-side) and the
skills installer (process-side) — same shape as openshell_ocsf::ctx.

Move the test-helpers Cargo feature alongside it: openshell-core gains the
feature, openshell-supervisor-process loses it, and openshell-sandbox's
dev-dependency now enables openshell-core/test-helpers. Update the sandbox
re-export shim to point at openshell_core::proposals.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(core): lift netns + nft_ruleset from openshell-sandbox

Move NetworkNamespace and the nft_ruleset bypass-rule generator from
crates/openshell-sandbox/src/sandbox/linux/ to crates/openshell-core/src/netns/.
Both eventual leaf crates (supervisor-networking and supervisor-process) read
from NetworkNamespace, so it must live somewhere both can depend on without
violating the Shape A no-leaf-to-leaf rule.

Replace crate::ocsf_ctx() shims in netns with direct openshell_ocsf::ctx::ctx()
calls, matching the pattern used in already-migrated process modules. Update
super::nft_ruleset references inside netns to nft_ruleset since the module
is now a sibling sub-module of netns/mod.rs.

Add openshell-ocsf and uuid as linux-only dependencies of openshell-core, and
gate pub mod netns on target_os = "linux" since the implementation uses
netlink, ip(8), and namespace fds. Delete the now-empty sandbox/{mod.rs,
linux/mod.rs} stubs and update NetworkNamespace import paths in lib.rs and
process.rs to point at openshell_core::netns.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move process.rs and ssh.rs from openshell-sandbox

Lift the entrypoint process spawn module and the embedded SSH server
module into openshell-supervisor-process. openshell-sandbox now
re-exports ProcessHandle/ProcessStatus and calls
openshell_supervisor_process::ssh::run_ssh_server directly.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-networking): move proxy, l7, opa, policy_local from openshell-sandbox

Lift the egress proxy, L7 enforcement modules, OPA engine, and policy.local
advisor API into openshell-supervisor-networking. Move accompanying data
files (sandbox-policy.rego), test fixtures (testdata/), and integration
tests (system_inference, websocket_upgrade). Sandbox lib.rs now references
these via openshell_supervisor_networking::* and ProxyHandle::start_with_bind_addr
is exposed as pub for the orchestrator call site.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(sandbox): hoist policy poll loop and denial aggregator into orchestrator

Move the symlink-resolver, policy poll loop, and denial-aggregator flush
spawns out of run_process and into run_sandbox so run_process no longer
needs OpaEngine, retained_proto, the local policy context, the sandbox
name, the gateway endpoint for telemetry, the OCSF flag, or the denial
receiver. These long-running orchestrator-owned tasks now live alongside
the other sandbox-startup wiring, matching the design log decision in
architecture/plans/sandbox-split-design-choices.md (Q5).

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move run_process from openshell-sandbox

Lift the workload supervision entry point (zombie reaper, SSH server
spawn, supervisor session, entrypoint child spawn, exit-with-timeout)
into its own module in openshell-supervisor-process. The orchestrator
in openshell-sandbox now calls openshell_supervisor_process::run::run_process
directly. With this move run_process names only types from openshell-core,
openshell-ocsf, openshell-supervisor-process itself, std, and tokio —
no openshell-supervisor-networking dependency.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-networking): move bypass_monitor from supervisor-process

Bypass detection is network-policy enforcement: it parses nftables LOG
entries from /dev/kmsg and emits OCSF NetworkActivity / DetectionFinding
events plus DenialEvents into the same channel the proxy feeds. Its
lifetime is tied to the network namespace, not to the workload child.
Moving it to openshell-supervisor-networking puts it next to the proxy
and the denial aggregator that consume its output, and unblocks moving
run_networking out of openshell-sandbox without a leaf-to-leaf dep.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-networking): move inference route helpers from openshell-sandbox

Move build_inference_context, partition_routes, bundle_to_resolved_routes,
spawn_route_refresh, the InferenceRouteSource enum, and the route refresh
interval helpers into a new openshell-supervisor-networking::inference_routes
module along with their unit tests. The orchestrator now calls into the
networking leaf for inference context construction; the leaf owns its own
route bundle resolution end-to-end.

The new module is named inference_routes to avoid colliding with the
existing l7::inference module, which handles request-time HTTP parsing
and pattern matching rather than route bundle setup.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-networking): move run_networking from openshell-sandbox

Move the Networking handle struct, run_networking, and the Linux-only
create_netns_for_proxy helper into a new openshell-supervisor-networking::run
module. The orchestrator in openshell-sandbox now invokes
openshell_supervisor_networking::run::{create_netns_for_proxy, run_networking}
and reads the Networking fields directly; the leaf owns the entire
networking-stack startup path (CA generation, proxy task, bypass monitor,
inference context, denial channel) end-to-end.

The Networking RAII handle fields (proxy, bypass_monitor) are now public
without leading underscores so the public API satisfies clippy's
pub_underscore_fields lint while still serving as drop guards held by the
orchestrator's frame.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* fix(workspace): align Cargo deps and call sites for split crates

The recent module lifts left two Linux-only gaps that the macOS host
workspace check skipped:

- openshell-core's netns module needs libc, tempfile, and nix on Linux,
  but only openshell-ocsf and uuid were carried over.
- openshell-supervisor-process's seccomp/landlock modules need landlock
  and seccompiler, which still lived on openshell-sandbox.
- openshell-sandbox's runtime_pid_limit branch referenced an unqualified
  process:: that pointed at the old in-crate module.

Move landlock/seccompiler to supervisor-process, add the missing core
deps, qualify the call sites, and drop sandbox deps that no longer have
runtime users (landlock, seccompiler, target-gated tempfile/uuid, the
unix libc/rustix block).

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-network): rename openshell-supervisor-networking to openshell-supervisor-network

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-network): own denial-aggregator flush end-to-end

Move the denial-aggregator spawn and flush_proposals_to_gateway out of
run_sandbox and into run_networking. The networking leaf already owns
every other input (proxy + bypass_monitor as producers, denial channel,
mechanistic_mapper, denial_aggregator) and already opens its own gRPC
connections (inference_routes, policy_local) — the orchestrator was the
only piece left straddling the boundary.

Networking now drives the full path: producers -> channel -> aggregator
-> flush -> gateway. Drops denial_rx from Networking; adds sandbox_name
to run_networking so SubmitPolicyAnalysis can resolve by sandbox name
(falls back to ID when unset). Same shape as log_push in the process leaf.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-network): own symlink-resolution task

Move the OPA binary-symlink resolver out of run_sandbox and into
run_networking. The task probes /proc/<entrypoint_pid>/root/ until the
workload's mount namespace is accessible, then rebuilds the OPA engine
with resolved binary paths so policy rules match canonical names instead
of symlinks.

Both inputs (Arc<OpaEngine>, retained_proto) are networking-leaf concerns
and were already plumbed into run_networking; the entrypoint_pid Arc is
read lazily after the process leaf populates it. Adds retained_proto as
a parameter and spawns the resolver early in run_networking so the probe
loop starts before the proxy comes up.

Same shape as the denial-flush move: networking owns its own background
task end-to-end; the orchestrator stops hosting work that doesn't
conceptually belong to it.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move seccomp install into run_process

The supervisor seccomp prelude is part of "set up the workload-side
process tree", not part of orchestration. Move the call site from
run_sandbox into the top of run_process and drop the now-unused
re-export from openshell-sandbox::lib.

Timing is preserved: by the time the orchestrator calls run_process,
run_networking has already returned, so netns + nftables setup is
complete.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move check_runtime_pid_limit into run_process

The PID-limit precondition is process-side: it gates whether the workload
child can be spawned at all. Move the call from run_sandbox into the top
of run_process, alongside the seccomp prelude. Same shape as the seccomp
move — function already lives process-side, only the call site relocates.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move validate_sandbox_user to process crate

The sandbox-user check is a precondition for privilege-dropping the
workload child; it has no relevance to networking. Move the function
next to drop_privileges in openshell-supervisor-process::process and
call it from the top of run_process.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move prepare_filesystem to process crate

Creating and chowning read_write directories is workload-side
preparation, not orchestration. Move prepare_filesystem and its
prepare_read_write_path helper (plus tests) into
openshell-supervisor-process::process and call from run_process,
alongside validate_sandbox_user.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-process): move startup skill install into run_process

The eager initial-settings fetch + agent skill install is process-side:
the install materializes files the workload's filesystem sees. The
orchestrator still owns the AGENT_PROPOSALS_ENABLED OnceLock init
because the policy poll loop also reads it; only the early fetch and
install hop into run_process.

Behavior unchanged. Best-effort: any RPC or install failure is logged
but does not fail startup.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-network): own PolicyLocalContext construction

Move the PolicyLocalContext construction from run_sandbox into
run_networking. The orchestrator was building it solely to thread it into
the networking leaf and to share it with the policy poll loop; now
run_networking builds it from inputs it already takes (retained_proto,
openshell_endpoint, sandbox_name|sandbox_id) and exposes it on the
returned Networking struct.

The orchestrator's poll loop now grabs the Arc clone from
networking.policy_local_ctx, so the orchestrator no longer imports
openshell_supervisor_network::policy_local.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* feat(supervisor): add --mode flag to gate network/process leaves

Add a --mode flag (default "network,process") that selects which
supervisor leaves run in the current process. Two new shapes are
unlocked without splitting the binary:

  --mode=network             # network-only sidecar
  --mode=process             # process-only supervisor
  --mode=network,process     # combined (default; current behavior)

In network-only mode the orchestrator skips run_process and waits on
SIGINT/SIGTERM before tearing down the proxy. The entrypoint PID stays
at 0 for the lifetime of the process, which silently degrades the
proxy's binary-identity TOFU and the bypass monitor's PID enrichment;
this is correct in a split-pod topology where the workload's /proc
lives in another pod.

In process-only mode run_networking is skipped entirely. SSH sessions
get no proxy URL, no netns FD, and no CA paths, matching what a
split-pod consumer would expect when network enforcement is delegated
to a sidecar.

The policy poll loop continues to run unconditionally; its OPA-reload
and policy.local hooks already gate on the resources only present when
network is enabled, and the env-refresh / proposals-toggle hooks
remain active in process mode.

Closes a step toward the RFC-0001 supervisor topology proposed in
issue #1305 by drew.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* style(supervisor-process): rustfmt long debug! line

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-network): pull DenialEvent down from core

DenialEvent is only emitted and consumed inside openshell-supervisor-network
(proxy, bypass monitor, denial aggregator). It never crossed the leaf
boundary, so the earlier lift to openshell-core was speculative. Move it
back into the network crate where its only callers live.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-network): pull procfs down from core

procfs was lifted to openshell-core under the assumption it would be
shared cross-leaf, but on the current branch all three callers
(bypass_monitor, identity, proxy) live in openshell-supervisor-network.
No file in openshell-supervisor-process imports it. Move the module to
the network crate and drop sha2/hex from openshell-core, which were
pulled in only for procfs.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* style(supervisor-network): run cargo fmt

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* fix(supervisor-network): add libc dev-dependency for procfs tests

The procfs/bypass_monitor/proxy test modules use libc::{fork, exec,
fcntl, kill, waitpid} but the dep wasn't declared in this crate's
Cargo.toml. It was previously satisfied transitively when these
modules lived in openshell-core; the move left the test target
unable to resolve libc.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(sandbox): move denial aggregator to orchestrator

The denial aggregator and mechanistic mapper consume denial events
produced by the proxy and (subsequently) the bypass monitor. With both
supervisor leaves becoming pure producers of `DenialEvent`, the
consumer-side aggregation belongs in the orchestrator, not in either
leaf.

Move `denial_aggregator.rs` and `mechanistic_mapper.rs` from
`openshell-supervisor-network` to `openshell-sandbox` (the
orchestrator). The orchestrator now owns the unbounded denial channel:
it constructs `(tx, rx)`, hands `tx` to `run_networking` for the proxy
to clone, drains `rx` via the aggregator task, and runs the gateway
flush helper itself.

`run_networking`'s signature gains a `denial_tx` parameter and loses
its internal channel construction, aggregator spawn, and
`flush_proposals_to_gateway` helper. `DenialEvent` stays in
`openshell-supervisor-network` for now; a follow-up commit will lift
it to `openshell-core` alongside the bypass monitor relocation.

* refactor(supervisor-process): pull bypass monitor down from network

`bypass_monitor` is process-isolation machinery: it tails the kernel
log via `dmesg --follow`, parses nftables LOG lines emitted from the
workload's network namespace, resolves PIDs via `/proc`, and emits
OCSF events plus optional `DenialEvent`s. None of this touches the
proxy, OPA, TLS, or any other supervisor-network state — it only
shared the denial channel because both feed the same aggregator.

Move `bypass_monitor.rs` from `openshell-supervisor-network` to
`openshell-supervisor-process` (as `bypass_monitor/mod.rs`). Spawn it
in `run_process` where the netns name and entrypoint PID are already
in scope. The orchestrator hands an extra `bypass_denial_tx` clone of
the denial channel sender to `run_process` for this purpose.

Lift `DenialEvent` from `openshell-supervisor-network` to
`openshell-core`. Both supervisor leaves now produce it, so it needs
a shared location that neither leaf depends on. This reverses an
earlier commit that pulled the type into the network leaf when it was
the only producer.

Copy the minimal subset of `/proc` parsers used by `bypass_monitor`
into a private `bypass_monitor::procfs` submodule. The alternative —
extracting a shared procfs crate — is a much larger refactor that
this commit does not need; supervisor-network's `procfs.rs` continues
to serve the proxy and identity cache.

* refactor(supervisor-process): derive ssh netns fd inside run_process

The ssh_netns_fd was computed in run_networking purely to forward it
through the Networking struct and back into run_process. supervisor-network
never read it. Move the derivation to run_process where the
NetworkNamespace handle is already in scope.

* refactor(supervisor-process): derive ssh proxy url inside run_process

The ssh_proxy_url was computed in run_networking purely to forward it
through the Networking struct and back into run_process. supervisor-network
never read it. Move the derivation to run_process where the
NetworkNamespace handle and SandboxPolicy are already in scope.

After this commit the Networking struct no longer carries any SSH-shaped
fields, and supervisor-network reads only host_ip from the netns (for the
proxy bind address).

* refactor(supervisor-network): take proxy bind ip directly instead of netns

run_networking only ever read host_ip from the netns it was passed (the
SSH plumbing reads moved to run_process in earlier commits). Replace the
NetworkNamespace parameter with a plain Option<IpAddr> the orchestrator
extracts. supervisor-network's run module no longer references the netns
type for any consumer, only for create_netns_for_proxy (which still lives
in this crate; relocates next).

* refactor(supervisor-process): move netns ownership out of core

Relocates the NetworkNamespace handle, nft ruleset builder, and
create_netns_for_proxy constructor into openshell-supervisor-process.
The orchestrator (openshell-sandbox) phantom-owns the RAII handle for
the duration of run_sandbox; supervisor-network no longer references
the type at all.

Drops uuid, libc, nix, openshell-ocsf, and tempfile from core's Linux
target deps (all were exclusive to netns). tempfile becomes a Linux
runtime dep on supervisor-process for nft ruleset application.

* chore(sandbox): prune leaf-only deps from orchestrator manifest

cargo-machete flagged 26 direct dependencies that were carried over
from the pre-split monolith and are no longer used by the orchestrator
itself: regorus, russh, rcgen, tokio-rustls, ipnet, apollo-parser,
openshell-router, anyhow, base64, bytes, flate2, glob, hex, hmac, nix,
rand_core, rustls-pemfile, serde, serde_yml, sha1, sha2, thiserror,
tokio-stream, uuid, webpki-roots.

These now live (transitively) in openshell-supervisor-network and
openshell-supervisor-process where they are actually consumed.

* chore(deps): prune unused deps from supervisor crates

- Drop unused `url` from openshell-supervisor-network.
- Mark `prost` and `prost-types` as cargo-machete-ignored in
  openshell-core: they have no source-level `use`, but the tonic-
  generated proto code references them via `::prost::Message` etc.
- openshell-supervisor-process is already clean.

* fix(supervisor-network): wait for entrypoint PID before symlink probe

The OPA symlink-resolution task reads entrypoint_pid once at the top
of the spawned closure. Because the spawn happens before run_process
publishes the workload PID, the load returns 0, the probe path bakes
in as /proc/0/root/, and the loop exhausts its retries against a path
that does not exist on Linux. The reload never fires, so policies that
whitelist symlinked binaries (e.g. /usr/bin/python3 → python3.11) get
silent denials when the workload exec's the realpath.

Split the wait into two phases: 5s polling entrypoint_pid for a
non-zero value, then the existing 5s window probing /proc/<pid>/root/.
Distinct warn messages on each timeout so future debugging can tell
"PID never published" apart from "container fs never appeared".

* fix(sandbox): restore GPU procfs baseline (#1522)

Signed-off-by: Evan Lezar <elezar@nvidia.com>
(cherry picked from commit 5102cb9413)
Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* fix(supervisor-process): use renamed tonic tls-native-roots feature

Upstream renamed the tonic `tls` feature to `tls-native-roots`. The
supervisor-process Cargo.toml still referenced the old name, which broke
the workspace build after merging upstream.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* refactor(supervisor-network): relocate token_grant and spiffe_endpoint

Upstream's SPIFFE-backed token grant feature landed in
crates/openshell-sandbox/src/. After the supervisor split, the L7
enforcement code in supervisor-network calls into token_grant, which
would require supervisor-network to depend back on sandbox.

Move token_grant.rs and spiffe_endpoint.rs into supervisor-network
where the only callers live, add the reqwest and spiffe deps to
supervisor-network's Cargo.toml, and drop them from sandbox.

Also fix two stale `openshell_core::proto::` self-references in
openshell-core (a pre-existing breakage that surfaced once the rest of
the merge compiled).

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

* fix(supervisor-process): broaden Path import cfg to all unix targets

The `Path` import was gated on `cfg(any(test, target_os = "linux"))`,
but `prepare_read_write_path` is gated on `cfg(unix)` — broader. On
non-Linux unix the function still referenced `&std::path::Path`
explicitly, so upstream's qualified path was load-bearing.

After the supervisor split, lint runs on Linux where `Path` IS in
scope, so `unused_qualifications` fires. Broaden the import cfg to
match the function's cfg and use the bare `Path` name everywhere.

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>

---------

Signed-off-by: Radoslav Hubenov <rrhubenov@gmail.com>
Signed-off-by: Evan Lezar <elezar@nvidia.com>
Co-authored-by: Evan Lezar <elezar@nvidia.com>
2026-06-15 15:16:23 -04:00
Alex Lewontin fb83d1a3c8 feat(gateway): add system registry support and source indicators (#1625)
* feat(bootstrap): add system gateway registry for installer defaults

Adds a read-only installer-seeded gateway registry that the CLI consults after per-user gateway config. The registry uses the same layout as per-user config with `active_gateway` at the root and `gateways/<name>/metadata.json` beneath it. By default the system config root is `/etc/openshell`, while `OPENSHELL_SYSTEM_GATEWAY_DIR` remains available as an override for packages that need a different location. User-managed gateways continue to shadow installer entries on name collision.

Originally-authored-by: Mark Shuttleworth <mark@ubuntu.com>
Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>

* feat(cli): show gateway config source in list and term

Expose whether a gateway registration comes from user or system config in `openshell gateway list`, the TUI gateway pane, and list JSON output. The CLI also refuses to remove system-managed registrations and the smoke tests cover the new list output.

Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>

* fix(bootstrap): preserve user shadowing on invalid metadata

Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>

* fix(cli): keep system fallback out of rollback state

* docs(gateway): describe system config fallback layout

* test(bootstrap): cover system gateway last_sandbox persistence

* test(gateway): cover system-only removal rejection

* fix(bootstrap): validate gateway names before path joins

---------

Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>
2026-06-11 16:25:12 -07:00
Derek Carr e73745f10e feat(gateway): add reconciler lease for HA multi-replica deployments (#1577)
Introduce a database-backed lease that ensures only one gateway replica
runs the watch and reconcile loops. Includes lease primitives with CAS
safety, cooperative cancellation via watch channels, SQLite bypass for
single-replica deployments, and integration tests covering failover,
contention, and CAS chain integrity.

Signed-off-by: Derek Carr <decarr@redhat.com>
2026-06-11 12:25:57 -04:00
alangou f33fd02f4d fix(server): use public tonic body type in gRPC rate limiter (#1872)
Signed-off-by: Adrien Langou <alangou@nvidia.com>
2026-06-11 17:29:57 +02:00
Evan Lezar 58a3777d07 fix(drivers): filter bind-backed named volumes (#1861)
* fix(podman): filter bind-backed named volumes

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(drivers): detect recursive bind-backed volumes

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-11 08:03:33 -07:00
alangou b6c87a76ab feat(server): add grpc rate limiting gateway-wide (#1566)
Signed-off-by: Adrien Langou <alangou@nvidia.com>
2026-06-11 13:49:41 +02:00
Evan Lezar 1dc59853ff feat(gpu): move device selection to driver config (#1815)
* refactor(kubernetes): use shared proto struct helpers

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(docker): extract sandbox template validation

* feat(gpu)!: move device selection to driver config

BREAKING CHANGE: The openshell sandbox create --gpu-device flag and
corresponding API field were removed. Select specific GPUs through
driver-specific driver_config fields instead.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(kubernetes): reject unknown driver config fields

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-11 12:53:06 +02:00
Sergey 42e7b80943 feat(podman): make container health check interval configurable (#1833)
* feat(podman): make container health check interval configurable

The Podman driver hardcoded a 3-second health check interval, which
spawns a conmon subprocess on every tick. On systems running multiple
sandboxes this creates sustained process churn and unnecessary CPU
overhead.

Add a `health_check_interval_secs` field to `PodmanComputeConfig`
(default: 10s) and wire it into the container health check spec.
Operators can tune it further via `[openshell.drivers.podman]` in
gateway.toml.

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>

* docs(podman): document health_check_interval_secs config field

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>

* docs(podman): document health_check_interval_secs zero-disables behavior

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>

---------

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
2026-06-10 15:08:45 -07:00
Drew Newberry 530aaf1360 feat(drivers): support docker and podman config mounts (#1785)
* feat(drivers): support docker and podman config mounts

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* docs(drivers): trim mount docs

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* test(e2e): cover local driver volume mounts

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(podman): satisfy linux clippy lint

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(drivers): gate bind mounts behind gateway config

* docs(sandbox): simplify mount examples

* cleanup

* test(e2e): stabilize branch checks

* fix(drivers): tighten local mount validation

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

---------

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
2026-06-10 11:46:20 -07:00
Taylor Mutch 702cbc4f63 feat(providers): support SPIFFE-backed token grants (#1784)
* feat(providers): support SPIFFE-backed token grants

Add provider profile token_grant metadata and expand endpoint-specific
dynamic credentials so sandbox supervisors can request SPIFFE JWT-SVIDs,
exchange them with an OAuth-style token endpoint, cache returned access
tokens, and inject bearer tokens into matching HTTP requests.

Wire Kubernetes and Helm deployments to mount the provider SPIFFE Workload
API socket into sandbox pods for token grant exchange.

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Signed-off-by: Gordon Sim <gsim@redhat.com>

* test(examples): add SPIFFE token grant demo

Add a reusable alpha/beta demo that deploys a SPIFFE-verifying token issuer
and protected services, imports a token-grant provider profile, creates a
sandbox, and verifies endpoint-specific bearer tokens.

The script leaves Kubernetes workloads in place, deletes sandboxes through
openshell unless KEEP_SANDBOX=1, and prints protected service logs as proof
of life.

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(providers): harden SPIFFE token grants

* fix(providers): harden dynamic token grants

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(providers): harden token grant handling

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Signed-off-by: Gordon Sim <gsim@redhat.com>
2026-06-10 10:54:39 -07:00
LateNightHackathonandlatenighthackathon c1d3b43ddb fix(policy): classify advisory private-IP notes with the canonical is_internal_ip (#1777) (#1824)
generate_security_notes() flagged a destination as internal/private using naive
string prefixes. host.starts_with("172.") matched all of 172.0.0.0/8 even
though only 172.16.0.0/12 is private (RFC 1918), so the advisory flagged public
addresses (172.15/172.32) and missed CGNAT (100.64.0.0/10), IPv6 ULA, and the
other reserved ranges; it also flagged non-IP hostnames that merely start with
10./172.. The two copies of the check (openshell-sandbox mechanistic_mapper.rs
and openshell-server grpc/policy.rs) now parse the host as an IP and defer to
openshell-core net::is_internal_ip, the same RFC-accurate, unit-tested classifier
the runtime SSRF enforcement path uses, so the proposed-rule advisory matches
actual enforcement.

Per review, is_internal_v4 now uses the stable Ipv4Addr predicates as far as
they go: is_documentation() (RFC 5737) replaces the two manual TEST-NET-2/3
checks and also closes a gap, since 192.0.2.0/24 (TEST-NET-1) was previously
unflagged, and is_broadcast() (RFC 919) is added. Manual checks remain only for
the ranges without a stable predicate: CGNAT 100.64.0.0/10, IETF protocol
assignments 192.0.0.0/24, and benchmarking 198.18.0.0/15.

Adds regression coverage: both note suites now assert hostname-prefix false
positives (10.example.com, 172.example.com) are not flagged and IPv6 ULA
(fd00::1) is flagged; net.rs covers the newly classified TEST-NET-1 and
broadcast addresses.

Closes #1777

Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
Co-authored-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
2026-06-10 09:51:38 -07:00
LateNightHackathonandlatenighthackathon 84c24a0e6d fix(ocsf): widen the shorthand [reason:] budget so denial endpoints stay readable (NVIDIA/NemoClaw#4760) (#1799)
A denied egress logs an OCSF NET:OPEN DENIED line whose [reason:] field carries the full destination endpoint and the rejecting policy name, but the shorthand formatter capped the reason at 80 bytes and appended a literal "...". A typical reason is longer than that, so the shorthand denial logs showed the endpoint cut mid-string with the port and policy name lost:

  NET:OPEN [MED] DENIED ... [reason:endpoint host.example:44...]

Raise MAX_REASON_LEN to 256 so a full endpoint plus policy name fits, and route both reason_tag and message_tag through a shared char-boundary truncation helper. The previous `&text[..MAX]` byte slice would also panic if the cut fell inside a multibyte UTF-8 character.

Per review: reason_tag now also normalizes embedded newlines to spaces before truncating (matching message_tag), so endpoint- or error-derived text cannot inject CR/LF into the single-line shorthand (CWE-117); the multibyte-boundary test now slices inside a multibyte character to actually exercise the step-back.

The issue was reported on the NemoClaw tracker (NVIDIA/NemoClaw#4760), but the OCSF shorthand emitter is OpenShell code, so the fix lands here. Cross-repo references do not auto-close, so no closing keyword is used.

Ref: NVIDIA/NemoClaw#4760  (https://github.com/NVIDIA/NemoClaw/issues/4760)

Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
Co-authored-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
2026-06-10 09:36:31 -07:00
Evan Lezar 27fd31c98b fix(cli)!: require explicit gpu sandbox flag (#1835)
BREAKING CHANGE: GPU sandbox mode is no longer inferred from sandbox image names. Users must pass --gpu to request GPU resources.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-10 07:59:19 +00:00
Eric Curtin 70acbaf407 refactor(driver-utils): centralize container mount path constants (#1841)
Move duplicate in-container path constants from the Docker, Podman,
Kubernetes, and VM driver crates into openshell-core::driver_utils:

- TLS_CA_MOUNT_PATH, TLS_CERT_MOUNT_PATH, TLS_KEY_MOUNT_PATH,
  SANDBOX_TOKEN_MOUNT_PATH — identical string literals that were
  copy-pasted between openshell-driver-docker and
  openshell-driver-podman.

- SUPERVISOR_CONTAINER_DIR (/opt/openshell/bin) and
  SUPERVISOR_CONTAINER_BINARY (/opt/openshell/bin/openshell-sandbox) —
  replaces three diverging local constants across docker, kubernetes,
  podman, and vm-rootfs that referred to the same paths under different
  names with different string values (the k8s constant was the directory;
  the docker/vm constant was the full binary path).

Each driver crate now assigns its local constant from the canonical core
value, keeping existing names visible to tests without any call-site
churn. The openshell-core constants carry doc comments explaining when
to use the dir vs. the full binary path.

Signed-off-by: Eric Curtin <eric.curtin@docker.com>
2026-06-09 14:20:06 -07:00