184 lines
4.8 KiB
TypeScript
184 lines
4.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { afterEach, describe, it } from "vitest";
|
|
import {
|
|
getFetchAllowedUrls,
|
|
isBlockedUrl,
|
|
setFetchAllowedUrls,
|
|
} from "../resources/extensions/search-the-web/url-utils.ts";
|
|
|
|
describe("isBlockedUrl — SSRF protection", () => {
|
|
it("blocks localhost", () => {
|
|
assert.equal(isBlockedUrl("http://localhost/admin"), true);
|
|
assert.equal(isBlockedUrl("http://localhost:8080/"), true);
|
|
});
|
|
|
|
it("blocks 127.0.0.0/8", () => {
|
|
assert.equal(isBlockedUrl("http://127.0.0.1/"), true);
|
|
assert.equal(isBlockedUrl("http://127.0.0.2:3000/path"), true);
|
|
});
|
|
|
|
it("blocks 10.0.0.0/8 (private)", () => {
|
|
assert.equal(isBlockedUrl("http://10.0.0.1/"), true);
|
|
assert.equal(isBlockedUrl("http://10.255.255.255/"), true);
|
|
});
|
|
|
|
it("blocks 172.16-31.x.x (private)", () => {
|
|
assert.equal(isBlockedUrl("http://172.16.0.1/"), true);
|
|
assert.equal(isBlockedUrl("http://172.31.255.255/"), true);
|
|
});
|
|
|
|
it("blocks 192.168.x.x (private)", () => {
|
|
assert.equal(isBlockedUrl("http://192.168.1.1/"), true);
|
|
assert.equal(isBlockedUrl("http://192.168.0.100:9200/"), true);
|
|
});
|
|
|
|
it("blocks 169.254.x.x (link-local / cloud metadata)", () => {
|
|
assert.equal(
|
|
isBlockedUrl("http://169.254.169.254/latest/meta-data/"),
|
|
true,
|
|
);
|
|
});
|
|
|
|
it("blocks cloud metadata hostnames", () => {
|
|
assert.equal(
|
|
isBlockedUrl("http://metadata.google.internal/computeMetadata/"),
|
|
true,
|
|
);
|
|
});
|
|
|
|
it("blocks non-http protocols", () => {
|
|
assert.equal(isBlockedUrl("file:///etc/passwd"), true);
|
|
assert.equal(isBlockedUrl("ftp://internal.server/data"), true);
|
|
});
|
|
|
|
it("blocks invalid URLs", () => {
|
|
assert.equal(isBlockedUrl("not-a-url"), true);
|
|
assert.equal(isBlockedUrl(""), true);
|
|
});
|
|
|
|
it("allows public URLs", () => {
|
|
assert.equal(isBlockedUrl("https://example.com"), false);
|
|
assert.equal(isBlockedUrl("https://api.github.com/repos"), false);
|
|
assert.equal(isBlockedUrl("http://docs.python.org/3/"), false);
|
|
});
|
|
|
|
it("allows public IPs", () => {
|
|
assert.equal(isBlockedUrl("http://8.8.8.8/"), false);
|
|
assert.equal(isBlockedUrl("https://1.1.1.1/"), false);
|
|
});
|
|
});
|
|
|
|
describe("REGRESSION #666: private URL blocked with no override", () => {
|
|
afterEach(() => {
|
|
setFetchAllowedUrls([]);
|
|
});
|
|
|
|
it("private IP is blocked by default, then unblocked by setFetchAllowedUrls", () => {
|
|
const internalUrl = "http://192.168.1.100/internal-docs/api-reference";
|
|
|
|
// Bug: private IP is blocked with no way to allowlist
|
|
assert.equal(
|
|
isBlockedUrl(internalUrl),
|
|
true,
|
|
"private IP is blocked by the hardcoded blocklist",
|
|
);
|
|
|
|
// Fix: override the allowlist to include this host
|
|
setFetchAllowedUrls(["192.168.1.100"]);
|
|
assert.equal(
|
|
isBlockedUrl(internalUrl),
|
|
false,
|
|
"private IP must not be blocked after override",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("setFetchAllowedUrls — user override", () => {
|
|
afterEach(() => {
|
|
setFetchAllowedUrls([]);
|
|
});
|
|
|
|
it("defaults to empty allowlist", () => {
|
|
assert.deepEqual(getFetchAllowedUrls(), []);
|
|
});
|
|
|
|
it("exempts an allowed hostname from blocking", () => {
|
|
assert.equal(
|
|
isBlockedUrl("http://192.168.1.100/docs"),
|
|
true,
|
|
"blocked by default",
|
|
);
|
|
setFetchAllowedUrls(["192.168.1.100"]);
|
|
assert.equal(
|
|
isBlockedUrl("http://192.168.1.100/docs"),
|
|
false,
|
|
"allowed after override",
|
|
);
|
|
});
|
|
|
|
it("exempts localhost when explicitly allowed", () => {
|
|
assert.equal(
|
|
isBlockedUrl("http://localhost:3000/api"),
|
|
true,
|
|
"blocked by default",
|
|
);
|
|
setFetchAllowedUrls(["localhost"]);
|
|
assert.equal(
|
|
isBlockedUrl("http://localhost:3000/api"),
|
|
false,
|
|
"allowed after override",
|
|
);
|
|
});
|
|
|
|
it("exempts cloud metadata hostname when allowed", () => {
|
|
assert.equal(
|
|
isBlockedUrl("http://metadata.google.internal/computeMetadata/"),
|
|
true,
|
|
"blocked by default",
|
|
);
|
|
setFetchAllowedUrls(["metadata.google.internal"]);
|
|
assert.equal(
|
|
isBlockedUrl("http://metadata.google.internal/computeMetadata/"),
|
|
false,
|
|
"allowed after override",
|
|
);
|
|
});
|
|
|
|
it("does not affect URLs not in the allowlist", () => {
|
|
setFetchAllowedUrls(["192.168.1.100"]);
|
|
assert.equal(
|
|
isBlockedUrl("http://192.168.1.200/secret"),
|
|
true,
|
|
"other private IPs still blocked",
|
|
);
|
|
assert.equal(
|
|
isBlockedUrl("http://localhost/admin"),
|
|
true,
|
|
"localhost still blocked",
|
|
);
|
|
});
|
|
|
|
it("still allows public URLs without configuration", () => {
|
|
setFetchAllowedUrls(["192.168.1.100"]);
|
|
assert.equal(isBlockedUrl("https://example.com"), false);
|
|
});
|
|
|
|
it("still blocks non-HTTP protocols even with allowlist", () => {
|
|
setFetchAllowedUrls(["localhost"]);
|
|
assert.equal(
|
|
isBlockedUrl("file:///etc/passwd"),
|
|
true,
|
|
"file:// still blocked",
|
|
);
|
|
assert.equal(
|
|
isBlockedUrl("ftp://localhost/data"),
|
|
true,
|
|
"ftp:// still blocked",
|
|
);
|
|
});
|
|
|
|
it("is case-insensitive for hostnames", () => {
|
|
setFetchAllowedUrls(["MyHost.Internal"]);
|
|
assert.equal(isBlockedUrl("http://myhost.internal/api"), false);
|
|
});
|
|
});
|