Skip to content

GatewayConfig.auth_token vault-hydrated field lacks #[serde(skip_serializing)], unlike sibling channel tokens #6248

Description

@bug-ops

Vulnerability

GatewayConfig.auth_token (crates/zeph-config/src/features.rs:915-916) is a live secret field: crates/zeph-core/src/config.rs:216-218 resolves ZEPH_GATEWAY_TOKEN from the age vault at startup and writes the real bearer token directly into self.gateway.auth_token on the in-memory Config. GatewayConfig derives Serialize and the field carries only #[serde(default)] — no #[serde(skip_serializing)].

This is the exact same latent-leak class that PR #6173 (commit 6ad8ed48) just fixed for TelegramConfig.token, DiscordConfig.token, SlackConfig.bot_token, and SlackConfig.signing_secret — all of which are vault-hydrated in place and previously had no skip_serializing. GatewayConfig.auth_token was not included in that fix. The fixing commit's own message explicitly names this exact field as a known, unfiled follow-up:

"Noted below as follow-ups (not yet filed as GitHub issues): the identical latent pattern on GatewayConfig.auth_token and AcpConfig.auth_token/auth_clients[].token..."

Note: AcpConfig.auth_token/auth_clients[].token do not need the same fix — src/acp.rs::resolve_acp_auth_clients resolves vault-backed ACP tokens into a separate zeph_acp::AcpClientToken struct and never mutates the serializable AcpConfig in place, so no analogous leak path exists there. Only GatewayConfig.auth_token reproduces the Telegram/Discord/Slack pattern.

Severity

Medium (P2) — no confirmed live Serialize-to-output path was found for a runtime-hydrated Config today (same audit posture PR #6173 used to justify deferring A2aServerConfig.auth_token/McpServerConfig fields, which are intentionally left plaintext because they're persisted to config.toml directly rather than vault-hydrated). But unlike those, gateway.auth_token genuinely holds a live secret value after vault resolution, so any future diagnostic dump, Serialize-based config export, or status/debug surface that touches the live Config would leak the real gateway bearer token in plaintext. One line away from Critical.

Location

  • crates/zeph-config/src/features.rs:915-916 (GatewayConfig.auth_token field, missing #[serde(skip_serializing)])
  • crates/zeph-core/src/config.rs:216-218 (vault hydration site, self.gateway.auth_token = Some(val))

Attack Scenario

An operator enables [gateway] with ZEPH_GATEWAY_TOKEN in the vault. Any future code path that serializes the live Config for diagnostics (e.g. a --dump-config/status export, a debug endpoint, a TUI settings view extended to cover gateway settings) would emit the real bearer token in plaintext output — logs, a support bundle, or a screen share — even though the equivalent Telegram/Discord/Slack tokens are already protected against this.

Remediation

Add #[serde(skip_serializing)] to GatewayConfig.auth_token, mirroring the fix already applied to TelegramConfig.token/DiscordConfig.token/SlackConfig.bot_token/SlackConfig.signing_secret in commit 6ad8ed48 (PR #6173). Add a doc # Security note per that PR's convention. Deserialize stays untouched so an inline auth_token in a hand-edited config.toml still loads. No change needed for AcpConfig fields (see note above).

References

Related: PR #6173 / commit 6ad8ed48, PR #6165 / commit 28c9a862, issue #6166

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