spb/tendril Public
Tendril — web ingestion platform (scrape/crawl/map/search) on macOS Apple Silicon: WebKit fidelity, authenticated pages, deterministic testable extraction. A self-hosted Firecrawl alternative.
JavaScript 82.6%
TypeScript 11.8%
HTML 5.3%
1// author: simon-pierre boucher <contact@spboucher.ai>2import { describe, expect, it } from "vitest";3import { validateEgress, type Resolver } from "./ssrf.js";45const publicResolver: Resolver = async () => ["93.184.216.34"];6const privateResolver: Resolver = async () => ["10.0.0.5"];78describe("validateEgress", () => {9 it("accepts a public https URL on an allowed port", async () => {10 const r = await validateEgress("https://example.com/x", publicResolver);11 expect(r.ok).toBe(true);12 if (r.ok) {13 expect(r.value.port).toBe(443);14 expect(r.value.addresses).toEqual(["93.184.216.34"]);15 }16 });1718 it("rejects non-http schemes", async () => {19 const r = await validateEgress("ftp://example.com", publicResolver);20 expect(r.ok).toBe(false);21 if (!r.ok) expect(r.error.code).toBe("ERR_INVALID_URL");22 });2324 it("rejects disallowed ports before resolving", async () => {25 const r = await validateEgress("http://example.com:22/", publicResolver);26 expect(r.ok).toBe(false);27 if (!r.ok) expect(r.error.code).toBe("ERR_SSRF_BLOCKED");28 });2930 it("rejects a literal private host without DNS", async () => {31 const r = await validateEgress("http://127.0.0.1/", publicResolver);32 expect(r.ok).toBe(false);33 if (!r.ok) expect(r.error.code).toBe("ERR_SSRF_BLOCKED");34 });3536 it("rejects when DNS resolves to a private address (rebinding defense)", async () => {37 const r = await validateEgress("https://evil.example/", privateResolver);38 expect(r.ok).toBe(false);39 if (!r.ok) expect(r.error.code).toBe("ERR_SSRF_BLOCKED");40 });4142 it("rejects if any resolved address is private", async () => {43 const mixed: Resolver = async () => ["93.184.216.34", "10.0.0.5"];44 const r = await validateEgress("https://example.com/", mixed);45 expect(r.ok).toBe(false);46 });4748 it("allows port 8080 and 8443", async () => {49 for (const p of [8080, 8443]) {50 const r = await validateEgress(`http://example.com:${p}/`, publicResolver);51 expect(r.ok).toBe(true);52 }53 });54});55