(value: string)
| 509 | } |
| 510 | |
| 511 | private normalizeDomain(value: string): string { |
| 512 | let normalized = value.trim().toLowerCase(); |
| 513 | |
| 514 | // Strip scheme if present. |
| 515 | if (normalized.startsWith("https://")) { |
| 516 | normalized = normalized.slice("https://".length); |
| 517 | } else if (normalized.startsWith("http://")) { |
| 518 | normalized = normalized.slice("http://".length); |
| 519 | } |
| 520 | |
| 521 | // Remove trailing slashes. |
| 522 | while (normalized.endsWith("/")) { |
| 523 | normalized = normalized.slice(0, -1); |
| 524 | } |
| 525 | |
| 526 | // Basic SSRF hardening: disallow obvious local / non-public hosts and raw IPs. |
| 527 | if ( |
| 528 | normalized === "" || |
| 529 | normalized === "localhost" || |
| 530 | normalized.endsWith(".localhost") || |
| 531 | normalized.endsWith(".local") |
| 532 | ) { |
| 533 | return ""; |
| 534 | } |
| 535 | |
| 536 | // Reject IPv4 addresses. |
| 537 | const ipv4Pattern = /^(?:\d{1,3}\.){3}\d{1,3}$/; |
| 538 | if (ipv4Pattern.test(normalized)) { |
| 539 | return ""; |
| 540 | } |
| 541 | |
| 542 | // Reject bracketed IPv6 literals. |
| 543 | if (normalized.startsWith("[") && normalized.endsWith("]")) { |
| 544 | return ""; |
| 545 | } |
| 546 | |
| 547 | return normalized; |
| 548 | } |
| 549 | |
| 550 | private normalizeUrl(value: string): string { |
| 551 | let normalized = value; |
no test coverage detected