feat(http): register and implement astrid:[email protected] host#1050
Conversation
Key each staged WIT dep dir by the full <pkg>@<ver> stem instead of the bare package name, so two versions of one package ([email protected] and [email protected]) land in separate deps/ dirs. wasmtime's WIT resolver rejects a dir that declares two distinct packages, so the old name-keyed layout could not host a second version. Regenerates the committed wit-staging/ tree (deps/astrid-<pkg>/ becomes deps/astrid-<pkg>@<ver>/) and bumps the wit submodule to the revision carrying host/[email protected].
Adds the @1.1.0 import to the kernel world and backs both http versions from one implementation. The @1.1.0 trait impls carry the real backend (full request-options control surface); the @1.0.0 http-request / http-stream-start are thin shims delegating to it with @1.0.0-equivalent options, dropping the new response meta on return. Implemented @1.1.0 controls: per-phase timeouts (connect/total/between-bytes/first-byte), redirect policy (follow with mandatory per-hop SSRF re-validation + cross-origin credential stripping, manual, error), max-response-bytes (clamped to the 10MB host ceiling), max-decompressed-bytes + auto-decompress toggle, https-only scheme enforcement, subresource integrity (sha256/384/512), and response-meta (final-url, redirect-count, elapsed-ms, wire-bytes). Buffered redirects are followed manually so each hop re-runs the async security gate and the egress airlock; method downgrades on 301/302/303 per RFC 7231. Streaming redirects delegate per-hop airlocking to the reqwest policy (matching @1.0.0). Deferred (TODO, honest errors/stubs): http-upload streaming request body, http-stream.trailers (Ok(None)), and subscribe-readable/body-stream (reuse the existing stubs). Splits host/http.rs into mod/backend/options/ssrf/tests submodules to stay under the file-size cap. Adds base64 + sha2 deps and reqwest gzip/brotli/deflate/zstd features for the decompression toggle.
… gate The streaming follow path previously delegated per-hop redirect handling to reqwest's sync redirect policy, which re-applied the IP-literal/resolver airlock but could NOT re-run the async check_http_request host-allowlist gate on a redirect target. A streaming follow request could be redirected to a host the gate would deny on a fresh request. Unify both paths onto one manual-follow driver (follow_redirects over send_one_hop): reqwest never follows redirects on either path; every hop — initial or redirect target — re-runs the full airlock (check_scheme + async check_http_request gate + egress airlock), strips Authorization/Cookie cross-origin, and downgrades method per RFC 7231. The only divergence is the terminal action (buffer the body vs hand the unread response to a stream resource). Exempt-no-follow now applies to both paths (Follow only): an operator-blessed endpoint returns its 3xx instead of following, so the port-scoped allowlist can't widen. Removes the now-dead streaming_redirect_policy / airlock_redirect_policy / build_redirect_policy / RedirectAirlock fixtures. Replaces the reqwest-policy-level redirect test with end-to-end tests over the real backend: exempt-no-follow on both buffered and streaming paths, and a streaming request that the async gate denies (proving the gate runs in send_one_hop, the per-hop call the loop makes for every redirect target).
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces astrid:[email protected], providing a robust control surface for HTTP requests while preserving the existing @1.0.0 interface. The changes significantly improve security by ensuring that all redirect hops are manually followed and re-validated against the host's SSRF airlock. Additionally, the infrastructure has been updated to support multi-version WIT staging, allowing for cleaner evolution of host APIs. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Two versions now live in the host, Of HTTP, the one we love most. With airlocks so tight, And redirects right, The capsule is safe at its post. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces astrid:[email protected], a new version of the HTTP host package that supports extended per-request controls such as custom timeouts, redirect policies, and subresource integrity. The implementation refactors the existing HTTP logic into a shared backend, updates the WIT staging process to support multi-version coexistence, and adds necessary security checks. The review identified several improvements: using append for headers to correctly handle duplicates, adding a timeout for the initial connection phase to prevent potential DoS, using from_bytes for header parsing to support UTF-8, and correctly gating the decompression bomb check.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
Registers and implements the astrid:[email protected] host interface in the capsule kernel, while keeping @1.0.0 available, and consolidates both versions onto a single shared host implementation. This enables per-request HTTP controls (timeouts, redirect policy, caps, scheme restrictions, integrity) and strengthens redirect SSRF handling by re-validating every hop on both buffered and streaming paths.
Changes:
- Added dual-version WIT + linker support for
astrid:[email protected]and@1.1.0, with@1.0.0implemented as shims over the shared@1.1.0backend. - Reworked
build.rsWIT staging to support multiple versions of the same package by keyingdeps/directories byname@version. - Implemented the new HTTP host backend/modules (
backend/options/ssrf) and expanded test coverage for SSRF/redirect behavior and option resolution.
Reviewed changes
Copilot reviewed 14 out of 27 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged uplink host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged sys host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged process host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged net host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged kv host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged ipc host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged io host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged identity host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Introduces astrid:[email protected] WIT contract (request-options, response-meta, new errors, upload/trailers shapes). |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Stages frozen astrid:[email protected] alongside @1.1.0. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged guest export WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged fs host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged elicit host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/wit-staging/deps/[email protected]/[email protected] | Staged approval host WIT dependency under versioned deps directory. |
| crates/astrid-capsule/src/security/mod.rs | Exposes AllowAllGate for tests to support HTTP e2e/SSRF tests. |
| crates/astrid-capsule/src/engine/wasm/host/http/tests.rs | Adds extensive SSRF/redirect/options tests (including e2e loopback server cases). |
| crates/astrid-capsule/src/engine/wasm/host/http/ssrf.rs | Extracts shared SSRF airlock logic and per-hop redirect re-validation used by both HTTP versions. |
| crates/astrid-capsule/src/engine/wasm/host/http/options.rs | Implements request-options resolution and scheme/integrity/credential-stripping helpers. |
| crates/astrid-capsule/src/engine/wasm/host/http/mod.rs | New module implementing both @1.0.0 and @1.1.0 host traits, with type bridging and shared backend wiring. |
| crates/astrid-capsule/src/engine/wasm/host/http/backend.rs | Shared manual-redirect-follow backend for buffered + streaming, and shared stream method implementations. |
| crates/astrid-capsule/src/engine/wasm/host/http.rs | Removes the previous single-version HTTP host implementation file. |
| crates/astrid-capsule/src/engine/wasm/host/http_tests.rs | Removes the previous test module location (tests moved under host/http/tests.rs). |
| crates/astrid-capsule/src/engine/wasm/bindings.rs | Adds astrid:http/[email protected] import and bindgen async selectors; updates multi-version documentation. |
| crates/astrid-capsule/Cargo.toml | Enables reqwest decompression features and adds hashing/base64 deps needed for SRI and decompression controls. |
| crates/astrid-capsule/build.rs | Updates WIT staging layout to support multiple versions of the same package. |
| CHANGELOG.md | Documents the new astrid:[email protected] host support and SSRF redirect hardening. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…rst-byte, clamp redirects Addresses review-bot findings on the @1.1.0 host: - build_headers used insert (last-write-wins) + from_str (ASCII-only), dropping duplicate request headers the WIT contract allows (multiple Cookie lines) and rejecting valid UTF-8/obs-text header values. Use append + from_bytes. - The streaming path clears the total timeout, so a server that accepts then hangs before sending response headers blocked send() forever (executor starvation). Bound the header phase with a deadline = first-byte-ms, else total, else a 120s floor — finite, but generous enough not to cut a slow-TTFT LLM stream. - from_options did not clamp caller max_redirects to the host ceiling; clamp with .min(MAX_HTTP_REDIRECTS). - The 4-concurrent-stream quota was dead: active_http_streams was checked but never populated (streams lived only in the resource table). Mirror each opened stream into active_http_streams keyed by rep (sharing the same Arc<Mutex<Response>>); remove on close and drop. The per-principal + global cap now triggers at 4. - finalize_buffered applied the decompressed-bytes cap even with auto_decompress=false, false-positiving DecompressionBomb on large raw/compressed downloads. Gate the cap on auto_decompress. - Fixed a stale doc path (http.rs -> http/mod.rs) in bindings.rs. Each fix ships a regression test (in http/regression.rs) verified to fail without the fix; the header-deadline decision table is unit-tested (the streaming no-timeout floor is the path that strictly requires the fix) alongside an end-to-end hung-server test.
#1054) ## Linked Issue Closes #1053 ## Summary Makes the seven hardcoded `astrid:http` host limits operator-tunable via a new global `[http]` config section, and folds in three follow-up review fixes from #1050. Builds on the now-merged `astrid:[email protected]` host work (#1050); defaults equal today's constants, so an absent `[http]` section changes nothing. ## Changes **`[http]` operator config** (`astrid-config` → daemon → kernel → `HostState`, mirroring `[security.capsule_local_egress]`): - New `HttpSection` (7 knobs: `default_timeout_secs`, `stream_connect_timeout_secs`, `stream_read_timeout_secs`, `header_deadline_secs`, `max_redirects`, `max_concurrent_streams`, `max_response_bytes`) → resolved by the daemon into a typed `HttpLimits` → stored on the kernel → snapshotted onto every `HostState` (global, like the runtime concurrency limits). The http host reads its defaults/ceilings off `HostState.http_limits` instead of module constants. - **Operator-only:** `merge::restrict` reverts the whole `[http]` section to the operator baseline if a workspace/project layer touches it — untrusted project config cannot widen the host's HTTP limits. - **Can only lower:** a per-request `request-options` may tighten within the ceiling; a configured ceiling never lets a caller exceed it; `max_response_bytes` is additionally hard-clamped to `MAX_GUEST_PAYLOAD_LEN`. - Added to `config show` + `defaults.toml` (documented, commented). **Follow-up fixes from #1050 review** (separate commit): - Honour an explicit `total-ms` equal to the configured default on the streaming path (explicit-flag instead of value-equality). - Simplify `classify_redirect` → `redirect_target_blocked` (IP-literal airlock only); the manual-follow loop already owns the hop ceiling. - Map genuine DNS-resolution failures to the typed `DnsError` (a `dns_failed` resolver flag mirroring the airlock `tripped` channel; airlock takes precedence). TLS error typing left as a documented TODO — rustls is transitive (`hyper-rustls`), so there's no stable typed error to downcast to without a brittle string matcher. ## Verification - `cargo build --workspace`, `cargo clippy --workspace --all-features -- -D warnings` (0), `cargo fmt --all --check`: green on the rebased branch. 45 http tests + 104 `astrid-config` tests pass locally. - New tests: defaults reproduce the constants; `config show` surfaces the section; the operator-only merge guard rejects a workspace override; and config actually changes behaviour (`max_concurrent_streams=2` → 3rd stream `Quota`; `default_timeout_secs=5` drives the resolved default; `max_redirects=3` clamps a larger caller value). Plus regressions for each of the three follow-up fixes. - **Operator-only boundary reviewed adversarially:** `test_http_section_cannot_be_set_by_workspace` proves a workspace raising `max_redirects=100`/`max_concurrent_streams=64`/`max_response_bytes=1GiB` is fully reverted to the operator baseline. ## Checklist - [x] Linked to an issue - [x] CHANGELOG.md updated (entry under `[Unreleased]`)
Linked Issue
Closes #1049
Summary
Registers
astrid:[email protected]in the wasmtime linker alongside the frozen@1.0.0and implements its host side, backing both versions from one shared implementation. Delivers the kernel half of the per-request-timeout work (#1012): caller-set per-request controls on the HTTP client, with the redirect/SSRF airlock strengthened to re-validate every hop. The contract landed in astrid-runtime/wit#17; this bumps thewitsubmodule to pick it up (which also carries the unrelatedastrid-bus:[email protected]contract — a bus interface that doesn't touch the host linker).Changes
bindings.rsimportsastrid:http/[email protected]into the kernel world; the singleKernel::add_to_linkerauto-registers both versions (corrected the stale 'add a second call' doc comment). Both generate version-suffixed modules (http1_0_0/http1_1_0);HostStateimpls both versions' traits.build.rsmulti-version WIT staging.astrid:httpis the first host package with two versions; wasmtime's bindgen (likewasm-tools) rejects two versions in onedeps/dir, so each staged dep dir is now keyed byname@version. Regeneratedwit-staging/accordingly (the committed-tree dirty-check gate is satisfied).@1.1.0host impl (host/http/{mod,backend,options,ssrf,tests}.rs, split for the 1000-line cap): therequest-optionssurface — four-phase timeouts, redirect policy, response/decompression caps,auto-decompress,https-only, SRI integrity — plusresponse-metaon every buffered response. The@1.0.0http-request/http-stream-startare thin shims reproducing prior behaviour exactly and delegating to the same backend.Authorization/Cookiecross-origin, and blocks IP-literal targets. This closes a redirect-SSRF gap on the streaming path where the async gate previously ran only on the initial URL (the IP airlock already ran per hop). reqwest's sync redirect-policy machinery was removed.http-uploadstreaming request body (returns a typed not-implemented error), responsetrailers(returnsnone), and un-stubbing the stream pollable/body-stream. Tracked as follow-ups.Verification
cargo build --workspace,cargo clippy --workspace --all-features -- -D warnings(0),cargo test --workspace(0 failures),cargo fmt --all --check: green. 33httptests (verified locally), including end-to-end tests over the real backend: per-hop async-gate enforcement on the streaming path (streaming_request_runs_async_gate_per_hop), exempt-endpoint no-follow (buffered + streaming), IP-literal redirect blocking, scheme/integrity/credential-strip, and the@1.0.0-reproduction shim.max-response-bytesis clamped to the host ceiling (a caller cannot raise the buffered cap);check_schemeblocksfile:/data:/ws:; the release binary never reads the test-only SSRF bypass.@1.0.0shim returnstoo-many-redirectson >10-hop chains where old reqwest returned the last 3xx response — a pathological redirect-loop edge case where erroring is the more correct outcome.Checklist
[Unreleased])