From 5c6017c043357cfc609f27d59f0dced07c763a27 Mon Sep 17 00:00:00 2001 From: Russell Bryant Date: Thu, 20 Aug 2026 10:17:13 -0400 Subject: [PATCH 1/2] feat(build): add defaults-without-telemetry feature alias Cargo cannot subtract a single default feature, so compiling telemetry out meant `--no-default-features` plus a hand-maintained keep-list of the crate's other defaults. That keep-list was already wrong for operators: telemetry is the only default on openshell-server and openshell-driver-vm, but openshell-sandbox also defaults to `bundled-ca-roots`, so a bare `--no-default-features` silently swapped the supervisor onto the platform trust store. Add a `defaults-without-telemetry` alias to each of the three telemetry- carrying binary crates, enumerating every default except `telemetry`. Telemetry-free builds become `--no-default-features --features defaults-without-telemetry` and stay correct as the default set grows. The alias is a keep-list, not a switch. Enabling it on top of the defaults would otherwise produce a telemetry-on binary that reads as telemetry-free, so each crate root carries a `compile_error!` for the `telemetry` + `defaults-without-telemetry` combination. Add `rust:verify:defaults-without-telemetry` to guard both properties: each alias still equals its crate's defaults minus `telemetry`, and the mutual-exclusion error is wired up. The additive-misuse check matches on the `compile_error!` text rather than a nonzero exit code so it cannot pass vacuously on hosts where openshell-driver-vm fails to build for unrelated reasons. `rust:verify:telemetry-off` now builds through the alias. Signed-off-by: Russell Bryant --- .github/workflows/branch-checks.yml | 3 + README.md | 10 ++- architecture/build.md | 27 ++++-- crates/openshell-driver-vm/Cargo.toml | 9 ++ crates/openshell-driver-vm/src/lib.rs | 10 +++ crates/openshell-sandbox/Cargo.toml | 11 +++ crates/openshell-sandbox/src/lib.rs | 10 +++ crates/openshell-server/Cargo.toml | 9 ++ crates/openshell-server/src/lib.rs | 10 +++ tasks/rust.toml | 12 ++- .../verify-defaults-without-telemetry.sh | 88 +++++++++++++++++++ 11 files changed, 189 insertions(+), 10 deletions(-) create mode 100755 tasks/scripts/verify-defaults-without-telemetry.sh diff --git a/.github/workflows/branch-checks.yml b/.github/workflows/branch-checks.yml index bece5c8825..ca68b6d9f3 100644 --- a/.github/workflows/branch-checks.yml +++ b/.github/workflows/branch-checks.yml @@ -146,6 +146,9 @@ jobs: - name: Verify telemetry can be compiled out run: mise run rust:verify:telemetry-off + - name: Verify the defaults-without-telemetry feature alias tracks the default feature set + run: mise run rust:verify:defaults-without-telemetry + - name: Verify system CA roots build mode compiles and excludes bundled Mozilla roots run: mise run rust:verify:system-ca-roots diff --git a/README.md b/README.md index ba1ccf9859..28674cbdbf 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,15 @@ OpenShell collects anonymous telemetry to help improve the project for developer Disable telemetry at runtime by setting `OPENSHELL_TELEMETRY_ENABLED=false` on the gateway deployment. For Helm installs, set `server.telemetryEnabled=false`. OpenShell propagates this deployment setting into sandbox supervisor environments so sandbox-side telemetry collection is disabled as well. -You can also compile telemetry out entirely. Telemetry support is a default-on `telemetry` Cargo feature; building with `--no-default-features` produces binaries that contain no telemetry endpoint, no telemetry HTTP client, and no emission code. Build telemetry-free artifacts with, for example, `cargo build --release -p openshell-server --no-default-features` (gateway) and the equivalent for `openshell-sandbox` and `openshell-driver-vm`. With telemetry compiled out, the gateway emits nothing and reports telemetry disabled to the sandboxes it launches. +You can also compile telemetry out entirely. Telemetry support is a default-on `telemetry` Cargo feature, and each crate that carries it also defines a `defaults-without-telemetry` alias covering every other default feature. Build telemetry-free artifacts with `--no-default-features --features defaults-without-telemetry`: + +```shell +cargo build --release -p openshell-server --no-default-features --features defaults-without-telemetry +cargo build --release -p openshell-sandbox --no-default-features --features defaults-without-telemetry +cargo build --release -p openshell-driver-vm --no-default-features --features defaults-without-telemetry +``` + +The resulting binaries contain no telemetry endpoint, no telemetry HTTP client, and no emission code. With telemetry compiled out, the gateway emits nothing and reports telemetry disabled to the sandboxes it launches. Cargo has no way to subtract a single default feature, so `defaults-without-telemetry` must be paired with `--no-default-features`; passing it on its own leaves the defaults in place and fails the build rather than producing a binary that still emits. Telemetry events are limited to anonymous operational categories and counts, such as sandbox lifecycle outcomes, provider profile buckets, policy decision counts, and aggregate network activity denial categories. OpenShell telemetry does not collect sandbox names or IDs, hostnames, file paths, binary paths, prompts, credentials, provider names, model names, or user content. diff --git a/architecture/build.md b/architecture/build.md index 7daf4a7c66..2dd350018a 100644 --- a/architecture/build.md +++ b/architecture/build.md @@ -34,12 +34,27 @@ is the single switch that enables `openshell-core/telemetry` for its build graph. In-process drivers (`docker`, `kubernetes`, `podman`) inherit the gateway's setting through feature unification and carry no passthrough. -Building a binary with `--no-default-features` compiles out telemetry entirely: -no endpoint, no telemetry HTTP client, and no emission code. With telemetry -compiled out, `telemetry::enabled()` is always `false` and the `emit_*` helpers -are no-ops, so the data-model types stay available and dependent crates compile -unchanged. The runtime `OPENSHELL_TELEMETRY_ENABLED` switch remains the way to -disable telemetry in a default (telemetry-enabled) build. +Building a binary without the `telemetry` feature compiles out telemetry +entirely: no endpoint, no telemetry HTTP client, and no emission code. With +telemetry compiled out, `telemetry::enabled()` is always `false` and the +`emit_*` helpers are no-ops, so the data-model types stay available and +dependent crates compile unchanged. The runtime `OPENSHELL_TELEMETRY_ENABLED` +switch remains the way to disable telemetry in a default (telemetry-enabled) +build. + +Cargo cannot subtract a single default feature, so each of the three binary +crates also defines a `defaults-without-telemetry` alias listing every default +except `telemetry`. Telemetry-free builds use +`--no-default-features --features defaults-without-telemetry` and stay correct +as the default set grows, instead of dropping unrelated defaults the way a bare +`--no-default-features` does on `openshell-sandbox`. The alias is a keep-list, +not a switch: enabling it on top of the defaults would otherwise yield a +telemetry-on binary that reads as telemetry-free, so each crate root carries a +`compile_error!` for the `telemetry` + `defaults-without-telemetry` combination. +`rust:verify:defaults-without-telemetry` guards both properties — that each +alias still equals its crate's defaults minus `telemetry`, and that the +mutual-exclusion error is wired up — and `rust:verify:telemetry-off` builds +through the alias and inspects the resulting binaries for telemetry markers. Supervisor upstream TLS root-store selection is controlled by the `bundled-ca-roots` Cargo feature (on by default). Default builds use Mozilla diff --git a/crates/openshell-driver-vm/Cargo.toml b/crates/openshell-driver-vm/Cargo.toml index ebcb9d2bc2..30a0f2a970 100644 --- a/crates/openshell-driver-vm/Cargo.toml +++ b/crates/openshell-driver-vm/Cargo.toml @@ -59,6 +59,15 @@ default = ["telemetry"] ## default; build with `--no-default-features` for a telemetry-free VM driver ## that reports telemetry disabled to the sandboxes it launches. telemetry = ["openshell-core/telemetry"] +## Convenience alias: every default feature except `telemetry`. Build a +## telemetry-free VM driver with +## `--no-default-features --features defaults-without-telemetry` and stay +## correct as new default features are added. Cargo cannot subtract a single +## default feature, so this alias must be paired with `--no-default-features`; +## enabling it alongside `telemetry` is a compile error rather than a silent +## telemetry-on build. Kept in sync with `default` by +## `rust:verify:defaults-without-telemetry`. +defaults-without-telemetry = [] [dev-dependencies] openshell-otel-test-support = { path = "../openshell-otel-test-support" } diff --git a/crates/openshell-driver-vm/src/lib.rs b/crates/openshell-driver-vm/src/lib.rs index 98ba6b0c9a..f34c7dda8d 100644 --- a/crates/openshell-driver-vm/src/lib.rs +++ b/crates/openshell-driver-vm/src/lib.rs @@ -1,6 +1,16 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +// `defaults-without-telemetry` is an alias for the default feature set minus +// `telemetry`, not a switch that turns telemetry off. Cargo cannot subtract a +// default feature, so adding it on top of the defaults would otherwise produce +// a telemetry-on build that reads as telemetry-free. Fail the build instead. +#[cfg(all(feature = "telemetry", feature = "defaults-without-telemetry"))] +compile_error!( + "features `telemetry` and `defaults-without-telemetry` are mutually exclusive; \ + build a telemetry-free VM driver with `--no-default-features --features defaults-without-telemetry`" +); + pub mod driver; mod embedded_runtime; mod ffi; diff --git a/crates/openshell-sandbox/Cargo.toml b/crates/openshell-sandbox/Cargo.toml index c653db84dd..3463f03767 100644 --- a/crates/openshell-sandbox/Cargo.toml +++ b/crates/openshell-sandbox/Cargo.toml @@ -60,6 +60,17 @@ default = ["telemetry", "bundled-ca-roots"] ## `--no-default-features --features system-ca-roots` to build a supervisor ## that uses the platform trust store with telemetry intact. system-ca-roots = ["telemetry"] +## Convenience alias: every default feature except `telemetry`. Build a +## telemetry-free supervisor with +## `--no-default-features --features defaults-without-telemetry` and stay +## correct as new default features are added. Cargo cannot subtract a single +## default feature, so this alias must be paired with `--no-default-features`; +## enabling it alongside `telemetry` is a compile error rather than a silent +## telemetry-on build. Kept in sync with `default` by +## `rust:verify:defaults-without-telemetry`. Do not pair it with +## `system-ca-roots`, which re-enables `telemetry`; a build with neither +## telemetry nor bundled CA roots is plain `--no-default-features`. +defaults-without-telemetry = ["bundled-ca-roots"] telemetry = ["openshell-core/telemetry"] bundled-ca-roots = ["openshell-supervisor-network/bundled-ca-roots"] diff --git a/crates/openshell-sandbox/src/lib.rs b/crates/openshell-sandbox/src/lib.rs index b4c2cfd9dd..53cfca5721 100644 --- a/crates/openshell-sandbox/src/lib.rs +++ b/crates/openshell-sandbox/src/lib.rs @@ -5,6 +5,16 @@ //! //! This crate provides process sandboxing and monitoring capabilities. +// `defaults-without-telemetry` is an alias for the default feature set minus +// `telemetry`, not a switch that turns telemetry off. Cargo cannot subtract a +// default feature, so adding it on top of the defaults would otherwise produce +// a telemetry-on build that reads as telemetry-free. Fail the build instead. +#[cfg(all(feature = "telemetry", feature = "defaults-without-telemetry"))] +compile_error!( + "features `telemetry` and `defaults-without-telemetry` are mutually exclusive; \ + build a telemetry-free supervisor with `--no-default-features --features defaults-without-telemetry`" +); + mod activity_aggregator; mod denial_aggregator; #[cfg_attr(not(target_os = "linux"), allow(dead_code))] diff --git a/crates/openshell-server/Cargo.toml b/crates/openshell-server/Cargo.toml index 9bcf18baca..b13f7c19cb 100644 --- a/crates/openshell-server/Cargo.toml +++ b/crates/openshell-server/Cargo.toml @@ -133,6 +133,15 @@ in-tree-compute-drivers = [ ## On by default; build with `--no-default-features` for a telemetry-free gateway ## that contains no telemetry endpoint, HTTP client, or emission code. telemetry = ["openshell-core/telemetry"] +## Convenience alias: every default feature except `telemetry`. Build a +## telemetry-free gateway with +## `--no-default-features --features defaults-without-telemetry` and stay +## correct as new default features are added. Cargo cannot subtract a single +## default feature, so this alias must be paired with `--no-default-features`; +## enabling it alongside `telemetry` is a compile error rather than a silent +## telemetry-on build. Kept in sync with `default` by +## `rust:verify:defaults-without-telemetry`. +defaults-without-telemetry = [] bundled-z3 = ["openshell-prover/bundled-z3"] test-support = [] diff --git a/crates/openshell-server/src/lib.rs b/crates/openshell-server/src/lib.rs index 2667611bcc..3592535330 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -13,6 +13,16 @@ //! startup. Runtime selection only consults that registry or a configured //! external endpoint; it does not switch on driver names. +// `defaults-without-telemetry` is an alias for the default feature set minus +// `telemetry`, not a switch that turns telemetry off. Cargo cannot subtract a +// default feature, so adding it on top of the defaults would otherwise produce +// a telemetry-on build that reads as telemetry-free. Fail the build instead. +#[cfg(all(feature = "telemetry", feature = "defaults-without-telemetry"))] +compile_error!( + "features `telemetry` and `defaults-without-telemetry` are mutually exclusive; \ + build a telemetry-free gateway with `--no-default-features --features defaults-without-telemetry`" +); + mod auth; pub mod certgen; pub mod cli; diff --git a/tasks/rust.toml b/tasks/rust.toml index 854c2ac939..6ef34f60df 100644 --- a/tasks/rust.toml +++ b/tasks/rust.toml @@ -53,13 +53,19 @@ run = [ # markers, so the absent checks below can never become silently vacuous. "cargo build -p openshell-server --bin openshell-gateway", "tasks/scripts/verify-telemetry-compiled-out.sh present target/debug/openshell-gateway", - # Guard: telemetry-free builds must contain no telemetry markers. - "cargo build -p openshell-server --bin openshell-gateway --no-default-features", + # Guard: telemetry-free builds must contain no telemetry markers. Built + # through the `defaults-without-telemetry` alias, which is how the docs tell + # operators to produce these artifacts. + "cargo build -p openshell-server --bin openshell-gateway --no-default-features --features defaults-without-telemetry", "tasks/scripts/verify-telemetry-compiled-out.sh absent target/debug/openshell-gateway", - "cargo build -p openshell-sandbox --bin openshell-sandbox --no-default-features --features bundled-ca-roots", + "cargo build -p openshell-sandbox --bin openshell-sandbox --no-default-features --features defaults-without-telemetry", "tasks/scripts/verify-telemetry-compiled-out.sh absent target/debug/openshell-sandbox", ] +["rust:verify:defaults-without-telemetry"] +description = "Verify the defaults-without-telemetry feature alias matches default minus telemetry and cannot be used additively" +run = "tasks/scripts/verify-defaults-without-telemetry.sh" + ["rust:verify:system-ca-roots"] description = "Verify system CA roots build mode compiles and excludes bundled Mozilla root crates" run = [ diff --git a/tasks/scripts/verify-defaults-without-telemetry.sh b/tasks/scripts/verify-defaults-without-telemetry.sh new file mode 100755 index 0000000000..4d78cbc5b4 --- /dev/null +++ b/tasks/scripts/verify-defaults-without-telemetry.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Verify the `defaults-without-telemetry` alias still means "every default +# feature except telemetry", and that it cannot be used additively. +# +# Cargo cannot subtract a single default feature, so telemetry-free builds use +# `--no-default-features --features defaults-without-telemetry`. Two failure +# modes follow from that, and this guard covers both: +# +# 1. Drift. The alias enumerates the keep-list by hand, so it silently rots +# the moment a crate gains a new default feature. Telemetry-free builds +# would then quietly lose an unrelated default. +# 2. Additive misuse. `--features defaults-without-telemetry` without +# `--no-default-features` would otherwise compile a telemetry-on binary +# that reads as telemetry-free. Each crate root carries a `compile_error!` +# for that combination; this asserts the error is actually wired up. + +set -euo pipefail + +# Crates that define the alias. Each must forward `telemetry` and define +# `defaults-without-telemetry`. +CRATES=( + openshell-server + openshell-sandbox + openshell-driver-vm +) + +if ! command -v jq >/dev/null 2>&1; then + echo "error: 'jq' is required to inspect cargo metadata" >&2 + exit 2 +fi + +metadata=$(cargo metadata --no-deps --format-version 1) + +failed=0 +for crate in "${CRATES[@]}"; do + features=$(jq -c --arg crate "$crate" \ + '.packages[] | select(.name == $crate) | .features' <<<"$metadata") + + if [[ -z $features || $features == "null" ]]; then + echo "FAIL: crate '$crate' not found in workspace metadata" >&2 + failed=1 + continue + fi + + if ! jq -e 'has("defaults-without-telemetry")' <<<"$features" >/dev/null; then + echo "FAIL: $crate defines no 'defaults-without-telemetry' feature" >&2 + failed=1 + continue + fi + + expected=$(jq -r '(.default // []) - ["telemetry"] | sort | join(",")' <<<"$features") + actual=$(jq -r '(."defaults-without-telemetry" // []) | sort | join(",")' <<<"$features") + + if [[ $expected != "$actual" ]]; then + echo "FAIL: $crate 'defaults-without-telemetry' is out of sync with 'default'" >&2 + echo " default minus telemetry: [${expected}]" >&2 + echo " defaults-without-telemetry: [${actual}]" >&2 + echo " Update 'defaults-without-telemetry' in crates/$crate/Cargo.toml to match." >&2 + failed=1 + continue + fi + + echo "OK: $crate 'defaults-without-telemetry' == default minus telemetry [${expected}]" +done + +# Additive misuse must be a hard error. Match on the `compile_error!` text +# rather than a nonzero exit code: openshell-driver-vm does not build on every +# host, and a check that failed for an unrelated reason would make this guard +# silently vacuous. +for crate in "${CRATES[@]}"; do + output=$(cargo check -p "$crate" --features defaults-without-telemetry 2>&1 || true) + + if grep -qF "features \`telemetry\` and \`defaults-without-telemetry\` are mutually exclusive" <<<"$output"; then + echo "OK: $crate rejects 'telemetry' + 'defaults-without-telemetry'" + continue + fi + + echo "FAIL: $crate did not reject 'telemetry' + 'defaults-without-telemetry'" >&2 + echo " Expected the mutual-exclusion compile_error! in crates/$crate/src/lib.rs." >&2 + echo " Got:" >&2 + sed 's/^/ /' <<<"$output" | tail -20 >&2 + failed=1 +done + +exit "$failed" From 95e18053350b101bc3f298ca1cf70e9e60e7be41 Mon Sep 17 00:00:00 2001 From: Russell Bryant Date: Tue, 25 Aug 2026 16:44:05 -0400 Subject: [PATCH 2/2] fix feature alias for openshell-server Signed-off-by: Russell Bryant --- crates/openshell-server/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/openshell-server/Cargo.toml b/crates/openshell-server/Cargo.toml index b13f7c19cb..0fe474647a 100644 --- a/crates/openshell-server/Cargo.toml +++ b/crates/openshell-server/Cargo.toml @@ -141,7 +141,7 @@ telemetry = ["openshell-core/telemetry"] ## enabling it alongside `telemetry` is a compile error rather than a silent ## telemetry-on build. Kept in sync with `default` by ## `rust:verify:defaults-without-telemetry`. -defaults-without-telemetry = [] +defaults-without-telemetry = ["in-tree-compute-drivers"] bundled-z3 = ["openshell-prover/bundled-z3"] test-support = []