Vulnerability
AuthConfig::new (crates/zeph-common/src/http_middleware.rs:92-97) does not distinguish an empty-string token from a real one — it hashes whatever Option<&str> it receives without checking for emptiness. In auth_middleware, a request with no Authorization header defaults the submitted token to "". If the configured secret also resolves to "", the constant-time hash comparison passes and the request is treated as authenticated.
This is compounded by two call sites that use Option::is_some() (true for Some("")) rather than "is non-empty" as their signal that auth is configured:
crates/zeph-gateway/src/server.rs:313-317 — the "running without bearer auth" startup warning does not fire for Some("").
src/serve/mod.rs:161,238,349-368 (check_require_auth_guard) — the safeguard that refuses to bind a non-loopback listener when require_auth is set but no token was resolved also does not fire for Some("").
Shared by three consumers of zeph_common::http_middleware: zeph-gateway (crates/zeph-gateway/src/router.rs:39), zeph-a2a (crates/zeph-a2a/src/server/mod.rs:161, crates/zeph-a2a/src/server/router.rs:32), and zeph serve-sessions (src/serve/router.rs:40).
Severity
High (P1) — a config/vault value resolving to an empty string (e.g. a placeholder vault entry, a blank field in a secrets-manager integration) silently produces a fully authenticated bypass state with none of the codebase's existing operator-facing safeguards (startup warning, bind-refusal guard) triggering. Not Critical: it requires that specific precondition rather than being reachable from network input alone.
Location
crates/zeph-common/src/http_middleware.rs:92-97 (AuthConfig::new, root cause)
crates/zeph-common/src/http_middleware.rs:280-325 (auth_middleware, missing-header path defaults to "")
crates/zeph-gateway/src/server.rs:313-317
src/serve/mod.rs:161,238,349-368
crates/zeph-core/src/config.rs:189,216-218 (vault hydration assigns Some(val) unconditionally, no non-empty check)
Remediation
- In
AuthConfig::new, normalize an empty token to None before hashing: token.filter(|t| !t.is_empty()).map(|t| blake3::hash(t.as_bytes())).
- Change the
has_auth_token computations in src/serve/mod.rs to check non-emptiness, not just is_some(), so check_require_auth_guard cannot be bypassed by an empty-string secret even if (1) is missed elsewhere.
- Consider having vault-hydration call sites in
crates/zeph-core/src/config.rs skip assignment when the resolved secret is an empty string, treating a blank vault entry as "not configured" workspace-wide.
References
CWE-287 (Improper Authentication) / CWE-306. Related: #6248 (same GatewayConfig.auth_token vault-hydration site, different defect).
Vulnerability
AuthConfig::new(crates/zeph-common/src/http_middleware.rs:92-97) does not distinguish an empty-string token from a real one — it hashes whateverOption<&str>it receives without checking for emptiness. Inauth_middleware, a request with noAuthorizationheader defaults the submitted token to"". If the configured secret also resolves to"", the constant-time hash comparison passes and the request is treated as authenticated.This is compounded by two call sites that use
Option::is_some()(true forSome("")) rather than "is non-empty" as their signal that auth is configured:crates/zeph-gateway/src/server.rs:313-317— the "running without bearer auth" startup warning does not fire forSome("").src/serve/mod.rs:161,238,349-368(check_require_auth_guard) — the safeguard that refuses to bind a non-loopback listener whenrequire_authis set but no token was resolved also does not fire forSome("").Shared by three consumers of
zeph_common::http_middleware:zeph-gateway(crates/zeph-gateway/src/router.rs:39),zeph-a2a(crates/zeph-a2a/src/server/mod.rs:161,crates/zeph-a2a/src/server/router.rs:32), andzeph serve-sessions(src/serve/router.rs:40).Severity
High (P1) — a config/vault value resolving to an empty string (e.g. a placeholder vault entry, a blank field in a secrets-manager integration) silently produces a fully authenticated bypass state with none of the codebase's existing operator-facing safeguards (startup warning, bind-refusal guard) triggering. Not Critical: it requires that specific precondition rather than being reachable from network input alone.
Location
crates/zeph-common/src/http_middleware.rs:92-97(AuthConfig::new, root cause)crates/zeph-common/src/http_middleware.rs:280-325(auth_middleware, missing-header path defaults to"")crates/zeph-gateway/src/server.rs:313-317src/serve/mod.rs:161,238,349-368crates/zeph-core/src/config.rs:189,216-218(vault hydration assignsSome(val)unconditionally, no non-empty check)Remediation
AuthConfig::new, normalize an empty token toNonebefore hashing:token.filter(|t| !t.is_empty()).map(|t| blake3::hash(t.as_bytes())).has_auth_tokencomputations insrc/serve/mod.rsto check non-emptiness, not justis_some(), socheck_require_auth_guardcannot be bypassed by an empty-string secret even if (1) is missed elsewhere.crates/zeph-core/src/config.rsskip assignment when the resolved secret is an empty string, treating a blank vault entry as "not configured" workspace-wide.References
CWE-287 (Improper Authentication) / CWE-306. Related: #6248 (same
GatewayConfig.auth_tokenvault-hydration site, different defect).