fix(security): close SSRF guard bypasses in ssrfGuard.js (#3714)

Closes four SSRF guard bypasses reported in #3714:
- Block alternate IPv6 encodings (hex format, NAT64, IPv4-compatible, IPv4-mapped) by parsing to 16-bit groups
- Normalize trailing dots on hostnames to prevent FQDN bypasses
- Add assertPublicUrlResolved() with DNS resolution to block wildcard DNS domains resolving to private/metadata IPs
- Add fetchPublic() to safely handle and validate HTTP redirects
This commit is contained in:
openhands
2026-09-03 09:22:22 +07:00
committed by decolua
parent 1f190bd00b
commit b870b5d41b
5 changed files with 329 additions and 21 deletions
+4 -3
View File
@@ -13,7 +13,7 @@ import { HTTP_STATUS } from "open-sse/config/runtimeConfig.js";
import * as log from "../utils/logger.js";
import { updateProviderCredentials, checkAndRefreshToken } from "../services/tokenRefresh.js";
import { handleComboChat, getComboModelsFromData } from "open-sse/services/combo.js";
import { assertPublicUrl } from "@/shared/utils/ssrfGuard.js";
import { assertPublicUrlResolved } from "@/shared/utils/ssrfGuard.js";
/**
* Handle web fetch (URL extraction) request for the SSE/Next.js server.
@@ -79,9 +79,10 @@ export async function handleFetch(request) {
return errorResponse(HTTP_STATUS.BAD_REQUEST, "Invalid URL format");
}
// SSRF guard: reject internal/private/metadata targets
// SSRF guard: reject internal/private/metadata targets, including
// hostnames that merely resolve to one (DNS lookup, not just literal checks).
try {
assertPublicUrl(targetUrl);
await assertPublicUrlResolved(targetUrl);
} catch (err) {
log.warn("FETCH", "Blocked URL", { url: targetUrl });
return errorResponse(HTTP_STATUS.BAD_REQUEST, err.message);