Secure Coding Practices Lab
A capstone review process: validate-input/encode-output, insecure framework defaults, a structured code review checklist, and dependency scanning as secure coding, not just ops.
Learning Objectives
- → Apply the 'validate input, encode output' principle across HTML, JS, and API boundaries
- → Identify insecure defaults in common web framework configurations and correct them
- → Conduct a structured security code review using a repeatable checklist
- → Explain why dependency scanning is part of secure coding, not just operations
- → Apply defense-in-depth thinking to a single code change, not just architecture-level decisions
Bringing It All Together
Every vulnerability class covered across Web Development, Databases, and Web Security traces back to the same handful of coding habits. This lab is a capstone: a repeatable checklist and review process for catching these issues in your own code, before they ever reach a pentest report.
The Universal Rule: Validate Input, Encode Output
Nearly every injection vulnerability (SQL injection, XSS, command injection) comes down to one broken rule:
- Validate input — reject or sanitize data as early as possible, based on what it's supposed to be (an email, a positive integer, one of a fixed set of values), not a blocklist of "bad" characters
- Encode output — for the context it's being written into (HTML-encode for HTML, use parameterized queries for SQL, JSON-encode for JSON), never assume upstream validation makes this step unnecessary
The two are not substitutes for each other: input validation limits what data enters your system; output encoding ensures that whatever data is there can't be misinterpreted as code by whatever it's rendered into.
Insecure Defaults: What Frameworks Get Wrong Out of the Box
| Framework setting | Insecure default | Secure correction |
|---|---|---|
| Express.js | No security headers set | Add helmet() middleware |
| Django | DEBUG = True in production |
DEBUG = False, custom error pages |
| Any ORM | Raw SQL string concatenation available | Always use the ORM's parameterized query builder |
| Cookies | No Secure/HttpOnly/SameSite flags |
Set all three explicitly |
Frameworks optimize their defaults for a smooth first-run developer experience, not a hardened production deployment — assume every default needs a security review, not just the parts you changed.
A Structured Security Code Review Checklist
- Auth & access control — is every endpoint's authorization check present and correct, not just "is the user logged in"?
- Input handling — is every external input (query params, body, headers, file uploads) validated before use?
- Output handling — is every value rendered into HTML/SQL/shell/JSON properly encoded for that specific context?
- Secrets — are credentials, API keys, and tokens absent from source code and version control history?
- Error handling — do error paths avoid leaking stack traces, file paths, or internal implementation details?
- Dependencies — does the change introduce or upgrade a dependency, and if so, has it been checked for known CVEs?
Dependency Scanning: Secure Coding, Not Just Ops
A perfectly-written function that calls a library with a known critical CVE is still vulnerable — this is why tools like npm audit, pip-audit, or Dependabot belong in the same review process as manual code inspection, not treated as a separate, later "ops" concern. The vulnerable code, from the application's perspective, is still your code path, even though you didn't write the vulnerable line yourself.
Defense in Depth at the Code Level
Defense in depth isn't only an architecture-diagram concept — it applies to a single function. A database query should be parameterized and the calling code should enforce least-privilege database credentials and the API layer above it should validate input types — each layer assuming the others might fail, rather than one control being the application's only line of defense.
Common Pitfalls
- Relying on client-side validation alone, forgetting it's trivially bypassed by anyone who controls their own HTTP requests
- Blocklisting specific "dangerous" characters instead of validating against an allowlist of what's actually expected
- Treating a security code review as a one-time gate instead of a habit applied to every change, including "small" ones
- Assuming a popular, well-maintained dependency can't have a vulnerability — checking is cheap; assuming is not
Input validation and output encoding defend at two different points in the data's journey through the system, so neither replaces the other.
Why are input validation and output encoding both necessary, rather than one making the other redundant?
Each of the three key cookie flags closes off a distinct attack vector against session cookies.
Which cookie attributes should be explicitly set to harden session cookies?
From the application's runtime perspective, a vulnerable dependency's code is functionally no different from a vulnerable line you wrote yourself.
Why does a known-vulnerable dependency count as part of secure coding, not just an operations concern?
💪 Exercises & Challenges
Secure Coding Practices Lab MCQ
Test your understanding of Secure Coding Practices Lab.
Run a Structured Code Review
Given a Node.js/Express route handler that takes a username query parameter and inserts it directly into a raw SQL string, returns raw error messages including stack traces to the client, and sets a s
Evaluate a Risky Dependency Justification
A code review of a pull request finds: (1) a new dependency was added that npm audit flags with a 'critical' severity CVE for remote code execution, (2) the PR author argues 'we don't call the vulnera