Skip to content

Commit 89fce8b

Browse files
committed
Fix network examples and add real assertions
The network demos across all backends were silently failing — every example printed success regardless of whether HTTP requests actually succeeded. This hid two real bugs: - AnyPollable::block() polled with a tight yield_now() loop capped at 100k iterations. Real HTTPS requests take hundreds of milliseconds, but 100k yields complete in microseconds, so the guest always gave up before the response arrived. Remove the iteration cap and keep the yield so the loop waits as long as the request needs. - The HyperlightJS sandbox eval-based handler couldn't support await. The handler ran guest code through synchronous eval(), so 'await fetch(...)' created an unresolved promise that crashed outside the try/catch. Wrap the code in AsyncFunction to make top-level await work. Additionally, denied HTTP requests now return proper HTTP responses (403/500/502 status codes) instead of throwing JS exceptions via json_error. All network demos now have real assertions that will fail CI if anything regresses. An HTTPS integration test against httpbin.org covers TLS end-to-end. Signed-off-by: James Sturtevant <jsturtevant@gmail.com>
1 parent 5e93e35 commit 89fce8b

12 files changed

Lines changed: 205 additions & 126 deletions

File tree

src/hyperlight_sandbox/tests/http_integration.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,24 @@ fn chunked_body_streams_correctly() {
118118
}
119119
.block_on();
120120
}
121+
122+
#[test]
123+
fn send_https_get_request() {
124+
async {
125+
let req = HttpRequest {
126+
url: url::Url::parse("https://httpbin.org/get").unwrap(),
127+
method: "GET".to_string(),
128+
headers: vec![],
129+
body: HttpRequest::body_from_bytes(None),
130+
};
131+
132+
let resp = http::send_http_request(req)
133+
.await
134+
.expect("HTTPS GET to httpbin.org failed — TLS or DNS issue");
135+
assert_eq!(resp.status, 200);
136+
137+
let echo: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
138+
assert!(echo["url"].as_str().unwrap().contains("httpbin.org"));
139+
}
140+
.block_on();
141+
}

src/javascript_sandbox/examples/network_demo.rs

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,59 +25,68 @@ 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
)
3737
.expect("test 1 failed");
3838
print!("{}", result.stdout);
39+
assert!(
40+
result.stdout.contains("Network blocked"),
41+
"test 1: expected network access to be blocked"
42+
);
3943

4044
separator("Test 2: Network access to allowed domain");
4145
let result = sandbox
4246
.run(
4347
r#"
44-
const resp = await fetch('https://httpbin.org/get');
48+
const resp = await fetch('https://httpbin.org/json', { headers: { 'accept': 'application/json' } });
4549
const body = await resp.text();
4650
console.log('HTTP status: ' + resp.status);
47-
console.log('Response body (first 200 chars):');
48-
console.log(body.slice(0, 200));
51+
console.log(body);
4952
"#,
5053
)
5154
.expect("test 2 failed");
5255
print!("{}", result.stdout);
53-
if result.exit_code == 0 {
54-
println!("Network access to allowed domain works!");
55-
} else {
56-
eprintln!("Network access failed");
57-
eprintln!("stderr: {}", &result.stderr[..result.stderr.len().min(300)]);
58-
}
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+
);
5962

6063
separator("Test 3: Method filtering — GET allowed, POST blocked");
6164
let result = sandbox
6265
.run(
6366
r#"
64-
try {
65-
const resp = await fetch('https://httpbin.org/get');
66-
console.log('GET allowed: status ' + resp.status);
67-
} catch (e) {
68-
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);
6974
}
70-
try {
71-
const resp = await fetch('https://httpbin.org/post', { method: 'POST' });
72-
console.log('POST allowed: status ' + resp.status);
73-
} catch (e) {
74-
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);
7578
console.log(' (httpbin.org only allows GET — correct!)');
79+
} else {
80+
console.log('POST allowed: status ' + postResp.status);
7681
}
7782
"#,
7883
)
7984
.expect("test 3 failed");
8085
print!("{}", result.stdout);
86+
assert!(
87+
result.stdout.contains("POST blocked"),
88+
"test 3: expected POST to be blocked"
89+
);
8190

8291
separator("All tests passed!");
8392
}

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/core/examples/javascript_network_demo.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@
2222
print("Test 1: Network access denied without permissions")
2323
print("═" * 60)
2424
result = sandbox.run("""
25-
try {
26-
const resp = await fetch('https://notallowed.example');
27-
console.log('Got response: ' + resp.status);
28-
} catch (e) {
29-
console.log('Network blocked: ' + e.message);
25+
const resp = await fetch('https://notallowed.example');
26+
if (resp.status === 403) {
27+
console.log('Network blocked: status ' + resp.status);
3028
console.log(' (notallowed.example is not in the allowlist — correct!)');
29+
} else {
30+
console.log('Got response: ' + resp.status);
3131
}
3232
""")
3333
print(result.stdout)
34+
assert "Network blocked" in result.stdout, "test 1: expected network access to be blocked"
3435

3536
# ═══════════════════════════════════════════════════════════════════
3637
# Test 2: Network access — allowed domain
@@ -47,11 +48,7 @@
4748
console.log(body.slice(0, 200));
4849
""")
4950
print(result.stdout)
50-
if result.success:
51-
print("✅ Network access to allowed domain works!")
52-
else:
53-
print("⚠️ Network access failed")
54-
print(f"stderr: {result.stderr[:300]}")
51+
assert result.success, f"test 2: network access to allowed domain failed\nstderr: {result.stderr[:300]}"
5552

5653
# ═══════════════════════════════════════════════════════════════════
5754
# Test 3: Method filtering — GET allowed, POST blocked
@@ -61,21 +58,24 @@
6158
print("Test 3: Method filtering — GET allowed, POST blocked")
6259
print("═" * 60)
6360
result = sandbox.run("""
64-
try {
65-
const resp = await fetch('https://httpbin.org/get');
66-
console.log('GET allowed: status ' + resp.status);
67-
} catch (e) {
68-
console.log('GET result: ' + e.message);
61+
const getResp = await fetch('https://httpbin.org/get');
62+
if (getResp.status === 200) {
63+
const body = await getResp.text();
64+
console.log('GET allowed: status ' + getResp.status);
65+
console.log(body);
66+
} else {
67+
console.log('GET failed: status ' + getResp.status);
6968
}
70-
try {
71-
const resp = await fetch('https://httpbin.org/post', { method: 'POST' });
72-
console.log('POST allowed: status ' + resp.status);
73-
} catch (e) {
74-
console.log('POST blocked: ' + e.message);
75-
console.log(' (httpbin.org only allows GET \u2014 correct!)');
69+
const postResp = await fetch('https://httpbin.org/post', { method: 'POST' });
70+
if (postResp.status === 403) {
71+
console.log('POST blocked: status ' + postResp.status);
72+
console.log(' (httpbin.org only allows GET — correct!)');
73+
} else {
74+
console.log('POST allowed: status ' + postResp.status);
7675
}
7776
""")
7877
print(result.stdout)
78+
assert "POST blocked" in result.stdout, "test 3: expected POST to be blocked"
7979

8080
print("═" * 60)
8181
print("✅ All tests passed!")

src/sdk/python/core/examples/jswasm_network_demo.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
}
2929
""")
3030
print(result.stdout)
31+
assert "Network blocked" in result.stdout, "test 1: expected network access to be blocked"
3132

3233
# ═══════════════════════════════════════════════════════════════════
3334
# Test 2: Network access to allowed domain (WASI-HTTP)
@@ -44,11 +45,7 @@
4445
console.log(body.slice(0, 200));
4546
""")
4647
print(result.stdout)
47-
if result.success:
48-
print("✅ Network access to allowed domain works via WASI-HTTP!")
49-
else:
50-
print("⚠️ Network access failed")
51-
print(f"stderr: {result.stderr[:300]}")
48+
assert result.success, f"test 2: network access to allowed domain failed\nstderr: {result.stderr[:300]}"
5249

5350
# ═══════════════════════════════════════════════════════════════════
5451
# Test 3: Method filtering — GET allowed, POST blocked
@@ -73,6 +70,7 @@
7370
}
7471
""")
7572
print(result.stdout)
73+
assert "POST blocked" in result.stdout, "test 3: expected POST to be blocked"
7674

7775
print("═" * 60)
7876
print("✅ All tests passed!")

src/sdk/python/core/examples/python_network_demo.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
print(" (notallowed.example is not in the allowlist — correct!)")
2828
""")
2929
print(result.stdout)
30-
if not result.success:
31-
print("(Network access correctly denied — sandbox terminated)")
30+
assert "Network blocked" in result.stdout, "test 1: expected network access to be blocked"
3231

3332
# ═══════════════════════════════════════════════════════════════════
3433
# Test 2: Network access to allowed domain (WASI-HTTP)
@@ -44,11 +43,7 @@
4443
print(resp['body'][:200])
4544
""")
4645
print(result.stdout)
47-
if result.success:
48-
print("✅ Network access to allowed domain works via WASI-HTTP!")
49-
else:
50-
print("⚠️ Network access failed")
51-
print(f"stderr: {result.stderr[:300]}")
46+
assert result.success, f"test 2: network access to allowed domain failed\nstderr: {result.stderr[:300]}"
5247

5348
# ═══════════════════════════════════════════════════════════════════
5449
# Test 3: Method filtering — GET allowed, POST blocked
@@ -72,6 +67,7 @@
7267
print(" (httpbin.org only allows GET \u2014 correct!)")
7368
""")
7469
print(result.stdout)
70+
assert "POST blocked" in result.stdout, "test 3: expected POST to be blocked"
7571

7672
print("═" * 60)
7773
print("✅ All tests passed!")

0 commit comments

Comments
 (0)