mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
fix(shell-command): harden PowerShell policy parsing
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn encode_commands(commands: &[&[&str]]) -> String {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(&u32::try_from(commands.len()).unwrap().to_le_bytes());
|
||||
for command in commands {
|
||||
bytes.extend_from_slice(&u32::try_from(command.len()).unwrap().to_le_bytes());
|
||||
for word in *command {
|
||||
bytes.extend_from_slice(&u32::try_from(word.len()).unwrap().to_le_bytes());
|
||||
bytes.extend_from_slice(word.as_bytes());
|
||||
}
|
||||
}
|
||||
BASE64_STANDARD.encode(bytes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn framed_protocol_preserves_ids_and_command_words() {
|
||||
for (id, payload, expected) in [
|
||||
(
|
||||
42,
|
||||
"RwBlAHQALQBDAG8AbgB0AGUAbgB0AA==",
|
||||
"42\tRwBlAHQALQBDAG8AbgB0AGUAbgB0AA==",
|
||||
),
|
||||
(43, "", "43\t"),
|
||||
] {
|
||||
assert_eq!(
|
||||
serialize_request(&PowershellParserRequest {
|
||||
id,
|
||||
payload: payload.into()
|
||||
})
|
||||
.unwrap(),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
let payload = encode_commands(&[&["Get-Content", "fóó.txt"], &["Measure-Object"]]);
|
||||
assert_eq!(
|
||||
deserialize_response(&format!("42\tok\t{payload}\r\n")).unwrap(),
|
||||
PowershellParserResponse {
|
||||
id: 42,
|
||||
status: "ok".into(),
|
||||
commands: Some(vec![
|
||||
["Get-Content", "fóó.txt"].map(str::to_string).to_vec(),
|
||||
vec!["Measure-Object".into()],
|
||||
]),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn framed_protocol_rejects_malformed_command_payloads() {
|
||||
let encode = |bytes| format!("1\tok\t{}", BASE64_STANDARD.encode(bytes));
|
||||
for response in [
|
||||
"missing-fields".into(),
|
||||
"1\tok".into(),
|
||||
"x\tunsupported\t".into(),
|
||||
"1\tunsupported\tnot-empty".into(),
|
||||
"1\tunsupported\t\textra".into(),
|
||||
"1\tok\tnot-base64!".into(),
|
||||
encode(vec![1, 0, 0]),
|
||||
encode(vec![u8::MAX; 4]),
|
||||
encode(vec![1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, u8::MAX]),
|
||||
encode(vec![1, 0, 0, 0, 1, 0, 0, 0, 255, 255, 255, 255]),
|
||||
encode(vec![0, 0, 0, 0, u8::MAX]),
|
||||
] {
|
||||
assert!(
|
||||
deserialize_response(&response).is_err(),
|
||||
"accepted {response:?}"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
serialize_request(&PowershellParserRequest {
|
||||
id: 1,
|
||||
payload: "bad\tpayload".into(),
|
||||
})
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn framed_protocol_bounds_lines_and_distinguishes_outcomes() {
|
||||
let mut complete = std::io::Cursor::new(b"1\tunsupported\t\n".as_slice());
|
||||
assert_eq!(
|
||||
read_bounded_response_line(&mut complete, 32).unwrap(),
|
||||
"1\tunsupported\t\n"
|
||||
);
|
||||
for bytes in [b"123456789\n".as_slice(), b"unterminated".as_slice()] {
|
||||
assert!(read_bounded_response_line(&mut std::io::Cursor::new(bytes), 8).is_err());
|
||||
}
|
||||
|
||||
for (status, commands, expected) in [
|
||||
("unsupported", None, PowershellParseOutcome::Unsupported),
|
||||
("ok", Some(Vec::new()), PowershellParseOutcome::Unsupported),
|
||||
(
|
||||
"ok",
|
||||
Some(vec![Vec::new()]),
|
||||
PowershellParseOutcome::Unsupported,
|
||||
),
|
||||
(
|
||||
"ok",
|
||||
Some(vec![vec![String::new()]]),
|
||||
PowershellParseOutcome::Unsupported,
|
||||
),
|
||||
("parse_failed", None, PowershellParseOutcome::Failed),
|
||||
("parse_errors", None, PowershellParseOutcome::Failed),
|
||||
("unknown", None, PowershellParseOutcome::Failed),
|
||||
] {
|
||||
assert_eq!(
|
||||
PowershellParserResponse {
|
||||
id: 0,
|
||||
status: status.into(),
|
||||
commands
|
||||
}
|
||||
.into_outcome(),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user