Testing Input Handling
Testing that attacks fail
The Web Security Fundamentals course built defenses against SQL injection, XSS, path traversal, and mass assignment. Tests verify these defenses survive code changes.
The approach: send known attack payloads, verify the response is safe.
SQL injection tests
describe("SQL injection prevention", () => {
it("search does not return all notes with OR injection", async () => {
const cookie = await loginAs("[email protected]");
const res = await authenticatedRequest(
"/orgs/org-acme/notes/search?q=%27%20OR%20%271%27%3D%271",
cookie,
);
const notes = await res.json();
// Should NOT return notes from other orgs
const foreignNotes = notes.filter((n: any) => n.org_id !== "org-acme");
assert.strictEqual(foreignNotes.length, 0, "Injection must not leak cross-org data");
});
it("UNION SELECT does not extract schema", async () => {
const cookie = await loginAs("[email protected]");
const res = await authenticatedRequest(
"/orgs/org-acme/notes/search?q=%27%20UNION%20SELECT%20sql%2C%27%27%2C%27%27%2C%27%27%2C%27%27%2C%27%27%20FROM%20sqlite_master%20--",
cookie,
);
const notes = await res.json();
// Should not contain CREATE TABLE statements
const leaked = notes.some(
(n: any) => typeof n.title === "string" && n.title.includes("CREATE TABLE"),
);
assert.ok(!leaked, "Schema must not be leaked via UNION injection");
});
}); Mass assignment tests
describe("mass assignment prevention", () => {
it("user_id in request body is ignored", async () => {
const cookie = await loginAs("[email protected]");
const res = await authenticatedRequest("/orgs/org-acme/notes", cookie, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
title: "Sneaky",
body: "Trying to change owner",
user_id: "admin-1", // Should be ignored
}),
});
assert.strictEqual(res.status, 201);
const { id } = await res.json();
// Verify the note is owned by alice, not admin
const noteRes = await authenticatedRequest(`/orgs/org-acme/notes/${id}`, cookie);
const note = await noteRes.json();
assert.strictEqual(note.created_by, "user-alice", "user_id from body must be ignored");
});
it("role in profile update is ignored", async () => {
const cookie = await loginAs("[email protected]");
await authenticatedRequest("/me", cookie, {
method: "PUT",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name: "Bob", role: "owner" }), // Should be ignored
});
// Verify Bob is still an editor, not an owner
const meRes = await authenticatedRequest("/me", cookie);
const me = await meRes.json();
// The role check depends on your implementation
// In an org context, check the membership role
});
}); Path traversal tests
describe("path traversal prevention", () => {
it("blocks ../ in filename", async () => {
const cookie = await loginAs("[email protected]");
const res = await authenticatedRequest("/files/..%2F..%2Fetc%2Fpasswd", cookie);
assert.strictEqual(res.status, 400, "Path traversal must be blocked");
});
it("blocks encoded traversal patterns", async () => {
const cookie = await loginAs("[email protected]");
const patterns = [
"....//....//etc/passwd",
"..\\..\\etc\\passwd",
"%2e%2e%2f%2e%2e%2fetc%2fpasswd",
];
for (const pattern of patterns) {
const res = await authenticatedRequest(`/files/${encodeURIComponent(pattern)}`, cookie);
assert.ok(
[400, 404].includes(res.status),
`Pattern "${pattern}" must not return file contents`,
);
}
});
}); XSS tests (for HTML-rendering routes)
describe("XSS prevention", () => {
it("script tags are encoded in HTML responses", async () => {
const cookie = await loginAs("[email protected]");
// Create a note with XSS payload
const createRes = await authenticatedRequest("/orgs/org-acme/notes", cookie, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
title: "<script>alert('xss')</script>",
body: "Test body",
}),
});
const { id } = await createRes.json();
// View the HTML-rendered version
const viewRes = await authenticatedRequest(`/orgs/org-acme/notes/${id}/view`, cookie);
const html = await viewRes.text();
assert.ok(!html.includes("<script>alert"), "Script tags must be encoded");
assert.ok(html.includes("<script>"), "Script tags should appear as encoded entities");
});
}); The test payload list
Keep a standard set of attack payloads for regression testing:
export const SQL_INJECTION_PAYLOADS = [
"' OR '1'='1",
"'; DROP TABLE users; --",
"' UNION SELECT 1,2,3 --",
"1; SELECT * FROM users",
];
export const XSS_PAYLOADS = [
"<script>alert(1)</script>",
"<img src=x onerror=alert(1)>",
'" onclick="alert(1)',
"javascript:alert(1)",
];
export const PATH_TRAVERSAL_PAYLOADS = [
"../../etc/passwd",
"..\\..\\etc\\passwd",
"....//....//etc/passwd",
"%2e%2e%2f%2e%2e%2fetc%2fpasswd",
]; Run every payload through every input that reaches a dangerous context.
Exercises
Exercise 1: Write the SQL injection tests. Run each payload against the search endpoint. All should return safe results.
Exercise 2: Write the mass assignment test. Verify that user_id and role fields in the request body are ignored.
Exercise 3: Create a test file tests/payloads.ts with the standard payload lists. Write a function that runs all payloads against a given endpoint and asserts none return unexpected data.
What is the most valuable test in this lesson?