web security audit fixes
This commit is contained in:
@@ -1,73 +1,112 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import {
|
||||
validateCorsOrigin,
|
||||
parseCorsAllowlist,
|
||||
} from "~/server/lib/cors-validation";
|
||||
|
||||
/**
|
||||
* Mirrors the isValidCorsOrigin function from middleware.ts
|
||||
*/
|
||||
function isValidCorsOrigin(origin: string): boolean {
|
||||
if (!origin || !origin.trim()) return false;
|
||||
if (origin === "*") return false;
|
||||
|
||||
try {
|
||||
const parsed = new URL(origin);
|
||||
if (!parsed.protocol.match(/^https?:$/)) return false;
|
||||
if (!parsed.hostname) return false;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
describe("isValidCorsOrigin", () => {
|
||||
describe("validateCorsOrigin", () => {
|
||||
describe("accepted origins", () => {
|
||||
it("accepts valid HTTPS origins", () => {
|
||||
expect(isValidCorsOrigin("https://app.kordant.com")).toBe(true);
|
||||
expect(isValidCorsOrigin("https://admin.kordant.com")).toBe(true);
|
||||
expect(isValidCorsOrigin("https://localhost:3000")).toBe(true);
|
||||
expect(validateCorsOrigin("https://app.kordant.com")).toBe(true);
|
||||
expect(validateCorsOrigin("https://admin.kordant.com")).toBe(true);
|
||||
expect(validateCorsOrigin("https://localhost:3000")).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts valid HTTP origins", () => {
|
||||
expect(isValidCorsOrigin("http://localhost:3000")).toBe(true);
|
||||
expect(isValidCorsOrigin("http://localhost:3001")).toBe(true);
|
||||
expect(isValidCorsOrigin("http://127.0.0.1:8080")).toBe(true);
|
||||
expect(validateCorsOrigin("http://localhost:3000")).toBe(true);
|
||||
expect(validateCorsOrigin("http://localhost:3001")).toBe(true);
|
||||
expect(validateCorsOrigin("http://127.0.0.1:8080")).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts origins with ports", () => {
|
||||
expect(isValidCorsOrigin("https://app.kordant.com:8443")).toBe(true);
|
||||
expect(isValidCorsOrigin("http://localhost:5173")).toBe(true);
|
||||
expect(validateCorsOrigin("https://app.kordant.com:8443")).toBe(true);
|
||||
expect(validateCorsOrigin("http://localhost:5173")).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts origins with paths", () => {
|
||||
expect(isValidCorsOrigin("https://app.kordant.com/api")).toBe(true);
|
||||
expect(validateCorsOrigin("https://app.kordant.com/api")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("rejected origins", () => {
|
||||
it("rejects wildcard", () => {
|
||||
expect(isValidCorsOrigin("*")).toBe(false);
|
||||
expect(validateCorsOrigin("*")).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects missing scheme", () => {
|
||||
expect(isValidCorsOrigin("evil.com")).toBe(false);
|
||||
expect(isValidCorsOrigin("localhost")).toBe(false);
|
||||
expect(isValidCorsOrigin("app.kordant.com")).toBe(false);
|
||||
expect(validateCorsOrigin("evil.com")).toBe(false);
|
||||
expect(validateCorsOrigin("localhost")).toBe(false);
|
||||
expect(validateCorsOrigin("app.kordant.com")).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects non-HTTP schemes", () => {
|
||||
expect(isValidCorsOrigin("ftp://evil.com")).toBe(false);
|
||||
expect(isValidCorsOrigin("file:///etc/passwd")).toBe(false);
|
||||
expect(isValidCorsOrigin("javascript:alert(1)")).toBe(false);
|
||||
expect(isValidCorsOrigin("data:text/html,test")).toBe(false);
|
||||
expect(validateCorsOrigin("ftp://evil.com")).toBe(false);
|
||||
expect(validateCorsOrigin("file:///etc/passwd")).toBe(false);
|
||||
expect(validateCorsOrigin("javascript:alert(1)")).toBe(false);
|
||||
expect(validateCorsOrigin("data:text/html,test")).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects empty and whitespace strings", () => {
|
||||
expect(isValidCorsOrigin("")).toBe(false);
|
||||
expect(isValidCorsOrigin(" ")).toBe(false);
|
||||
expect(isValidCorsOrigin("\t")).toBe(false);
|
||||
expect(validateCorsOrigin("")).toBe(false);
|
||||
expect(validateCorsOrigin(" ")).toBe(false);
|
||||
expect(validateCorsOrigin("\t")).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects malformed URLs", () => {
|
||||
expect(isValidCorsOrigin("not a url")).toBe(false);
|
||||
expect(isValidCorsOrigin("://missing-protocol")).toBe(false);
|
||||
expect(validateCorsOrigin("not a url")).toBe(false);
|
||||
expect(validateCorsOrigin("://missing-protocol")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseCorsAllowlist", () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns empty array for undefined/null/empty input", () => {
|
||||
expect(parseCorsAllowlist(undefined)).toEqual([]);
|
||||
expect(parseCorsAllowlist(null)).toEqual([]);
|
||||
expect(parseCorsAllowlist("")).toEqual([]);
|
||||
expect(parseCorsAllowlist(" ")).toEqual([]);
|
||||
});
|
||||
|
||||
it("parses and validates a comma-separated list of origins", () => {
|
||||
const result = parseCorsAllowlist(
|
||||
"https://app.kordant.com,https://admin.kordant.com",
|
||||
);
|
||||
expect(result).toEqual([
|
||||
"https://app.kordant.com",
|
||||
"https://admin.kordant.com",
|
||||
]);
|
||||
});
|
||||
|
||||
it("filters out invalid origins and warns", () => {
|
||||
const result = parseCorsAllowlist(
|
||||
"https://app.kordant.com,evil.com,*,ftp://bad.com",
|
||||
);
|
||||
expect(result).toEqual(["https://app.kordant.com"]);
|
||||
expect(console.warn).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("rejects http://localhost:9999 when not in the allowlist", () => {
|
||||
// This origin is not in the configured list
|
||||
const result = parseCorsAllowlist("https://app.kordant.com");
|
||||
expect(result).not.toContain("http://localhost:9999");
|
||||
expect(result).toEqual(["https://app.kordant.com"]);
|
||||
});
|
||||
|
||||
it("handles whitespace around commas", () => {
|
||||
const result = parseCorsAllowlist(
|
||||
" https://app.kordant.com , https://admin.kordant.com ",
|
||||
);
|
||||
expect(result).toEqual([
|
||||
"https://app.kordant.com",
|
||||
"https://admin.kordant.com",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user