Intermediate Application & Code

Secure coding — input validation, output encoding, fail secure

Writing functional code is different from writing secure code. Most well-known vulnerabilities — SQL Injection, XSS, Path Traversal — originate from three simple failures: trusting user input, mixing data with code in output, and letting the application expose internal information when something goes wrong.

Input Validation

Every external input is potentially malicious. This includes form fields, query strings, HTTP headers, cookies, uploaded files, and even data from third-party APIs.

Practical rules:

  • Whitelist, not blacklist. Define what is accepted; reject the rest.
  • Validate type, size, and format before any processing.
  • Never rely on client-side validation alone. JavaScript can be disabled or bypassed.
import re

def validate_username(username: str) -> bool:
    # Only allows letters, digits and underscore, 3-32 chars
    pattern = r'^[a-zA-Z0-9_]{3,32}$'
    return bool(re.match(pattern, username))

# Bad: accepts any string
# Good: restricts to expected format
if not validate_username(username):
    raise ValueError("Invalid username")

Output Encoding

Data rendered in output must be encoded for the context where it appears. The same piece of data may require different encodings depending on the destination.

ContextRiskSolution
HTML bodyXSSHTML entity encoding
HTML attributeXSSAttribute encoding
JavaScriptXSSJS string encoding
SQLSQL InjectionPrepared statements
FilesystemPath traversalSanitize and resolve path
# Bad — direct concatenation in SQL
query = f"SELECT * FROM users WHERE name = '{name}'"

# Good — prepared statement
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
<!-- Bad — raw data in HTML -->
<p>{{ user.bio }}</p>

<!-- Good — escaped by the template engine (Jinja2, etc.) -->
<p>{{ user.bio | e }}</p>

Fail Secure

When something goes wrong, the application should deny access by default, not grant it. And it should never leak internal information.

# Bad — exception exposes database details
@app.route("/user/<id>")
def get_user(id):
    user = db.query(f"SELECT * FROM users WHERE id={id}")
    return jsonify(user)

# Good — catch exception, log internally, return generic response
@app.route("/user/<id>")
def get_user(id):
    try:
        user = db.get_user_by_id(id)
        if user is None:
            return jsonify({"error": "Not found"}), 404
        return jsonify(user.to_dict())
    except Exception:
        logger.exception("Error fetching user id=%s", id)
        return jsonify({"error": "Internal server error"}), 500

Error messages shown to users must be generic. Details belong in server-side logs only.

Principle of Least Privilege in Code

Code should operate with the minimum permissions required for the task:

  • Database connection: user with SELECT/INSERT only, not DBA.
  • File reading: open as read-only when writing is not needed.
  • Process: do not run as root unless strictly necessary.

Quick Checklist

  • All input validated via whitelist before use
  • Prepared statements in every query
  • Output escaped for the correct context
  • Errors logged internally, generic message to the client
  • Dependencies up to date (no known CVEs)
  • No secrets hardcoded in the codebase