331 Commits
Author SHA1 Message Date
Evan Lezar 495fe4cc73 fix(docker): set migrations dir permissions to 755 on COPY (#475)
Docker COPY preserves build-host file permissions. On hosts with a
restrictive umask (e.g. 0027), the migrations directory is copied as
750 root:root, making it unreadable by the openshell user at runtime
and causing the server to crash with a permission denied error on
startup.

Using --chmod=755 ensures the directory is always world-readable
regardless of the build host umask.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
v0.0.12
2026-03-20 14:09:24 +01:00
Piotr Mlocek c0cdd665b6 fix(gateway): allow first live network policy update (#493) 2026-03-19 20:09:52 -07:00
Glen Maddern 5e4d0a0b61 fix(router): increase inference validation token budget (#432) 2026-03-19 15:47:28 -07:00
Drew Newberry de9dcaa44b fix(e2e): update log-reading helpers for rolling file appender (#480) (#481) 2026-03-19 14:06:39 -07:00
Serge Panev 564c4118fd fix(sandbox): rotate openshell.log daily, keep 3 files (#431) 2026-03-19 10:20:12 -07:00
Miyoung Choi 510dcd1d92 docs: add guidance for OpenAI-compatible cloud providers (#458)
* add guidance for OpenAI-compatible cloud providers

* add provider list and xref link

* add xref

* add provider name value column
2026-03-19 09:58:33 -07:00
Drew Newberry a4883d8286 fix(bootstrap): surface diagnostics for K8s namespace not ready failures (#466) 2026-03-19 09:13:11 -07:00
Drew Newberry 4878b9b086 fix(bootstrap): auto-cleanup Docker resources on failed gateway deploy (#464) v0.0.11 2026-03-18 21:24:21 -07:00
Drew Newberry a912848217 refactor(build): unify image build graph for cache reuse (#390) 2026-03-18 15:01:04 -07:00
Drew Newberry e45d415230 chore(repo): migrate github label taxonomy (#454) 2026-03-18 14:01:48 -07:00
Drew Newberry 1a706f33cf fix(ci): simplify dev release install instructions to use install.sh (#453) 2026-03-18 13:53:03 -07:00
John T. MyersandJohn Myers 3566e556f4 fix(ci): use env context instead of secrets in step-level if condition (#452)
The secrets context is not available in step-level if expressions in
GitHub Actions — only in with and env blocks. This caused the workflow
file to be invalid, failing every run with:

  Unrecognized named-value: 'secrets'

Fix by exporting the secret to a job-level env var and checking that
in the step condition instead.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-18 10:20:27 -07:00
Drew Newberry 5565a8bba3 fix(cli): suppress browser popup during auth via OPENSHELL_NO_BROWSER env var (#419) 2026-03-18 09:38:51 -07:00
John T. MyersandJohn Myers e26732bfae fix(ci): split vouch gate into two steps with separate tokens (#446)
The ORG_READ_TOKEN (read:org PAT) was being used for all API calls,
including closing PRs and posting comments, which it lacks permissions
for. Split into two steps:

1. Org membership check — uses ORG_READ_TOKEN exclusively
2. VOUCHED.td check + close — uses default GITHUB_TOKEN (has repo write)

Step 2 is skipped entirely if step 1 confirms org membership.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-18 09:29:23 -07:00
John T. MyersandJohn Myers 82cb8d2bed fix(ci): use ORG_READ_TOKEN for org membership check in vouch gate (#445)
The GITHUB_TOKEN cannot determine org membership — it lacks read:org
scope and this is not configurable via the permissions block. Both
author_association and orgs.checkMembershipForUser return NONE/404 for
org members (even public ones) when called with the repo-scoped token.

Use an ORG_READ_TOKEN secret (fine-grained PAT with read:org) when
available, falling back to GITHUB_TOKEN. Also restores the
checkCollaborator fallback since the PAT can resolve that too.

Setup required: create a fine-grained PAT with Organization > Members >
Read permission, then add it as a repo secret named ORG_READ_TOKEN.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-18 09:24:19 -07:00
Piotr Mlocek 73e19134b0 fix(ci): pass wheel filenames as job output instead of re-downloading (#418)
The trigger-wheel-publish job was downloading the wheel artifact onto a
persistent self-hosted runner just to glob the filenames. Since
download-artifact does not clean the destination directory, stale .whl
files from every previous run accumulated in release/ and were all sent
to GitLab as WHEEL_FILENAMES.

Confirmed on the runner: /home/ubuntu/actions-runner/_work/OpenShell/
OpenShell/release/ contained 120+ wheels spanning versions 0.0.5
through 0.0.10.

Fix: capture wheel filenames as a job output in build-python-wheels and
pass them directly to trigger-wheel-publish. The trigger job no longer
downloads the artifact at all — it only needs the filenames to construct
GitHub release asset URLs for GitLab/Kitmaker.
2026-03-18 09:20:18 -07:00
John T. MyersandJohn Myers 85a3d83d75 fix(ci): fetch author_association via REST API instead of webhook payload (#444)
The webhook payload field context.payload.pull_request.author_association
is unreliable under pull_request_target events — it was absent or not
populated, causing the previous fix (#442) to still fail.

Switch to fetching author_association via pulls.get REST API, which only
needs pull-requests permission (already granted) and reliably returns
MEMBER for org members regardless of membership visibility.

Also removes the redundant orgs.checkMembershipForUser and
repos.checkCollaborator fallbacks, which suffered from the same
GITHUB_TOKEN permission limitation that started this bug.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-18 09:12:56 -07:00
John T. MyersandJohn Myers 240d0ee3ed fix(ci): check author_association before API calls in vouch gate (#442)
The vouch-check workflow was closing PRs from NVIDIA org members because
the GITHUB_TOKEN lacks read:org scope, causing orgs.checkMembershipForUser
to return 404 for non-public members. The catch block silently swallowed
these as expected 'not found' responses.

Add an author_association check from the webhook payload as the primary
bypass. GitHub sets this field server-side (MEMBER, OWNER, COLLABORATOR)
regardless of membership visibility, with no extra token permissions
needed. The existing API calls are kept as fallbacks.

Fixes the false positive that closed #430.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-18 08:53:10 -07:00
Yuxi Shi 1d071b8d97 fix(deploy): remove duplicate glob pattern in manifest cleanup loop (#428) 2026-03-18 00:05:20 -07:00
Drew Newberry dcd991275c fix(ci): use published install script in release workflows (#416) 2026-03-17 21:09:26 -07:00
Drew Newberry 13f13c2ea1 fix(server): add startup probe for gateway boot (#417) v0.0.10 2026-03-17 17:08:52 -07:00
Drew Newberry cf66d05c25 fix(installer): remove duplicate app name in install output (#408)
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
v0.0.9
2026-03-17 14:35:27 -07:00
Parth Sareen efb80e7382 docs(ollama): add ollama to community sandboxes catalog and supported agents (#383)
* docs(ollama): add ollama to community sandboxes catalog and supported agents

* update readme
2026-03-17 14:02:40 -07:00
Drew Newberry 925160e848 refactor: simplify install.sh to print PATH guidance (#403) 2026-03-17 12:22:25 -07:00
Piotr Mlocek 00ae3edb5b fix(docs): resolve Pygments console lexer error in LM Studio tutorial (#402)
The console lexer cannot tokenize single-quoted JSON arguments in
multi-line shell commands. Switch the Step 5 code blocks to the bash
lexer which handles quoting correctly. Also standardize earlier blocks
to use the console lexer with $ prompts for simple commands, and
replace -H/-d curl flags with --json for consistency.
2026-03-17 10:45:36 -07:00
Drew Newberry 5439f478ac fix(ci): skip auto-tag when no new commits since latest tag (#399) 2026-03-17 09:28:55 -07:00
will-lms 389454f02b fix(verification): send content type (#382)
Signed-off-by: Will Burford <will@lmstudio.ai>
2026-03-17 09:26:22 -07:00
will-lms 0463046ad0 docs(inference): Add LM Studio guide (#386)
Signed-off-by: Will Burford <will@lmstudio.ai>
2026-03-16 21:30:42 -07:00
Drew Newberry bb4545ff15 fix(ci): skip remote sccache config for fork PRs (#388) v0.0.7 v0.0.8 2026-03-16 19:04:34 -07:00
Piotr Mlocek 18fb7af4e2 perf(docker): move version ARG below cached layers to fix cache invalidation (#385)
The OPENSHELL_CARGO_VERSION build arg contains a git commit hash that
changes on every build (e.g. 0.0.7-dev.11+g085b131ae). Declaring this
ARG near the top of each Dockerfile invalidated every layer below it --
including expensive dependency installs, toolchain setup, and the Rust
dependency pre-build step -- on every single commit.

Move the ARG declaration to just before the RUN that actually uses it so
upstream layers stay cached. This recovers ~5-6 minutes per build on the
gateway image (from ~9m back toward ~2-3m) and similarly improves cluster,
CLI, and Python wheel builds.

Also removes unused OPENSHELL_IMAGE_TAG ARG from cli-macos, python-wheels,
and python-wheels-macos Dockerfiles.
2026-03-16 18:56:30 -07:00
Drew Newberry 48cb68914f ci(release): enable scheduled nightly release auto-tag (#384) 2026-03-16 17:44:58 -07:00
John T. Myers 8227719d82 fix: use dedicated vouched branch to avoid branch protection (#379) 2026-03-16 16:07:57 -07:00
John T. Myers 20dab0bbf1 chore: replace mitchellh/vouch with hand-rolled workflows (#378) 2026-03-16 15:56:51 -07:00
John T. MyersandJohn Myers c95a954b0f chore: pin mitchellh/vouch actions to SHA (#377)
Signed-off-by: John Myers <johntmyers@users.noreply.github.com>
Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-16 15:18:37 -07:00
John T. Myers a4e2c91008 chore: add vouch system for first-time contributors (#375)
chore: add vouch system for first-time contributors
2026-03-16 15:06:50 -07:00
Kirit Thadaka 8235fe9712 docs: add docs badge to readme (#370) 2026-03-16 14:56:44 -07:00
Piotr Mlocek 34804e1e83 fix(e2e): replace Docker Hub images in E2E tests to avoid rate limits (#369) 2026-03-16 13:51:32 -07:00
John T. Myersandjohntmyers 241e95dc39 feat(policy): support host wildcards and multi-port endpoints (#366)
* feat(policy): support host wildcards and multi-port endpoints

Add glob-style host wildcards to endpoints[].host using OPA's
glob.match with "." as delimiter — *.example.com matches a single
DNS label, **.example.com matches across labels. Validation rejects
bare * and requires *. prefix; warns on broad patterns like *.com.

Add repeated uint32 ports field to NetworkEndpoint for multi-port
support. Backwards compatible: existing port scalar is normalized to
ports array. Both the proto-to-JSON and YAML-to-JSON conversion paths
emit a ports array; Rego always references endpoint.ports[_].

Fix OpaEngine::reload() to route through the full preprocessing
pipeline instead of bypassing L7 validation and port normalization.

Closes #359

* fix(policy): reject configs with both port and ports set

---------

Co-authored-by: johntmyers <johntmyers@users.noreply.github.com>
2026-03-16 13:36:33 -07:00
Drew Newberry 085b131aef fix(cli): use --name flag in gateway destroy help messages (#368)
Error messages and recovery hints were showing 'openshell gateway destroy <name>'
as a positional argument, but the CLI defines name as a --name flag. This caused
'unexpected argument' errors when users copied the suggested commands.
2026-03-16 12:48:56 -07:00
John T. Myers 475ee166a0 refactor(proxy): distinguish CONNECT_L7 from CONNECT in policy logs (#365)
Closes #362

Split the CONNECT log emission in the proxy so denied connections log
immediately as CONNECT while allowed connections defer logging until
after the L7 config check. When L7 inspection follows, emit CONNECT_L7
instead of CONNECT so log consumers can distinguish standalone L4
policy decisions from tunnel lifecycle events.
2026-03-16 11:56:43 -07:00
Drew Newberry c1195be848 fix(ci): add actions:write permission to release-auto-tag workflow (#361)
The 'Trigger Release Tag workflow' step uses gh workflow run to dispatch
release-tag.yml, which requires the actions:write permission. Without it
the GITHUB_TOKEN gets HTTP 403: Resource not accessible by integration.
2026-03-16 10:47:50 -07:00
Drew Newberry bee0ea8ea1 fix(bootstrap): support cgroup v1 hosts by disabling kubelet failCgroupV1 check (#360)
Kubernetes 1.35 (k3s v1.35.x) rejects cgroup v1 by default via the
failCgroupV1 kubelet config option. This breaks gateway startup on
distros that still use cgroup v1 (e.g. Rocky Linux 8, CentOS 7/8).

Detect cgroup v1 at container startup by checking for the absence of
/sys/fs/cgroup/cgroup.controllers and pass
--kubelet-arg=fail-cgroupv1=false so kubelet warns instead of exiting.
2026-03-16 10:36:14 -07:00
Drew Newberry a29acae13c chore: update readme (#357) 2026-03-16 09:36:08 -07:00
Alexander Watson a0aea6904b Added pauses and syntax highlighting to demo for clarity (#358) 2026-03-16 09:05:34 -07:00
Drew Newberry a458ca6c5c fix(bootstrap): use host cgroup namespace for gateway container (#329)
Docker Desktop 29.x defaults to private cgroupns which prevents k3s
kubelet from accessing cgroup v2 controllers (cpu, cpuset, memory,
pids, hugetlb). This causes ContainerManager to fail during startup.

Explicitly set cgroupns_mode to host, which is backwards compatible
with all Docker versions and matches what k3s-in-Docker tooling
(k3d) requires.
2026-03-16 08:20:05 -07:00
Drew Newberry 5b70865857 chore: update readme (#356) 2026-03-16 08:10:31 -07:00
John T. Myers 647b7947f6 fix: security hardening from aardvark/codex scanner findings (#352)
* fix(sandbox): prevent overread request smuggling in L7 REST parser

The HTTP/1.1 parser used a 1024-byte read buffer that could capture
bytes from a pipelined second request. Those overflow bytes were
forwarded upstream as body overflow without L7 policy evaluation,
enabling request smuggling that bypasses per-request method/path
enforcement.

Replace multi-byte read with byte-at-a-time read_u8 that stops exactly
at the CRLFCRLF header terminator. Add regression test proving two
pipelined requests are parsed independently.

Refs: #350

* fix(server): harden sandbox TLS secret volume permissions to 0400

Kubernetes secret volumes default to 0644, allowing the unprivileged
sandbox user to read the mTLS client private key via the Landlock
baseline /etc read set. A compromised sandbox could use the key to
impersonate the control-plane client.

Set defaultMode to 256 (octal 0400, owner-read only) on both the
default and custom pod template paths. The supervisor reads TLS
materials as root before forking, so this does not affect normal
operation.

Refs: #350

* fix(sandbox): stop using cmdline paths for binary policy matching

/proc/pid/cmdline is fully attacker-controlled (argv[0] can be set to
any string via execve) and had no integrity verification, unlike
exec.path and ancestors which are kernel-managed and get TOFU/SHA256
checks. The Rego policy used cmdline_paths as a grant-access signal,
allowing any sandboxed process to claim the identity of an allowed
binary and bypass network restrictions.

Remove the cmdline exact-match rule and exclude cmdline_paths from the
glob-match rule. Only exec.path and exec.ancestors (from /proc/pid/exe)
are now used for binary identity. cmdline_paths remain in the OPA input
for deny-reason diagnostics only.

Refs: #350

* fix(sandbox): reject symlink and non-dir read_write paths before chown

The supervisor runs as root and calls chown on each read_write path.
Since chown follows symlinks, a malicious container image could place a
symlink (e.g. /sandbox -> /etc/shadow) to trick the supervisor into
transferring ownership of arbitrary files to the sandbox user.

Add symlink_metadata (lstat) check before chown to reject symlinks and
non-directory entries. The TOCTOU window is not exploitable because no
untrusted child process has been forked yet at this point.

Refs: #350

* fix(server): redact provider credentials in gRPC CRUD responses

Provider CRUD RPCs (create, get, list, update) returned full Provider
objects including plaintext credentials (API keys, secrets). Any
authenticated client -- including sandbox workloads running untrusted
code -- could read credentials for all providers.

Add redact_provider_credentials helper that clears the credentials map
before returning. Internal server paths (inference routing, sandbox env
injection) read from the store directly and are unaffected. Update
tests to verify redaction and assert persistence via direct store reads.

Refs: #350

* fix(sandbox): enforce non-root fallback when process user unset

drop_privileges silently returned Ok(()) when both run_as_user and
run_as_group were None, even when running as root. In local/dev mode
policies are loaded from disk without passing through the server-side
ensure_sandbox_process_identity normalization, so child processes could
retain root and all capabilities (SYS_ADMIN, NET_ADMIN).

When running as root with no process identity configured, fall back to
sandbox:sandbox instead of no-oping. Non-root runtimes are unaffected.

Refs: #350

* fix(sandbox): deny forward proxy for L7-configured endpoints

The forward proxy path only performed L4 (endpoint) and allowed_ips
checks. If an endpoint had L7 rules (method/path restrictions), a
sandboxed process could bypass them by using HTTP_PROXY with plain
http:// requests instead of CONNECT tunneling, since L7 inspection
only runs in the CONNECT path.

Add a guard in handle_forward_proxy that queries the endpoint's L7
config and returns 403 if any L7 rules are present, forcing traffic
through the CONNECT path where per-request inspection happens.

Refs: #350

* test(e2e): add regression test for forward proxy L7 bypass

Verifies that the forward proxy path (plain http:// via HTTP_PROXY)
returns 403 for endpoints with L7 rules configured, preventing
sandboxed processes from bypassing per-request method/path enforcement
by avoiding the CONNECT tunnel.

Refs: #350

* fix: update tests for cmdline path and TLS volume changes

Update OPA tests to verify cmdline_paths no longer grant access
(regression tests for the security fix). Fix formatting in TLS
volume test.

Refs: #350

* test(e2e): update provider e2e tests for credential redaction

Provider CRUD gRPC responses no longer include credential values.
Update three e2e tests to assert credentials are empty in responses.
Provider functionality is verified by existing e2e tests that check
env var injection into sandboxes (which read from the store directly).

Refs: #350

* chore: reformat for Rust 1.94 assert! macro style

* fix(sandbox): make drop_privileges tests root-aware for CI

CI runs as root but has no 'sandbox' user. The security fix correctly
errors when running as root with no process identity and no fallback
user available -- this is the intended behavior (refuse to run as root).
Update tests to expect that error in root-without-sandbox-user
environments instead of unconditionally asserting Ok.

Refs: #350

* fix(sandbox): allow non-directory read_write entries like /dev/null

The symlink guard incorrectly rejected all non-directory entries.
Character devices like /dev/null are legitimate read_write paths
used in sandbox policies. Only reject symlinks, which are the
actual attack vector for the chown privilege escalation.

Refs: #350
2026-03-16 07:55:19 -07:00
Piotr Mlocek ee40fc82ac fix(ci): use github-script for wheel pruning instead of gh CLI (#354) 2026-03-16 07:53:02 -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
Piotr Mlocek 85903b95e0 docs: add debug-inference skill, Ollama tutorial, and remove stale inference policy references (#353) v0.0.6 2026-03-16 07:26:25 -07:00