From 325e1e813d30bd41ab5ab885504f5873db9e7fdb Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 2 Sep 2026 19:00:11 -0500 Subject: [PATCH] fix(runner): load sealed CommonJS entrypoints by descriptor --- .../runner-core/src/acpx_provider_backend.rs | 13 ++++- .../crates/runner-core/src/codex_provider.rs | 4 +- .../runner-core/src/process_supervisor.rs | 55 +++++++++++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs index edd2c630db..fb8c8c3e79 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs @@ -213,7 +213,8 @@ impl AcpxProviderDescriptor { let verified_args = launch_profile .args .iter() - .map(|argument| { + .enumerate() + .map(|(index, argument)| { let path = Path::new(argument); if !path.is_absolute() { return Ok(VerifiedProcessArgument::Literal(argument.clone())); @@ -221,7 +222,13 @@ impl AcpxProviderDescriptor { verified .get(path) .cloned() - .map(VerifiedProcessArgument::Artifact) + .map(|artifact| { + if index == 0 { + VerifiedProcessArgument::CommonJsArtifact(artifact) + } else { + VerifiedProcessArgument::Artifact(artifact) + } + }) .ok_or_else(|| { DurableRunnerError::invalid( "ACPX runner launch profile does not authenticate an absolute argument", @@ -1419,7 +1426,7 @@ mod tests { let verified_launch = transport.verified_launch.as_ref().unwrap(); assert!(matches!( verified_launch.arguments().first(), - Some(VerifiedProcessArgument::Artifact(_)) + Some(VerifiedProcessArgument::CommonJsArtifact(_)) )); let mut drifted_path = descriptor.clone(); diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs index b6002a8cd5..b54eaa74b9 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs @@ -1995,7 +1995,7 @@ fn verified_opencode_launch( let executable = verify_launch_artifact(&profile.executable, "OpenCode provider executable") .map_err(|error| LocalRunnerError::invalid(error.to_string()))?; let args = vec![ - VerifiedProcessArgument::Artifact(proxy), + VerifiedProcessArgument::CommonJsArtifact(proxy), VerifiedProcessArgument::Literal(TRUSTED_OPENCODE_EXECUTABLE_ARG.to_owned()), VerifiedProcessArgument::ExecutableArtifact(executable), ]; @@ -2746,7 +2746,7 @@ mod tests { let launch = verified_opencode_launch(&profile).unwrap(); assert!(matches!( launch.arguments().first(), - Some(VerifiedProcessArgument::Artifact(_)) + Some(VerifiedProcessArgument::CommonJsArtifact(_)) )); assert!(matches!( launch.arguments().get(1), diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs b/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs index 1e4e8bb1d5..4bf21c4d46 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs @@ -26,6 +26,7 @@ use sha2::{Digest, Sha256}; use crate::local_runner::LocalRunnerError; const PROCESS_OUTPUT_QUEUE_CAPACITY: usize = 256; +const VERIFIED_COMMONJS_ARTIFACT_LOADER: &str = r#"const fs=require("node:fs");const Module=require("node:module");const filename=process.argv[1];const source=fs.readFileSync(filename,"utf8").replace(/^#![^\r\n]*(?:\r?\n|$)/,"");const artifact=new Module(filename);artifact.filename=filename;artifact.paths=[];artifact._compile(source,filename);"#; #[derive(Clone, Debug)] pub struct VerifiedProcessArtifact { @@ -185,6 +186,7 @@ fn snapshot_error(display_path: &Path, error: impl std::fmt::Display) -> LocalRu pub enum VerifiedProcessArgument { Literal(String), Artifact(VerifiedProcessArtifact), + CommonJsArtifact(VerifiedProcessArtifact), ExecutableArtifact(VerifiedProcessArtifact), } @@ -226,6 +228,13 @@ impl VerifiedProcessLaunch { inherited.push(fd); args.push(path.to_string_lossy().into_owned()); } + VerifiedProcessArgument::CommonJsArtifact(artifact) => { + let (fd, path) = inherited_artifact(artifact)?; + inherited.push(fd); + args.push("--eval".to_owned()); + args.push(VERIFIED_COMMONJS_ARTIFACT_LOADER.to_owned()); + args.push(path.to_string_lossy().into_owned()); + } VerifiedProcessArgument::ExecutableArtifact(artifact) => { #[cfg(target_os = "linux")] { @@ -858,3 +867,49 @@ fn exit_fact(status: ExitStatus) -> ProcessExitFact { } } } + +#[cfg(all(test, any(target_os = "linux", target_os = "macos")))] +mod tests { + use super::*; + use std::time::{SystemTime, UNIX_EPOCH}; + + fn verified_artifact(path: &Path, bytes: &[u8]) -> VerifiedProcessArtifact { + fs::write(path, bytes).unwrap(); + let digest = format!("sha256:{:x}", Sha256::digest(bytes)); + VerifiedProcessArtifact::snapshot_verified( + path.to_owned(), + File::open(path).unwrap(), + &digest, + ) + .unwrap() + } + + #[test] + fn commonjs_artifacts_use_the_bounded_descriptor_loader() { + let nonce = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let directory = std::env::temp_dir().join(format!( + "paperclip-commonjs-launch-{}-{nonce}", + std::process::id() + )); + fs::create_dir_all(&directory).unwrap(); + let program = verified_artifact(&directory.join("node"), b"node"); + let script = verified_artifact(&directory.join("sidecar.cjs"), b"module.exports = {};\n"); + let launch = VerifiedProcessLaunch::new( + program, + vec![VerifiedProcessArgument::CommonJsArtifact(script)], + ); + + let inherited = launch.inherited_command().unwrap(); + + assert_eq!(inherited.args[0], "--eval"); + assert_eq!(inherited.args[1], VERIFIED_COMMONJS_ARTIFACT_LOADER); + #[cfg(target_os = "linux")] + assert!(inherited.args[2].starts_with("/proc/self/fd/")); + #[cfg(target_os = "macos")] + assert!(inherited.args[2].starts_with("/dev/fd/")); + fs::remove_dir_all(directory).unwrap(); + } +}