[hardening] Wrap ProviderKey.secret (+ guardrail/exporter creds) in a redacting Secret type — make log leakage impossible by construction
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 157
- Forks
- 32
- Avg merge
- 1h 25m
- Merged PRs (30d)
- 145
Description
Summary
Provider/guardrail/exporter secrets are stored as plaintext String that derive Debug. Leakage to logs is prevented today only by discipline (an audit confirmed no current log statement prints them), not by construction. A future debug!("{:?}", provider_key) or any struct-dump would print the upstream secret into production logs. Wrapping secrets in a redacting newtype makes that a compile-time impossibility.
Current state
crates/aisix-core/src/models/provider_key.rs:
Plaintext +#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema, PartialEq)] pub struct ProviderKey { pub secret: String, /* … */ }Debug-derived.- Same trust boundary (per the
secretfield's own doc comment): Guardrail credentials (models/guardrail.rssecret_access_key/access_key_secret) and ObservabilityExporter headers (models/observability_exporter.rs). - Verified: no log statement currently prints these (so no active leak); protection is discipline-only.
Proposal — a redacting Secret newtype
Option A — secrecy crate (SecretString): zeroize-on-drop, battle-tested. Needs a serde-transparent wrapper for the etcd-projection round-trip (cp-api projects the plaintext secret over mTLS).
Option B — hand-rolled newtype (fits the existing serde-transparent need):
// crates/aisix-core/src/models/secret.rs
#[derive(Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
#[serde(transparent)] // wire / on-disk / etcd stay a bare string
pub struct Secret(String);
impl std::fmt::Debug for Secret {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Secret([redacted])")
}
}
impl Secret {
pub fn expose(&self) -> &str { &self.0 } // sole plaintext accessor → greppable audit
}
Changes
ProviderKey.secret: String→Secret.- Every use site (bridges building the
api-key/Authorizationheader, Bedrock SigV4 signer) →provider_key.secret.expose()— making every plaintext access explicit andgrep-auditable. #[derive(Debug)]onProviderKeyis now safe by construction (printsSecret([redacted])).- Extend to
Guardrail(secret_access_key,access_key_secret) andObservabilityExportersecret/header fields.
Non-breaking
#[serde(transparent)] keeps the wire / on-disk / etcd projection format a bare string — behavior unchanged; this only adds a Debug/Display guard.
Known boundary
This guards accidental Debug/Display logging, not a deliberate serde_json::to_string(&pk) written into a log (rarer path). Call that out in review / add a lint if desired.
Test
api7/ai-gateway has Rust unit tests; add:
#[test]
fn provider_key_debug_redacts_secret() {
let pk = ProviderKey { secret: Secret("super-secret".into()), /* … */ };
let d = format!("{:?}", pk);
assert!(!d.contains("super-secret"));
assert!(d.contains("[redacted]"));
}
Priority / sequencing
Defense-in-depth. No active leak today (verified), so not urgent — but it's the structural fix that makes the whole leakage class impossible. Pairs with the CI-side backstop already merged in api7/AISIX-Cloud#713 (test-harness redactSecrets for dumped docker logs). Suggested as a 2-step PR: (1) Secret newtype + ProviderKey; (2) extend to guardrail / observability creds.
Refs: api7/AISIX-Cloud#713 (test-harness redaction backstop, merged).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with crates/aisix-core/src/models/provider_key.rs, then inspect the secret fields in models/guardrail.rs and models/observability_exporter.rs and their use sites in api7/ai-gateway. Review the proposed Secret representation and run the existing Rust unit tests, adding coverage that struct Debug output contains [redacted] and never the plaintext secret while serde projections remain unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- security
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100