87 lines
3.1 KiB
TypeScript
87 lines
3.1 KiB
TypeScript
import {
|
|
afterAll,
|
|
beforeAll,
|
|
createHarness,
|
|
describe,
|
|
expect,
|
|
test,
|
|
type Harness,
|
|
} from "@wrnexus/test";
|
|
|
|
describe("real application security abuse checks", () => {
|
|
let app: Harness;
|
|
beforeAll(async () => {
|
|
app = await createHarness(import.meta.dir + "/..");
|
|
});
|
|
afterAll(() => app?.close());
|
|
|
|
test("sets browser hardening headers on rendered pages", async () => {
|
|
const response = await app.fetch("/");
|
|
expect(response.headers.get("content-security-policy")).toContain("default-src");
|
|
expect(response.headers.get("x-content-type-options")).toBe("nosniff");
|
|
expect(response.headers.get("x-frame-options")).toBeTruthy();
|
|
expect(response.headers.get("referrer-policy")).toBeTruthy();
|
|
});
|
|
|
|
test("does not reflect script payloads into HTML", async () => {
|
|
const payload = `<script>globalThis.__attacked=true</script>`;
|
|
const response = await app.fetch(`/?search=${encodeURIComponent(payload)}`);
|
|
expect(response.status).toBe(200);
|
|
expect(await response.text()).not.toContain(payload);
|
|
});
|
|
|
|
test("rejects traversal attempts without exposing source files", async () => {
|
|
for (const path of ["/../../package.json", "/%2e%2e/%2e%2e/package.json", "/..%5c..%5c.env"]) {
|
|
const response = await app.fetch(path);
|
|
expect([400, 404]).toContain(response.status);
|
|
const body = await response.text();
|
|
expect(body).not.toContain("DATABASE_URL");
|
|
expect(body).not.toContain('"workspaces"');
|
|
}
|
|
});
|
|
|
|
test("does not grant CORS credentials to an untrusted origin", async () => {
|
|
const response = await app.fetch("/api/hello", {
|
|
headers: { origin: "https://evil.example" },
|
|
});
|
|
expect(response.headers.get("access-control-allow-origin")).toBeNull();
|
|
expect(response.headers.get("access-control-allow-credentials")).toBeNull();
|
|
});
|
|
|
|
test("requires CSRF for login and avoids credential oracle details", async () => {
|
|
const response = await app.fetch("/api/login", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ email: "victim@example.com", password: "wrong-password" }),
|
|
});
|
|
expect(response.status).toBe(403);
|
|
expect(await response.text()).not.toContain("passwordHash");
|
|
});
|
|
|
|
test("handles malformed JSON without a stack trace", async () => {
|
|
const response = await app.fetch("/api/echo", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: "{broken",
|
|
});
|
|
expect(response.status).toBe(400);
|
|
const body = await response.text();
|
|
expect(body).not.toContain(" at ");
|
|
expect(body).not.toContain("node_modules");
|
|
});
|
|
|
|
test("refuses actual request bodies over the configured limit", async () => {
|
|
try {
|
|
const response = await app.fetch("/api/echo", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: `"${"x".repeat(10 * 1024 * 1024)}"`,
|
|
});
|
|
expect(response.status).toBe(413);
|
|
} catch (error) {
|
|
// Bun rejects the oversized socket before application dispatch on some versions.
|
|
expect(String(error)).toMatch(/ECONNRESET|socket connection was closed/i);
|
|
}
|
|
});
|
|
});
|