diff --git a/packages/wordpress-plugin/src/class-wp-codebox-agent-sandbox-runner.php b/packages/wordpress-plugin/src/class-wp-codebox-agent-sandbox-runner.php index 99e31980..f32fa319 100644 --- a/packages/wordpress-plugin/src/class-wp-codebox-agent-sandbox-runner.php +++ b/packages/wordpress-plugin/src/class-wp-codebox-agent-sandbox-runner.php @@ -981,6 +981,20 @@ private function command_prefix( string $bin ): string|WP_Error { if ( is_wp_error( $node ) ) { return $node; } + + $entrypoint = dirname( dirname( $bin ) ) . DIRECTORY_SEPARATOR . 'packages' . DIRECTORY_SEPARATOR . 'cli' . DIRECTORY_SEPARATOR . 'dist' . DIRECTORY_SEPARATOR . 'index.js'; + if ( ! is_file( $entrypoint ) ) { + return new WP_Error( + 'wp_codebox_bin_missing', + 'The bundled WP Codebox JavaScript entrypoint is missing.', + array( + 'status' => 500, + 'path' => $entrypoint, + ) + ); + } + + return escapeshellarg( $node ) . ' ' . escapeshellarg( $entrypoint ); } return escapeshellarg( $bin ); diff --git a/scripts/php-agent-runtime-execution-smoke.php b/scripts/php-agent-runtime-execution-smoke.php index b2ffeb34..9555501d 100644 --- a/scripts/php-agent-runtime-execution-smoke.php +++ b/scripts/php-agent-runtime-execution-smoke.php @@ -2,7 +2,17 @@ declare(strict_types=1); +$plugin_root = sys_get_temp_dir() . '/wp-codebox-nonexecutable-cli-' . bin2hex( random_bytes( 4 ) ); +$wrapper = $plugin_root . '/vendor/wp-codebox-cli/bin/wp-codebox'; +$entrypoint = $plugin_root . '/vendor/wp-codebox-cli/packages/cli/dist/index.js'; +mkdir( dirname( $wrapper ), 0777, true ); +mkdir( dirname( $entrypoint ), 0777, true ); +file_put_contents( $wrapper, "#!/usr/bin/env bash\n" ); +file_put_contents( $entrypoint, "#!/usr/bin/env node\n" ); +chmod( $wrapper, 0644 ); + define( 'ABSPATH', __DIR__ ); +define( 'WP_CODEBOX_PLUGIN_PATH', $plugin_root . '/' ); final class WP_Error { private string $code; @@ -151,4 +161,27 @@ function smoke_assert( bool $condition, string $message ): void { $case['assert']( $result ); } +$runner = new WP_Codebox_Agent_Sandbox_Runner( + array( + 'command_resolver' => static fn( string $command ): string => 'node' === $command ? '/usr/bin/node' : '', + ) +); +$method = new ReflectionMethod( $runner, 'command_prefix' ); +$prefix = $method->invoke( $runner, $wrapper ); +smoke_assert( ! is_wp_error( $prefix ), 'non-executable bundled wrapper resolves to a command prefix' ); +smoke_assert( + escapeshellarg( '/usr/bin/node' ) . ' ' . escapeshellarg( $entrypoint ) === $prefix, + 'non-executable bundled wrapper runs its JavaScript entrypoint through Node' +); + +unlink( $wrapper ); +unlink( $entrypoint ); +rmdir( dirname( $wrapper ) ); +rmdir( dirname( $entrypoint ) ); +rmdir( dirname( dirname( $entrypoint ) ) ); +rmdir( dirname( dirname( dirname( $entrypoint ) ) ) ); +rmdir( dirname( dirname( dirname( dirname( $entrypoint ) ) ) ) ); +rmdir( dirname( dirname( dirname( $wrapper ) ) ) ); +rmdir( $plugin_root ); + echo "agent runtime execution smoke passed\n";