Skip to content
Educora
Advanced18 min7 / 12

Secure coding

Four core habits: validating input with an allow-list, the principle of least privilege, keeping secrets out of code and tracking vulnerabilities in dependencies.

Check yourself
In this lesson you will learn
  • Write server-side input validation based on an allow-list
  • Apply least privilege to databases and services
  • Move secrets out of code and act correctly after a leak
  • Understand CVE, CVSS and dependency-audit tools

Most vulnerabilities come not from exotic tricks but from ordinary programmer habits: trusting input, running a service with admin rights, committing a password to a Git repository, a library that has not been updated for three years. In this lesson you will learn four habits that make your code safer every day.

1. Input validation: the allow-list

Every input is untrusted: forms, URL parameters, headers, cookies, uploaded files, even requests from your own mobile app (they can be modified). Validation must happen on the server; checks in the browser are only for the user's convenience. Check type, length, range and format, and describe what is valid instead of hunting for bad characters.

Deny-list: always incomplete
BAD = ['<script>', 'DROP TABLE', '--']

def is_safe(text):
    return not any(bad in text for bad in BAD)

is_safe('<SCRIPT>')   # True - uppercase slips through
Allow-list: only valid input passes
import re

def is_valid_username(text):
    return re.fullmatch(r'[a-z0-9_]{3,16}', text) is not None

is_valid_username('<SCRIPT>')   # False
An attacker can find thousands of ways around a deny-list; an allow-list accepts only the format you described.
Python
import re

USERNAME = re.compile(r'[a-z0-9_]{3,16}')

def validate_signup(username, age):
    errors = []
    if not USERNAME.fullmatch(username):
        errors.append('username: 3-16 of a-z 0-9 _')
    if not (type(age) is int and 10 <= age <= 120):
        errors.append('age: whole number 10-120')
    return errors or ['OK']

for username, age in [('aysel_07', 15), ('Murad!', 16), ('elvin', -3), ('x', 200)]:
    print(username.ljust(9), validate_signup(username, age))
▸ Expected output
aysel_07  ['OK']
Murad!    ['username: 3-16 of a-z 0-9 _']
elvin     ['age: whole number 10-120']
x         ['username: 3-16 of a-z 0-9 _', 'age: whole number 10-120']
The function returns all errors at once so the user can fix the form in one go. fullmatch requires the whole string to match the format.

2. The principle of least privilege

Every user, process and service should get only the rights its job needs, and only for as long as needed. A web app's database account should not be an administrator, services should not run as root or Administrator, and admin work needs a separate account. Even if the app is hacked, the blast radius stays small.

Everything, from anywhere
GRANT ALL PRIVILEGES ON *.* TO 'shop_app'@'%';
Only what is needed, only from the internal network
GRANT SELECT, INSERT, UPDATE ON shop.orders TO 'shop_app'@'10.0.0.%';
GRANT SELECT ON shop.products TO 'shop_app'@'10.0.0.%';
A MySQL example: a hacked app cannot run DROP DATABASE or read other databases, because it was never granted those rights.

This principle has two close relatives. Secure defaults: a new account, file or API key starts with no permissions, and rights are added deliberately later. Fail closed: when a check ends in an error — for example, the authorisation server does not answer — the code must refuse access instead of letting the request through “just in case”.

3. Managing secrets

Passwords, API keys, tokens and private keys are never stored in code. Git history remembers even deleted files forever, and bots search public repositories for keys. Keep secrets in environment variables or a secrets manager (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault), add the .env file to .gitignore and run a secret scanner (for example gitleaks) before committing.

Secret inside the code
DB_PASSWORD = 'Baku2024!'   # now in Git history forever

connect(user='shop_app', password=DB_PASSWORD)
Secret comes from the environment
import os

DB_PASSWORD = os.environ['DB_PASSWORD']   # set by the server or a secrets manager

connect(user='shop_app', password=DB_PASSWORD)

4. Vulnerabilities in dependencies

Most of a modern application's code is other people's libraries. A known vulnerability receives a CVE identifier and a CVSS score (0–10). Tools such as npm audit, pip-audit, Dependabot and OWASP Dependency-Check compare your dependencies with these databases. Lock files (package-lock.json, poetry.lock) pin exact versions, and an SBOM lists every component. Also watch out for fake packages with names similar to popular ones (typosquatting).

CVSS scoreSeverityWhat to do
0.1–3.9Lowfix in the next planned update
4.0–6.9Mediumfix within weeks
7.0–8.9Highprioritise, fix within days
9.0–10.0Criticalupdate at once or apply a temporary mitigation
Ratings follow the CVSS v3 scale (0.1–3.9, 4.0–6.9, 7.0–8.9, 9.0–10.0); the final decision also depends on whether the component is actually reachable in your system.
Terminal
# JavaScript project: check dependencies against known vulnerabilities
npm audit

# Python project
pip install pip-audit
pip-audit
Run these commands in your own project and sort the findings by CVSS score.
Exercise

Write is_valid_quantity(text) for the “quantity” field of an order form: only strings that represent a whole number from 1 to 99 ([0-9] digits, 1–2 characters) return True. Spaces, a minus sign, 5e3 and characters like ² must be rejected, and the function must never crash.

Exercise · Python
import re

def is_valid_quantity(text):
    # allow-list: 1-2 ASCII digits, value 1..99
    return False

for value in ['3', '99', '0', '100', '-1', ' 7', '5e3', '²']:
    print(repr(value), is_valid_quantity(value))
▸ Expected output
'3' True
'99' True
'0' False
'100' False
'-1' False
' 7' False
'5e3' False
'²' False

Key points

  • Validate every input on the server with an allow-list: type, length, range, format.
  • Validation does not replace parameterised queries and escaping.
  • Least privilege limits the damage a compromised component can do.
  • Secrets live in the environment or a secrets manager, not in code; a leaked secret is rotated at once.
  • Audit dependencies with tools, prioritise by CVSS score and keep an SBOM.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
Why is deny-list validation considered unreliable?