top of page

Tutorial 5: Spot Security Issues in Review

  • Contributor
  • 3 days ago
  • 2 min read

Most security issues are recurring patterns. Learn the classes; you'll spot them in any codebase.

Step 1: Trust Boundaries (10 min)

Every PR review: identify boundaries.

  • External → Internal — HTTP requests, API webhooks, file uploads

  • User-controlled → Privileged — user IDs becoming admin operations

  • Untrusted JSON → Code — JSON.parse with eval

At each boundary, ask: "Is this input validated?"

Step 2: SQL Injection (10 min)

# Bad
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")

# Good
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

Any string concatenation or f-string into SQL = red flag.

ORMs usually safe. Raw queries: need parameterization.

Step 3: XSS (Cross-Site Scripting) (10 min)

<!-- Bad: user content rendered as HTML -->
<div>${userBio}</div>

<!-- Good: framework escapes -->
<div>{userBio}</div>   <!-- React, Vue: auto-escape -->

Watch for:

  • dangerouslySetInnerHTML (React)

  • v-html (Vue)

  • innerHTML (vanilla)

  • Server templates without auto-escape

If user content reaches HTML output: escape.

Step 4: Auth Bypasses (15 min)

Patterns that bypass auth:

# Bad — no auth check on this endpoint
@app.get("/admin/users")
def list_users():
    return User.all()

Reviewer asks: "Where's the auth?"

Common bugs:

  • Endpoint missing decorator

  • Decorator on parent class but not override

  • Permission check on wrong object (own resource vs. any resource)

Specifically check: every new endpoint has explicit auth + authorization.

Step 5: IDOR (Insecure Direct Object Reference) (10 min)

@app.get("/orders/{id}")
def get_order(id, user = current_user()):
    return Order.find(id)   # any user can see any order!

Should be:

return Order.where(id=id, user_id=user.id).first()

Always: filter by current user's ownership. Even for "internal" tools.

Step 6: Secret Handling (10 min)

# Bad
API_KEY = "sk_live_abc123"

# Bad
logger.info(f"Authenticating with key: {api_key}")

# Bad
return jsonify(token=user.session_token)

Watch for:

  • Hardcoded secrets

  • Secrets in logs

  • Secrets in error messages

  • Secrets in responses

Use secret managers; don't ship secrets in code.

Step 7: SSRF (Server-Side Request Forgery) (10 min)

def fetch(url):
    return requests.get(url)

# user controls `url`; can hit internal services
fetch("http://169.254.169.254/latest/meta-data/iam/...")  # AWS metadata

Validate user-provided URLs:

  • Block private IP ranges

  • Block 169.254.* (cloud metadata)

  • Whitelist domains where possible

Step 8: Path Traversal (5 min)

@app.get("/files/{name}")
def serve(name):
    return send_file(f"/uploads/{name}")

# user requests: ../../etc/passwd

Sanitize or use safe_join:

path = Path("/uploads") / name
if "/uploads" not in str(path.resolve()):
    abort(403)

Step 9: Race Conditions (10 min)

# Bad
def transfer(from_id, to_id, amount):
    from_acct = Account.find(from_id)
    if from_acct.balance >= amount:
        from_acct.balance -= amount
        Account.find(to_id).balance += amount

Two concurrent transfers can both pass the check. Lost money.

Fix: transaction + atomic update or row lock (Path 33 Tutorial 5).

In review: ask "what if two requests race?"

Step 10: Dependencies (5 min)

PRs adding new dependencies:

  • Is the package well-known?

  • How recent? (Recently-published packages can be malicious)

  • Downloads / GitHub stars

  • Maintained?

For npm: npm audit. For Python: pip-audit. For Go: govulncheck.

A typo-squat dep can pwn the project.

What You Just Did

Security in review: trust boundaries, SQL, XSS, auth bypass, IDOR, secrets, SSRF, traversal, races, dependencies. Common patterns to spot.

Common Failure Modes

No auth check; rubber stamp. Endpoint shipped wide open.

Trust user input in SQL. Injection.

Hardcoded secrets. Repo cloned; secrets leaked.

No dependency review. Supply chain attack.

Custom crypto. Roll your own = wrong.

bottom of page