mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
refactor: unify SSRF hostname/ip precheck and add policy regression
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
type LookupFn,
|
||||
resolvePinnedHostname,
|
||||
resolvePinnedHostnameWithPolicy,
|
||||
SsrFBlockedError,
|
||||
} from "./ssrf.js";
|
||||
|
||||
describe("ssrf pinning", () => {
|
||||
@@ -107,4 +108,34 @@ describe("ssrf pinning", () => {
|
||||
}),
|
||||
).rejects.toThrow(/allowlist/i);
|
||||
});
|
||||
|
||||
it("blocks ISATAP embedded private IPv4 before DNS lookup", async () => {
|
||||
const lookup = vi.fn(async () => [
|
||||
{ address: "93.184.216.34", family: 4 },
|
||||
]) as unknown as LookupFn;
|
||||
|
||||
await expect(
|
||||
resolvePinnedHostnameWithPolicy("2001:db8:1234::5efe:127.0.0.1", {
|
||||
lookupFn: lookup,
|
||||
}),
|
||||
).rejects.toThrow(SsrFBlockedError);
|
||||
expect(lookup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows ISATAP embedded private IPv4 when private network is explicitly enabled", async () => {
|
||||
const lookup = vi.fn(async () => [
|
||||
{ address: "2001:db8:1234::5efe:127.0.0.1", family: 6 },
|
||||
]) as unknown as LookupFn;
|
||||
|
||||
await expect(
|
||||
resolvePinnedHostnameWithPolicy("2001:db8:1234::5efe:127.0.0.1", {
|
||||
lookupFn: lookup,
|
||||
policy: { allowPrivateNetwork: true },
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
hostname: "2001:db8:1234::5efe:127.0.0.1",
|
||||
addresses: ["2001:db8:1234::5efe:127.0.0.1"],
|
||||
});
|
||||
expect(lookup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -316,6 +316,10 @@ export function isBlockedHostname(hostname: string): boolean {
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return isBlockedHostnameNormalized(normalized);
|
||||
}
|
||||
|
||||
function isBlockedHostnameNormalized(normalized: string): boolean {
|
||||
if (BLOCKED_HOSTNAMES.has(normalized)) {
|
||||
return true;
|
||||
}
|
||||
@@ -331,7 +335,7 @@ export function isBlockedHostnameOrIp(hostname: string): boolean {
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return isBlockedHostname(normalized) || isPrivateIpAddress(normalized);
|
||||
return isBlockedHostnameNormalized(normalized) || isPrivateIpAddress(normalized);
|
||||
}
|
||||
|
||||
export function createPinnedLookup(params: {
|
||||
@@ -415,14 +419,8 @@ export async function resolvePinnedHostnameWithPolicy(
|
||||
throw new SsrFBlockedError(`Blocked hostname (not in allowlist): ${hostname}`);
|
||||
}
|
||||
|
||||
if (!allowPrivateNetwork && !isExplicitAllowed) {
|
||||
if (isBlockedHostname(normalized)) {
|
||||
throw new SsrFBlockedError(`Blocked hostname: ${hostname}`);
|
||||
}
|
||||
|
||||
if (isPrivateIpAddress(normalized)) {
|
||||
throw new SsrFBlockedError("Blocked: private/internal IP address");
|
||||
}
|
||||
if (!allowPrivateNetwork && !isExplicitAllowed && isBlockedHostnameOrIp(normalized)) {
|
||||
throw new SsrFBlockedError("Blocked hostname or private/internal IP address");
|
||||
}
|
||||
|
||||
const lookupFn = params.lookupFn ?? dnsLookup;
|
||||
|
||||
Reference in New Issue
Block a user