You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The connectors guidance tells plugin authors that annotating a SecretString field with iggy_common::serde_secret::serialize_secret gives them redaction. It does the opposite: the helper calls expose_secret() and writes the value in plaintext. Nine connector plugins follow that guidance today.
Its own module doc says the opposite of the skill:
These helpers are for fields that must be serialized (e.g., wire protocol payloads, persisted TOML configs, API responses that already expose credentials by design).
Do not add serialize_with to fields that should remain redacted in serialized output — rely on SecretString's default behavior instead.
And its test serialize_optional_secret_with_some_value asserts the output is {"token":"tok_123"}.
The Debug half of the claim is true — SecretBox's Debug prints [REDACTED]. Only the serialization half is inverted.
Why it matters
SecretString deliberately has no Serialize impl, so a config struct holding one cannot derive Serialize at all. Adding serialize_with is what unblocks the derive — it converts a compile-time guarantee into plaintext output, while the documentation says it does the reverse.
Nine plugins carry the annotation on credential fields:
In practice the attribute appears inert: the runtime keeps plugin configuration as serde_json::Value and never deserializes into a plugin's config struct, so nothing calls these serializers today. The problem is that the protection authors believe they have does not exist, and the first caller that serializes a plugin config leaks every credential in it.
Suggested fix
Correct the SKILL.md row — the helper exposes; SecretString's default (no Serialize impl) is what redacts.
If a config struct genuinely needs Serialize, a serialize_redacted helper alongside the existing one would give authors the thing the docs currently promise.
Summary
The connectors guidance tells plugin authors that annotating a
SecretStringfield withiggy_common::serde_secret::serialize_secretgives them redaction. It does the opposite: the helper callsexpose_secret()and writes the value in plaintext. Nine connector plugins follow that guidance today.The claim
.claude/skills/connectors-overview/SKILL.md, "Secrets":The code
core/common/src/utils/serde_secret.rs:Its own module doc says the opposite of the skill:
And its test
serialize_optional_secret_with_some_valueasserts the output is{"token":"tok_123"}.The
Debughalf of the claim is true —SecretBox'sDebugprints[REDACTED]. Only the serialization half is inverted.Why it matters
SecretStringdeliberately has noSerializeimpl, so a config struct holding one cannot deriveSerializeat all. Addingserialize_withis what unblocks the derive — it converts a compile-time guarantee into plaintext output, while the documentation says it does the reverse.Nine plugins carry the annotation on credential fields:
sinks/{postgres,s3,influxdb,elasticsearch,mongodb,surrealdb}_sink,sources/{postgres,elasticsearch,influxdb}_source.In practice the attribute appears inert: the runtime keeps plugin configuration as
serde_json::Valueand never deserializes into a plugin's config struct, so nothing calls these serializers today. The problem is that the protection authors believe they have does not exist, and the first caller that serializes a plugin config leaks every credential in it.Suggested fix
SecretString's default (noSerializeimpl) is what redacts.Serializefrom plugin config structs that do not need it.iggy_connector_http_source(feat(connectors): add the HTTP source webhook gateway connector #3798) does this, which makes the property compiler-enforced rather than convention-enforced.Serialize, aserialize_redactedhelper alongside the existing one would give authors the thing the docs currently promise.Found while preparing #3798.