fix(ssrf): document bare-wildcard deny-all behavior and restore config in test

This commit is contained in:
Davanum Srinivas 2026-03-19 08:41:30 -04:00
parent 56a533ee40
commit 3c9e319047
No known key found for this signature in database
GPG Key ID: 6DEA177048756885
3 changed files with 37 additions and 0 deletions

View File

@ -636,6 +636,11 @@ describe("OpenResponses HTTP API (e2e)", () => {
await allowlistServer.close({ reason: "responses allowlist hardening test done" });
}
const configPath = process.env.OPENCLAW_CONFIG_PATH;
const previousConfig = configPath
? await fs.readFile(configPath, "utf-8").catch(() => undefined)
: undefined;
const denyAllConfig = {
gateway: {
http: {
@ -685,6 +690,13 @@ describe("OpenResponses HTTP API (e2e)", () => {
expect(agentCommand).not.toHaveBeenCalled();
} finally {
await denyAllServer.close({ reason: "responses empty allowlist hardening test done" });
if (configPath) {
if (previousConfig === undefined) {
await fs.rm(configPath, { force: true });
} else {
await fs.writeFile(configPath, previousConfig, "utf-8");
}
}
}
const capConfig = buildResponsesUrlPolicyConfig(0);

View File

@ -122,6 +122,27 @@ describe("ssrf pinning", () => {
expect(lookup).not.toHaveBeenCalled();
});
it("blocks all hostnames when hostnameAllowlist contains only bare wildcards", async () => {
const lookup = vi.fn(async () => [
{ address: "93.184.216.34", family: 4 },
]) as unknown as LookupFn;
await expect(
resolvePinnedHostnameWithPolicy("example.com", {
lookupFn: lookup,
policy: { hostnameAllowlist: ["*"] },
}),
).rejects.toThrow(/allowlist/i);
expect(lookup).not.toHaveBeenCalled();
await expect(
resolvePinnedHostnameWithPolicy("example.com", {
lookupFn: lookup,
policy: { hostnameAllowlist: ["*."] },
}),
).rejects.toThrow(/allowlist/i);
});
it("allows all hostnames when hostnameAllowlist is undefined (not configured)", async () => {
const lookup = vi.fn(async () => [
{ address: "93.184.216.34", family: 4 },

View File

@ -51,6 +51,7 @@ function normalizeHostnameSet(values?: string[]): Set<string> {
}
// Returns `null` when not configured (allow all), `[]` when explicit empty (deny all).
// Non-empty input where every entry is invalid (e.g. ["*"]) also returns [] (fail closed).
function normalizeHostnameAllowlist(values?: string[]): string[] | null {
if (values === undefined || values === null) {
return null;
@ -58,6 +59,9 @@ function normalizeHostnameAllowlist(values?: string[]): string[] | null {
if (values.length === 0) {
return [];
}
// Bare "*" and "*." are not valid hostname patterns (they would only match a literal
// "*" hostname in isHostnameAllowedByPattern). Strip them so operators who mistakenly
// configure ["*"] get a clear deny-all rather than a silent pass-through.
return Array.from(
new Set(
values