Address PR review: backup correctness/security + drop hardening
Build App / compute-version (pull_request) Successful in 8s
Build Container / build-container (pull_request) Successful in 51s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 2m52s
Build App / build-linux (pull_request) Successful in 6m5s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
Build App / compute-version (pull_request) Successful in 8s
Build Container / build-container (pull_request) Successful in 51s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 2m52s
Build App / build-linux (pull_request) Successful in 6m5s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
Fixes from the code review of this branch: - Backup requires a running container (it runs via `docker exec`, which can't run on a stopped one). Removed the misleading "Backup" button from the stopped-project actions, added an explicit running check with a clear error, and corrected the doc comment. (H1) - jq sanitization fallback no longer leaks secrets: if ~/.claude.json can't be parsed, the backup substitutes an empty object and warns to stderr instead of copying the raw file (which held primaryApiKey / oauthAccount). Verified the raw key never reaches the archive. (H2) - Dropped-file paths typed into the terminal are now always single-quoted (with '\'' escaping), not only when they contain whitespace — a name like `foo$(whoami).txt` was previously sent raw into the shell. (M2) - write_bedrock_static_credentials checks the exec exit code via the new exec_oneshot_env_status and fails loudly on a write/chmod error instead of silently reporting success. exec_oneshot keeps its ignore-exit-code behavior so list_container_files is unaffected. (M4) - Backup removes a partial/truncated archive on any stream error and treats a non-zero tar exit code as failure (a truncated gzip was previously reported as success). (L1) - Dropped files are capped at 256 MiB to avoid ballooning host RAM (the file is read fully into memory then re-tarred). (M3) - Stopped excluding .git/objects from the backup so git history, including unpushed commits, is preserved faithfully. (L3) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1163,10 +1163,17 @@ fi
|
||||
chmod 600 "$HOME/.aws/credentials""#;
|
||||
|
||||
let cmd = vec!["sh".to_string(), "-c".to_string(), script.to_string()];
|
||||
crate::docker::exec::exec_oneshot_env(container_id, cmd, env)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("Failed to write AWS credentials into container: {}", e))?;
|
||||
let (output, exit_code) =
|
||||
crate::docker::exec::exec_oneshot_env_status(container_id, cmd, env)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write AWS credentials into container: {}", e))?;
|
||||
if exit_code != 0 {
|
||||
return Err(format!(
|
||||
"Writing AWS credentials into container failed (exit {}): {}",
|
||||
exit_code,
|
||||
output.trim()
|
||||
));
|
||||
}
|
||||
|
||||
log::info!("Wrote Bedrock static credentials into container {}", container_id);
|
||||
Ok(())
|
||||
|
||||
@@ -288,11 +288,27 @@ pub async fn exec_oneshot(container_id: &str, cmd: Vec<String>) -> Result<String
|
||||
/// process. Secrets passed this way live only in `/proc/<pid>/environ` (readable
|
||||
/// by the same user / root) rather than in the process argv, so they are not
|
||||
/// exposed via `ps`.
|
||||
///
|
||||
/// NOTE: the command's exit code is NOT checked — callers that need to know
|
||||
/// whether the command succeeded should use `exec_oneshot_env_status`.
|
||||
pub async fn exec_oneshot_env(
|
||||
container_id: &str,
|
||||
cmd: Vec<String>,
|
||||
env: Vec<String>,
|
||||
) -> Result<String, String> {
|
||||
exec_oneshot_env_status(container_id, cmd, env)
|
||||
.await
|
||||
.map(|(output, _exit_code)| output)
|
||||
}
|
||||
|
||||
/// Like `exec_oneshot_env`, but also returns the command's exit code (0 on
|
||||
/// success). The returned string contains both stdout and stderr, interleaved
|
||||
/// in arrival order, which is useful for surfacing failure detail.
|
||||
pub async fn exec_oneshot_env_status(
|
||||
container_id: &str,
|
||||
cmd: Vec<String>,
|
||||
env: Vec<String>,
|
||||
) -> Result<(String, i64), String> {
|
||||
let docker = get_docker()?;
|
||||
|
||||
let exec = docker
|
||||
@@ -315,17 +331,27 @@ pub async fn exec_oneshot_env(
|
||||
.await
|
||||
.map_err(|e| format!("Failed to start exec: {}", e))?;
|
||||
|
||||
let mut combined = String::new();
|
||||
match result {
|
||||
StartExecResults::Attached { mut output, .. } => {
|
||||
let mut stdout = String::new();
|
||||
while let Some(msg) = output.next().await {
|
||||
match msg {
|
||||
Ok(data) => stdout.push_str(&String::from_utf8_lossy(&data.into_bytes())),
|
||||
Ok(data) => combined.push_str(&String::from_utf8_lossy(&data.into_bytes())),
|
||||
Err(e) => return Err(format!("Exec output error: {}", e)),
|
||||
}
|
||||
}
|
||||
Ok(stdout)
|
||||
}
|
||||
StartExecResults::Detached => Err("Exec started in detached mode".to_string()),
|
||||
StartExecResults::Detached => return Err("Exec started in detached mode".to_string()),
|
||||
}
|
||||
|
||||
// Exit code is only available after the process has finished (the stream above
|
||||
// has drained), so inspect now.
|
||||
let exit_code = docker
|
||||
.inspect_exec(&exec.id)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to inspect exec: {}", e))?
|
||||
.exit_code
|
||||
.unwrap_or(0);
|
||||
|
||||
Ok((combined, exit_code))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user