60 Commits
Author SHA1 Message Date
Taylor Mutch d9908222f2 feat(kubernetes): support sandbox image pull secrets (#1671) 2026-06-01 20:28:50 -07:00
mjamivandJohn Myers f1fc87e1ad fix(sandbox): trust exact declared private endpoints (#1560)
* fix(sandbox): trust exact declared private endpoints

* fix(sandbox): preserve advisor endpoint provenance

* fix(sandbox): repair advisor provenance lint failures

---------

Co-authored-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-05-29 16:18:42 -07:00
Taylor Mutch 0f73d117a5 fix(podman): avoid host-gateway on macOS machines (#1637)
Closes #1307

Default the Podman host gateway alias override to gvproxy's host-loopback IP on macOS while preserving host-gateway resolution on Linux. Wire the setting through Podman config, gateway TOML inheritance, and the standalone driver, and document the platform behavior.

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-05-29 17:38:56 -05:00
Taylor Mutch 188b355033 docs(config): update gateway config reference (#1624) 2026-05-28 20:51:06 -07:00
Mesut Oezdil b2f0f22cfe docs(kubernetes): note that Sandbox volumeClaimTemplates is immutable (#1543) 2026-05-27 15:13:52 +00:00
Piotr Mlocek 0dc08a185e fix(release): build host Linux binaries with glibc floor (#1490) 2026-05-22 12:12:03 -07:00
Taylor Mutch a3b16c18ab feat(auth): per-sandbox authentication to gateway (#1404) 2026-05-21 17:58:55 -07:00
mjamivandJohn Myers 528fb29147 fix(sandbox): allow first-label L7 host wildcards (#1304)
* fix(sandbox): allow first-label L7 host wildcards

* docs(sandbox): document L7 host wildcard contract + add OPA runtime tests

- Add Host Wildcards section to architecture/security-policy.md
  describing accepted (first-label *, **, intra-label *-X) and
  rejected (bare, TLD, non-first-label, recursive-in-label) forms,
  and noting that wildcards never cross '.' boundaries.
- Expand the policy-schema.mdx 'host' field description to reflect
  the same contract instead of only mentioning '*.example.com'.
- Add OPA runtime tests asserting '*-aiplatform.googleapis.com'
  matches 'us-central1-aiplatform.googleapis.com' and does not match
  'us-central1.aiplatform.googleapis.com' (cross-dot boundary). Locks
  validator/runtime alignment for intra-label wildcards.

* chore: update mise lockfile

* test(server): tolerate serialized inference upserts

---------

Co-authored-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-05-21 09:32:37 -07:00
Russell Bryant c5d1d76d94 refactor(sandbox): replace iptables with nftables for network policy enforcement (#1401)
Migrate all sandbox and VM driver network policy enforcement from
iptables to nftables. nftables provides atomic ruleset loading, a
cleaner rule syntax, and is the standard netfilter interface in modern
kernels.

Sandbox bypass enforcement (openshell-sandbox):
- Replace iptables chain of individual rule insertions with a single
  atomic nftables ruleset load via nft -f
- New nft_ruleset module with pure functions for ruleset generation
  and unit tests
- Combine log and reject rules in one inet family table (handles both
  IPv4 and IPv6 in a single ruleset)
- Fall back to reject-only ruleset when kernel lacks nft_log support
- Enable net.netfilter.nf_log_all_netns so log rules work from
  non-init network namespaces
- Use temp file for nft ruleset loading instead of stdin for
  compatibility with minimal VM guest environments

VM TAP networking (openshell-driver-vm):
- Replace iptables NAT/forwarding rules with nftables equivalents
- New nft_ruleset module for TAP network rule generation with unit
  tests
- Atomic table-per-TAP-device lifecycle (create/destroy)
- Host-side rules provide NAT infrastructure and defense-in-depth
  isolation (input chain restricts VM to gateway port only, forward
  chain blocks unsolicited inbound); primary security enforcement
  happens inside the VM guest via the sandbox supervisor's own rules

VM init script:
- Load nft kernel modules at sandbox init
- Enable nf_log_all_netns sysctl for bypass detection logging

OCSF / docs:
- Update firewall rule engine references from iptables to nftables
- Document host firewall interaction model and two-layer enforcement
  architecture in VM driver README and compute drivers reference

Closes #1335

Signed-off-by: Russell Bryant <rbryant@redhat.com>
2026-05-18 16:37:17 -07:00
Drew Newberry f257ed0193 refactor(packaging): rely on gateway runtime defaults (#1415)
* fix(packaging): use gateway TOML config in packages

* refactor(packaging): rely on gateway runtime defaults
2026-05-18 14:13:19 -07:00
Drew Newberry f819f7dcb5 fix(vm): restore sandboxes after gateway restart (#1407) 2026-05-15 15:08:59 -07:00
Drew Newberry 910d3f09f2 feat(vm): boot sandboxes from ext4 root disks (#1263)
* feat(vm): boot sandboxes from ext4 root disks

* feat(vm): boot sandboxes with writable overlay disk

* fix(sandbox): improve sandbox provisioning behavior

* fix(vm): repair sandbox image provisioning

* fix(vm): clean up rebase fallout

* perf(vm): cache empty overlay images

* perf(vm): skip repeated sandbox ownership repair

* fix(vm): address lifecycle review issues

* fix(vm): repair gateway vm startup

* wip
2026-05-15 13:23:10 -07:00
Taylor MutchandDrew Newberry b61a98dbad feat(gateway): add TOML configuration file (RFC 0003) (#1317)
* feat(gateway): add TOML configuration file (RFC 0003)

Introduces an opt-in --config / OPENSHELL_GATEWAY_CONFIG flag that loads a
TOML file with gateway-wide settings and per-driver tables. Source
precedence is CLI > env > file > built-in default, implemented via clap's
ValueSource so existing flags and env vars keep their priority.

Driver crates (kubernetes, docker, podman, vm) now derive Deserialize on
their config structs. SupervisorSideloadMethod gains Deserialize with
kebab-case rename. A per-driver inheritance allowlist on the loader side
overlays [openshell.gateway] shared defaults (default_image,
supervisor_image, image_pull_policy, guest_tls_*, ssh_handshake_skew_secs,
client_tls_secret_name, host_gateway_ip, enable_user_namespaces) onto
each [openshell.drivers.<name>] table before deserialization.

The Helm chart renders a new gateway-config ConfigMap and mounts it at
/etc/openshell/gateway.toml. The migrated OPENSHELL_* env entries are
dropped from the StatefulSet — only the Secret-backed
OPENSHELL_SSH_HANDSHAKE_SECRET remains. database_url stays on --db-url.

Adds examples/gateway/gateway.example.toml and updates architecture/gateway.md
with the source precedence and inheritance rules.

* docs(gateway): drop ssh_handshake_skew_secs and ssh_handshake_secret from examples

Both fields are scheduled for removal. Remove the example values and the
env-only note so the gateway.toml example and the architecture doc stop
recommending settings that will not exist much longer.

* docs(rfc): correct OPENSHELL_CONFIG to OPENSHELL_GATEWAY_CONFIG in RFC 0003

* docs(gateway): add per-driver TOML example configurations

Adds focused single-driver examples next to the comprehensive
gateway.example.toml: kubernetes, docker, podman, and microvm. Each one
demonstrates the realistic settings for that driver plus how shared
[openshell.gateway] defaults inherit into the driver table.

A new unit test (`checked_in_examples_parse`) loads every example through
the config_file loader so schema drift fails CI rather than silently
shipping a broken example.

* refactor(gateway): drop image_pull_policy from shared inheritance

Kubernetes and Podman use mutually-incompatible vocabularies for the same
TOML key:

  - Kubernetes: `Always | IfNotPresent | Never` (free-form string passed
    verbatim to the K8s API).
  - Podman: `always | missing | never | newer` (strict lowercase enum
    deserialised into `ImagePullPolicy`).

No value means the same thing in both drivers. Sharing the key at
`[openshell.gateway]` scope and inheriting it into every active driver's
table meant any value safe for one driver was either wrong or silently
dropped for the other (`IfNotPresent` → `ImagePullPolicy::Missing` after
`.unwrap_or_default()`). Operators run one driver per gateway, so the
"shared default" never pays for itself.

Make `image_pull_policy` driver-local:

  - Remove the field from `GatewayFileSection` and from
    `inheritable_keys()` for both Kubernetes and Podman.
  - Drop the file→`RunArgs` merge for the gateway-scope key.
  - Stop unconditionally clobbering the driver value with
    `config.sandbox_image_pull_policy` in the runtime wiring — only apply
    the CLI/env override when it was set (and, for Podman, only when it
    parses into the lowercase enum).
  - Move the key under `[openshell.drivers.kubernetes]` and
    `[openshell.drivers.podman]` in every example, the RFC, the
    architecture doc, and the Helm-rendered gateway ConfigMap.

The supervisor pull policy follows the same shape: it is K8s-only and
moves into `[openshell.drivers.kubernetes]` alongside `image_pull_policy`
in the Helm template.

* fix(gateway): address review feedback on TOML configuration

Resolves the P1 and P2 issues raised in PR #1317:

- Helm gateway ConfigMap moves `grpc_endpoint` under
  `[openshell.drivers.kubernetes]` so the default install no longer fails
  the gateway's `deny_unknown_fields` schema check.
- `kubernetes_config_from_file` and `podman_config_from_file` only let
  the gateway-wide CLI/env `grpc_endpoint` overwrite the driver-table
  value when it was actually supplied, preserving file-only configs.
- Kubernetes driver default `image_pull_policy` is now empty (was Podman
  vocabulary "missing"), so default deployments let the Kubernetes API
  apply its own policy instead of being rejected.
- New `disable_tls` gateway field plumbs `.Values.server.disableTls`
  through the TOML ConfigMap instead of relying on env vars dropped from
  the StatefulSet.
- StatefulSet pod template now carries a `checksum/gateway-config`
  annotation so `helm upgrade` rolls pods when the ConfigMap changes.
- Auxiliary listener resolution preserves the full `SocketAddr` from
  `health_bind_address` / `metrics_bind_address`, so a loopback-pinned
  health port is not silently relocated onto the public bind address.
- `ssh_session_ttl_secs` from the file is now applied to `Config` (it
  was previously accepted by the loader but never read).

New regression coverage: cli-level merge tests for the new fields plus
helm-unittest assertions for the ConfigMap shape, checksum annotation,
and `disable_tls` rendering.

* docs(gateway): consolidate gateway TOML examples into docs reference

Replaces the per-driver example files under examples/gateway/ with a
single published reference page at docs/reference/gateway-config.mdx
covering source precedence, layout, the full example, and the four
per-driver examples (Kubernetes, Docker, Podman, microVM). Drew flagged
during PR #1317 review that the examples belong with the user-facing
docs rather than in a sibling examples/ directory.

The cross-references in architecture/gateway.md and RFC 0003 are updated
to point at the new docs page; the round-trip test in config_file.rs is
removed (schema coverage stays on the inline parses_full_example test
and per-field merge tests — doc-snippet drift belongs in a separate
docs-lint, not in a cross-tree Rust unit test).

* refactor(core): move DEFAULT_K8S_NAMESPACE into K8s driver

The constant is Kubernetes-specific (used only by KubernetesComputeConfig's
Default impl) and does not belong in openshell-core. Relocate it to the
driver crate that owns the K8s vocabulary; openshell-core retains only
truly cross-cutting defaults.

* refactor(core): move Podman bridge default into Podman driver

DEFAULT_NETWORK_NAME is Podman vocabulary, consumed only by the Podman
driver. Also drops the unused DEFAULT_IMAGE_PULL_POLICY constant.

* docs(auth): scrub remaining SSH handshake secret references

Sweeps the trailing mentions left after the rebase: the gateway
config-file module doc, the Helm gateway-config ConfigMap header,
the gateway-config.mdx env-only note, and the RPM systemd unit
comment for init-gateway-env.sh.

* docs(gateway): clarify OPENSHELL_GRPC_ENDPOINT applies to all drivers

The previous comment implied the callback endpoint was Kubernetes-only,
but the value is propagated to every compute driver (Kubernetes, Docker,
Podman, VM) and must be reachable from wherever the sandbox runs.

* refactor(gateway): move driver options into config (#1394)

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

* fix(e2e): regenerate gateway config via TOML for docker + podman harnesses

The gateway CLI flags moved into TOML config tables in 560550d2 (#1394),
which made every existing e2e/with-{docker,podman}-gateway.sh invocation
fail with "unexpected argument '--sandbox-namespace'" (and a long tail
of similar driver-specific options) before the gateway could even bind.

Replace the obsolete CLI flags with a synthesized
`[openshell.drivers.<driver>]` table written to `${STATE_DIR}/gateway.toml`
and passed via the new `--config` flag. Only the gateway-wide flags
that survived 560550d2 (bind-address, port, drivers, db-url, tls-*,
disable-tls, log-level, health-port) stay on the command line.

Both scripts get a small `toml_string` helper to properly TOML-quote
the values (the previous `%q` printf format produced bash-escape, not
TOML-escape). The Docker harness also corrects two field names that
diverged from the driver schema: `docker_network_name` →
`network_name`, and the supervisor binary/image plumbing now reads
through to `supervisor_bin` / `supervisor_image` in the same table.

The Podman harness drops `--ssh-gateway-port` (deleted in 560550d2 —
gRPC + SSH are multiplexed on the same port now) and substitutes
`network_name` + `gateway_port` for the obsolete `--sandbox-namespace`
(which the Podman driver never had as a typed field).

* fix(core): swap bind-only 0.0.0.0 SSH gateway host for cluster URL host

CLI's resolve_ssh_gateway treated 0.0.0.0 as a loopback "keep as-is"
when the cluster URL was also loopback, so the SSH proxy connected to
0.0.0.0:port. The unspecified address is never a valid connect target
and is not present in any TLS cert SAN, which produced BadCertificate
TLS handshake failures during `openshell sandbox create -- ...` in
docker/podman e2e (e.g. bypass_detection).

Resolution: when the server returns 0.0.0.0 or :: as the gateway host
and both endpoints are loopback, fall back to the cluster URL's host
(which the CLI is already using to reach the gateway, so it must
resolve and match the cert).

* fix(e2e): repair podman harness on macOS

Podman 5.x with the applehv/libkrun provider no longer creates the legacy
~/.local/share/containers/podman/machine/podman.sock symlink, and
`podman system service` is a Linux-only subcommand — the macOS client
delegates the API service to the VM. Both assumptions in the harness
were stale, so the script tried to start a temporary service that podman
rejected with "unknown flag: --time".

- Discover the macOS socket via `podman machine inspect` instead of the
  hardcoded path.
- On Darwin, fail fast with a "start podman machine" message rather than
  attempting the Linux-only `podman system service` fallback.
- Write socket_path into [openshell.drivers.podman] so the in-process
  driver picks up the discovered socket; the driver reads TOML only
  after the config refactor (560550d2), so OPENSHELL_PODMAN_SOCKET alone
  was no longer enough.

* fix(server): use clone_from for TLS client CA assignment

clippy 1.95.0 rejects assigning the result of `Clone::clone()` to an
existing variable under `-D warnings` (`assigning_clones`). Switch to
`clone_from(&...)` to satisfy the lint and avoid the redundant
allocation.

---------

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Co-authored-by: Drew Newberry <anewberry@nvidia.com>
2026-05-15 12:43:48 -07:00
Russell Bryant 44e843ede3 feat(vm): fall back to Podman socket when Docker is unavailable (#1370)
* test(e2e): add bypass detection test for sandbox REJECT rules

Validates that direct TCP connections bypassing the HTTP CONNECT proxy
are rejected immediately (ECONNREFUSED) rather than hanging until a
network timeout. This is implementation-agnostic — it tests observable
kernel behavior regardless of whether rules are installed via iptables
or nftables.

* feat(vm): fall back to Podman socket when Docker is unavailable

The VM driver resolves sandbox images from a local container engine
before falling back to registry pulls. Previously it only checked
Docker. Now it tries the Podman socket as a fallback, since Podman
exposes a Docker-compatible API via bollard.

On Linux, enable the Podman API socket with:
  systemctl --user start podman.socket

Signed-off-by: Russell Bryant <russell.bryant@gmail.com>

* chore(vm): fix formatting

Signed-off-by: Russell Bryant <russell.bryant@gmail.com>

* refactor(vm): rename docker-specific identifiers to engine-neutral names

Rename resolve_local_docker_image → resolve_local_container_image,
local_docker_image_platform_mismatch → local_image_platform_mismatch,
and the local docker variable → engine since the function now supports
both Docker and Podman backends.

Signed-off-by: Russell Bryant <russell.bryant@gmail.com>

---------

Signed-off-by: Russell Bryant <russell.bryant@gmail.com>
2026-05-14 22:19:03 -07:00
Taylor Mutch 7a0c444445 refactor!(auth): drop SSH handshake secret (#1274)
* refactor!(auth): drop SSH handshake secret in favor of mTLS

The OPENSHELL_SSH_HANDSHAKE_SECRET / x-sandbox-secret mechanism was
misnamed: it does not authenticate SSH (which flows over the
RelayStream gRPC RPC and is gated by mTLS plus supervisor Unix-socket
permissions). It only gated a small set of sandbox-to-gateway
control-plane RPCs, and production deployments already enforce mTLS
on that channel — so the shared secret was redundant.

Replace the secret check with an mTLS-presence marker. Sandbox-class
methods (ReportPolicyStatus, PushSandboxLogs,
GetSandboxProviderEnvironment, SubmitPolicyAnalysis, GetSandboxConfig,
GetInferenceBundle) accept callers without a Bearer token; the gRPC
mTLS handshake is the trust boundary. Dual-auth methods treat
Bearer-present as full-scope CLI access and Bearer-absent as
sandbox-restricted scope via validate_sandbox_caller_update.

Drops the secret from all drivers (K8s, Podman, VM), the sandbox gRPC
interceptor, the Helm chart (values + pre-install hook + StatefulSet
env), the RPM bootstrap script, the man pages, and the
debug-openshell-cluster skill. Also removes the never-read
ssh_handshake_skew_secs flag and config field.

BREAKING CHANGE: --ssh-handshake-secret / OPENSHELL_SSH_HANDSHAKE_SECRET
and --ssh-handshake-skew-secs / OPENSHELL_SSH_HANDSHAKE_SKEW_SECS are
removed from the gateway, sandbox, and all driver binaries. The
openshell-ssh-handshake K8s Secret is no longer managed by the chart;
operators may delete the orphan. Deployments using
--disable-gateway-auth must enforce caller authentication at the
fronting proxy, since the gateway no longer validates a per-request
secret on sandbox-class methods.

Refs OS-174.

* docs(auth): scrub residual SSH handshake secret references

Sweep across docs, e2e scripts, the Podman driver README/NETWORKING
notes, the RPM/Helm/setup guides, the gateway man page, and RFC 0003
to remove instructions and examples that still referenced
OPENSHELL_SSH_HANDSHAKE_SECRET / --ssh-handshake-secret /
ssh_handshake_skew_secs. The mechanism is gone; nothing should still
suggest setting it. Negative-assertion regression tests are kept so
the env var cannot silently be re-introduced.

* test(drivers): drop SSH handshake secret negative-assertion tests

The supporting code, env vars, CLI flags, and config plumbing are
gone — these tests asserted absence of strings that no longer have
any path to being set. Remove the guards from the Docker, Podman,
Kubernetes, and VM driver test modules.
2026-05-14 13:14:30 -07:00
Drew Newberry f855c3d8bb feat(cli): add sandbox resource flags (#1376)
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
2026-05-13 22:37:47 -07:00
Drew Newberry 0471c6d2ab fix(gateway): keep vm driver opt-in (#1375) 2026-05-13 22:32:58 -07:00
Miyoung Choi 8322e4fd00 docs: style fixes (#1341)
* docs: style fixes

* docs: drop observability section overview page and rename a section title

* docs: title updates
2026-05-12 16:36:15 -07:00
Aaron Erickson 🦞andJohn Myers 9ea94b645d fix(sandbox): rewrite messaging credential placeholders (#1286)
* fix(sandbox): rewrite credential placeholders in websocket text frames

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): harden websocket credential rewrite

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* feat(sandbox): add websocket l7 inspection and compression

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): harden websocket upgrade validation

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* test(sandbox): cover route-selected websocket upgrades

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): harden websocket negotiation parsing

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* test(sandbox): add websocket conformance relay matrix

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* test(e2e): add websocket conformance lane

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(policy): support websocket incremental rules

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* feat(policy): enable websocket credential rewrite updates

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(cli): make websocket rewrite endpoint-local

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* feat(sandbox): support graphql websocket policy

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(policy): allow private IPs for websocket endpoints

* feat(sandbox): rewrite REST credential placeholders

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* refactor(sandbox): generalize credential aliases

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): rewrite encoded form credentials

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): close websocket policy and provider alias gaps

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(e2e): route websocket probe through host gateway

* fix(e2e): stabilize websocket probe handshake

* fix(e2e): exercise websocket probe through proxy

* ci: remove websocket conformance workflow

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Co-authored-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-05-11 22:40:44 -07:00
Mesut Oezdil 957daa0a0f docs(helm): document supervisor.sideloadMethod and sandboxNamespace default (#1309)
Add supervisor.sideloadMethod to the Kubernetes setup chart values table
and the compute drivers reference table. The value was added in the
ImageVolumeSource sideload PR and controls whether the supervisor binary
is delivered via an OCI image volume mount or an init container, with
auto-detection based on cluster version when left empty.

Update the server.sandboxNamespace description in both pages to reflect
that the Helm chart now derives it from the release namespace by default
when the value is left empty.
2026-05-11 13:21:56 -07:00
Drew Newberry daa2a362d5 fix(packaging): enable mTLS for local packages (#1271) 2026-05-08 13:02:12 -07:00
Drew Newberry 8ab5ee875b fix(vm): harden compute driver socket (#1248) 2026-05-07 20:03:16 -07:00
Drew Newberry 084c93b6ad fix(installer): repair dev install package and service setup (#1252) 2026-05-07 16:05:40 -07:00
Drew Newberry 310e1a5c14 docs(podman): restore driver architecture details (#1244) 2026-05-07 13:22:24 -07:00
Drew Newberry 70a0f6c547 refactor(cli): remove gateway lifecycle management (#1221) 2026-05-07 09:54:13 -07:00
Drew Newberry 728165a12c docs: consolidate documentation structure (#1231) 2026-05-07 09:14:08 -07:00
Drew Newberry f56c09c7df docs: update gateway deployment architecture (#1108) 2026-05-04 22:52:29 -07:00
John T. Myers 6b21804258 feat(policy): add GraphQL L7 inspection (#1083)
Support GraphQL L7 policies
2026-05-04 11:50:09 -07:00
Piotr Mlocek ee2de81bc9 fix(sandbox): preserve encoded slash policy from proto (#1073) 2026-04-29 16:36:49 -07:00
Piotr Mlocek df38d1f66f feat(ci): add Markdown and Mermaid linting (#933) 2026-04-24 11:27:02 -07:00
John T. Myers 40e9bf6feb feat(policy): add incremental sandbox policy updates (#860)
* feat(policy): add incremental sandbox policy updates

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

* docs(policies): expand incremental update guidance

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

* feat(policy): audit incremental updates in gateway logs

* docs(policy): quote glob specs in shell examples

---------

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-04-20 08:00:02 -07:00
Drew Newberry 5718553b97 feat(release): publish standalone openshell-gateway binaries (#853) 2026-04-16 16:58:24 -07:00
John T. Myers 28e1ff7b40 feat(policy): add deny rules to network policy schema (#822)
* feat(policy): add deny rules to network policy schema

Closes #565

Add L7 deny rules that block specific requests even when allowed by
access presets or explicit allow rules. Deny rules mirror the full
capability set of allow rules (method, path, query params, SQL command)
and take precedence -- if a request matches any deny rule, it is blocked
regardless of allow rules.

This enables the "allow everything except these specific operations"
pattern without enumerating every allowed endpoint. For example, granting
read-write access to GitHub while blocking PR approvals, branch
protection changes, and ruleset modifications.

* fix(policy): deny query matching fails closed, mirror allow-side validation

Addresses PR review findings P1 and P3:

P1: Deny-side query matching now uses fail-closed semantics. If ANY
value for a query key matches the deny matcher, the deny fires. The
previous implementation reused allow-side "all values must match"
logic which allowed ?force=true&force=false to bypass a deny on
force=true.

P3: Deny-side query validation now mirrors the full allow-side checks:
empty any lists, non-string matcher values, glob+any mutual exclusion,
glob type checks, and glob syntax warnings are all validated.
2026-04-14 21:52:14 -07:00
Drew Newberry 463f65a0bb fix(cli): support plaintext gateway registration (#824) 2026-04-13 17:08:08 -07:00
John T. Myers 2ca553a4a0 fix(sandbox): validate always-blocked IPs at load time, enrich denial logs, and filter un-fixable proposals (#814) (#815)
Policies with allowed_ips entries targeting loopback, link-local, or
unspecified ranges now fail at connection time instead of being silently
blocked at runtime. The shorthand log format for DENIED events includes
a [reason:...] suffix so operators can distinguish 'allowlist miss' from
'structurally un-allowable'. The mechanistic mapper skips proposals for
always-blocked destinations, preventing the infinite TUI notification
loop. The gateway validates proposed rules on approval as defense-in-depth.

- Extract shared IP helpers (is_always_blocked_ip, is_always_blocked_net,
  is_internal_ip) to openshell_core::net
- Reject always-blocked entries in parse_allowed_ips with hard error
- Skip implicit allowed_ips synthesis for always-blocked literal IP hosts
- Add status_detail to HttpActivityBuilder for denial reason propagation
- Enrich NET and HTTP shorthand with [reason:...] for DENIED events
- Add engine: tag to HTTP shorthand (consistency with NET shorthand)
- Filter always-blocked proposals in mechanistic mapper generate_proposals
- Add validate_rule_not_always_blocked server-side defense-in-depth
- Update architecture docs, published docs, and E2E test assertions
2026-04-13 09:47:50 -07:00
Piotr Mlocek 8b15ef772e docs(fern): move published docs into docs tree (#796)
Remove the legacy Sphinx pipeline and make docs/ the single source of truth so the published site matches the repository layout.
2026-04-09 15:02:27 -07:00
John T. MyersandJohn Myers e8950e624c feat(sandbox): add L7 query parameter matchers (#617)
* feat(sandbox): add L7 query parameter matchers

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

* fix(sandbox): decode + as space in query params and validate glob syntax

Three improvements from PR #617 review:

1. Decode + as space in query string values per the
   application/x-www-form-urlencoded convention. This matches Python's
   urllib.parse, JavaScript's URLSearchParams, Go's url.ParseQuery, and
   most HTTP frameworks. Literal + should be sent as %2B.

2. Add glob pattern syntax validation (warnings) for query matchers.
   Checks for unclosed brackets and braces in glob/any patterns. These
   are warnings (not errors) because OPA's glob.match is forgiving,
   but they surface likely typos during policy loading.

3. Add missing test cases: empty query values, keys without values,
   unicode after percent-decoding, empty query strings, and literal +
   via %2B encoding.

* fix(sandbox): add missing query_params field in forward proxy L7 request info

* style(sandbox): fix formatting in proxy L7 query param parsing

---------

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-30 13:53:12 -07:00
John T. MyersandJohn Myers 758c62d18d fix(sandbox): handle per-path Landlock errors instead of abandoning entire ruleset (#677)
* fix(sandbox): handle per-path Landlock errors instead of abandoning entire ruleset

A single missing path (e.g., /app in containers without that directory)
caused PathFd::new() to propagate an error out of the entire Landlock
setup closure. Under BestEffort mode, this silently disabled all
filesystem restrictions for the sandbox.

Changes:
- Extract try_open_path() and classify_path_error() helpers that handle
  PathFd failures per-path instead of per-ruleset
- BestEffort mode: skip inaccessible paths with a warning, apply
  remaining rules
- HardRequirement mode: fail immediately on any inaccessible path
- Add zero-rule safety check to prevent applying an empty ruleset that
  would block all filesystem access
- Pre-filter system-injected baseline paths (e.g., /app) in enrichment
  functions so missing paths never reach Landlock
- Add unit tests for try_open_path, classify_path_error, and error
  classification for ENOENT, EACCES, ELOOP, ENAMETOOLONG, ENOTDIR
- Update user-facing docs and architecture docs with Landlock behavior
  tables, baseline path filtering, and compatibility mode semantics
- Fix stale ABI::V1 references in docs (code uses ABI::V2)

Closes #664

* fix(sandbox): use debug log for NotFound in Landlock best-effort mode

NotFound errors for stale baseline paths (e.g. /app persisted in the
server-stored policy but absent in this container) are expected in
best-effort mode. Downgrade from warn! to debug! so the message does
not leak into SSH exec stdout (the pre_exec hook inherits the tracing
subscriber whose writer targets fd 1).

Genuine errors (permission denied, symlink loops, etc.) remain at warn!
for operator visibility.

Also move custom_image e2e marker from /opt to /etc (a Landlock baseline
read-only path) since the security fix now properly enforces filesystem
restrictions.

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-30 13:19:19 -07:00
John T. Myers f37b69b5e5 feat(sandbox): auto-detect TLS and terminate unconditionally for credential injection (#544)
* feat(sandbox): auto-detect TLS and terminate unconditionally for credential injection

Closes #533

The proxy now auto-detects TLS by peeking the first bytes of each
connection. When TLS is detected, it terminates unconditionally —
enabling credential injection and optional L7 inspection without
requiring explicit 'tls: terminate' in the policy.
2026-03-23 18:45:18 -07:00
Miyoung Choi 0792dcb425 docs: unify install command in landing page, change docs skill name, update contributing guides (#355) 2026-03-16 07:51:42 -07:00
Miyoung ChoiandKirit93 c420109a6a docs: add dedicated gateway docs, network policy tutorial, and license page (#294)
* Added gateway and tutorial

* add new content and polish, add license terms

* small fix

* tiny fix

* polish bit more

* change warning to important

* fix support matrix

* small fix

* add a note about example script

* fix link

* fix link

* improve

* fix links

* unclutter sandbax index more

* improve titles

* tiny style fix

* style guide on inferences, release notes link to gh

* polish

* nit

* switch version to 0.0.3

* incorporate feedback

---------

Co-authored-by: Kirit93 <kthadaka@nvidia.com>
2026-03-14 09:53:01 -07:00
Drew Newberry fbd93a4632 refactor: rename navigator- crate prefix to openshell- (#277) 2026-03-13 02:02:18 -07:00
Miyoung Choi 7430c75438 remove manully generated cli reference (#272) 2026-03-12 22:08:17 -07:00
Drew Newberry 89d21d7852 refactor(sandbox): sandboxes are managed as separate community images (#267) 2026-03-12 22:06:52 -07:00
Piotr Mlocek 14e296d318 fix(cli): add --no-keep for ephemeral sandbox create cleanup (#258) 2026-03-12 17:18:39 -07:00
Drew Newberry 95d7ae077e refactor(cli): remove kubeconfig port, add doctor llm-help, update debug docs (#252) 2026-03-11 21:25:25 -07:00
Drew Newberry bcc6dad1d0 feat(cli): launch sandbox editors via managed ssh include (#226) 2026-03-11 20:06:38 -07:00
Drew Newberry f97270f988 refactor(docker): rename server image to gateway (#246)
* refactor(docker): rename server image to gateway

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

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

* fix: catch remaining server->gateway references in docs and comments
2026-03-11 16:01:26 -07:00
Miyoung Choi 4e893322a1 update docs per new dev prs (#238) 2026-03-11 13:48:03 -07:00
Drew Newberry 756950140c refactor(python): rename navigator module to openshell and migrate config to gateway paths (#220) 2026-03-10 22:24:04 -07:00