security

Security Model

A security model describes the boundaries, trusted components, assets, and controls in an application. It helps developers reason about risk before reaching for isolated fixes.

What Matters

  • List assets worth protecting: accounts, personal data, money, files, secrets, and administrative actions.
  • Treat request data, uploaded files, third-party responses, and queue messages as untrusted input.
  • Apply least privilege: each user, process, and credential should have only the access it needs.
  • Fail closed when a permission check, dependency, or security control cannot complete safely.
  • Keep logs useful for investigation without recording passwords, tokens, or unnecessary personal data.

Practical Example

PHP example
<?php

declare(strict_types=1);

function controlForBoundary(string $boundary): string
{
    return match ($boundary) {
        'html_output' => 'escape for HTML context',
        'database_query' => 'use prepared statements',
        'state_change' => 'require authorisation and CSRF protection',
        'uploaded_file' => 'validate content, size, name, and storage path',
        default => 'identify trust boundary before choosing a control',
    };
}

echo controlForBoundary('state_change') . PHP_EOL;

// Prints:
// require authorisation and CSRF protection

In Application Work

In review work, start with the route or command boundary and trace data through validation, authorisation, storage, output, and logging. This makes missing controls visible.

Start With A Data Flow

Security reviews become practical when you follow one piece of data through the application. For a profile-image upload, ask:

Who can submit the request?
Which file properties come from the browser?
Where is the file validated?
Where is it stored?
Can the web server execute or serve it directly?
Who can download it later?
What is written to logs?

This is more useful than adding a generic "security check" at the end of development. Each answer identifies a boundary where a specific control belongs.

Threats, Controls, And Residual Risk

A control reduces a risk; it rarely removes every risk by itself. Prepared statements protect SQL values, but they do not authorise which records a user may read. Authentication identifies a user, but it does not prove the user may issue a refund. Output escaping protects one rendering context, but it does not validate a file path.

Layer controls deliberately. Record the risks that remain, especially around privileged actions, third-party services, and operational access.

Least Privilege In PHP Projects

Least privilege applies beyond application users. The database account used by the web app should not normally create tables. A queue worker should receive only the secrets it needs. An upload directory should not be executable. Production logs should not be readable by every process on the server.

What To Check

Before moving on, make sure you can:

  • identify assets, trust boundaries, and attacker-controlled values;
  • choose controls for the correct boundary;
  • explain least privilege and fail-closed behaviour;
  • recognise why layered controls matter;
  • trace one feature from request boundary to storage and output.

Practice

Practice: Map Security Controls

Create a helper that maps application boundaries to appropriate controls.

Requirements

  • Cover HTML output, SQL queries, state-changing requests, and uploads.
  • Return a deliberate fallback for unknown boundaries.
  • Show that different boundaries receive different controls.
Show solution

The mapping keeps the reason for each control visible.

PHP example
<?php

declare(strict_types=1);

function securityControl(string $boundary): string
{
    return match ($boundary) {
        'html' => 'escape output',
        'sql' => 'bind prepared-statement parameters',
        'state_change' => 'authorise and verify CSRF token',
        'upload' => 'validate and store outside the public web root',
        default => 'review the trust boundary',
    };
}

foreach (['html', 'sql', 'state_change', 'upload'] as $boundary) {
    echo $boundary . ': ' . securityControl($boundary) . PHP_EOL;
}

// Prints:
// html: escape output
// sql: bind prepared-statement parameters
// state_change: authorise and verify CSRF token
// upload: validate and store outside the public web root

The helper is intentionally small: it demonstrates that secure development is boundary-specific.