hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Testing Auth and Security with @hectoday/http

Why Auth Tests Are Different

  • Testing Security, Not Just Functionality
  • Project Setup

Testing Authentication

  • Testing Login Flows
  • Testing Sessions and Cookies
  • Testing 2FA Flows

Testing Authorization

  • Testing Access Boundaries
  • Testing API Keys and Scopes

Testing Security Properties

  • Testing Rate Limiting and Lockout
  • Testing Token Security
  • Testing Input Handling

Putting It Together

  • A Security Test Suite

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("&lt;script&gt;"), "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?

← Testing Token Security A Security Test Suite →

© 2026 hectoday. All rights reserved.