Establish the Phase 1 reference implementation proposed by RFC 0012
while retaining the existing Cargo and Mise workflows during evaluation.
- pin Bazel 9.1.1 and configure Bzlmod, rules_rs, LLVM, protobuf, and
Rust 1.95 toolchains
- import third-party crates from Cargo metadata and propagate the
workspace version into Bazel targets
- add library, binary, proc-macro, unit-test, and integration-test
targets across the supported Rust workspace crates and drivers
- generate protobuf Rust sources and descriptor sets under Bazel while
preserving Cargo-compatible generated-code imports
- annotate aws-lc-sys and zstd-sys native dependencies, build Z3 4.15.2
from source, and generate z3-sys bindings
- make CLI and procfs test fixtures available as explicit Bazel inputs
without relying on fixed host binary paths
- define optimized release targets for Linux x86_64 and aarch64 CLI,
sandbox, and gateway binaries, plus macOS aarch64 artifacts
- add Bazel, buildifier, and lcov to the Nix development environment
RFC: 0012 (rfc12 branch)
Refs: #2491
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
The log prefix value passed to nft contains colons (e.g.
openshell:bypass:sandbox-cc817378:) but was not wrapped in double
quotes. Since nft concatenates argv entries and parses the result,
the bare colons cause a syntax error that silently prevents all
bypass-attempt LOG rules from installing.
Wrap log prefix values in nft-quoted strings via a new nft_quote()
helper that strips embedded double-quotes (nft quoted strings don't
support escape sequences). All four log-rule generation sites (TCP
and UDP, for both per-sandbox and sidecar rulesets) are updated.
Fixes#2470
Signed-off-by: Grace Smith <grasmith@redhat.com>
* test(server): stabilize watch span cancellation test
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* test(server): poll span exporter at fixed interval
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
---------
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(podman): combine sandbox stop and removal
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(podman): use libpod volume removal parameter
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* fix(podman): preserve removal timeout margin
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
* test(podman): use virtual time for removal delay
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
---------
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
Give each span-assertion test its own thread-scoped in-memory exporter so
parallel tests cannot contaminate or reset captured spans. Keep a bare global
tracing registry only to preserve callsite interest, and serialize scoped
subscriber changes because tracing caches that interest process-wide.
Remove the test-only OTLP collector, polling delivery barrier,
transport-specific test, and direct opentelemetry-proto dependency. Seed the
expected persistence conflict before tracing begins so its assertion window
contains only the operation under test.
Signed-off-by: Kris Hicks <khicks@nvidia.com>
Extract common OpenTelemetry provider construction into openshell-otel so
OpenShell services share one OTLP/gRPC export implementation.
The shared crate owns:
- typed exporter setup errors and non-fatal provider enablement;
- endpoint trimming and URI validation before lazy exporter connection;
- fixed and environment-or-default service-name policies;
- service version and caller-supplied resource attributes;
- batch tracer-provider construction; and
- span-only tracing layers that exclude OpenTelemetry exporter callsites.
Migrate the gateway to the shared provider while retaining its configurable
service name, error marking, and tracing test collector. Add the shared crate
to the architecture inventory and document the tracing boundary.
Refs #2507
Signed-off-by: Kris Hicks <khicks@nvidia.com>
Add a published Issue Triage and Lifecycle page and align AGENTS.md,
CONTRIBUTING.md, README.md, the PR template, and the issue-handling
skills on the state:*/agent:* label model.
Signed-off-by: Kris Hicks <khicks@nvidia.com>
Add an opt-in OTLP/gRPC trace exporter to the gateway. Export is enabled
by the presence of an `[openshell.gateway.otlp]` table with an endpoint;
there is no separate toggle.
Instrumented:
- Inbound request server spans, named for the RPC (`$service/$method`) or
`{method} {path}` for plain HTTP. They continue valid W3C `traceparent`
context when present and start a new trace otherwise. gRPC spans also
carry `rpc.system`, `rpc.service`, `rpc.method`, and trailer-derived
`rpc.grpc.status_code`.
- Compute driver calls (create, delete, list, get, validate, watch) as
client spans anchored on the `ComputeDriver` contract.
- Store reads and writes as children of the current request or loop span.
- Work with no inbound request: compute driver initialization, the sandbox
reconcile sweep, provider credential refresh tick, and driver watch events.
Each roots one operation trace so its child work does not arrive as anonymous
single-span traces.
This is deliberately not exhaustive. Auth, policy evaluation, and
middleware remain uninstrumented, as do store lifecycle calls (`ping`,
`close`) that a readiness poll would turn into a span per tick. The aim is
a useful trace tree at a reviewable size; coverage can grow against real
traces.
Design notes:
- The TOML table owns whether and where to export. The SDK `OTEL_*`
variables own how; sampling, batching, and limits are not mirrored into
gateway config.
- The OpenTelemetry layer exports spans only. Existing `tracing` events
remain on the stdout and sandbox-log paths and are not copied into trace
payloads.
- Telemetry never blocks the gateway. A malformed endpoint logs an error
and disables export rather than failing startup, and buffered spans are
drained during graceful shutdown.
- Failed spans carry error status without a separate `error.type` attribute.
Request spans use HTTP status and gRPC response trailers; driver spans use
the returned gRPC status; autonomous loop spans record failed results
explicitly. Store spans exempt `UniqueViolation` and `Conflict`, because
those errors report expected contention such as a held lease or an
optimistic-concurrency retry.
- The compute driver is reachable only through `TracedDriver::call`, so a
call cannot skip its span. This is the client half of a client/server pair
and the single place to inject context if drivers move out of process.
- Tests share one process-wide subscriber and in-memory exporter because
`tracing` caches callsite interest globally.
Inbound W3C trace context is propagated into gateway request spans. Context
is not yet injected into outbound driver calls, so a future out-of-process
driver would still need propagation at the `TracedDriver` seam.
The Helm chart is intentionally unchanged, so OTLP export cannot yet be
enabled on a chart-deployed gateway.
Refs #2507
Signed-off-by: Kris Hicks <khicks@nvidia.com>
* feat(auth): implement RFC 0011 Phase 2 workspace authorization
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(auth): address PR review feedback on workspace authorization
- Docker e2e: add --health-port and switch readiness probe from
`openshell status` to `curl /healthz`, fixing a false-positive
readiness check in OIDC mode where the CLI exited 0 without
actually contacting the gateway
- ListWorkspaces: move membership filtering from post-query N+1
lookups into a SQL EXISTS subquery so pagination applies to the
visible set, not the global ordering. Add generic
list_with_membership to the persistence layer.
- Descriptor validator: reject role/scope fields on unauthenticated
and sandbox auth modes, and allow-list workspace_role as
user/admin and global_role as platform_admin to catch typos at
startup
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(server): use authed request in delete telemetry test
The workspace authorization added by the Phase 2 auth changes requires
a Principal on every delete request. The delete-telemetry test was still
using a bare Request::new, so extract_principal failed before the handler
could acquire the delete gate, causing a 5-second timeout flake.
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(auth): address gator review findings for workspace authorization
- Inject unauthenticated-local-dev principal in no-auth gateway mode so
handlers that call extract_principal() always find one.
- Cap label-selector membership query at MAX_PAGE_SIZE instead of
u32::MAX to bound the in-memory read.
- Authorize workspace membership before resolving workspace existence in
all sandbox RPCs to prevent workspace-name enumeration by non-members.
- Remove dead_code allow on AuthorizedWorkspace.workspace now that
callers use the normalized name from the authz result.
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(auth): close workspace-name oracle and label-selector truncation
Swap authorize-before-resolve ordering in 27 handlers across
provider.rs, service.rs, policy.rs, and workspace.rs to prevent
CWE-203 workspace-name enumeration by non-members.
Add combined membership+label SQL query (list_with_membership_and_selector)
to both persistence backends so ListWorkspaces with label selectors no
longer silently drops results beyond the first page of membership matches.
Signed-off-by: Derek Carr <decarr@redhat.com>
* test(auth): add non-member rejection and membership+label persistence tests
Add comprehensive test coverage for workspace authorization changes:
- Non-member rejection tests across all 44 workspace-scoped handlers
(sandbox, provider, service, policy, workspace, inference) verifying
PERMISSION_DENIED is returned instead of NOT_FOUND to prevent
CWE-203 workspace-name oracle
- Persistence test for list_with_membership_and_selector verifying
SQL-level membership EXISTS + label filtering, multiple predicates,
no-match cases, and pagination
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(auth): format merged import line in sandbox tests
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(auth): address gator re-review findings on workspace authorization
- Fix TUI unconditionally setting providers_v2_enabled after provider
refresh; read the actual gateway setting via GetGatewayConfig at
startup instead
- Fix SQLite json_extract with dotted label keys (e.g. example.com/env)
by quoting the key in the JSON path
- Add authed_request wrappers to upstream OCI identity tests that were
missing a principal after rebase
- Add test proving GetGatewayConfig is accessible without Platform Admin
- Add test for dotted/prefixed Kubernetes-style label key filtering
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(auth): address second gator re-review findings
- Loosen GetGatewayConfig from platform_admin to scope-only so workspace
users can discover providers_v2_enabled during sandbox creation with
inferred-provider commands; update proto descriptor, descriptor
validation, and RFC 0011 access table
- Add validate_label_selector to handle_list_workspaces and escape
single quotes in SQLite json_extract interpolation (CWE-89
defense-in-depth)
- Re-fetch providers_v2_enabled after TUI gateway switch so the new
gateway's capability is reflected
- Add e2e test for workspace user with inferred-provider command
- Add persistence test for adversarial label keys with SQL injection
attempts
- Add handler test for invalid label selector rejection in
ListWorkspaces
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(auth): address third gator review findings
- Cap label selector pairs at 64 (CWE-400) to bound SQLite dynamic SQL
- Add SCOPE_ONLY_METHODS allowlist for scope-without-role RPCs (CWE-863)
- Normalize ID-based data-plane handlers to return NOT_FOUND for
unauthorized sandboxes, closing the cross-workspace oracle (CWE-203)
- Fix TUI provider profile cache lookup key mismatch for legacy
providers with empty profile_workspace
- Add whoami to CLI skill reference command tree
- Update TUI skill doc with workspace, provider, and settings coverage
- Document scope/workspace orthogonality on GetGatewayConfig proto
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(auth): extend CWE-203 normalization to policy.rs sandbox handlers
GetSandboxConfig and GetSandboxLogs in policy.rs had the same
fetch-before-authorize pattern that leaked cross-workspace sandbox
existence. Promote fetch_and_authorize_sandbox to pub(super) and
use it from both sandbox.rs and policy.rs handlers.
Signed-off-by: Derek Carr <decarr@redhat.com>
* test(auth): update assertions for CWE-203 sandbox ID normalization
Cross-workspace sandbox access via ID-based handlers now returns
NOT_FOUND instead of PERMISSION_DENIED to prevent existence inference.
Update the unit test and OIDC e2e assertion to match.
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(auth): narrow CWE-203 error mapping and correct whoami output formats
Only remap PERMISSION_DENIED to NOT_FOUND in fetch_and_authorize_sandbox
and RevokeSshSession, letting INTERNAL and UNAUTHENTICATED propagate
as-is. Fix whoami --output format values in cli-reference.md to match
the actual CLI (table/json/yaml, not text/json).
Signed-off-by: Derek Carr <decarr@redhat.com>
* fix(ci): share network namespace with Keycloak in containerized CI
In GitHub Actions job containers, Docker port publishing lands on the
host, not inside the job container. Detect this environment and attach
Keycloak to the job container's network namespace instead, with
hardened defaults (cap-drop ALL, no-new-privileges, loopback-only
listener).
Signed-off-by: Derek Carr <decarr@redhat.com>
---------
Signed-off-by: Derek Carr <decarr@redhat.com>
ForwardSpec accepts IPv6 bind addresses, but ssh_forward_arg() emitted
them unbracketed (e.g. ::1:8080:127.0.0.1:8080), which OpenSSH rejects
as a bad local forwarding specification. access_url() likewise produced
invalid URLs like http://::1:8080/.
Extract a shared bracket_ipv6_host() helper and use it in
ssh_forward_arg(), access_url() (via format_gateway_url), and
format_gateway_url() so IPv6 literals are bracketed consistently.
Fixes#2279
Signed-off-by: Russell Bryant <rbryant@redhat.com>
The subprocess-based integration tests inherit the full parent
environment. If the developer has OPENSHELL_GATEWAY_INSECURE=true
set in their shell, it leaks into the spawned CLI process and causes
it to connect with .with_no_client_auth(), skipping the mTLS client
certificate. The test server requires mTLS, so it responds with
CertificateRequired and the test fails.
Strip OPENSHELL_GATEWAY_INSECURE, OPENSHELL_GATEWAY,
OPENSHELL_GATEWAY_ENDPOINT, and OPENSHELL_WORKSPACE from the
subprocess environment. The test already sets --gateway and
--gateway-endpoint explicitly via CLI args, so these env vars
should not influence the subprocess behavior.
Reported-by: Seth Jennings
Signed-off-by: Roland Huß <rhuss@redhat.com>
* refactor(compute): extract create_sandbox_record and update_sandbox_record helpers
Split apply_sandbox_update_locked into named helpers to make the two
distinct paths explicit: create_sandbox_record for first-observation events
and update_sandbox_record for subsequent driver snapshots on existing sandboxes.
The dispatcher now uses a match on the existing record rather than an
early-return guard.
No behavior change.
Signed-off-by: Evan Lezar <elezar@nvidia.com>
* refactor(compute): make sandbox readiness gateway-owned across all drivers
Introduce compute_phase_components and apply_readiness_conditions to
centralise the gateway's phase composition logic. The public SandboxPhase
is now determined by combining the backend phase reported by the driver with
supervisor session presence, independent of the driver implementation.
Remove SupervisorReadiness from the driver contract. Running containers
always report BackendReady; the gateway owns the Ready decision. Rename
the dispatcher match to three arms so that status-less events for existing
sandboxes are a documented no-op rather than a silent pass-through.
Drop backend_ready_no_session and the SupervisorNotConnected condition.
The BackendReady driver condition plus the Provisioning phase already
communicates that the backend is up but the supervisor has not connected.
The redundant condition added noise without new information.
Closes#1951
Signed-off-by: Evan Lezar <elezar@nvidia.com>
* fix(compute): expose disconnected supervisor readiness
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
* docs(sandboxes): clarify supervisor readiness lifecycle
Signed-off-by: Evan Lezar <elezar@nvidia.com>
---------
Signed-off-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Co-authored-by: Drew Newberry <anewberry@nvidia.com>
parse_duration_to_ms was moved to commands/common.rs since the
original PR was opened, but it still split the last byte of the
input with split_at(s.len() - 1), which panics when the final
character is multi-byte UTF-8 (e.g. 'openshell logs my-sandbox
--since 5€').
Split off the last character using its UTF-8 length instead, so
invalid units surface the intended 'unknown duration unit' error.
Add regression tests in commands/common.rs.
Signed-off-by: Andrew White <andrewh@cdw.com>
The Kubernetes driver's default workspace PVC never set storageClassName,
so on clusters with no default StorageClass the PVC stayed Pending and
sandbox creation failed.
Add a workspace_storage_class option to KubernetesComputeConfig, wired
through SandboxPodParams into the generated volumeClaimTemplates. When
non-empty it sets storageClassName; empty preserves the current behavior
of relying on the cluster default StorageClass.
Expose it via the OPENSHELL_K8S_WORKSPACE_STORAGE_CLASS env var on both
the standalone driver and the embedded gateway runtime defaults, and via
the server.workspaceStorageClass Helm value.
Closes#2442
Signed-off-by: lr90 <qiuweimin@matrixorigin.cn>
Replace yield_now() spin-loop with sleep(10ms) polling in
delete_handler_ends_telemetry_for_the_resolved_sandbox_id. The
single-threaded tokio runtime starves the spawn_blocking threads
used by SQLite when yield_now() burns 100% CPU waiting for the
delete gate entry count. Increase the timeout from 1s to 5s for
consistency with similar guard tests.
Signed-off-by: Derek Carr <decarr@redhat.com>
Move gateway management functions (gateway_status, gateway_info,
gateway_add, gateway_select, gateway_login, gateway_logout, gateway_list,
gateway_remove, gateway_use, gateway_info_not_configured) and their
private helpers from run.rs into commands/gateway.rs. Add pub use
re-exports in run.rs so main.rs call sites remain unchanged.
Extract shared test utilities (EnvVarGuard, with_tmp_xdg) into a
crate-level test_utils module to avoid duplication between gateway
tests and the remaining run.rs tests.
Part of #2304 (PR 2/6).
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>
Remove vm_slow_progress_before_ready from the subprocess-based
structured output tests. The 1.8s artificial delay (3x 600ms sleeps)
left only ~0.7s headroom within the 5s OPENSHELL_PROVISION_TIMEOUT,
causing sporadic TLS handshake failures on loaded Linux machines.
Root cause analysis:
- The CertificateRequired TLS alert occurs when the provision timeout
fires mid-handshake, causing the client to drop the connection before
presenting its certificate.
- tokio::process::Command is NOT the cause: it is a thin async wrapper
around std::process::Command that delegates environment setup, process
spawning, and I/O identically.
- The single-threaded tokio runtime is NOT the cause: testing with
multi_thread flavor produces identical timing.
- TempDir lifetime is sound: the directory lives until .output().await
returns, so certs exist for the subprocess's entire lifetime.
- Cert path resolution is deterministic: the subprocess resolves
XDG_CONFIG_HOME/openshell/gateways/openshell/mtls/ correctly.
The subprocess tests exist to verify that the real CLI binary produces
clean stdout (no ANSI chrome, spinners, or progress text leaking into
structured output). This invariant does not depend on slow progress
events; the ProgressOutput::Silent branch that swallows progress events
is already covered by the in-process test
sandbox_create_keeps_waiting_while_vm_progress_arrives.
Fixes#2501
Signed-off-by: Roland Huß <rhuss@redhat.com>
The Stderr arm of the interactive exec event loop locked stdout,
so stderr payloads were written to stdout. The non-interactive exec
path already handles the same event with stderr. Write stderr
payloads to stderr so shell redirection (e.g. 'openshell exec ...
2>err.txt') works in interactive mode.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(proxy): retry with backoff on transient accept errors instead of exiting
The proxy accept loop unconditionally broke on any accept() error,
permanently killing the proxy while the sandbox continued to report
Ready. Replace the break with a sleep-and-continue pattern: EMFILE/ENFILE
errors get exponential backoff (100ms to 3.2s) to let file descriptors
drain, and all other accept errors get a fixed 100ms backoff matching
the existing metadata_server and edge_tunnel patterns.
Closes#2337
Signed-off-by: Sean Burdine <sburdine@nvidia.com>
Signed-off-by: politerealism <burdcat17@gmail.com>
* fix(proxy): address review feedback on accept retry backoff
- Use libc::EMFILE / libc::ENFILE instead of raw errno values; promote
libc from dev-dependency to regular dependency
- Extract accept_backoff() and is_fd_exhaustion_error() into testable
helpers
- Fix unreachable 5s cap: bump exponent limit from min(6) to min(7) so
the 5_000ms ceiling is reachable (100 * 2^6 = 6400, capped to 5000)
- Add 6 unit tests covering exponential progression, counter reset,
saturation at cap, EMFILE/ENFILE detection, and non-FD error rejection
Signed-off-by: Sean Burdine <sburdine@nvidia.com>
Signed-off-by: politerealism <burdcat17@gmail.com>
* fix(proxy): classify accept errors and exit on terminal failures
Three-way error classification for the accept loop: transient errors
(EMFILE, ECONNABORTED) retry with backoff, terminal errors (EBADF,
EINVAL, ENOTSOCK) exit immediately, and unknown errors exit after 5
consecutive failures. Adds unit tests for the classifier and error
handler, plus a subprocess-isolated integration test that validates
EMFILE recovery by lowering RLIMIT_NOFILE.
Signed-off-by: Quinn Burdine <sburdine@redhat.com>
Signed-off-by: politerealism <burdcat17@gmail.com>
* fix(proxy): use nested or-patterns to satisfy clippy lint
Signed-off-by: Quinn Burdine <sburdine@redhat.com>
Signed-off-by: politerealism <burdcat17@gmail.com>
* fix(proxy): expand transient error allowlist and fix clippy/test issues
Widen the accept-error classification to cover all Linux accept(2)
transient errnos (ENETDOWN, EPROTO, ENOPROTOOPT, EHOSTDOWN,
EHOSTUNREACH, EOPNOTSUPP, ENETUNREACH, ENONET behind cfg gate).
Rename fd-exhaustion helpers to resource-pressure to reflect ENOBUFS
and ENOMEM coverage. Fix clippy lints in the integration test
(borrow_as_ptr, collection_is_never_read, while_let_loop) and make
post-EMFILE recovery verification cross-platform by handling Linux's
stale backlog behavior.
Signed-off-by: Quinn Burdine <sburdine@redhat.com>
Signed-off-by: politerealism <burdcat17@gmail.com>
* fix(proxy): add remaining Linux accept(2) transient errnos and make test skip explicit
Classify ENOSR, ESOCKTNOSUPPORT, EPROTONOSUPPORT, and ETIMEDOUT as
transient accept errors per Linux accept(2) documentation. Treat ENOSR
as resource pressure (exponential backoff). Make the EMFILE integration
test skip path emit a diagnostic instead of silently returning success.
Signed-off-by: Quinn Burdine <sburdine@redhat.com>
Signed-off-by: politerealism <burdcat17@gmail.com>
* fix(proxy): panic instead of silent skip when EMFILE is not triggered
The integration test child runs inside Command::output() which captures
stderr. A silent return on the Ok path meant CI would report a passing
test without any diagnostic. Panic instead so the parent test fails
visibly if the platform cannot induce EMFILE.
Signed-off-by: Quinn Burdine <sburdine@redhat.com>
Signed-off-by: politerealism <burdcat17@gmail.com>
---------
Signed-off-by: Sean Burdine <sburdine@nvidia.com>
Signed-off-by: politerealism <burdcat17@gmail.com>
Signed-off-by: Quinn Burdine <sburdine@redhat.com>
truncate_for_display sliced at a fixed byte index (&s[..77]), which
panics when the index is not a char boundary. A policy with an
over-long filesystem path containing multi-byte characters crashed
the sandbox supervisor / OPA policy loader instead of producing the
intended FieldTooLong violation.
Back off to the nearest char boundary before slicing, and add
regression tests.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(core): avoid i64 underflow on expired GCP token
When a cached GCP access token was already expired (expires_at_ms < now),
the remaining lifetime calculation used plain subtraction:
(expires_at_ms - now) / 1000. This underflows in debug builds and wraps
in release builds.
Use saturating_sub so an expired token simply yields a non-positive
remaining lifetime and is skipped. Add a regression test.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(server): avoid i64 underflow in lease age calculation
The lease steal path computed age as now_ms() - record.updated_at_ms.
If the stored updated_at timestamp is in the future (e.g. clock skew),
the subtraction underflows. Use saturating_sub to treat a future
timestamp as age 0, keeping the lease considered fresh.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(server): avoid i64 overflow on large credential max lifetime
max_lifetime_seconds is only validated as >= 0. The code multiplied the
raw i64 value by 1000 to compute max_expires, which overflows for
values above i64::MAX / 1000. The capped i32 value used for the STS
request was already computed but not reused.
Compute max_lifetime_ms from the capped value with saturating_mul and
use it for both expires_at fallback and max_expires.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(server): avoid hardcoded indexing into base_url_config_keys
resolve_vertex_ai_route assumed every inference provider profile has at
least two base URL config keys by indexing [0] and [1]. Only the Vertex
profile satisfies that today; other profiles would panic here.
Iterate base_url_config_keys with find_map instead, and add a test with
an empty key list to ensure no panic.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(server): clamp lease age at zero for future timestamps
Address review feedback: add regression coverage for future-timestamp
lease clock skew. Extract lease_is_expired() and clamp the age at zero —
i64::saturating_sub saturates at i64::MIN, not zero, so a future
updated_at_ms must be clamped explicitly to be treated as age zero and
remain unstealable with a positive TTL.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(server): fall back past blank preferred Vertex base URL
Address review feedback: find_map stopped at the first present key even
when its value was blank, and the outer filter discarded it without
considering lower-priority aliases. Filter blank values inside the
closure so a blank preferred key falls back to a valid alias, matching
the documented priority order. Add a regression test with a blank
preferred key and a valid fallback.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(server): saturate AWS STS expiry cap on saturated clock
Address review feedback: now_ms + max_lifetime_ms could still overflow
when current_time_ms() saturates near i64::MAX. Compute
max_expires = now_ms.saturating_add(max_lifetime_ms) once and use it for
both the conversion fallback and the expiry cap. Add a boundary unit
test covering a saturated clock.
Signed-off-by: Andrew White <andrewh@cdw.com>
---------
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(supervisor-process): use slice patterns when rewriting passwd/group lines
rewrite_passwd_at and rewrite_group_at indexed into a Vec of fields
with fields[N] after checking fields.len(). Use slice patterns so a
malformed line falls through to the no-op branch instead of risking a
panic if the guard is ever changed. Add regression tests for
malformed sandbox entries.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(bootstrap): avoid byte-index slice after @ in SSH destination
extract_host_from_ssh_destination sliced dest[at_pos + 1..] after
finding '@'. '@' is ASCII so this is currently safe, but it is a
latent panic surface if the split logic changes. Use get() instead
and add a multi-byte hostname test.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(bootstrap): avoid byte-index slice in .dockerignore glob matcher
glob_match sliced path[idx + 1..] after matching '/'. '/' is ASCII so
this is currently safe, but it is a latent UTF-8 panic surface. Use
get() instead.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(router): avoid literal byte offset when stripping /v1 prefix
build_backend_url sliced &path[3..] after verifying the /v1 prefix.
Use strip_prefix() so the code stays correct if the prefix length
ever changes.
Signed-off-by: Andrew White <andrewh@cdw.com>
* fix(supervisor-network): avoid byte-index slice in inference path matching
The Bedrock-style /*/ path matcher sliced rest[slash_at + 1..] after
finding '/'. '/' is ASCII so this is currently safe, but it is a
latent UTF-8 panic surface. Use get() instead.
Signed-off-by: Andrew White <andrewh@cdw.com>
---------
Signed-off-by: Andrew White <andrewh@cdw.com>
Claude Code v2.1.156+ sends `context_management` on every request. The
direct Anthropic API accepts it, but Vertex AI rawPredict rejects it with
HTTP 400 "Extra inputs are not permitted". The inference proxy was only
stripping the `model` field for Vertex routes; extend it to also strip
Anthropic-SDK-only beta fields listed in VERTEX_UNSUPPORTED_BODY_FIELDS.
Fixes#2444
Signed-off-by: Adel Zaalouk <azaalouk@redhat.com>
Move L7 endpoint semantic checks into the openshell-policy crate so both
profile lint and the runtime validator share one implementation. This
eliminates drift between the two validation paths.
The shared validator covers 9 checks: unknown protocol, rules/access
mutual exclusivity, JSON-RPC family access rejection, json-rpc requires
rules, non-JSON-RPC protocol requires rules or access, MCP requires
rules when allow_all is false, rules-would-deny-all detection,
deny_rules require protocol, and deny_rules require base allow set.
Changes rules/deny_rules fields to Option<Vec<...>> so absent vs empty
is distinguishable at lint time. Adds is_effectively_empty() to
L7AllowProfile for deny-all detection of allow: {} objects. Makes
rules_would_deny_all MCP-aware by checking tool/params.name selectors
before classifying a rule as deny-all. Adds params field to
L7AllowProfile so MCP tool selectors survive proto round-trip.
Signed-off-by: Grace Smith <gsmith@redhat.com>
Signed-off-by: Grace Smith <grasmith@redhat.com>