|
| 1 | +import { describe, expect, it } from "vitest"; |
| 2 | +import { |
| 3 | + isHttpOrHttpsUrl, |
| 4 | + isSafeExternalUrl, |
| 5 | + isSecureTokenTransport, |
| 6 | + tokenTransportError, |
| 7 | +} from "@/lib/security"; |
| 8 | + |
| 9 | +describe("security URL helpers", () => { |
| 10 | + it("only allows http and https URLs for external OS opens", () => { |
| 11 | + expect(isSafeExternalUrl("https://example.com/webhook")).toBe(true); |
| 12 | + expect(isSafeExternalUrl("http://localhost:3000/webhook")).toBe(true); |
| 13 | + expect(isSafeExternalUrl("file:///etc/passwd")).toBe(false); |
| 14 | + expect(isSafeExternalUrl("javascript:alert(1)")).toBe(false); |
| 15 | + expect(isSafeExternalUrl("openconcho://settings")).toBe(false); |
| 16 | + }); |
| 17 | + |
| 18 | + it("only accepts http and https webhook endpoints", () => { |
| 19 | + expect(isHttpOrHttpsUrl("https://hooks.example.com/a")).toBe(true); |
| 20 | + expect(isHttpOrHttpsUrl("http://hooks.example.com/a")).toBe(true); |
| 21 | + expect(isHttpOrHttpsUrl("ftp://hooks.example.com/a")).toBe(false); |
| 22 | + expect(isHttpOrHttpsUrl("notaurl")).toBe(false); |
| 23 | + }); |
| 24 | + |
| 25 | + it("requires HTTPS before sending tokens to non-loopback hosts", () => { |
| 26 | + expect(isSecureTokenTransport("https://honcho.example.com")).toBe(true); |
| 27 | + expect(isSecureTokenTransport("http://localhost:8000")).toBe(true); |
| 28 | + expect(isSecureTokenTransport("http://127.0.0.1:8000")).toBe(true); |
| 29 | + expect(isSecureTokenTransport("http://192.168.1.50:8000")).toBe(false); |
| 30 | + expect(isSecureTokenTransport("http://100.67.206.76:8000")).toBe(false); |
| 31 | + }); |
| 32 | + |
| 33 | + it("returns a user-facing error for insecure token transport", () => { |
| 34 | + expect(tokenTransportError("http://100.67.206.76:8000")).toMatch(/HTTPS/); |
| 35 | + expect(tokenTransportError("https://honcho.example.com")).toBeNull(); |
| 36 | + }); |
| 37 | +}); |
0 commit comments