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
Vulnerability
GatewayConfig.auth_token(crates/zeph-config/src/features.rs:915-916) is a live secret field:crates/zeph-core/src/config.rs:216-218resolvesZEPH_GATEWAY_TOKENfrom the age vault at startup and writes the real bearer token directly intoself.gateway.auth_tokenon the in-memoryConfig.GatewayConfigderivesSerializeand 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 forTelegramConfig.token,DiscordConfig.token,SlackConfig.bot_token, andSlackConfig.signing_secret— all of which are vault-hydrated in place and previously had noskip_serializing.GatewayConfig.auth_tokenwas not included in that fix. The fixing commit's own message explicitly names this exact field as a known, unfiled follow-up:Note:
AcpConfig.auth_token/auth_clients[].tokendo not need the same fix —src/acp.rs::resolve_acp_auth_clientsresolves vault-backed ACP tokens into a separatezeph_acp::AcpClientTokenstruct and never mutates the serializableAcpConfigin place, so no analogous leak path exists there. OnlyGatewayConfig.auth_tokenreproduces the Telegram/Discord/Slack pattern.Severity
Medium (P2) — no confirmed live Serialize-to-output path was found for a runtime-hydrated
Configtoday (same audit posture PR #6173 used to justify deferringA2aServerConfig.auth_token/McpServerConfigfields, which are intentionally left plaintext because they're persisted toconfig.tomldirectly rather than vault-hydrated). But unlike those,gateway.auth_tokengenuinely holds a live secret value after vault resolution, so any future diagnostic dump,Serialize-based config export, or status/debug surface that touches the liveConfigwould leak the real gateway bearer token in plaintext. One line away from Critical.Location
crates/zeph-config/src/features.rs:915-916(GatewayConfig.auth_tokenfield, 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]withZEPH_GATEWAY_TOKENin the vault. Any future code path that serializes the liveConfigfor 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)]toGatewayConfig.auth_token, mirroring the fix already applied toTelegramConfig.token/DiscordConfig.token/SlackConfig.bot_token/SlackConfig.signing_secretin commit6ad8ed48(PR #6173). Add a doc# Securitynote per that PR's convention.Deserializestays untouched so an inlineauth_tokenin a hand-editedconfig.tomlstill loads. No change needed forAcpConfigfields (see note above).References
Related: PR #6173 / commit
6ad8ed48, PR #6165 / commit28c9a862, issue #6166