Skip to content

feat(http): astrid:http@1.1.0 — per-request control, streaming uploads, trailers#17

Merged
joshuajbouw merged 3 commits into
mainfrom
feat/http-1-1-0
Jun 24, 2026
Merged

feat(http): astrid:http@1.1.0 — per-request control, streaming uploads, trailers#17
joshuajbouw merged 3 commits into
mainfrom
feat/http-1-1-0

Conversation

@joshuajbouw

Copy link
Copy Markdown
Contributor

Summary

Adds host/http@1.1.0.wit, the first additive successor in the host ABI. astrid:http@1.0.0 stays frozen and untouched; @1.1.0 is a self-contained superset the kernel registers alongside @1.0.0, so existing capsules are unaffected and new ones opt in package-by-package.

Design record: RFC #22 (host_abi) — see the ### HTTP: per-request control and streaming uploads section.

What @1.1.0 adds

  • request-options (all-optional; an empty value reproduces @1.0.0 exactly): four-phase timeouts (connect / first-byte / between-bytes / total), redirect policy (follow/error/manual) + max-redirects, max-response-bytes, max-decompressed-bytes, auto-decompress, https-only, integrity (SRI).
  • response-meta on the buffered response — final URL after redirects, redirect count, elapsed ms, wire bytes.
  • Streaming request body via the http-upload resource (body-sinkoutput-stream, finish → response stream); mirrors the wasi:http outgoing-body pattern.
  • http-stream.trailers for gRPC-web.
  • New error-code arms: redirect-blocked, too-many-redirects, integrity-mismatch, scheme-denied, decompression-bomb.
  • Options-bearing functions http-request-opts / http-stream-start-opts / http-upload-start; the plain @1.0.0-shaped http-request / http-stream-start are carried forward (they equal the -opts calls with defaulted options).

Security posture

The SSRF airlock owns DNS resolution and the connect path. Knobs that would let a guest influence either are excluded by construction — no field for custom resolvers, resolve/resolve-to-addrs, proxy, source-address/interface binding, or TLS-verification bypass. Conversely @1.1.0 strengthens the airlock: redirect follow must re-validate every Location hop and strip Authorization/Cookie cross-origin (default policy for untrusted capsules is error), and https-only / scheme restriction / SRI / a decompressed-size ceiling are added as security-positive controls.

Tooling

scripts/validate-wit.sh collapsed every version of a package into one deps/astrid-<pkg> dir; since wasm-tools treats a deps dir as a single package, the first second-version broke all validation. Fixed to key each staged dep by name@version. First commit; isolated from the contract.

Validation

scripts/validate-wit.sh passes all 14 host files (incl. both http@1.0.0 and http@1.1.0). New file only — frozen-file discipline satisfied by construction.

Follow-on (separate PRs, after this lands on main)

Per the pin rule (land on main first; squash rewrites SHAs): core registers @1.1.0 + shared host impl with the @1.0.0 shim → sdk-rust mirrors + adds the Request options surface → capsule-openai-compat adopts it for fast-fail /v1/models discovery.

validate-wit.sh collapsed every version of a package into one deps/astrid-<pkg> dir. wasm-tools treats a deps directory as a single package, so the first second-version (astrid:http@1.1.0 alongside @1.0.0) made every validation fail with 'package identifier does not match previous package name'. Key each staged dep dir by the full name@version so coexisting versions resolve independently.
…loads, trailers

Additive successor to astrid:http@1.0.0 (frozen, untouched). Adds an all-optional request-options record (per-request connect/first-byte/between-bytes/total timeouts, redirect policy with mandatory per-hop airlock re-validation, response/decompression size caps, auto-decompress toggle, https-only, subresource integrity), response-meta on the buffered response (final URL, redirect count, timing, wire bytes), a streaming request body via the http-upload resource (mirrors wasi:http outgoing-body), and http-stream.trailers for gRPC-web. An empty request-options reproduces @1.0.0 exactly, so the host backs both from one impl. SSRF-sensitive knobs (custom DNS, resolve overrides, proxy, source binding, TLS-verification bypass) are excluded by construction. Design record: RFC #22 (host_abi).
Copilot AI review requested due to automatic review settings June 24, 2026 06:05
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds the next additive HTTP host ABI version (astrid:http@1.1.0) and updates local WIT validation tooling/docs so both http@1.0.0 and http@1.1.0 can coexist and validate cleanly.

Changes:

  • Introduces host/http@1.1.0.wit with per-request options, response metadata, streaming uploads, trailers, and new error codes.
  • Updates scripts/validate-wit.sh to stage each name@version into its own deps subdirectory to avoid wasm-tools multi-version conflicts.
  • Documents the new HTTP ABI version in the README host ABI table.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
scripts/validate-wit.sh Stages each WIT version into a separate deps directory so wasm-tools can validate multi-version packages.
README.md Adds host/http@1.1.0.wit to the host ABI table with a feature summary.
host/http@1.1.0.wit Defines the new astrid:http@1.1.0 contract: request options, response meta, streaming upload resource, trailers, and expanded error codes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread host/http@1.1.0.wit Outdated
Comment thread scripts/validate-wit.sh Outdated
Per Copilot review on PR #17: the header said every @1.0.0 type was carried forward unchanged, but http-response-data/http-stream/error-code are extended — reworded to state the request shape and function signatures are preserved while the surface is extended. Also hoist the staged dep-dir path into a local in validate-wit.sh (computed twice).
@joshuajbouw joshuajbouw merged commit 812c833 into main Jun 24, 2026
1 check passed
@joshuajbouw joshuajbouw deleted the feat/http-1-1-0 branch June 24, 2026 06:16
joshuajbouw added a commit to astrid-runtime/astrid that referenced this pull request Jun 24, 2026
## Linked Issue

Closes #1049

## Summary

Registers `astrid:http@1.1.0` in the wasmtime linker alongside the
frozen `@1.0.0` and 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 the
`wit` submodule to pick it up (which also carries the unrelated
`astrid-bus:session@1.1.0` contract — a bus interface that doesn't touch
the host linker).

## Changes

- **Dual-version registration.** `bindings.rs` imports
`astrid:http/host@1.1.0` into the kernel world; the single
`Kernel::add_to_linker` auto-registers both versions (corrected the
stale 'add a second call' doc comment). Both generate version-suffixed
modules (`http1_0_0` / `http1_1_0`); `HostState` impls both versions'
traits.
- **`build.rs` multi-version WIT staging.** `astrid:http` is the first
host package with two versions; wasmtime's bindgen (like `wasm-tools`)
rejects two versions in one `deps/` dir, so each staged dep dir is now
keyed by `name@version`. Regenerated `wit-staging/` accordingly (the
committed-tree dirty-check gate is satisfied).
- **`@1.1.0` host impl**
(`host/http/{mod,backend,options,ssrf,tests}.rs`, split for the
1000-line cap): the `request-options` surface — four-phase timeouts,
redirect policy, response/decompression caps, `auto-decompress`,
`https-only`, SRI integrity — plus `response-meta` on every buffered
response. The `@1.0.0` `http-request`/`http-stream-start` are thin shims
reproducing prior behaviour exactly and delegating to the same backend.
- **Security — per-hop redirect re-validation on both paths.** Redirects
are followed MANUALLY by the host (reqwest never auto-follows), so every
hop re-runs scheme + the async per-capsule allow-list gate + the egress
airlock, strips `Authorization`/`Cookie` cross-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.
- **Deferred (honest stubs, not silent gaps):** `http-upload` streaming
request body (returns a typed not-implemented error), response
`trailers` (returns `none`), 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. 33 `http` tests (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.
- **Adversarial security review (on the diff):** confirmed every
redirect hop — buffered and streaming — re-validates through the async
gate before connecting; `max-response-bytes` is clamped to the host
ceiling (a caller cannot raise the buffered cap); `check_scheme` blocks
`file:`/`data:`/`ws:`; the release binary never reads the test-only SSRF
bypass.
- **One accepted behaviour refinement:** the `@1.0.0` shim returns
`too-many-redirects` on >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

- [x] Linked to an issue
- [x] CHANGELOG.md updated (entry under `[Unreleased]`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants