SPB Git

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%
2.0 KB · 55 lines typescript
Raw Blame History
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