Skip to content

wasi-http: Filter forbidden headers #7538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

elliottt
Copy link
Member

@elliottt elliottt commented Nov 14, 2023

Make sure that forbidden headers don't show up in either incoming-request or outgoing-response values. One place that this PR doesn't currently address is incoming trailers. The future-trailers.get method returns a new fields resource every time it's called once it has resolved, and it's not clear where the best place to filter forbidden headers is in that case. I think the right fix for that would be to make future-trailers.get method return a result exactly once, like our other future-*.get methods, enabling us to apply the filter when we create the new owned fields resource.

@elliottt elliottt requested a review from a team as a code owner November 14, 2023 21:11
@elliottt elliottt requested review from alexcrichton and pchickey and removed request for a team November 14, 2023 21:11
Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

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

Should this be backported to Wasmtime 15 as well?

@elliottt elliottt added this pull request to the merge queue Nov 14, 2023
Merged via the queue into bytecodealliance:main with commit 3db4c91 Nov 14, 2023
@elliottt elliottt deleted the trevor/filter-out-forbidden-headrs branch November 14, 2023 22:51
elliottt added a commit to elliottt/wasmtime that referenced this pull request Nov 14, 2023
elliottt added a commit that referenced this pull request Nov 15, 2023
* wasi-http: Implement http-error-code, and centralize error conversions (#7534)
* Filter out forbidden headers on incoming request and response resources (#7538)
@sporkmonger
Copy link

sporkmonger commented Dec 21, 2023

I'm wondering if this is the really the right place for this logic, particularly on incoming requests (as opposed to outgoing requests). I'm using the incoming request type in the context of detections (rather than in request handlers) where it's preferred that these headers aren't stripped because they may carry information indicating malicious behavior by a client. If they're stripped in a constructor before they can be inspected, detections will be potentially working with missing crucial information. I want to be able to compose detection logic with request handlers, which is why I'm using the same type.

@dicej
Copy link
Contributor

dicej commented Aug 27, 2024

@elliottt Sorry for chiming in super late here, but @bacongobbler and I tripped over this code today and have some questions about it.

For context: we're currently working on porting an ASP.NET Core app to WASI. This app happens to use Dapr, which in turn uses gRPC, which sets a TE: trailers header on outgoing requests, which is currently rejected by is_forbidden_header by way of HostFields::from_list.

Our questions:

  • In your PR summary, you said "Make sure that forbidden headers don't show up in either incoming-request or outgoing-response values," but this code also affects outgoing requests as well since is_forbidden_header is used when constructing headers for any purpose. Was that intentional? I'm assuming so, but wanted to verify.
  • I can see that the WasiHttpView trait also has an is_forbidden_header function, presumably so embedders can override it, but AFAICT the WasiHttpView version is never used -- only the top-level function is used. Am I missing something? Or, if not, was that also intentional?
  • Where did the FORBIDDEN_HEADERS list come from? I found https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name and see some overlap, but it's not an exact match.

I have other questions about how to use gRPC via wasi-http, but they're probably off topic here, so I'll ask those elsewhere.

@elliottt
Copy link
Member Author

elliottt commented Aug 27, 2024

  • In your PR summary, you said "Make sure that forbidden headers don't show up in either incoming-request or outgoing-response values," but this code also affects outgoing requests as well since is_forbidden_header is used when constructing headers for any purpose. Was that intentional? I'm assuming so, but wanted to verify.

That was intentional. The list seemed pretty small, and the overlap between requests and responses seemed acceptable.

  • I can see that the WasiHttpView trait also has an is_forbidden_header function, presumably so embedders can override it, but AFAICT the WasiHttpView version is never used -- only the top-level function is used. Am I missing something? Or, if not, was that also intentional?

It's called in types_impl.rs, but called as is_forbidden_header(self, ...) rather than as a method of self.

if is_forbidden_header(self, &header) {

That function ultimately bottoms out in the view's function here:

FORBIDDEN_HEADERS.contains(name) || view.is_forbidden_header(name)

There's a test that ensures it's working by adding a custom forbidden header.

async fn wasi_http_proxy_tests() -> anyhow::Result<()> {
let req = hyper::Request::builder()
.header("custom-forbidden-header", "yes")
.uri("http://example.com:8080/test-path")
.method(http::Method::GET);

It grew slightly over time, and was informed by some folks that @lukewagner coordinated with in Fastly, he might remember more context about where the full list came from.

(edited to add the link to the actual call to view.is_forbidden_header)

@dicej
Copy link
Contributor

dicej commented Aug 27, 2024

Thanks, @elliottt! As I learn more about gRPC, I'm realizing that gRPC-on-wasi-http isn't possible given the current state of wasi-http, so we'll pursue the wasi-sockets route instead.

@lukewagner
Copy link
Contributor

It grew slightly over time, and was informed by some folks that @lukewagner coordinated with in Fastly, he might remember more context about where the full list came from.

FWIW, the list is in this doc, this section. Ideally I think this list would go into the wasi-http spec itself, but what's in the doc was the result of asking various HTTP folks (inside and outside Fastly).

@pchickey
Copy link
Contributor

It's unfortunate that wasi-http can't support gRPC as currently specified! Concretely, it seems to me that setting a header TE: trailers (directives list here https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/TE) and other values should be "ok" if the underlying http implementation is actually able to accept those (e.g. whatwg fetch cannot support TE: trailers, but hyper can). Should we refine the spec to allow this behavior?

@dicej
Copy link
Contributor

dicej commented Aug 27, 2024

We'll also need some way for the guest to require HTTP/2.

And yeah, I'd definitely be in favor of making wasi-http gRPC-compatible!

@lukewagner
Copy link
Contributor

Yes, agreed on the goal of making wasi-http gRPC-compatible; that has been a goal since the beginning (and the reason we have trailers in the first place).

The goal of putting TE in the definitely-forbidden list is so that the host is empowered to set this as a transport-/implementation-detail (similar to Transport-Encoding). Could we perhaps have some "expect-trailers" flag on outgoing-request that clients can set that tells the host impl to both set TE: trailers and also require H2 (or, reading RFC 9110 6.5.1, maybe it even works for HTTP/1.1?).

@dicej
Copy link
Contributor

dicej commented Aug 27, 2024

I think the trailers issue and the "require H2" issue are somewhat orthogonal. The latter is because gRPC is defined to use HTTP/2 specifically.

@pavelsavara
Copy link

This will be impossible or interesting in JCO because of browser fetch limitations.

@dicej
Copy link
Contributor

dicej commented Aug 28, 2024

This will be impossible or interesting in JCO because of browser fetch limitations.

That's true, but a wasi-aockets-based solution would also be difficult or impossible. The grpc-web protocol exists specifically to handle the browser case, so presumably an app intended to be maximally portable would be prepared to fall back to that.

@pchickey
Copy link
Contributor

pchickey commented Apr 1, 2025

from @sporkmonger:

I'm wondering if this is the really the right place for this logic, particularly on incoming requests (as opposed to outgoing requests). I'm using the incoming request type in the context of detections (rather than in request handlers) where it's preferred that these headers aren't stripped because they may carry information indicating malicious behavior by a client. If they're stripped in a constructor before they can be inspected, detections will be potentially working with missing crucial information. I want to be able to compose detection logic with request handlers, which is why I'm using the same type.

I just ran into this exact situation - I don't believe these headers should be stripped out of incoming requests because I have wasi-http guests who need to examine those headers - like the parent comment, to compose detection logic with request handlers.

rvolosatovs added a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 1, 2025
As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>
rvolosatovs added a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 1, 2025
As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>
rvolosatovs added a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 1, 2025
As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>
rvolosatovs added a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 2, 2025
As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>
rvolosatovs added a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 3, 2025
As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>
dicej pushed a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 3, 2025
As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>
rvolosatovs added a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 4, 2025
As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>
github-merge-queue bot pushed a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 4, 2025
* feat(p3): implement `wasi:http`

Signed-off-by: Roman Volosatovs <[email protected]>

* fix rebase breakage

Signed-off-by: Joel Dice <[email protected]>

* drop `Watch<StreamWriter<T>>::into_inner` in wasi-http

Dropping the `Watch` itself just decrements the reference count, which won't
actually drop the `StreamWriter` until the promise completes.  The result was
that some of the HTTP tests were hanging.  This was my mistake, sorry!

Signed-off-by: Joel Dice <[email protected]>

* http: do not strip incoming request headers

As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>

* Update tests with new wit-bindgen APIs

* feat(p3/http): handle `content-length`

Signed-off-by: Roman Volosatovs <[email protected]>

* chore: update to new API

Signed-off-by: Roman Volosatovs <[email protected]>

* fix more rebase damage; assert read buffers have non-zero capacity

This should make #100 more debuggable by turning it in to an assertion failure
in `OutgoingRequestBody::poll_frame`.  It seems to be doing a zero-length read
(presumably by accident), but I haven't traced it further than that, yet.

Signed-off-by: Joel Dice <[email protected]>

* fix: reserve buffer capacity after reads

Signed-off-by: Roman Volosatovs <[email protected]>

* feat(p3/http): implement serving

Signed-off-by: Roman Volosatovs <[email protected]>

* feat(p3/http): implement `wasmtime serve`

Signed-off-by: Roman Volosatovs <[email protected]>

* cli: enable CM async feature

Signed-off-by: Roman Volosatovs <[email protected]>

* feat(p3/http): link p3 http in `run`

Signed-off-by: Roman Volosatovs <[email protected]>

* fix(p3/http): always set "path and query"

Signed-off-by: Roman Volosatovs <[email protected]>

* fix: feature-gate CLI HTTP support

Signed-off-by: Roman Volosatovs <[email protected]>

* avoid setting a default path and query

Personally, I think defaulting to `/` if no path with query is set would
be the expected behavior, but wasip2 contained tests explicitly testing
against that behavior, so I disagree and commit

Signed-off-by: Roman Volosatovs <[email protected]>

---------

Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Joel Dice <[email protected]>
Co-authored-by: Joel Dice <[email protected]>
Co-authored-by: Alex Crichton <[email protected]>
github-merge-queue bot pushed a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 4, 2025
* feat(p3): implement `wasi:http`

Signed-off-by: Roman Volosatovs <[email protected]>

* fix rebase breakage

Signed-off-by: Joel Dice <[email protected]>

* drop `Watch<StreamWriter<T>>::into_inner` in wasi-http

Dropping the `Watch` itself just decrements the reference count, which won't
actually drop the `StreamWriter` until the promise completes.  The result was
that some of the HTTP tests were hanging.  This was my mistake, sorry!

Signed-off-by: Joel Dice <[email protected]>

* http: do not strip incoming request headers

As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>

* Update tests with new wit-bindgen APIs

* feat(p3/http): handle `content-length`

Signed-off-by: Roman Volosatovs <[email protected]>

* chore: update to new API

Signed-off-by: Roman Volosatovs <[email protected]>

* fix more rebase damage; assert read buffers have non-zero capacity

This should make #100 more debuggable by turning it in to an assertion failure
in `OutgoingRequestBody::poll_frame`.  It seems to be doing a zero-length read
(presumably by accident), but I haven't traced it further than that, yet.

Signed-off-by: Joel Dice <[email protected]>

* fix: reserve buffer capacity after reads

Signed-off-by: Roman Volosatovs <[email protected]>

* feat(p3/http): implement serving

Signed-off-by: Roman Volosatovs <[email protected]>

* feat(p3/http): implement `wasmtime serve`

Signed-off-by: Roman Volosatovs <[email protected]>

* cli: enable CM async feature

Signed-off-by: Roman Volosatovs <[email protected]>

* feat(p3/http): link p3 http in `run`

Signed-off-by: Roman Volosatovs <[email protected]>

* fix(p3/http): always set "path and query"

Signed-off-by: Roman Volosatovs <[email protected]>

* fix: feature-gate CLI HTTP support

Signed-off-by: Roman Volosatovs <[email protected]>

* avoid setting a default path and query

Personally, I think defaulting to `/` if no path with query is set would
be the expected behavior, but wasip2 contained tests explicitly testing
against that behavior, so I disagree and commit

Signed-off-by: Roman Volosatovs <[email protected]>

* fix tracing import

Signed-off-by: Roman Volosatovs <[email protected]>

---------

Signed-off-by: Roman Volosatovs <[email protected]>
Signed-off-by: Joel Dice <[email protected]>
Co-authored-by: Joel Dice <[email protected]>
Co-authored-by: Alex Crichton <[email protected]>
rvolosatovs added a commit to bytecodealliance/wasip3-prototyping that referenced this pull request Apr 7, 2025
As discussed with @pchickey

See also
bytecodealliance/wasmtime#7538 (comment) and following discussion

Signed-off-by: Roman Volosatovs <[email protected]>
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.

7 participants