Skip to content

feat(reborn): route WASM HTTP through shared egress#3123

Merged
serrrfirat merged 4 commits into
reborn-integrationfrom
reborn-wasm-http-egress
May 1, 2026
Merged

feat(reborn): route WASM HTTP through shared egress#3123
serrrfirat merged 4 commits into
reborn-integrationfrom
reborn-wasm-http-egress

Conversation

@serrrfirat
Copy link
Copy Markdown
Collaborator

@serrrfirat serrrfirat commented Apr 30, 2026

Summary

Follow-up after #3098 to route the Reborn WASM WIT http-request host import through the shared RuntimeHttpEgress boundary instead of a WASM-local/mock HTTP surface.

What changed

  • Adds WasmRuntimeHttpAdapter<E> in ironclaw_wasm:
    • converts WIT-native method/header/body/timeout shapes into RuntimeHttpEgressRequest
    • forwards host-supplied scope, network policy, response limit, and request-scoped credential injections
    • uses a host-owned WasmRuntimeCredentialProvider so credential injections are resolved from the actual method/URL/headers rather than reused adapter-wide
    • sanitizes credential-provider and shared-egress errors to stable runtime-visible categories
    • does not create HTTP clients, resolve DNS, apply ad-hoc SSRF policy, or inject credentials
    • marks post-send shared-egress failures so WASM execution accounting preserves outbound request bytes, including zero-body response-stage failures
    • strips sensitive response headers before encoding the WIT headers-json object using the shared runtime-sensitive header vocabulary, including token/auth/secret/credential/password/cookie-bearing header names beyond exact matches
    • combines duplicate non-sensitive response headers case-insensitively at the WIT JSON-object boundary
  • Threads timeout_ms through RuntimeHttpEgressRequest -> NetworkHttpRequest -> NetworkTransportRequest; reqwest-backed transport applies the smaller of the request timeout and the host transport default.
  • Updates Script/MCP HTTP request structs to carry the shared timeout field and return stable sanitized shared-egress error categories.
  • Bumps Wasmtime/WASI patch versions from 43.0.1 to 43.0.2 to clear the cargo-deny advisory gate.
  • Updates the WASM dispatch integration smoke to use the shared egress adapter for the HTTP trap path.
  • Documents the WASM/shared-egress boundary in docs/reborn/contracts/wasm.md.

Verification

cargo fmt --all -- --check
CARGO_TARGET_DIR=/tmp/ironclaw-reborn-wasm-egress-target cargo test -p ironclaw_wasm
CARGO_TARGET_DIR=/tmp/ironclaw-reborn-wasm-egress-target cargo test -p ironclaw_host_api -p ironclaw_host_runtime -p ironclaw_network -p ironclaw_scripts -p ironclaw_mcp
CARGO_TARGET_DIR=/tmp/ironclaw-reborn-wasm-egress-target cargo clippy -q -p ironclaw_host_api -p ironclaw_wasm -p ironclaw_host_runtime -p ironclaw_network -p ironclaw_scripts -p ironclaw_mcp --all-targets -- -D warnings
CARGO_TARGET_DIR=/tmp/ironclaw-reborn-wasm-egress-target cargo test -p ironclaw_architecture
cargo deny check
git diff --check

Links

Completes #3085.
Part of #2987.

@github-actions github-actions Bot added size: M 50-199 changed lines risk: low Changes to docs, tests, or low-risk modules labels Apr 30, 2026
@serrrfirat serrrfirat added the reborn IronClaw Reborn architecture and landing work label Apr 30, 2026
@github-actions github-actions Bot added contributor: core 20+ merged PRs scope: docs Documentation labels Apr 30, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the WasmRuntimeHttpAdapter, which bridges WASM WIT HTTP imports to the shared RuntimeHttpEgress service, handling header sanitization and error mapping. Feedback was provided to improve the robustness of header decoding for empty strings and to use a Vec instead of a Map for encoding headers to support duplicate keys. Additionally, the logic for determining if a request was sent should be refined to correctly account for bodyless requests like GET, ensuring accurate error reporting and resource tracking.

Comment on lines +185 to +187
let value = serde_json::from_str::<Value>(headers_json).map_err(|_| {
WasmHostError::Denied("WASM HTTP headers must be a JSON object".to_string())
})?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The serde_json::from_str call will fail with an error if headers_json is an empty string. While the WIT contract usually provides a JSON object string (like {}), it's safer to handle the empty string case explicitly. Per repository guidelines, when handling empty strings in the context of JSON schemas, they should be preserved unless the schema specifically requires null or disallows strings.

References
  1. When coercing an empty string to a value based on a JSON schema, convert it to null only if the schema allows null or does not allow string. Otherwise, preserve the empty string as it may be a valid value.

Comment on lines +207 to +213
let mut encoded = Map::new();
for (name, value) in headers {
if sensitive_response_header(&name) {
continue;
}
encoded.insert(name, Value::String(value));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a serde_json::Map to encode headers means that if the response from the egress service contains duplicate header names (e.g., multiple Set-Cookie headers), only the last one will be preserved. Consider using a Vec<(String, String)> to maintain all headers and their insertion order. For small collections, a Vec is often preferred over a Map or Set in this repository to ensure deterministic behavior and support duplicate keys.

References
  1. When choosing data structures, consider the trade-offs between performance, readability, and deterministic ordering for debugging. For small, non-performance-critical collections, a Vec might be preferred over a HashSet if insertion order is important for debugging.

Comment thread crates/ironclaw_wasm/src/host.rs Outdated
}

fn wasm_http_error(error: RuntimeHttpEgressError) -> WasmHostError {
let request_was_sent = error.request_bytes() > 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic error.request_bytes() > 0 to determine if a request was sent might be inaccurate for requests with no body (e.g., a simple GET request). If request_bytes only tracks the body size, then a failed GET request that was actually sent to the network will be reported as Denied instead of FailedAfterRequestSent. Ensure that any accumulated metrics like resource usage are correctly tracked and propagated even when an operation fails.

References
  1. When an operation can fail, ensure that any accumulated metrics (like resource usage) are propagated out along with the error, not discarded.

@github-actions github-actions Bot added size: L 200-499 changed lines and removed size: M 50-199 changed lines labels Apr 30, 2026
@github-actions github-actions Bot added size: XL 500+ changed lines scope: dependencies Dependency updates risk: medium Business logic, config, or moderate-risk modules and removed size: L 200-499 changed lines size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules labels Apr 30, 2026
This was referenced May 7, 2026
@ironclaw-ci ironclaw-ci Bot mentioned this pull request May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs reborn IronClaw Reborn architecture and landing work risk: medium Business logic, config, or moderate-risk modules scope: dependencies Dependency updates scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant