API token (and other secrets) leak into tracing logs via #[tracing::instrument] on every endpoint
Context: API Makeathon participant. Found in a code review of the official clients; independently flagged by two separate review passes.
Client derives Debug while holding token: String, and the generator puts a bare #[tracing::instrument] on every generated endpoint method. tracing::instrument records all arguments as span fields using their Debug representation, including &self. So self (which contains the bearer token) becomes a span field on every API call.
Evidence
kittycad/src/lib.rs:165:
#[derive(Clone, Debug)]
#[cfg(feature = "requests")]
pub struct Client {
token: String,
base_url: String,
...
}
Every endpoint, e.g. kittycad/src/api_calls.rs:16:
#[tracing::instrument]
pub async fn get<'a>(&'a self, id: uuid::Uuid) -> Result<...> { ... }
The authors clearly know this matters: Client::new uses #[tracing::instrument(skip(token))] (lib.rs:200). The generated endpoint methods do not carry that skip. The client also installs reqwest_tracing::TracingMiddleware by default (lib.rs:219), so spans are active in normal operation. A static scan finds ~240 instrumented endpoint methods. Some methods additionally take secrets as direct arguments (API-token creation takes a token value; SAML request bodies can carry a private signing key), which are logged the same way.
Concrete failure
Any user who initializes a standard subscriber (tracing_subscriber::fmt(), INFO level, the default) has their Zoo API token written verbatim to stdout / log files / any span exporter on every request:
INFO get{self=ApiCalls { client: Client { token: "kc-REAL-TOKEN", base_url: "https://api.zoo.dev" } } id=...}: sending request
format!("{client:?}") alone also prints the token.
Suggested fix
Emit #[tracing::instrument(skip(self))] (or skip_all with an explicit allowlist) from the generator, and add a manual Debug for Client that redacts token. Consider a secrecy::Secret<String> wrapper for the token field so it cannot be Debug-printed at all.
Verify
format!("{:?}", Client::new("sentinel-token")) contains sentinel-token. Or install tracing_subscriber::fmt().with_test_writer(), call any endpoint against a wiremock server, and assert the captured output does not contain the token (it will).
Environment
Reviewed against the current main of KittyCAD/kittycad.rs.
API token (and other secrets) leak into tracing logs via
#[tracing::instrument]on every endpointContext: API Makeathon participant. Found in a code review of the official clients; independently flagged by two separate review passes.
ClientderivesDebugwhile holdingtoken: String, and the generator puts a bare#[tracing::instrument]on every generated endpoint method.tracing::instrumentrecords all arguments as span fields using theirDebugrepresentation, including&self. Soself(which contains the bearer token) becomes a span field on every API call.Evidence
kittycad/src/lib.rs:165:Every endpoint, e.g.
kittycad/src/api_calls.rs:16:The authors clearly know this matters:
Client::newuses#[tracing::instrument(skip(token))](lib.rs:200). The generated endpoint methods do not carry that skip. The client also installsreqwest_tracing::TracingMiddlewareby default (lib.rs:219), so spans are active in normal operation. A static scan finds ~240 instrumented endpoint methods. Some methods additionally take secrets as direct arguments (API-token creation takes a token value; SAML request bodies can carry a private signing key), which are logged the same way.Concrete failure
Any user who initializes a standard subscriber (
tracing_subscriber::fmt(), INFO level, the default) has their Zoo API token written verbatim to stdout / log files / any span exporter on every request:format!("{client:?}")alone also prints the token.Suggested fix
Emit
#[tracing::instrument(skip(self))](orskip_allwith an explicit allowlist) from the generator, and add a manualDebugforClientthat redactstoken. Consider asecrecy::Secret<String>wrapper for the token field so it cannot beDebug-printed at all.Verify
format!("{:?}", Client::new("sentinel-token"))containssentinel-token. Or installtracing_subscriber::fmt().with_test_writer(), call any endpoint against awiremockserver, and assert the captured output does not contain the token (it will).Environment
Reviewed against the current
mainof KittyCAD/kittycad.rs.