hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Web Security Fundamentals with @hectoday/http

The Attacker's Mindset

  • Thinking Like an Attacker
  • Project Setup

Injection Attacks

  • SQL Injection
  • SQL Injection: Beyond the Basics
  • Command Injection
  • Header Injection

Cross-Site Scripting (XSS)

  • What Is XSS?
  • Output Encoding
  • Content Security Policy in Practice

Broken Access and Redirects

  • Insecure Direct Object References (IDOR)
  • Open Redirects
  • Server-Side Request Forgery (SSRF)

File and Data Handling

  • Path Traversal
  • Mass Assignment
  • Denial of Service via Input

Putting It All Together

  • Security Testing
  • The OWASP Top 10
  • Capstone: Hardened Notes API

Capstone: Hardened Notes API

What changed

The project setup had deliberately vulnerable routes. This capstone shows the final version with every defense applied.

VulnerabilityDefenseFile
SQL injectionParameterized queriesAll db.prepare calls
LIKE injectionescapeLike functionnotes.ts search route
Command injectionwriteFile instead of execnotes.ts export route
Header injectionAllowlisted header valuesAny route setting headers from input
Stored/reflected XSSescapeHtml on all HTML outputnotes.ts view route
CSPNonce-based policyapp.ts onResponse
IDOROwnership check in every queryAll notes.ts routes
Open redirectsRelative-path validationauth.ts login redirect
SSRFURL validation with DNS checkbookmarks.ts
Path traversalpath.resolve + startsWithfiles.ts
Mass assignmentZod schema / explicit field pickingnotes.ts create route
ReDoSNo nested quantifiers, use ZodAll validation
Large payloadsContent-Length checkapp.ts onRequest

The hardened project structure

src/
  app.ts                 # setup(), security headers, CSP, body size limit
  server.ts              # starts the server
  db.ts                  # SQLite database, tables, seed data
  sessions.ts            # session store
  cookies.ts             # cookie helpers
  auth.ts                # authenticate function
  escape.ts              # escapeHtml, escapeLike
  url-validator.ts       # isUrlSafe for SSRF prevention
  routes/
    auth.ts              # login with redirect validation
    notes.ts             # CRUD with IDOR checks, parameterized queries, XSS encoding
    bookmarks.ts         # URL fetching with SSRF protection
    files.ts             # file serving with path traversal protection

The defense functions

Every defense is a small, reusable function:

// src/escape.ts
export function escapeHtml(input: string): string {
  return input
    .replace(/&/g, "&")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, """)
    .replace(/'/g, "'");
}

export function escapeLike(input: string): string {
  return input.replace(/[%_\\]/g, "\\$&");
}
// src/url-validator.ts (summarized)
export async function isUrlSafe(url: string): Promise<boolean> {
  // 1. Parse the URL (reject invalid URLs)
  // 2. Check protocol (only http/https)
  // 3. Check hostname against blocklist (localhost, metadata endpoints)
  // 4. Resolve DNS and check for private IPs
  return true; // or false
}
// Path traversal check (inline in route)
const filePath = path.resolve(UPLOADS_DIR, filename);
if (!filePath.startsWith(UPLOADS_DIR + path.sep)) {
  return Response.json({ error: "Invalid filename" }, { status: 400 });
}
// Open redirect check (inline in route)
function isSafeRedirect(url: string): boolean {
  return url.startsWith("/") && !url.startsWith("//");
}

Each defense is a few lines. No frameworks, no middleware. They are functions you call when you need them.

The hardened notes search route

This single route demonstrates four defenses:

route.get("/notes/search", {
  resolve: (c) => {
    const user = authenticate(c.request); // Authentication
    if (user instanceof Response) return user;

    const query = new URL(c.request.url).searchParams.get("q") ?? "";

    const notes = db
      .prepare("SELECT * FROM notes WHERE user_id = ? AND title LIKE ? ESCAPE '\\'")
      .all(user.id, `%${escapeLike(query)}%`); // SQL injection + LIKE injection + IDOR

    return Response.json(notes);
  },
});
  1. Authentication: authenticate checks the session
  2. IDOR: user_id = ? ensures only the user’s notes are returned
  3. SQL injection: ? placeholders, no concatenation
  4. LIKE injection: escapeLike neutralizes % and _ wildcards

What you learned

This course taught one principle applied twelve different ways: never trust input that crosses the trust boundary.

The specific defenses vary by context:

Where the input goesWhat can go wrongHow to fix it
SQL querySQL injectionParameterized queries
Shell commandCommand injectionexecFile with arg arrays, or avoid shell
HTTP headerHeader injectionValidate/allowlist values
HTML pageXSSOutput encoding + CSP
Database query with IDIDOROwnership check
Redirect URLOpen redirectValidate path is relative
Server-side fetchSSRFValidate URL, block private IPs
File pathPath traversalresolve + startsWith check
Database recordMass assignmentExplicit field picking / Zod
Regex / JSON parserDoSAvoid nested quantifiers, limit body size

Every row is the same idea: untrusted input reaches an interpreter or system, and the fix is to validate, encode, or restrict it before it gets there.

Challenges

Challenge 1: Add rate limiting to the search endpoint. An attacker could use the search endpoint for enumeration (trying different queries to discover note contents). Apply the rate limiter from the Securing Your API course.

Challenge 2: Add an audit log. Log every security-relevant event (failed SQL injection attempts, IDOR attempts, SSRF blocks, path traversal blocks). Use the structured logging pattern from the Securing Your API course.

Challenge 3: Run a security scan. Use an open-source tool like OWASP ZAP to scan your app. Compare its findings with the vulnerabilities you fixed. Did it find anything you missed?

Challenge 4: Add Content-Type validation on file uploads. The path traversal lesson secured file downloads. Add a file upload route that validates the file’s MIME type (do not trust the Content-Type header — check the file’s magic bytes).

What is the single principle behind every defense in this course?

Which defense from this course is used the most often (appears in the most routes)?

← The OWASP Top 10 Back to course →

© 2026 hectoday. All rights reserved.