Skip to content

Prevent url requests for internal addresses#788

Merged
kfranqueiro merged 3 commits into
mainfrom
kgf-ssrf
Jul 1, 2026
Merged

Prevent url requests for internal addresses#788
kfranqueiro merged 3 commits into
mainfrom
kgf-ssrf

Conversation

@kfranqueiro

@kfranqueiro kfranqueiro commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

This responds with 400 status to any request whose URL points to an internal/local IP.

  • Checks centrally in validateParams which covers all code paths (both GET and POST, all generators) before any generator is called
  • Covers IPv4, IPv6, and domain lookups; also rejects non-HTTP(S) URLs
  • If the initial URL is determined to be unicast, also performs HEAD request(s) to check for redirects to an internal host
  • Centralizes one regular expression which had previously existed in generators/bikeshed.ts

I've added a collection of test cases which go through the full code path to verify the 400 response from the server and the proper handling of all types of URLs (including IPv6 which need to be enclosed in square brackets). The one thing that wouldn't be straightforward to automate testing of is the maximum redirects handling, which I have verified manually for the time being. (We might be able to introduce nock to this repo to automate testing that case, but perhaps I should do that separately later.)

@deniak deniak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The PR looks good to me. Regarding the tests on the redirects, instead of relying on another nock, it seems possible to use globalThis to override the fetch behavior to return a redirect:

import assert from "assert";
import { afterEach, beforeEach, describe, it } from "node:test";

describe("redirect following", () => {
  const originalFetch = globalThis.fetch;

  afterEach(() => {
    globalThis.fetch = originalFetch;
  });

  function mockRedirectTo(location: string) {
    globalThis.fetch = async () =>
      new Response(null, { status: 307, headers: { location } });
  }

  it("blocks a redirect to a private IP", async () => {
    mockRedirectTo("https://192.168.1.1/");
    assert.equal(await isUnicastHttpUrl("https://example.com/"), false);
  });
  ...
});

Similarly, it should be possible to create an infinite redirect loop by redirecting to the same URL.

@kfranqueiro

Copy link
Copy Markdown
Contributor Author

Fair point, since the HEAD requests are all that would need to be mocked and those specifically use fetch, we can get away with only mocking that directly. I've added 2 redirect tests, one redirecting to an internal IP and one redirecting infinitely.

@kfranqueiro kfranqueiro merged commit d4c8e56 into main Jul 1, 2026
8 checks passed
@kfranqueiro kfranqueiro deleted the kgf-ssrf branch July 1, 2026 15:10
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.

3 participants