Files
codex/codex-rs/network-proxy/src/authorization_path.rs
T
viyatb-oai 82ef0672e3 Scope MITM credential substitution to trusted origins and placeholders (#56003)
## Why

Credential substitution needs to authorize the destination and the original request before exposing host secrets. Project configuration must not redirect trusted credential policies, and older clients must reject scoped policies rather than silently apply unconditional injection.

## What changed

- Add hook `schemes` and `ports` constraints and an exact whole-header `placeholder` for secret substitution. Require `network.mitm.version = 2` for these options in permission profiles.
- Restrict placeholder substitution to HTTPS on port 443 and reject missing, mismatched, or duplicate placeholder headers.
- Reject project changes to trusted MITM definitions and their inheritance chains. Allow self-contained project header-stripping policies only when no trusted MITM policy exists.
- Reject ambiguous query values, value-constrained headers, and semicolon paths during authorization, and preserve case-sensitive extension methods.
- Mark resolved secret headers sensitive, reject secret injection into `User-Agent`, restrict substitution headers, and limit secret files to 64 KiB.

## Testing

Add regression coverage for scoped TOML resolution, legacy-format rejection, unknown fields, project policy restrictions, origin and placeholder denials, ambiguous request matching, secret redaction, and hook application after credential brokerage.

GitOrigin-RevId: 78dc115ab6c88c9a734377626429bc4840731f89
2026-10-21 22:42:48 +00:00

65 lines
1.9 KiB
Rust

/// Returns whether `path` has an unambiguous interpretation for authorization.
///
/// MITM hooks authorize the request before the upstream server parses it. Reject
/// path forms that common upstreams may decode or normalize into a different
/// resource after a hook has matched.
pub(crate) fn is_safe_for_authorization(path: &str) -> bool {
path.split('/').all(is_safe_segment_for_authorization)
}
fn is_safe_segment_for_authorization(segment: &str) -> bool {
let bytes = segment.as_bytes();
let mut index = 0;
let mut decoded_dots = 0;
let mut has_non_dot = false;
while index < bytes.len() {
match bytes[index] {
b'.' => {
decoded_dots += 1;
index += 1;
}
b'\\' | b';' => return false,
b'%' => {
let Some(high) = bytes
.get(index + 1)
.and_then(|byte| decode_hex_digit(*byte))
else {
return false;
};
let Some(low) = bytes
.get(index + 2)
.and_then(|byte| decode_hex_digit(*byte))
else {
return false;
};
let decoded = high << 4 | low;
match decoded {
b'%' | b'/' | b'\\' | b';' => return false,
b'.' => decoded_dots += 1,
_ => has_non_dot = true,
}
index += 3;
}
_ => {
has_non_dot = true;
index += 1;
}
}
}
has_non_dot || !matches!(decoded_dots, 1 | 2)
}
fn decode_hex_digit(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
b'A'..=b'F' => Some(byte - b'A' + 10),
_ => None,
}
}
#[cfg(test)]
#[path = "authorization_path_tests.rs"]
mod tests;