Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/execution/src/node_import_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const NODE_IMPORT_CACHE_MATERIALIZE_TIMEOUT_MS_ENV: &str =
"AGENTOS_NODE_IMPORT_CACHE_MATERIALIZE_TIMEOUT_MS";
const NODE_IMPORT_CACHE_SCHEMA_VERSION: &str = "1";
const NODE_IMPORT_CACHE_LOADER_VERSION: &str = "8";
const NODE_IMPORT_CACHE_ASSET_VERSION: &str = "82";
const NODE_IMPORT_CACHE_ASSET_VERSION: &str = "83";
const NODE_IMPORT_CACHE_DIR_PREFIX: &str = "agentos-node-import-cache";
const DEFAULT_NODE_IMPORT_CACHE_MATERIALIZE_TIMEOUT: Duration = Duration::from_secs(30);
const PYODIDE_DIST_DIR: &str = "pyodide-dist";
Expand Down
113 changes: 108 additions & 5 deletions crates/sidecar/src/execution.rs

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions crates/sidecar/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use rusqlite::Connection;
use rustls::{ClientConnection, ServerConnection, StreamOwned};
use secure_exec_bridge::{BridgeTypes, FilesystemSnapshot};
use secure_exec_execution::{
JavascriptExecution, JavascriptSyncRpcRequest, PythonExecution, PythonVfsRpcRequest,
WasmExecution,
v8_host::V8SessionHandle, JavascriptExecution, JavascriptSyncRpcRequest, PythonExecution,
PythonVfsRpcRequest, WasmExecution,
};
use secure_exec_kernel::kernel::{KernelProcessHandle, KernelVm};
use secure_exec_kernel::mount_table::MountTable;
Expand Down Expand Up @@ -730,12 +730,19 @@ pub(crate) enum JavascriptTcpSocketEvent {
},
}

#[derive(Clone, Debug)]
pub(crate) struct JavascriptSocketEventPusher {
pub(crate) session: V8SessionHandle,
pub(crate) socket_id: String,
}

#[derive(Debug)]
pub(crate) struct ActiveTcpSocket {
pub(crate) stream: Option<Arc<Mutex<TcpStream>>>,
pub(crate) pending_read_stream: Option<Arc<Mutex<Option<TcpStream>>>>,
pub(crate) events: Option<Receiver<JavascriptTcpSocketEvent>>,
pub(crate) event_sender: Option<Sender<JavascriptTcpSocketEvent>>,
pub(crate) event_pusher: Arc<Mutex<Option<JavascriptSocketEventPusher>>>,
pub(crate) kernel_socket_id: Option<SocketId>,
pub(crate) no_delay: bool,
pub(crate) keep_alive: bool,
Expand Down
122 changes: 121 additions & 1 deletion crates/sidecar/tests/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ mod service {
use std::process::Command;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc, Barrier, Mutex, OnceLock,
mpsc, Arc, Barrier, Mutex, OnceLock,
};
use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
Expand Down Expand Up @@ -15889,6 +15889,120 @@ console.log(JSON.stringify(summary));
"stdout: {stdout}"
);
}

fn javascript_http_external_get_reaches_host_listener() {
assert_node_available();

let listener = TcpListener::bind("127.0.0.1:0").expect("bind host HTTP listener");
let port = listener
.local_addr()
.expect("host HTTP listener address")
.port();
let (server_done_tx, server_done_rx) = mpsc::channel();
let server = thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("accept host HTTP request");
let mut request = [0_u8; 1024];
let read = stream.read(&mut request).expect("read host HTTP request");
let request_text = String::from_utf8_lossy(&request[..read]);
assert!(
request_text.starts_with("GET /external HTTP/1.1\r\n"),
"unexpected request: {request_text:?}"
);
stream
.write_all(
b"HTTP/1.1 200 OK\r\n\
Transfer-Encoding: chunked\r\n\
Connection: keep-alive\r\n\
\r\n\
12\r\nexternal-host-body\r\n\
0\r\n\
\r\n",
)
.expect("write host HTTP response");
stream.flush().expect("flush host HTTP response");
let _ = server_done_rx.recv_timeout(Duration::from_secs(5));
});

let mut sidecar = create_test_sidecar();
let (connection_id, session_id) =
authenticate_and_open_session(&mut sidecar).expect("authenticate and open session");
let vm_id = create_vm(
&mut sidecar,
&connection_id,
&session_id,
PermissionsPolicy::allow_all(),
)
.expect("create vm");
sidecar
.dispatch_blocking(request(
4,
OwnershipScope::vm(&connection_id, &session_id, &vm_id),
RequestPayload::ConfigureVm(ConfigureVmRequest {
mounts: Vec::new(),
software: Vec::new(),
permissions: None,
module_access_cwd: None,
instructions: Vec::new(),
projected_modules: Vec::new(),
command_permissions: std::collections::HashMap::new(),
loopback_exempt_ports: vec![port],
packages: Vec::new(),
packages_mount_at: String::new(),
}),
))
.expect("configure loopback-exempt host listener port");

let cwd = temp_dir("secure-exec-sidecar-js-http-external-cwd");
write_fixture(
&cwd.join("entry.mjs"),
&format!(
r#"
import http from "node:http";

const result = await Promise.race([
new Promise((resolve, reject) => {{
const req = http.get(
{{
host: "127.0.0.1",
port: {port},
path: "/external",
}},
(res) => {{
let body = "";
res.setEncoding("utf8");
res.on("data", (chunk) => {{
body += chunk;
}});
res.on("end", () => {{
resolve({{ status: res.statusCode, body }});
}});
}},
);
req.on("error", reject);
}}),
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3000)),
]);

console.log(JSON.stringify(result));
"#
),
);

let (stdout, stderr, exit_code) =
run_javascript_entry(&mut sidecar, &vm_id, &cwd, "proc-js-http-external");
let _ = server_done_tx.send(());
server.join().expect("join host HTTP listener");

assert_eq!(exit_code, Some(0), "stderr: {stderr}");
let parsed: Value =
serde_json::from_str(stdout.trim()).expect("parse external http JSON");
assert_eq!(parsed["status"], json!(200));
assert_eq!(
parsed["body"],
Value::String(String::from("external-host-body"))
);
}

fn javascript_fetch_posts_to_guest_loopback_http_server() {
assert_node_available();

Expand Down Expand Up @@ -19213,6 +19327,7 @@ console.log(JSON.stringify({
javascript_http2_secure_listen_connect_request_and_respond_round_trip();
javascript_http2_server_respond_records_pending_response();
javascript_http_rpc_requests_gets_and_serves_over_guest_net();
javascript_http_external_get_reaches_host_listener();
javascript_fetch_posts_to_guest_loopback_http_server();
javascript_fetch_reaches_http_server_in_parallel_guest_process();
javascript_net_rpc_listens_accepts_connections_and_reports_listener_state();
Expand Down Expand Up @@ -19285,6 +19400,11 @@ console.log(JSON.stringify({
javascript_net_loopback_socket_churn_releases_kernel_slots();
}

#[test]
fn javascript_http_external_get_reaches_host_listener_regression() {
javascript_http_external_get_reaches_host_listener();
}

#[test]
fn aaa_crypto_handle_tables_are_bounded() {
run_isolated_service_test("crypto-handle-tables");
Expand Down
2 changes: 2 additions & 0 deletions crates/v8-runtime/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/// - "http_request" → _httpServerDispatch
/// - "http2" → _http2Dispatch
/// - "stdin", "stdin_end" → _stdinDispatch
/// - "net_socket" → _netSocketDispatch
/// - "signal" → __secureExecWasmSignalDispatch or _signalDispatch
/// - "timer" → _timerDispatch
pub fn dispatch_stream_event(scope: &mut v8::HandleScope, event_type: &str, payload: &[u8]) {
Expand All @@ -21,6 +22,7 @@ pub fn dispatch_stream_event(scope: &mut v8::HandleScope, event_type: &str, payl
"http_request" => &["_httpServerDispatch"],
"http2" => &["_http2Dispatch"],
"stdin" | "stdin_end" => &["_stdinDispatch"],
"net_socket" => &["_netSocketDispatch"],
"signal" => &["__secureExecWasmSignalDispatch", "_signalDispatch"],
"timer" => &["_timerDispatch"],
_ => return, // Unknown event type — ignore
Expand Down
Loading
Loading