Root Cause:
The Rust client codegen emits #[instrument] on endpoint functions. Request structs containing password fields are logged by tracing as function arguments. Since codegen maps all opaque string-like types to plain String, the Debug impl prints passwords in cleartext to logs.
On the server side, secret::SecretString correctly redacts on Debug. But the generated client has no awareness of this — the field is just String, and #[instrument] happily prints it.
Solution:
Add first-class support for secrecy::SecretString in reflectapi, following the same pattern as uuid::Uuid and chrono::DateTime:
reflectapi crate: Add secrecy feature flag. Implement reflectapi::Input + reflectapi::Output for secrecy::SecretString. Registers as primitive "secrecy::SecretString".
Rust codegen: Recognize "secrecy::SecretString" → emit secrecy::SecretString in generated structs. Add secrecy dep to generated Cargo.toml. Since secrecy::SecretString implements Debug as SecretString([REDACTED]), #[instrument] no longer leaks values.
OpenAPI codegen: Map to { type: "string", format: "password" } (standard OpenAPI convention — tools like Swagger UI mask the input).
Python codegen: Map to pydantic.SecretStr (redacts on repr()/str()).
TypeScript codegen: Map to string (no runtime debug concern in TS; optionally a branded SecretString type for documentation).
Migration for private codebases:
Private secret::SecretString impls should set their reflectapi type to fall back to the public type. In the private Input impl for secret::SecretString, register as "secrecy::SecretString" (or use #[reflectapi(type = "secrecy::SecretString")]). This ensures the schema carries the public type name, and all codegen backends emit the correct secret-safe type.
Related: secret::SecretString is not capable of reflectapi::Output and a bunch of api responses *.signin return tokens as bare strings. These would be better converted to secret strings as well
Root Cause:
The Rust client codegen emits #[instrument] on endpoint functions. Request structs containing password fields are logged by tracing as function arguments. Since codegen maps all opaque string-like types to plain String, the Debug impl prints passwords in cleartext to logs.
On the server side, secret::SecretString correctly redacts on Debug. But the generated client has no awareness of this — the field is just String, and #[instrument] happily prints it.
Solution:
Add first-class support for secrecy::SecretString in reflectapi, following the same pattern as uuid::Uuid and chrono::DateTime:
reflectapi crate: Add secrecy feature flag. Implement reflectapi::Input + reflectapi::Output for secrecy::SecretString. Registers as primitive "secrecy::SecretString".
Rust codegen: Recognize "secrecy::SecretString" → emit secrecy::SecretString in generated structs. Add secrecy dep to generated Cargo.toml. Since secrecy::SecretString implements Debug as SecretString([REDACTED]), #[instrument] no longer leaks values.
OpenAPI codegen: Map to { type: "string", format: "password" } (standard OpenAPI convention — tools like Swagger UI mask the input).
Python codegen: Map to pydantic.SecretStr (redacts on repr()/str()).
TypeScript codegen: Map to string (no runtime debug concern in TS; optionally a branded SecretString type for documentation).
Migration for private codebases:
Private secret::SecretString impls should set their reflectapi type to fall back to the public type. In the private Input impl for secret::SecretString, register as "secrecy::SecretString" (or use #[reflectapi(type = "secrecy::SecretString")]). This ensures the schema carries the public type name, and all codegen backends emit the correct secret-safe type.
Related: secret::SecretString is not capable of reflectapi::Output and a bunch of api responses *.signin return tokens as bare strings. These would be better converted to secret strings as well