Skip to content

feat(gateway): a keyed deploy's role key, forwarded headers, and triggers by route (FD-4683) - #443

Open
williamloo-scale wants to merge 1 commit into
wloo/website-notes-examplefrom
wloo/website-gateway-auth
Open

williamloo-scale wants to merge 1 commit into
wloo/website-notes-examplefrom
wloo/website-gateway-auth

Conversation

@williamloo-scale

@williamloo-scale williamloo-scale commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #429.

The gateway side of website/v1: roles that can't be spoofed, the address a site is reached at, and triggers matched by route.

  • Role key, opt-in per deploy. A deploy with keyed_roles (deploy_env's keyed_roles: true, or env deploy --keyed-roles) draws a secret role key, gives it to the gateway (AGENTENV_ROLE_KEY) and to each website server (WEBSITE_ROLE_KEY), and records it on the deployed env (DeployedEnv.role_key, sent by role_headers(role)). Its gateway believes a role other than default, on REST and MCP, only beside AgentEnv-Role-Key equal to the key. It strips a client's key and sends its own upstream only beside a verified role, so SDK sites believe it. deploy_agent, the episode proxy, post-turn hook /step calls and the generated MCP CLI send the key. Any other deploy draws none, and its gateway believes the role header alone, as before. A step asking for keyed_roles of an env whose provider doesn't take them is refused at save.
  • Forwarded headers. /svc and /website set X-Forwarded-Host, -Proto and -Prefix and drop the client's copies. The SDK takes the prefix as the root path, so base_url, request.url and redirects name the gateway's address.
  • Triggers by route. A website trigger's when may name op, a route of the site's card, and then matches only requests that reach it. Registration refuses an op the card doesn't name. triggers/v1 declares website_op, so an older gateway is refused.

Before merging

  • On a keyed_roles deploy, a caller that sends a non-default role without role_headers(role) is default; agentenvhub-sdk and the hub don't send the key yet, so their tasks shouldn't turn it on until they do. Unkeyed deploys, the default, are unaffected.
  • An agent deployed with a role receives the key, so the key protects against pages and browsers, not a hostile agent.
  • The key is stored in plaintext on the instance record, beside the control token.
  • The CLI's default role cli is default on a keyed deploy.

Test

Unit suite: 12,712 passed (one GitHub-clone test from main, which hangs on the local machine on main too, deselected).

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge, with the earlier non-blocking reserved-path finding still open.

Fix All in CursorFindings

  1. P2 Reserved paths reach site handlers ▶
Fix with agent prompt
### Issue 1
packages/agentenv-protocol/src/agentenv_protocol/website/environment.py:undefined-312
Adding the forwarded prefix to `scope["path"]` breaks `_unreserved`, which still checks that full path with `reserved_path`. For a site with `@api.post("/{rest:path}")`, a request to `/agentenv/unknown` with `X-Forwarded-Prefix: /website/shop` now runs the site's handler instead of returning 404. A write handler could change site data on a request that should be refused.

Check the site-relative path with `get_route_path(request.scope)` in `_unreserved`, as `_Fallback` now does.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds optional role keys, forwards the website’s public address, and lets website triggers match named routes.

  • Keyed deployments require a matching key before the gateway accepts a non-default role.
  • Website servers use the gateway’s public address in their URLs and redirects.
  • Website triggers can match the named route that handled a request.

Diagram

sequenceDiagram
    participant Caller
    participant Gateway
    participant Website
    participant Triggers
    Caller->>Gateway: Request with role and optional key
    Gateway->>Gateway: Check role key when enabled
    Gateway->>Website: Verified role and forwarded address
    Website-->>Gateway: Response
    Gateway->>Triggers: Match method, upstream path, and named route
    Triggers-->>Gateway: Complete any response barrier
    Gateway-->>Caller: Response
Loading

Reviews (12) · Last reviewed commit: "feat(gateway): a keyed deploy's role key..." · Reviewed by Greptile

@williamloo-scale williamloo-scale changed the title website: gateway role key, forwarded headers, triggers by route (FD-4683) feat(gateway): a keyed deploy's role key, forwarded headers, and triggers by route (FD-4683) Nov 3, 2026
@williamloo-scale
williamloo-scale marked this pull request as ready for review November 3, 2026 00:24
@williamloo-scale
williamloo-scale requested a review from a team as a code owner November 3, 2026 00:24
Comment thread src/agent_env/env/gateway/rest_proxy.py Resolved
Comment thread src/agent_env/env/gateway/gateway.py Resolved
Comment thread src/agent_env/env/gateway/gateway.py Resolved
Comment thread src/agent_env/env/gateway/rest_proxy.py Resolved
prefix = forwarded_prefix(_first(headers.get(b"x-forwarded-prefix")))
if prefix:
scope["root_path"] = unquote(prefix) + scope.get("root_path", "")
scope["path"] = unquote(prefix) + scope["path"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Reserved paths reach site handlers

Adding the forwarded prefix to scope["path"] breaks _unreserved, which still checks that full path with reserved_path. For a site with @api.post("/{rest:path}"), a request to /agentenv/unknown with X-Forwarded-Prefix: /website/shop now runs the site's handler instead of returning 404. A write handler could change site data on a request that should be refused.

Check the site-relative path with get_route_path(request.scope) in _unreserved, as _Fallback now does.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/agentenv-protocol/src/agentenv_protocol/website/environment.py
Line: 348

Comment:
**Reserved paths reach site handlers**

Adding the forwarded prefix to `scope["path"]` breaks `_unreserved`, which still checks that full path with `reserved_path`. For a site with `@api.post("/{rest:path}")`, a request to `/agentenv/unknown` with `X-Forwarded-Prefix: /website/shop` now runs the site's handler instead of returning 404. A write handler could change site data on a request that should be refused.

Check the site-relative path with `get_route_path(request.scope)` in `_unreserved`, as `_Fallback` now does.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

…gers by route (FD-4683)

This PR's change, rebased as one commit onto #429.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@williamloo-scale
williamloo-scale force-pushed the wloo/website-notes-example branch from 250afe3 to 30113cb Compare November 4, 2026 03:37
@williamloo-scale
williamloo-scale force-pushed the wloo/website-gateway-auth branch from 253eaec to 18b1b98 Compare November 4, 2026 03:37

This branch has not been deployed

No deployments
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.

1 participant