mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
Preserve complete shell snapshot exports through filtering and replay (#43907)
## Why Line-based export parsing can truncate multiline values or mistake their contents for declarations. Snapshot replay also needs to restore Bash options before parsing functions that use extended glob syntax. ## What changed - Capture shell state, aliases, and complete export records with NUL-delimited boundaries, separating capture, credential preparation, and rendering. - Apply credential and environment policy to whole exports, preserving multiline values and making credential aliases follow allowed overrides. - Restore Bash `shopt` options before functions, including for Bash running as `sh`. - Support non-evaluating POSIX `ENV` path expansion and preserve native environment metadata and unset exports. - Move core and exec-server snapshot consumers to the shared capture parser. Allow capture overhead while enforcing the exec-server's 512 KiB state-and-environment limit before filtering. ## Testing Add regression coverage for complete capture records, multiline export filtering and replay, credential alias overrides, `ENV` expansion, and `PATH` export state. Extend exec-server tests to cover Bash-backed `sh`, `extglob`, `nocasematch`, `set -u`, and large environments. GitOrigin-RevId: 7589ab4d137529a395dcee10b5162718de24a621
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
//! Serialize prepared snapshot values without inspecting credentials or executing shell code.
|
||||
//! Native declarations stay unchanged unless the credential stage supplies a replacement.
|
||||
|
||||
use super::capture::CapturedSnapshot;
|
||||
|
||||
impl CapturedSnapshot<'_> {
|
||||
/// Render native functions, options, and aliases; the executor restores environment separately.
|
||||
pub fn render_state(&self) -> String {
|
||||
format!("{}{}", self.state, self.aliases)
|
||||
}
|
||||
|
||||
/// Render a complete replay script without applying credential or environment policy.
|
||||
pub fn render_script(&self) -> String {
|
||||
let mut script = self.render_state();
|
||||
script.push_str("# exports (native declarations)\n");
|
||||
for export in &self.exports {
|
||||
script.push_str(&export.source);
|
||||
}
|
||||
script
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Value {
|
||||
pub parts: Vec<ValuePart>,
|
||||
}
|
||||
|
||||
pub(super) enum ValuePart {
|
||||
Literal(String),
|
||||
Credential { key: String },
|
||||
}
|
||||
|
||||
pub(super) enum Export<'a> {
|
||||
Captured(&'a str),
|
||||
Assignment { declaration: &'a str, value: Value },
|
||||
}
|
||||
|
||||
impl Value {
|
||||
fn render(&self) -> Option<String> {
|
||||
let mut output = String::new();
|
||||
for part in &self.parts {
|
||||
match part {
|
||||
ValuePart::Literal(value) => {
|
||||
output.push_str(shlex::try_quote(value).ok()?.as_ref())
|
||||
}
|
||||
ValuePart::Credential { key } => {
|
||||
output.push_str(&format!("\"${{{key}-}}\""));
|
||||
}
|
||||
}
|
||||
}
|
||||
if output.is_empty() {
|
||||
output.push_str("''");
|
||||
}
|
||||
Some(output)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn render(state: &str, aliases: &str, exports: &[Export<'_>]) -> Option<String> {
|
||||
let mut output = format!("{state}{aliases}");
|
||||
for export in exports {
|
||||
match export {
|
||||
Export::Captured(source) => output.push_str(source),
|
||||
Export::Assignment { declaration, value } => {
|
||||
output.push_str(&format!("{declaration}={}\n", value.render()?));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(output)
|
||||
}
|
||||
Reference in New Issue
Block a user