Skip to content

High: empty-string bearer token can silently bypass auth in shared AuthConfig/auth_middleware #6268

Description

@bug-ops

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

  1. In AuthConfig::new, normalize an empty token to None before hashing: token.filter(|t| !t.is_empty()).map(|t| blake3::hash(t.as_bytes())).
  2. 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.
  3. 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).

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingsecuritySecurity-related issue

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions