Skip to content

Commit fbf34e1

Browse files
committed
Fix javascript demo
Signed-off-by: James Sturtevant <jsturtevant@gmail.com>
1 parent 97ec7b0 commit fbf34e1

9 files changed

Lines changed: 132 additions & 79 deletions

File tree

‎src/hyperlight_sandbox/Cargo.toml‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,10 @@ http-body-util = "0.1"
1616
hyper = { version = "1", features = ["server", "http1"] }
1717
hyper-util = { version = "0.1", features = ["tokio"] }
1818
log = "0.4"
19-
rcgen = { version = "0.14", optional = true }
20-
rustls = { version = "0.23", optional = true, default-features = false, features = ["ring"] }
2119
serde = { version = "1", features = ["derive"] }
2220
serde_json = "1"
2321
tempfile = "3"
2422
tokio = { version = "1", features = ["rt-multi-thread", "net", "sync", "macros"] }
25-
tokio-rustls = { version = "0.26", optional = true }
2623
url = "2"
2724
wasmtime-wasi-http = "43.0.0"
2825

@@ -33,4 +30,4 @@ proptest = "1"
3330
tokio = { version = "1", features = ["full"] }
3431

3532
[features]
36-
test-utils = ["dep:rcgen", "dep:rustls", "dep:tokio-rustls"]
33+
test-utils = []

‎src/javascript_sandbox/examples/network_demo.rs‎

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ fn main() {
2525
let result = sandbox
2626
.run(
2727
r#"
28-
try {
29-
const resp = await fetch('https://notallowed.example');
30-
console.log('Got response: ' + resp.status);
31-
} catch (e) {
32-
console.log('Network blocked: ' + e.message);
28+
const resp = await fetch('https://notallowed.example');
29+
if (resp.status === 403) {
30+
console.log('Network blocked: status ' + resp.status);
3331
console.log(' (notallowed.example is not in the allowlist — correct!)');
32+
} else {
33+
console.log('Got response: ' + resp.status);
3434
}
3535
"#,
3636
)
@@ -45,33 +45,39 @@ try {
4545
let result = sandbox
4646
.run(
4747
r#"
48-
const resp = await fetch('https://httpbin.org/get');
48+
const resp = await fetch('https://httpbin.org/json', { headers: { 'accept': 'application/json' } });
4949
const body = await resp.text();
5050
console.log('HTTP status: ' + resp.status);
51-
console.log('Response body (first 200 chars):');
52-
console.log(body.slice(0, 200));
51+
console.log(body);
5352
"#,
5453
)
5554
.expect("test 2 failed");
5655
print!("{}", result.stdout);
57-
assert_eq!(result.exit_code, 0, "test 2: network access to allowed domain failed\nstderr: {}", &result.stderr[..result.stderr.len().min(300)]);
56+
assert_eq!(
57+
result.exit_code,
58+
0,
59+
"test 2: network access to allowed domain failed\nstderr: {}",
60+
&result.stderr[..result.stderr.len().min(300)]
61+
);
5862

5963
separator("Test 3: Method filtering — GET allowed, POST blocked");
6064
let result = sandbox
6165
.run(
6266
r#"
63-
try {
64-
const resp = await fetch('https://httpbin.org/get');
65-
console.log('GET allowed: status ' + resp.status);
66-
} catch (e) {
67-
console.log('GET result: ' + e.message);
67+
const getResp = await fetch('https://httpbin.org/get');
68+
if (getResp.status === 200) {
69+
const body = await getResp.text();
70+
console.log('GET allowed: status ' + getResp.status);
71+
console.log(body);
72+
} else {
73+
console.log('GET failed: status ' + getResp.status);
6874
}
69-
try {
70-
const resp = await fetch('https://httpbin.org/post', { method: 'POST' });
71-
console.log('POST allowed: status ' + resp.status);
72-
} catch (e) {
73-
console.log('POST blocked: ' + e.message);
75+
const postResp = await fetch('https://httpbin.org/post', { method: 'POST' });
76+
if (postResp.status === 403) {
77+
console.log('POST blocked: status ' + postResp.status);
7478
console.log(' (httpbin.org only allows GET — correct!)');
79+
} else {
80+
console.log('POST allowed: status ' + postResp.status);
7581
}
7682
"#,
7783
)

‎src/javascript_sandbox/src/lib.rs‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ Object.defineProperty(globalThis, "fetch", { value: function(url, init = {}) {
8686
});
8787
}, writable: false, configurable: false });
8888
89-
function handler(event) {
89+
async function handler(event) {
9090
try {
91-
(0, eval)(event);
91+
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
92+
const fn_ = new AsyncFunction(event);
93+
await fn_();
9294
return { stderr: "", exit_code: 0 };
9395
} catch (error) {
9496
const msg = __stringify(error && error.stack ? error.stack : error);
@@ -304,13 +306,18 @@ impl JsGuestSandbox {
304306

305307
{
306308
let Ok(network) = http_state.network.lock() else {
307-
return json_error("network mutex poisoned");
309+
return json_response(serde_json::json!({
310+
"status": 500,
311+
"headers": {},
312+
"body": "network mutex poisoned",
313+
}));
308314
};
309315
if !network.is_allowed(&parsed_url, &method) {
310-
return json_error(format!(
311-
"HTTP request denied for {} {}",
312-
method, parsed_url
313-
));
316+
return json_response(serde_json::json!({
317+
"status": 403,
318+
"headers": {},
319+
"body": format!("HTTP request denied for {} {}", method, parsed_url),
320+
}));
314321
}
315322
}
316323

@@ -329,7 +336,13 @@ impl JsGuestSandbox {
329336

330337
let resp = match sandbox_http::send_http_request(http_req).block_on() {
331338
Ok(r) => r,
332-
Err(error) => return json_error(error),
339+
Err(error) => {
340+
return json_response(serde_json::json!({
341+
"status": 502,
342+
"headers": {},
343+
"body": format!("{error}"),
344+
}));
345+
}
333346
};
334347

335348
let body = match String::from_utf8(resp.body) {

‎src/sdk/python/hyperlight_js_backend/src/lib.rs‎

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
use std::collections::HashMap;
22

3-
use pyo3::exceptions::PyRuntimeError;
4-
use pyo3::prelude::*;
5-
63
use hyperlight_javascript_sandbox::HyperlightJs;
7-
use hyperlight_sandbox::{DirPerms, FilePerms, Sandbox, SandboxConfig, HttpMethod};
8-
4+
use hyperlight_sandbox::{DirPerms, FilePerms, HttpMethod, Sandbox, SandboxConfig};
95
use hyperlight_sandbox_pyo3_common::{
106
PyExecutionResult, PySnapshot, build_tool_registry, parse_size, parse_tool_registration,
117
};
8+
use pyo3::exceptions::PyRuntimeError;
9+
use pyo3::prelude::*;
1210

1311
#[pyclass(unsendable)]
1412
pub struct JSSandbox {
@@ -86,16 +84,22 @@ impl JSSandbox {
8684
builder = builder.input_dir(dir);
8785
}
8886
if let Some(ref dir) = self.output_dir {
89-
builder = builder.output_dir(dir, DirPerms::READ | DirPerms::MUTATE, FilePerms::READ | FilePerms::WRITE);
87+
builder = builder.output_dir(
88+
dir,
89+
DirPerms::READ | DirPerms::MUTATE,
90+
FilePerms::READ | FilePerms::WRITE,
91+
);
9092
} else if self.temp_output {
9193
builder = builder.temp_output();
9294
}
93-
let mut sandbox = builder.build()
95+
let mut sandbox = builder
96+
.build()
9497
.map_err(|e| PyRuntimeError::new_err(format!("Failed to create sandbox: {e:#}")))?;
9598
for (target, methods) in std::mem::take(&mut self.pending_networks) {
9699
let methods = HttpMethod::parse_list(methods)
97100
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
98-
sandbox.allow_domain(&target, methods)
101+
sandbox
102+
.allow_domain(&target, methods)
99103
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
100104
}
101105
self.inner = Some(sandbox);
@@ -116,7 +120,8 @@ impl JSSandbox {
116120
if let Some(sandbox) = self.inner.as_mut() {
117121
let methods = HttpMethod::parse_list(methods)
118122
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
119-
sandbox.allow_domain(target, methods)
123+
sandbox
124+
.allow_domain(target, methods)
120125
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
121126
} else {
122127
self.pending_networks.push((target.to_string(), methods));
@@ -125,32 +130,44 @@ impl JSSandbox {
125130
}
126131

127132
fn snapshot(&mut self) -> PyResult<PySnapshot> {
128-
let sandbox = self.inner.as_mut()
133+
let sandbox = self
134+
.inner
135+
.as_mut()
129136
.ok_or_else(|| PyRuntimeError::new_err("Sandbox not initialized"))?;
130-
let snap = sandbox.snapshot()
137+
let snap = sandbox
138+
.snapshot()
131139
.map_err(|e| PyRuntimeError::new_err(format!("Snapshot failed: {e}")))?;
132140
Ok(PySnapshot { inner: snap })
133141
}
134142

135143
fn restore(&mut self, snapshot: &PySnapshot) -> PyResult<()> {
136-
let sandbox = self.inner.as_mut()
144+
let sandbox = self
145+
.inner
146+
.as_mut()
137147
.ok_or_else(|| PyRuntimeError::new_err("Sandbox not initialized"))?;
138-
sandbox.restore(&snapshot.inner)
148+
sandbox
149+
.restore(&snapshot.inner)
139150
.map_err(|e| PyRuntimeError::new_err(format!("Restore failed: {e}")))?;
140151
Ok(())
141152
}
142153

143154
fn get_output_files(&self) -> PyResult<Vec<String>> {
144-
let sandbox = self.inner.as_ref()
155+
let sandbox = self
156+
.inner
157+
.as_ref()
145158
.ok_or_else(|| PyRuntimeError::new_err("Sandbox not initialized"))?;
146-
sandbox.get_output_files()
159+
sandbox
160+
.get_output_files()
147161
.map_err(|e| PyRuntimeError::new_err(format!("Failed to get output files: {e}")))
148162
}
149163

150164
fn output_path(&self) -> PyResult<Option<String>> {
151-
let sandbox = self.inner.as_ref()
165+
let sandbox = self
166+
.inner
167+
.as_ref()
152168
.ok_or_else(|| PyRuntimeError::new_err("Sandbox not initialized"))?;
153-
let path = sandbox.output_path()
169+
let path = sandbox
170+
.output_path()
154171
.map_err(|e| PyRuntimeError::new_err(format!("Failed to get output path: {e}")))?;
155172
Ok(path.map(|p| p.display().to_string()))
156173
}
@@ -163,4 +180,4 @@ fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> {
163180
m.add_class::<PySnapshot>()?;
164181
m.add("__version__", "0.1.0")?;
165182
Ok(())
166-
}
183+
}

‎src/sdk/python/pyo3_common/src/lib.rs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
use hyperlight_sandbox::{ArgType, Snapshot, ToolRegistry, ToolSchema};
12
use pyo3::exceptions::{PyRuntimeError, PyTypeError};
23
use pyo3::prelude::*;
34
use pyo3::types::{PyDict, PyModule};
45

5-
use hyperlight_sandbox::{ArgType, Snapshot, ToolRegistry, ToolSchema};
6-
76
/// Convert a human-readable size string (e.g. `"200Mi"`) to bytes.
87
pub fn parse_size(size: &str) -> PyResult<u64> {
98
let size = size.trim();

‎src/sdk/python/wasm_backend/src/lib.rs‎

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
use std::collections::HashMap;
22

3-
use pyo3::exceptions::PyRuntimeError;
4-
use pyo3::prelude::*;
5-
6-
use hyperlight_sandbox::{DirPerms, FilePerms, Sandbox, SandboxConfig, HttpMethod};
7-
use hyperlight_wasm_sandbox::Wasm;
8-
3+
use hyperlight_sandbox::{DirPerms, FilePerms, HttpMethod, Sandbox, SandboxConfig};
94
use hyperlight_sandbox_pyo3_common::{
105
PyExecutionResult, PySnapshot, build_tool_registry, parse_size, parse_tool_registration,
116
};
7+
use hyperlight_wasm_sandbox::Wasm;
8+
use pyo3::exceptions::PyRuntimeError;
9+
use pyo3::prelude::*;
1210

1311
#[pyclass(unsendable)]
1412
pub struct WasmSandbox {
@@ -80,16 +78,22 @@ impl WasmSandbox {
8078
builder = builder.input_dir(dir);
8179
}
8280
if let Some(ref dir) = self.output_dir {
83-
builder = builder.output_dir(dir, DirPerms::READ | DirPerms::MUTATE, FilePerms::READ | FilePerms::WRITE);
81+
builder = builder.output_dir(
82+
dir,
83+
DirPerms::READ | DirPerms::MUTATE,
84+
FilePerms::READ | FilePerms::WRITE,
85+
);
8486
} else if self.temp_output {
8587
builder = builder.temp_output();
8688
}
87-
let mut sandbox = builder.build()
89+
let mut sandbox = builder
90+
.build()
8891
.map_err(|e| PyRuntimeError::new_err(format!("Failed to create sandbox: {e:#}")))?;
8992
for (target, methods) in std::mem::take(&mut self.pending_networks) {
9093
let methods = HttpMethod::parse_list(methods)
9194
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
92-
sandbox.allow_domain(&target, methods)
95+
sandbox
96+
.allow_domain(&target, methods)
9397
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
9498
}
9599
self.inner = Some(sandbox);
@@ -110,7 +114,8 @@ impl WasmSandbox {
110114
if let Some(sandbox) = self.inner.as_mut() {
111115
let methods = HttpMethod::parse_list(methods)
112116
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
113-
sandbox.allow_domain(target, methods)
117+
sandbox
118+
.allow_domain(target, methods)
114119
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
115120
} else {
116121
self.pending_networks.push((target.to_string(), methods));
@@ -119,32 +124,44 @@ impl WasmSandbox {
119124
}
120125

121126
fn snapshot(&mut self) -> PyResult<PySnapshot> {
122-
let sandbox = self.inner.as_mut()
127+
let sandbox = self
128+
.inner
129+
.as_mut()
123130
.ok_or_else(|| PyRuntimeError::new_err("Sandbox not initialized"))?;
124-
let snap = sandbox.snapshot()
131+
let snap = sandbox
132+
.snapshot()
125133
.map_err(|e| PyRuntimeError::new_err(format!("Snapshot failed: {e}")))?;
126134
Ok(PySnapshot { inner: snap })
127135
}
128136

129137
fn restore(&mut self, snapshot: &PySnapshot) -> PyResult<()> {
130-
let sandbox = self.inner.as_mut()
138+
let sandbox = self
139+
.inner
140+
.as_mut()
131141
.ok_or_else(|| PyRuntimeError::new_err("Sandbox not initialized"))?;
132-
sandbox.restore(&snapshot.inner)
142+
sandbox
143+
.restore(&snapshot.inner)
133144
.map_err(|e| PyRuntimeError::new_err(format!("Restore failed: {e}")))?;
134145
Ok(())
135146
}
136147

137148
fn get_output_files(&self) -> PyResult<Vec<String>> {
138-
let sandbox = self.inner.as_ref()
149+
let sandbox = self
150+
.inner
151+
.as_ref()
139152
.ok_or_else(|| PyRuntimeError::new_err("Sandbox not initialized"))?;
140-
sandbox.get_output_files()
153+
sandbox
154+
.get_output_files()
141155
.map_err(|e| PyRuntimeError::new_err(format!("Failed to get output files: {e}")))
142156
}
143157

144158
fn output_path(&self) -> PyResult<Option<String>> {
145-
let sandbox = self.inner.as_ref()
159+
let sandbox = self
160+
.inner
161+
.as_ref()
146162
.ok_or_else(|| PyRuntimeError::new_err("Sandbox not initialized"))?;
147-
let path = sandbox.output_path()
163+
let path = sandbox
164+
.output_path()
148165
.map_err(|e| PyRuntimeError::new_err(format!("Failed to get output path: {e}")))?;
149166
Ok(path.map(|p| p.display().to_string()))
150167
}
@@ -157,4 +174,4 @@ fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> {
157174
m.add_class::<PySnapshot>()?;
158175
m.add("__version__", "0.1.0")?;
159176
Ok(())
160-
}
177+
}

‎src/wasm_sandbox/examples/js_network_demo.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ console.log(body.slice(0, 200));
6666
)
6767
.expect("test 2 failed");
6868
print!("{}", result.stdout);
69-
assert_eq!(result.exit_code, 0, "test 2: network access to allowed domain failed\nstderr: {}", &result.stderr[..result.stderr.len().min(300)]);
69+
assert_eq!(
70+
result.exit_code,
71+
0,
72+
"test 2: network access to allowed domain failed\nstderr: {}",
73+
&result.stderr[..result.stderr.len().min(300)]
74+
);
7075

7176
separator("Test 3: Method filtering — GET allowed, POST blocked");
7277
let result = sandbox

0 commit comments

Comments
 (0)