Skip to content

An agent endpoint check is bypassed by a redirect, at registration and on every run #36

Description

@beardthelion

checkAgentEndpoint decides whether this deployment is willing to dial an address. Both places that dial one then hand the URL to a fetch that follows redirects, so the address that was checked and the address that is reached are the same address only while nobody redirects.

A registrable agent at a public URL answering 307 Location: http://169.254.169.254/latest/meta-data/ puts the server on its own cloud metadata endpoint, which checkAgentEndpoint refuses under every configuration when asked directly.

Verified at main 06a1a84. A local server returning a 307 to a second server got the second server hit, and testAgentConnection returned ok: true for the redirected run. Pointing the same redirect at 169.254.169.254 came back 405, which is what this host's own metadata service answers a POST with (curl -X POST http://169.254.169.254/latest/meta-data/ returns 405 and GET / returns 404 here), so the request reached it.

Two call sites, and the second is the more serious:

  • server/src/agents/connection-test.ts:100 runs once, when somebody registers an endpoint.
  • server/src/copilot.ts:239 builds the HttpAgent every run dials. It gets the stored URL and either the runtime's own fetch or the stall-guard wrapper, neither of which constrains redirects, and it carries whatever auth header the registration supplied. An endpoint that passed review can start redirecting afterwards, and nothing looks again.

A PR follows. It applies the check to each hop rather than only the first, capped at three. Redirects are followed rather than refused, since a deployment putting its agent behind one has done nothing wrong and http to https is ordinary; each destination has to pass the same check, so following one can only reach somewhere registering it directly would have reached.

Worth stating plainly, since the fix does not close it: the check is by hostname, so a name that passes and then resolves to a private address between the check and the connection still gets through. Refusing redirects would not close that either.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions