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 b8ce17a6d2..61586b5bb8 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 5c5751772a..dc597189e4 100644 --- a/architecture/build.md +++ b/architecture/build.md @@ -33,12 +33,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 0ee2740c77..2614281e05 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] temp-env = "0.3" 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 70603524a8..2abc529131 100644 --- a/crates/openshell-sandbox/Cargo.toml +++ b/crates/openshell-sandbox/Cargo.toml @@ -59,6 +59,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 c1dbada149..2cfe467e83 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 772590d1b0..2f2d25cb5b 100644 --- a/crates/openshell-server/Cargo.toml +++ b/crates/openshell-server/Cargo.toml @@ -125,6 +125,15 @@ default = ["telemetry"] ## 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 979e372084..435daad09a 100644 --- a/crates/openshell-server/src/lib.rs +++ b/crates/openshell-server/src/lib.rs @@ -23,6 +23,16 @@ //! The VM launch plumbing now lives in [`compute::vm`]; keep this file limited //! to selecting and acquiring drivers. +// `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"