Summary
The connectors runtime control API returns plugin configuration verbatim, credentials included, and its authentication is disabled by default. The default bind is loopback, so this is a hardening gap rather than a remote disclosure — but nothing warns an operator who moves the listener off loopback, which is a normal thing to do when running the runtime in a container.
Filing publicly rather than to the security list because of the loopback default. Happy to move it if maintainers judge otherwise.
Configuration returns secrets
core/connectors/runtime/src/api/source.rs::get_source_plugin_config serves source.config.plugin_config as-is:
let (content_type, config) = map_connector_config(config, format)?;
plugin_config is the raw serde_json::Value parsed from TOML, so every credential an operator configured comes back in the clear. GET /sources/{key}/configs and /configs/{version} and /configs/active return the enclosing SourceConfig, which carries the same field. The sink routes mirror all of it. A repo-wide grep finds no redaction anywhere in core/connectors/runtime/src — the only redact hits are log-line truncation in stream.rs.
For most connectors that means a database connection string. For iggy_connector_http_source (#3798) it also means a management_token, which mints webhook endpoints, and each endpoint's HMAC secret.
Authentication is off by default
core/connectors/runtime/src/api/auth.rs::resolve_api_key:
if context.api_key.expose_secret().is_empty() {
return Ok(next.run(request).await);
}
core/connectors/runtime/config.toml ships:
address = "127.0.0.1:8081"
api_key = "" # Optional API key for authentication to be passed as `api-key` header
So out of the box the API is unauthenticated. Loopback confines that to local processes, which is a defensible posture for an admin API — the gap is that the two defaults compose into "any local process can read every connector credential", and an operator who changes address to 0.0.0.0 to reach the API from outside a container gets an unauthenticated endpoint serving credentials with no warning at any layer.
Suggested fixes, roughly in order of value
- Redact credential-bearing fields in the config responses, or gate those specific routes behind a configured
api_key regardless of the global default.
- Log a warning at startup when
api_key is empty and address is not loopback.
- Document in the runtime README that the control API returns credentials in plaintext and should be treated as privileged.
Related: #3801, which is why the plugin-side SecretString annotations do not help here.
Found while preparing #3798, whose README now documents this exposure for its own users.
Summary
The connectors runtime control API returns plugin configuration verbatim, credentials included, and its authentication is disabled by default. The default bind is loopback, so this is a hardening gap rather than a remote disclosure — but nothing warns an operator who moves the listener off loopback, which is a normal thing to do when running the runtime in a container.
Filing publicly rather than to the security list because of the loopback default. Happy to move it if maintainers judge otherwise.
Configuration returns secrets
core/connectors/runtime/src/api/source.rs::get_source_plugin_configservessource.config.plugin_configas-is:plugin_configis the rawserde_json::Valueparsed from TOML, so every credential an operator configured comes back in the clear.GET /sources/{key}/configsand/configs/{version}and/configs/activereturn the enclosingSourceConfig, which carries the same field. The sink routes mirror all of it. A repo-wide grep finds no redaction anywhere incore/connectors/runtime/src— the onlyredacthits are log-line truncation instream.rs.For most connectors that means a database connection string. For
iggy_connector_http_source(#3798) it also means amanagement_token, which mints webhook endpoints, and each endpoint's HMAC secret.Authentication is off by default
core/connectors/runtime/src/api/auth.rs::resolve_api_key:core/connectors/runtime/config.tomlships:So out of the box the API is unauthenticated. Loopback confines that to local processes, which is a defensible posture for an admin API — the gap is that the two defaults compose into "any local process can read every connector credential", and an operator who changes
addressto0.0.0.0to reach the API from outside a container gets an unauthenticated endpoint serving credentials with no warning at any layer.Suggested fixes, roughly in order of value
api_keyregardless of the global default.api_keyis empty andaddressis not loopback.Related: #3801, which is why the plugin-side
SecretStringannotations do not help here.Found while preparing #3798, whose README now documents this exposure for its own users.