Summary
gitoxide's credential-helper request serialization accepts a bare carriage return in URL context values. A caller-supplied URL reaches the helper line protocol as a url= field, but validation rejects only NUL and LF, so CR-sensitive credential helpers can parse one URL value as multiple helper fields. In a configuration with such a helper, an attacker-controlled URL can select credentials for a trusted host and cause those credentials to be returned to the caller handling the attacker URL.
Details
The public credential lookup constructor stores caller-provided URL bytes directly in the request context: Action::get_for_url() builds Action::Get(Context { url: Some(url.into()), .. }) in gix-credentials/src/helper/mod.rs:85. When the action is sent to a helper, Context::write_to() serializes that optional URL through the generic key/value writer at gix-credentials/src/protocol/context/serde.rs:33; write_key() writes key, =, the raw value, and then LF at gix-credentials/src/protocol/context/serde.rs:14. The validation immediately before serialization rejects NUL and LF in keys and values, but it does not reject \r, as shown in gix-credentials/src/protocol/context/serde.rs:146. helper::raw() then starts the configured helper, writes the serialized action to helper stdin, and later decodes helper stdout into an Outcome in gix-credentials/src/helper/invoke.rs:43 and gix-credentials/src/helper/invoke.rs:29; the process plumbing that exposes stdin/stdout to the external helper is in gix-credentials/src/program/mod.rs:117. The result is the same credential-helper line-protocol shape as CVE-2024-52006: URL-controlled bytes are serialized after an incomplete record-terminator check, leaving bare CR available to helpers whose line reader treats it as a separator.
Reproduction
poc.zip
GITOXIDE_CR_HELPER_INJECTION: host=trusted.example
The fingerprint is printed by the local credential-helper fixture only after it receives an injected host=trusted.example field through gitoxide's helper stdin. A build failure or non-zero exit without that fingerprint would be setup noise or an unrelated failure, not this CR field-injection path.
Impact
An unauthenticated remote attacker needs a workflow where a victim application or user passes an attacker-controlled Git or credential URL to gitoxide for credential lookup. Exploitation also depends on a configured external credential helper that treats bare carriage return as a line terminator and has stored credentials for the injected trusted host; helpers that split only on LF are not demonstrated affected. Under those preconditions, the attacker can supply a URL such as https://evil.example\rhost=trusted.example\rprotocol=https, bypass the LF/NUL-only validation, and cause the helper to return credentials for trusted.example to the gitoxide caller handling evil.example. The local PoC demonstrates credential selection and return to the in-process caller; direct disclosure to the attacker depends on the surrounding application using, logging, or returning that credential in the attacker-controlled workflow.
Suggested fix
diff --git a/gix-credentials/src/protocol/context/serde.rs b/gix-credentials/src/protocol/context/serde.rs
index 18a44c5..7e2aec2 100644
--- a/gix-credentials/src/protocol/context/serde.rs
+++ b/gix-credentials/src/protocol/context/serde.rs
@@ -144,7 +144,13 @@ pub mod decode {
}
fn validate(key: &str, value: &BStr) -> Result<(), Error> {
- if key.contains('\0') || key.contains('\n') || value.contains(&0) || value.contains(&b'\n') {
+ if key.contains('\0')
+ || key.contains('\n')
+ || key.contains('\r')
+ || value.contains(&0)
+ || value.contains(&b'\n')
+ || value.contains(&b'\r')
+ {
return Err(Error::Encoding {
key: key.to_owned(),
value: value.to_owned(),
Reported by Team Atlanta.
Summary
gitoxide's credential-helper request serialization accepts a bare carriage return in URL context values. A caller-supplied URL reaches the helper line protocol as a
url=field, but validation rejects only NUL and LF, so CR-sensitive credential helpers can parse one URL value as multiple helper fields. In a configuration with such a helper, an attacker-controlled URL can select credentials for a trusted host and cause those credentials to be returned to the caller handling the attacker URL.Details
The public credential lookup constructor stores caller-provided URL bytes directly in the request context:
Action::get_for_url()buildsAction::Get(Context { url: Some(url.into()), .. })ingix-credentials/src/helper/mod.rs:85. When the action is sent to a helper,Context::write_to()serializes that optional URL through the generic key/value writer atgix-credentials/src/protocol/context/serde.rs:33;write_key()writeskey,=, the raw value, and then LF atgix-credentials/src/protocol/context/serde.rs:14. The validation immediately before serialization rejects NUL and LF in keys and values, but it does not reject\r, as shown ingix-credentials/src/protocol/context/serde.rs:146.helper::raw()then starts the configured helper, writes the serialized action to helper stdin, and later decodes helper stdout into anOutcomeingix-credentials/src/helper/invoke.rs:43andgix-credentials/src/helper/invoke.rs:29; the process plumbing that exposes stdin/stdout to the external helper is ingix-credentials/src/program/mod.rs:117. The result is the same credential-helper line-protocol shape as CVE-2024-52006: URL-controlled bytes are serialized after an incomplete record-terminator check, leaving bare CR available to helpers whose line reader treats it as a separator.Reproduction
poc.zip
The fingerprint is printed by the local credential-helper fixture only after it receives an injected
host=trusted.examplefield through gitoxide's helper stdin. A build failure or non-zero exit without that fingerprint would be setup noise or an unrelated failure, not this CR field-injection path.Impact
An unauthenticated remote attacker needs a workflow where a victim application or user passes an attacker-controlled Git or credential URL to gitoxide for credential lookup. Exploitation also depends on a configured external credential helper that treats bare carriage return as a line terminator and has stored credentials for the injected trusted host; helpers that split only on LF are not demonstrated affected. Under those preconditions, the attacker can supply a URL such as
https://evil.example\rhost=trusted.example\rprotocol=https, bypass the LF/NUL-only validation, and cause the helper to return credentials fortrusted.exampleto the gitoxide caller handlingevil.example. The local PoC demonstrates credential selection and return to the in-process caller; direct disclosure to the attacker depends on the surrounding application using, logging, or returning that credential in the attacker-controlled workflow.Suggested fix
Reported by Team Atlanta.