Skip to content

atunnel: strip X-Ate-Assignment-Stale from upstream actor responses - #2410

Open
Pancho Trujillo (panchoruy) wants to merge 1 commit into
agent-substrate:mainfrom
panchoruy:atunnel-strip-stale-header
Open

Pancho Trujillo (panchoruy) wants to merge 1 commit into
agent-substrate:mainfrom
panchoruy:atunnel-strip-stale-header

Conversation

@panchoruy

@panchoruy Pancho Trujillo (panchoruy) commented Oct 9, 2026 •

Copy link
Copy Markdown

Motivation

atunnel defines StaleAssignmentHeader = "X-Ate-Assignment-Stale" to distinguish an atunnel infrastructure-level routing rejection (HTTP 421 Misdirected Request, returned when an actor is no longer active on that worker) from an HTTP 421 returned by the guest actor application itself (internal/atunnel/ingress.go#L45-L47).

While newActorProxy already strips internal Substrate headers (ate-target-port, ate-target-actor) on the request leg into the sandbox, it lacked response sanitization on the return leg. As a result, an untrusted actor workload running inside the container could return X-Ate-Assignment-Stale: true and spoof an infrastructure routing rejection to downstream routers or clients.

Changes

  1. internal/atunnel/ingress.go: Added a ModifyResponse hook to newActorProxy() that removes X-Ate-Assignment-Stale from any response received from the actor upstream.
  2. internal/atunnel/ingress_test.go: Added unit test TestServeHTTPStripsStaleAssignmentHeaderFromActor asserting that even if the actor application returns X-Ate-Assignment-Stale: true, the header is stripped before the response reaches the client.

Testing

  • go test -race -count=1 ./internal/atunnel/...
  • ./hack/verify/boilerplate.sh
  • ./hack/verify/gofmt.sh
  • ./hack/verify/golangci-lint.sh

The PR title becomes the release note. Write it for users: what changed for them, not how the code changed.

AI was used to assist in the development of these changes.

  • Tests pass
  • Appropriate changes to documentation are included in the PR

// Prevent untrusted actor responses from spoofing routing rejections
// to trigger cache evictions and stream restarts in the ingress router.
ModifyResponse: func(resp *http.Response) error {
resp.Header.Del(StaleAssignmentHeader)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Follow up: is this the only header that we need to strip?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hm that's a good point. From my exploration it seems no other headers like this one are being set at this time, but I interpret this question as whether we should eagerly strip "any headers" of this nature in the future as well. It would seem to me like the right thing to do to prevent exposing internal implementation details to untrusted actors. Lmk if you'd like this PR to strip all headers prefixed with "X-ate" or "ate" etc, or do it in a follow-up!

atunnel uses the X-Ate-Assignment-Stale response header when returning
HTTP 421 (Misdirected Request) to distinguish an infrastructure-level
routing rejection (when an actor is not active on this worker) from a 421
returned by the actor application itself.

However, newActorProxy previously lacked response sanitization, allowing
an untrusted actor workload inside the sandbox to emit this header and
spoof an atunnel routing rejection to downstream routers or clients.

Add a ModifyResponse hook to newActorProxy that deletes the header from
actor responses, ensuring only atunnel's reject handler can emit it.
auto-merge was automatically disabled October 11, 2026 15:32

Head branch was pushed to by a user without write access

@panchoruy

Copy link
Copy Markdown
Author

Rebased to head and updated, since e2e tests failure seems unrelated and it passed in an otherwise PR against my own fork: panchoruy#1

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