api7 / api7/aisix

[hardening] Wrap ProviderKey.secret (+ guardrail/exporter creds) in a redacting Secret type — make log leakage impossible by construction

Open
#547 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement priority-normal vulnerability
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:
    #[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema, PartialEq)]
    pub struct ProviderKey { pub secret: String, /* … */ }
    
    Plaintext + Debug-derived.
  • Same trust boundary (per the secret field's own doc comment): Guardrail credentials (models/guardrail.rs secret_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
  1. ProviderKey.secret: StringSecret.
  2. Every use site (bridges building the api-key / Authorization header, Bedrock SigV4 signer) → provider_key.secret.expose() — making every plaintext access explicit and grep-auditable.
  3. #[derive(Debug)] on ProviderKey is now safe by construction (prints Secret([redacted])).
  4. Extend to Guardrail (secret_access_key, access_key_secret) and ObservabilityExporter secret/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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.