fix(exec-server): validate runtime artifact paths

This commit is contained in:
acrognale-oai
2026-05-21 12:16:25 -04:00
parent 569df87a39
commit da5b44358e

View File

@@ -562,7 +562,10 @@ async fn validate_runtime_root(
.join("bin")
.join(node_executable_name(target_platform));
let node_modules_path = node_root.join("node_modules");
let python_path = find_python_path(runtime_root, bundle_format_version, target_platform).await;
require_runtime_file(&node_path, "node executable").await?;
require_runtime_directory(&node_modules_path, "node modules directory").await?;
let python_path =
find_python_path(runtime_root, bundle_format_version, target_platform).await?;
let bundled_plugin_marketplace_paths = runtime_contained_paths(
runtime_root,
metadata.bundled_plugins.unwrap_or_default(),
@@ -588,7 +591,7 @@ async fn find_python_path(
runtime_root: &Path,
bundle_format_version: u32,
target_platform: &str,
) -> PathBuf {
) -> Result<PathBuf, JSONRPCErrorError> {
let python_root = if bundle_format_version >= 2 {
runtime_root.join("dependencies").join("python")
} else {
@@ -607,12 +610,23 @@ async fn find_python_path(
python_root.join("bin").join("python"),
]
};
for candidate in candidates.iter() {
if path_exists(candidate).await {
return candidate.clone();
for candidate in &candidates {
match fs::metadata(candidate).await {
Ok(metadata) if metadata.is_file() => return Ok(candidate.clone()),
Ok(_) => {}
Err(err) if err.kind() == ErrorKind::NotFound => {}
Err(err) => {
return Err(internal_error(format!(
"failed to inspect runtime python executable {}: {err}",
candidate.display()
)));
}
}
}
candidates[0].clone()
Err(invalid_params(format!(
"runtime python executable is missing under {}",
python_root.display()
)))
}
fn runtime_contained_paths(
@@ -677,6 +691,42 @@ async fn path_exists(path: &Path) -> bool {
fs::metadata(path).await.is_ok()
}
async fn require_runtime_file(path: &Path, label: &str) -> Result<(), JSONRPCErrorError> {
match fs::metadata(path).await {
Ok(metadata) if metadata.is_file() => Ok(()),
Ok(_) => Err(invalid_params(format!(
"runtime {label} is not a file: {}",
path.display()
))),
Err(err) if err.kind() == ErrorKind::NotFound => Err(invalid_params(format!(
"runtime {label} is missing: {}",
path.display()
))),
Err(err) => Err(internal_error(format!(
"failed to inspect runtime {label} {}: {err}",
path.display()
))),
}
}
async fn require_runtime_directory(path: &Path, label: &str) -> Result<(), JSONRPCErrorError> {
match fs::metadata(path).await {
Ok(metadata) if metadata.is_dir() => Ok(()),
Ok(_) => Err(invalid_params(format!(
"runtime {label} is not a directory: {}",
path.display()
))),
Err(err) if err.kind() == ErrorKind::NotFound => Err(invalid_params(format!(
"runtime {label} is missing: {}",
path.display()
))),
Err(err) => Err(internal_error(format!(
"failed to inspect runtime {label} {}: {err}",
path.display()
))),
}
}
async fn remove_dir_if_exists(path: &Path) -> Result<(), JSONRPCErrorError> {
match fs::remove_dir_all(path).await {
Ok(()) => Ok(()),
@@ -778,6 +828,71 @@ mod tests {
);
}
#[tokio::test]
async fn validate_runtime_root_rejects_missing_node_executable() {
let temp_dir = tempfile::tempdir().expect("tempdir");
let runtime_root = temp_dir.path().join(PUBLISHED_ARTIFACT_NAME);
create_runtime_root(&runtime_root, "v1").await;
fs::remove_file(
runtime_root
.join("dependencies")
.join("node")
.join("bin")
.join(node_executable_name(target_platform())),
)
.await
.expect("remove node");
let error = validate_runtime_root(&runtime_root, Some(2), target_platform())
.await
.expect_err("node executable should be required");
assert!(error.message.contains("node executable is missing"));
}
#[tokio::test]
async fn validate_runtime_root_rejects_missing_node_modules_directory() {
let temp_dir = tempfile::tempdir().expect("tempdir");
let runtime_root = temp_dir.path().join(PUBLISHED_ARTIFACT_NAME);
create_runtime_root(&runtime_root, "v1").await;
fs::remove_dir(
runtime_root
.join("dependencies")
.join("node")
.join("node_modules"),
)
.await
.expect("remove node_modules");
let error = validate_runtime_root(&runtime_root, Some(2), target_platform())
.await
.expect_err("node_modules directory should be required");
assert!(error.message.contains("node modules directory is missing"));
}
#[tokio::test]
async fn validate_runtime_root_rejects_missing_python_executable() {
let temp_dir = tempfile::tempdir().expect("tempdir");
let runtime_root = temp_dir.path().join(PUBLISHED_ARTIFACT_NAME);
create_runtime_root(&runtime_root, "v1").await;
fs::remove_file(
runtime_root
.join("dependencies")
.join("python")
.join("bin")
.join(python_executable_name(target_platform())),
)
.await
.expect("remove python");
let error = validate_runtime_root(&runtime_root, Some(2), target_platform())
.await
.expect_err("python executable should be required");
assert!(error.message.contains("python executable is missing"));
}
#[tokio::test]
async fn install_from_archive_rejects_checksum_mismatch() {
let temp_dir = tempfile::tempdir().expect("tempdir");