[P1 security] Agent web fetch can reach private networks through DNS or redirects #133

Open
opened 2026-07-28 23:01:29 +00:00 by lost-rob0t · 0 comments
lost-rob0t commented 2026-07-28 23:01:29 +00:00 (Migrated from github.com)

Finding

src/lib/agent-web.js blocks literal private hostnames/IP addresses before calling fetch(), but that does not enforce a network boundary.

const url = publicHttpUrl(value);
const response = await fetch(url, {
  redirect: "follow"
});
const finalUrl = publicHttpUrl(response.url || url.href);

The browser performs DNS resolution and follows redirects before response.url is validated.

A URL that looks public can therefore:

  1. resolve to a loopback/RFC1918/link-local address through DNS rebinding; or
  2. redirect to a private address, which is contacted before the final URL check runs.

CORS may prevent reading some responses, but it does not reliably prevent the request itself or GET side effects. Browser Private Network Access behavior is not a portable security boundary and does not cover every target/browser/deployment state.

Because agents can invoke these tools based on model output, untrusted document/web content can potentially induce local-network requests.

Required fix

A browser-only arbitrary URL fetcher cannot safely prove the resolved destination. Route external fetch/scrape through a trusted gateway that validates DNS and every redirect hop server-side.

export async function fetchUrlContent(value, options = {}) {
  const requested = normalizePublicHttpUrl(value);
  const response = await fetch(`${gatewayBase}/api/v1/web/fetch`, {
    method: "POST",
    credentials: "omit",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${gatewayToken}`
    },
    body: JSON.stringify({
      url: requested.href,
      maxBytes: boundedMaxBytes(options.maxBytes)
    }),
    signal: options.signal
  });
  return readBoundedGatewayResponse(response);
}

Gateway requirements:

- Resolve A and AAAA records before connecting.
- Reject loopback, private, link-local, multicast, unspecified, and reserved ranges.
- Pin the validated IP for the connection to prevent DNS rebinding.
- Disable automatic redirects; validate and re-resolve every Location hop.
- Limit redirect count, response bytes, decompressed bytes, duration, and content types.
- Strip credentials, cookies, ambient authorization, and proxy headers.
- Block cloud metadata destinations and alternate numeric IP representations.
- Log the requested URL, resolved IP, redirects, and denial reason.

Until that gateway exists, disable agent-driven arbitrary fetch/scrape by default. A safer temporary browser mode is an explicit user-open workflow rather than background fetching.

Do not treat redirect: "manual" in browser JavaScript as a complete fix: cross-origin redirect responses can be opaque, and DNS resolution still cannot be verified from the page.

Acceptance criteria

  • The browser never directly fetches an agent-supplied arbitrary URL.
  • Every resolved address and redirect hop is validated and pinned by a trusted gateway.
  • Requests omit ambient browser credentials.
  • Tests cover IPv4/IPv6 private ranges, DNS rebinding, public-to-private redirects, metadata IPs, encoded/numeric IP forms, redirect loops, and decompression bombs.
  • Web tools are disabled or clearly user-gated when no safe gateway is configured.
## Finding `src/lib/agent-web.js` blocks literal private hostnames/IP addresses before calling `fetch()`, but that does not enforce a network boundary. ```js const url = publicHttpUrl(value); const response = await fetch(url, { redirect: "follow" }); const finalUrl = publicHttpUrl(response.url || url.href); ``` The browser performs DNS resolution and follows redirects before `response.url` is validated. A URL that looks public can therefore: 1. resolve to a loopback/RFC1918/link-local address through DNS rebinding; or 2. redirect to a private address, which is contacted before the final URL check runs. CORS may prevent reading some responses, but it does not reliably prevent the request itself or GET side effects. Browser Private Network Access behavior is not a portable security boundary and does not cover every target/browser/deployment state. Because agents can invoke these tools based on model output, untrusted document/web content can potentially induce local-network requests. ## Required fix A browser-only arbitrary URL fetcher cannot safely prove the resolved destination. Route external fetch/scrape through a trusted gateway that validates DNS and every redirect hop server-side. ```js export async function fetchUrlContent(value, options = {}) { const requested = normalizePublicHttpUrl(value); const response = await fetch(`${gatewayBase}/api/v1/web/fetch`, { method: "POST", credentials: "omit", headers: { "Content-Type": "application/json", Authorization: `Bearer ${gatewayToken}` }, body: JSON.stringify({ url: requested.href, maxBytes: boundedMaxBytes(options.maxBytes) }), signal: options.signal }); return readBoundedGatewayResponse(response); } ``` Gateway requirements: ```text - Resolve A and AAAA records before connecting. - Reject loopback, private, link-local, multicast, unspecified, and reserved ranges. - Pin the validated IP for the connection to prevent DNS rebinding. - Disable automatic redirects; validate and re-resolve every Location hop. - Limit redirect count, response bytes, decompressed bytes, duration, and content types. - Strip credentials, cookies, ambient authorization, and proxy headers. - Block cloud metadata destinations and alternate numeric IP representations. - Log the requested URL, resolved IP, redirects, and denial reason. ``` Until that gateway exists, disable agent-driven arbitrary fetch/scrape by default. A safer temporary browser mode is an explicit user-open workflow rather than background fetching. Do not treat `redirect: "manual"` in browser JavaScript as a complete fix: cross-origin redirect responses can be opaque, and DNS resolution still cannot be verified from the page. ## Acceptance criteria - The browser never directly fetches an agent-supplied arbitrary URL. - Every resolved address and redirect hop is validated and pinned by a trusted gateway. - Requests omit ambient browser credentials. - Tests cover IPv4/IPv6 private ranges, DNS rebinding, public-to-private redirects, metadata IPs, encoded/numeric IP forms, redirect loops, and decompression bombs. - Web tools are disabled or clearly user-gated when no safe gateway is configured.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nsaspy/quasar-ui#133
No description provided.