Skip to main content

What is Security by Design?

Security by Design is a proactive approach where security is integrated into every phase of the Software Development Life Cycle (SDLC), rather than being added as an afterthought. It ensures that security and privacy are fundamental considerations from requirements gathering through to maintenance.
The Normo Unsecure PWA intentionally violates security by design principles to create a learning environment for students. This page explains proper security principles and how this app fails to implement them.

Security in the SDLC

Security should be considered at every phase of development:
PhaseSecurity by Design ProcessesWhat Normo PWA Does Instead
Requirements Definition• Gather specific security and privacy requirements
• Vulnerability assessment
❌ No security requirements defined
❌ No threat assessment performed
Determining Specifications• Explicit security and privacy specifications
• Risk assessment
❌ No security specifications
❌ Risks deliberately introduced
Design• Threat modelling
• Security design review
• Security tests included in test designs
❌ Insecure architecture by design
❌ No security controls planned
Development• Code reviews
• Static application security testing
❌ Vulnerable code intentionally written
❌ No code review process
Integration• Risk assessment
• Code reviews
• Dynamic application security testing
• Grey-box penetration testing
❌ No integration security testing
❌ Vulnerabilities not addressed
Testing & Debugging• Code reviews
• SAST
• DAST
• Penetration testing
This is your phase!
Students perform security testing
Installation• Penetration testing
• Vulnerability assessment
⚠️ Sandbox-only deployment required
Maintenance• Log monitoring & reporting
• Vulnerability assessment
❌ No logging or monitoring implemented

Core Security Principles

1. Principle of Least Privilege

What it means: Users and processes should have the minimum level of access necessary to perform their functions. How Normo PWA violates it:
@app.route("/success.html", methods=["POST", "GET", "PUT", "PATCH", "DELETE"])
def addFeedback():
    # ❌ Accepts unnecessary HTTP methods
    # Should only accept POST for form submission
Other violations:
  • host="0.0.0.0" exposes app to all network interfaces instead of localhost
  • No role-based access control (RBAC)
  • All users have equal privileges

2. Defense in Depth

What it means: Multiple layers of security controls should be implemented so that if one fails, others provide protection. How Normo PWA violates it:
Security LayerWhat Should ExistWhat Actually Exists
NetworkHTTPS/TLS, Firewall rules❌ HTTP only, no network security
ApplicationInput validation, Output encoding❌ No validation or encoding
AuthenticationStrong passwords, MFA, session management❌ No password policy, no MFA, no sessions
DatabaseParameterized queries, encrypted storage⚠️ Mixed (some queries secure, others not)
LoggingSecurity event logging, monitoring❌ No security logging
Real example:
user_management.py:17-39
def retrieveUsers(username, password):
    con = sql.connect("database_files/database.db")
    cur = con.cursor()
    # ❌ No input validation layer
    # ❌ SQL injection vulnerability
    cur.execute(f"SELECT * FROM users WHERE username = '{username}'")
    # ❌ No rate limiting
    # ❌ No failed login tracking
    # ❌ No logging of authentication attempts
A defense-in-depth approach would include:
  1. Input validation before the query
  2. Parameterized queries
  3. Rate limiting to prevent brute force
  4. Failed login tracking and account lockout
  5. Security event logging

3. Fail Securely

What it means: When systems fail, they should default to a secure state and not expose sensitive information. How Normo PWA violates it:
main.py:70-73
app.run(debug=True, host="0.0.0.0", port=5000)
debug=True causes Flask to display detailed error messages and stack traces when exceptions occur, revealing internal application structure, file paths, and code to attackers.
What happens when debug=True:
  • Full stack traces exposed to users
  • Interactive debugger accessible via browser
  • Source code snippets revealed
  • Database structure exposed in SQL errors
Secure alternative:
Secure Configuration
app.config['DEBUG'] = False  # ✅ No debug info in production
app.config['TESTING'] = False

@app.errorhandler(500)
def handle_error(e):
    # ✅ Log the error internally
    app.logger.error(f"Internal error: {str(e)}")
    # ✅ Show generic message to user
    return render_template('error.html', message="An error occurred"), 500

4. Secure by Default

What it means: Default configurations should be secure, requiring users to explicitly opt into less secure options. How Normo PWA violates it:
main.py:12-13
from flask_cors import CORS
CORS(app)  # ❌ Allows ALL origins by default
This allows any website to make requests to the application, enabling CSRF attacks.Secure default:
CORS(app, origins=["https://yourdomain.com"])  # ✅ Whitelist specific origins
user_management.py:6-14
def insertUser(username, password, DoB):
    # ❌ Stores password in plaintext
    cur.execute(
        "INSERT INTO users (username,password,dateOfBirth) VALUES (?,?,?)",
        (username, password, DoB)
    )
Secure default:
import bcrypt

def insertUser(username, password, DoB):
    # ✅ Hash password before storage
    hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
    cur.execute(
        "INSERT INTO users (username,password,dateOfBirth) VALUES (?,?,?)",
        (username, hashed, DoB)
    )

5. Separation of Concerns

What it means: Different aspects of the application should be isolated into distinct modules with well-defined interfaces. How Normo PWA violates it:
main.py:46-67
@app.route("/", methods=["POST", "GET"])
def home():
    # ❌ Mixing routing, input handling, business logic, and presentation
    if request.method == "GET" and request.args.get("url"):
        url = request.args.get("url", "")
        return redirect(url, code=302)  # ❌ No validation
    elif request.method == "POST":
        username = request.form["username"]  # ❌ No validation
        password = request.form["password"]  # ❌ No validation
        isLoggedIn = dbHandler.retrieveUsers(username, password)  # ❌ Direct DB call
        if isLoggedIn:
            return render_template("/success.html", value=username, state=isLoggedIn)
Secure separation:
Layered Architecture
# validators.py - Input validation layer
def validate_username(username):
    if not username or len(username) < 3:
        raise ValueError("Invalid username")
    return username.strip()

# services.py - Business logic layer
def authenticate_user(username, password):
    validated_username = validate_username(username)
    validated_password = validate_password(password)
    return dbHandler.retrieveUsers(validated_username, validated_password)

# routes.py - Routing layer
@app.route("/", methods=["POST"])
def home():
    try:
        username = request.form["username"]
        password = request.form["password"]
        if authenticate_user(username, password):
            return render_template("/success.html", username=username)
    except ValueError as e:
        return render_template("/index.html", error=str(e))

6. Don’t Trust User Input

What it means: All user input should be treated as potentially malicious and validated/sanitized before use. How Normo PWA violates it:
user_management.py:45
# ❌ Trusts user input directly in SQL query
cur.execute(f"INSERT INTO feedback (feedback) VALUES ('{feedback}')")
Attack: feedback = "'); DROP TABLE users; --"Secure: Use parameterized queries
cur.execute("INSERT INTO feedback (feedback) VALUES (?)", (feedback,))

7. Minimize Attack Surface

What it means: Reduce the number of potential entry points and exposed functionality. How Normo PWA violates it:
Unnecessary ExposureImpact
Accepts all HTTP methods (GET, POST, PUT, PATCH, DELETE)Increases attack vectors
host="0.0.0.0" exposes to all network interfacesShould use 127.0.0.1 for local development
Debug mode enabledExposes interactive debugger and stack traces
CORS allows all originsAny website can make requests
No authentication on routesAll endpoints publicly accessible
Includes unused dependenciesIncreases dependency vulnerability surface

8. Keep Security Simple

What it means: Complex security systems are harder to implement correctly and maintain. Example of unnecessary complexity:
user_management.py:32-33
# ⚠️ Simulates random response time for "testing"
# This actually creates a timing attack vulnerability!
time.sleep(random.randint(80, 90) / 1000)
This adds complexity that makes the system less secure, not more secure. Attackers can use timing differences to enumerate valid usernames. Simple and secure:
# ✅ Constant-time comparison
import hmac
def constant_time_compare(a, b):
    return hmac.compare_digest(a.encode(), b.encode())

Privacy by Design

Privacy by Design complements Security by Design with privacy-specific principles:

Data Minimization

Principle: Collect only necessary dataViolation: App collects date of birth (DoB) but never uses it

Consent & Control

Principle: Users should control their dataViolation: No way to view, export, or delete user data

Transparency

Principle: Clear privacy policiesViolation: No privacy policy or terms of service

Secure Storage

Principle: Protect personal dataViolation: Plaintext passwords, unencrypted database

Threat Modeling Exercise

A proper threat model would identify threats and countermeasures:
ThreatAttack VectorCurrent StateCountermeasure
Attacker steals user passwordsSQL injection in login form❌ VulnerableParameterized queries
Attacker bypasses authenticationNo session management❌ VulnerableImplement Flask-Login
Attacker executes JavaScript in victim’s browserXSS in feedback system❌ VulnerableHTML escape all output
Attacker performs actions on behalf of victimNo CSRF protection❌ VulnerableImplement CSRF tokens
Attacker intercepts credentials in transitHTTP instead of HTTPS❌ VulnerableConfigure SSL/TLS
Attacker cracks passwords from database dumpPlaintext password storage❌ VulnerableHash with bcrypt + salt
Attacker performs brute force login attemptsNo rate limiting❌ VulnerableImplement Flask-Limiter
Attacker redirects users to phishing siteOpen redirect vulnerability❌ VulnerableValidate redirect URLs

Cost of Insecurity

The later in the SDLC that vulnerabilities are discovered, the more expensive they are to fix:
1

Requirements Phase

Cost Multiplier: 1xIdentifying security requirements early is cheapest
2

Design Phase

Cost Multiplier: 5xArchitectural changes become more expensive
3

Development Phase

Cost Multiplier: 10xRefactoring code is costly
4

Testing Phase

Cost Multiplier: 15xMajor rework may be required (← You are here!)
5

Production Phase

Cost Multiplier: 30x+Includes data breach costs, reputation damage, legal liability
The Normo Unsecure PWA gives you experience identifying vulnerabilities in the Testing phase. In real-world projects, you should implement security from the Requirements phase onward.

Your Learning Objectives

As you analyze this application, consider:
  1. Identify violations: Which security principles does each vulnerability violate?
  2. Assess impact: What’s the worst-case scenario for each vulnerability?
  3. Design fixes: How would you implement proper security by design?
  4. Prevent recurrence: What processes would prevent these issues in future projects?

Next Steps

Testing Approaches

Learn how to systematically test for these vulnerabilities

Architecture Review

Understand the technical implementation details

Vulnerability Catalog

Explore each vulnerability in detail

Secure Coding Guide

Learn how to implement security by design

Build docs developers (and LLMs) love