Finding
build_search_url and build_caps_url append the indexer API key as a plain query parameter (&apikey=…) onto the URL string. That URL is then embedded verbatim into the SearchIndexerError::HttpRequest { url } and SearchIndexerError::ParseResponse { url, … } variants, both of which surface the URL through their Display impl. The caller logs these errors at warn! with error = %e, invoking Display. Every network or parse failure therefore writes the full URL — API key included — to the logs in plaintext.
Evidence
crates/zetesis/src/client/mod.rs:135 embeds the key into the URL string:
url.push_str(&format!("&apikey={key}"));
crates/zetesis/src/client/torznab.rs:52 constructs the error from that same URL:
result.context(error::HttpRequestSnafu { url })?
crates/zetesis/src/search.rs:102 logs the error via Display, which includes the URL and key:
warn!(
indexer_id = indexer.id,
error = %e,
"search failed for indexer"
);
Why this matters
Anyone with read access to application logs — operators, monitoring agents, log aggregators, a SIEM — obtains a working API key for every indexer that hits a network or parse error. On a self-hosted LAN deployment these logs are frequently world-readable on the server, and the keys persist long after the transient failure that exposed them. A single network hiccup permanently discloses a credential.
Desired correction
Strip the apikey parameter from URLs before they enter any error struct. Add a redact_api_key(url: &str) -> String helper that rewrites apikey=<value> to apikey=[REDACTED], and apply it at the error-construction sites; alternatively, keep the API key out of the URL field stored in the error entirely. Done when: a test confirms that the Display output of an HttpRequest or ParseResponse error does not contain the literal API key value.
Finding
build_search_urlandbuild_caps_urlappend the indexer API key as a plain query parameter (&apikey=…) onto the URL string. That URL is then embedded verbatim into theSearchIndexerError::HttpRequest { url }andSearchIndexerError::ParseResponse { url, … }variants, both of which surface the URL through theirDisplayimpl. The caller logs these errors atwarn!witherror = %e, invokingDisplay. Every network or parse failure therefore writes the full URL — API key included — to the logs in plaintext.Evidence
crates/zetesis/src/client/mod.rs:135embeds the key into the URL string:crates/zetesis/src/client/torznab.rs:52constructs the error from that same URL:crates/zetesis/src/search.rs:102logs the error viaDisplay, which includes the URL and key:Why this matters
Anyone with read access to application logs — operators, monitoring agents, log aggregators, a SIEM — obtains a working API key for every indexer that hits a network or parse error. On a self-hosted LAN deployment these logs are frequently world-readable on the server, and the keys persist long after the transient failure that exposed them. A single network hiccup permanently discloses a credential.
Desired correction
Strip the
apikeyparameter from URLs before they enter any error struct. Add aredact_api_key(url: &str) -> Stringhelper that rewritesapikey=<value>toapikey=[REDACTED], and apply it at the error-construction sites; alternatively, keep the API key out of the URL field stored in the error entirely. Done when: a test confirms that theDisplayoutput of anHttpRequestorParseResponseerror does not contain the literal API key value.