Skip to main content
< All Topics
Print

Chapter 26: Security Standards

Chapter 26: Security Standards

Last Updated: 2026-03

## 26.1 Non-Negotiable Rules

These rules apply to all ITI code on all platforms. No exceptions. AI-generated code that violates these rules must be fixed before it is committed.

| Rule | Applies To |

|——|———–|

| Never hardcode API keys, passwords, or secrets | All platforms |

| Sanitize all user input before processing or storing | All platforms |

| Escape all output before rendering to the user | All platforms |

| Never commit .env, credential files, or API keys to Git | All platforms |

| Never use eval(), exec(), or equivalent on user-controlled data | PHP, JS, Python |

| Log all security events (failed auth, invalid tokens, unauthorized access) | All platforms |

| Use prepared statements or parameterized queries for all database operations | PHP, Python, Rust |

| Validate all input server-side, even when validated client-side | All web platforms |

26.2 Secret Management by Platform

PHP (WordPress)


// Store: always encrypt API keys before saving to wp_options
update_option('iti_my_plugin_api_key', iti_encrypt($api_key));

// Retrieve: decrypt on use
$api_key = iti_decrypt(get_option('iti_my_plugin_api_key', ''));

Never use define() for API keys. Never put keys in wp-config.php.

Python (Flask / FastAPI)


# .env file (never committed)
ANTHROPIC_API_KEY=sk-ant-...
TAVILY_API_KEY=tvly-...

# In code
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.environ['ANTHROPIC_API_KEY']  # raises KeyError if missing — intentional

TypeScript (Node.js / Tauri frontend)


// .env file (never committed)
VITE_SOME_PUBLIC_KEY=...  // Only non-secret values

// For secrets in Tauri: use IPC to read from Keychain, never store in frontend code
const apiKey = await invoke<string>('get_api_key', { service: 'anthropic' });

Swift (iOS/macOS)


// Store in Keychain
try KeychainService.store(key: "ANTHROPIC_API_KEY", value: apiKey)

// Retrieve from Keychain
let apiKey = try KeychainService.retrieve(key: "ANTHROPIC_API_KEY")

Rust (Tauri backend)


// Read from environment (set via tauri.conf.json or OS env)
let api_key = std::env::var("ANTHROPIC_API_KEY")
    .map_err(|_| "ANTHROPIC_API_KEY not set".to_string())?;

26.3 Input Sanitization by Platform

PHP (WordPress)


// Text: strip HTML and extra whitespace
$clean = sanitize_text_field($_POST['field'] ?? '');

// Email: validate and sanitize
$email = sanitize_email($_POST['email'] ?? '');
if (!is_email($email)) {
    wp_send_json_error(['message' => 'Invalid email.']);
}

// URL: validate and sanitize
$url = esc_url_raw($_POST['url'] ?? '');

// Integer: ensure positive integer
$count = absint($_POST['count'] ?? 0);

// HTML content: strip disallowed tags and attributes
$html = wp_kses_post($_POST['content'] ?? '');

Warning: sanitize_text_field() is for plain text. Never use it on HTML content — use wp_kses_post() instead.

Python


from pydantic import BaseModel, Field, validator

class UserRequest(BaseModel):
    message: str = Field(..., min_length=1, max_length=10000)
    email: str | None = None

    @validator('email')
    def validate_email(cls, v):
        if v is not None:
            import re
            if not re.match(r'^[^@]+@[^@]+\.[^@]+$', v):
                raise ValueError('Invalid email format')
        return v

TypeScript


function sanitizeInput(input: unknown): string {
    if (typeof input !== 'string') {
        throw new Error('Expected string input');
    }
    return input.trim().slice(0, 10000);  // enforce max length
}

26.4 Output Escaping by Platform

PHP (WordPress)


// HTML context: escape for display in HTML
echo esc_html($user_data);

// HTML attribute context
echo '<input value="' . esc_attr($value) . '">';

// URL context
echo '<a href="' . esc_url($url) . '">';

// JavaScript context
echo '<script>var data = ' . wp_json_encode($data) . ';</script>';

Python (Jinja2 templates)

Jinja2 auto-escapes by default when autoescape=True. Verify this is set:


from jinja2 import Environment, select_autoescape
env = Environment(autoescape=select_autoescape(['html', 'xml']))

React/TypeScript

React escapes by default when rendering with {}. Never use dangerouslySetInnerHTML unless the content has been explicitly sanitized with a library like DOMPurify.


26.5 CSRF Protection (WordPress)

Every WordPress AJAX handler must verify a nonce:


// On the frontend, output a nonce for the AJAX action
wp_nonce_field('iti_my_action_nonce', 'nonce');

// Or in JavaScript
const nonce = iti_my_plugin_data.nonce;  // passed via wp_localize_script

// On the backend, verify the nonce
check_ajax_referer('iti_my_action_nonce', 'nonce');
// This function wp_die()'s if the nonce is invalid — no further code needed after it

26.6 Database Security

WordPress (wpdb)


// Always use prepare() for parameterized queries
global $wpdb;
$result = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}my_table WHERE user_id = %d AND status = %s",
        $user_id,
        $status
    )
);

Warning: Never concatenate user input directly into SQL strings. "SELECT * FROM table WHERE id = " . $_POST['id'] is a SQL injection vulnerability.

Python (psycopg2 / SQLAlchemy)


# psycopg2 — use parameterized queries
cursor.execute("SELECT * FROM sessions WHERE user_id = %s", (user_id,))

# SQLAlchemy ORM — parameterized by default
result = session.query(Session).filter(Session.user_id == user_id).all()

Rust (rusqlite)


// Use named parameters
conn.query_row(
    "SELECT * FROM sessions WHERE user_id = ?1",
    params![user_id],
    |row| row.get(0),
)?;

26.7 Git Security


# Ensure .env is in .gitignore
echo ".env" >> .gitignore
echo "*.env" >> .gitignore

# Before committing, verify no secrets are staged
git diff --staged | grep -i "api_key\|password\|secret\|token"

Warning: If a secret is accidentally committed, it is in Git history even after deletion. Immediately rotate the compromised key and purge the history.


26.8 Security Event Logging

Log these events with sufficient context to enable audit and incident response:

Event What to Log
Failed authentication Timestamp, user/IP, reason
Invalid nonce Timestamp, action, user/IP
Unauthorized access attempt Timestamp, user, resource, action
API key error Timestamp, service, error code (not the key itself)
Unusual data access Timestamp, user, what was accessed

// WordPress logging example
error_log(sprintf(
    '[ITI Security] Unauthorized access attempt: user=%d, action=%s, ip=%s, time=%s',
    get_current_user_id(),
    sanitize_text_field($_POST['action'] ?? 'unknown'),
    $_SERVER['REMOTE_ADDR'] ?? 'unknown',
    current_time('mysql')
));

26.9 Application Security Skills

For deeper security analysis beyond coding standards, five application security skills are available:

Skill Specialty
appsec-api-security-engineer API attack surface analysis, rate limiting, abuse prevention
appsec-cloud-container-security-engineer Kubernetes hardening, image scanning, IaC review
appsec-devsecops-engineer CI/CD pipeline security, SAST/DAST, supply chain hardening
appsec-iam-security-engineer OAuth/OIDC hardening, JWT analysis, authorization models
appsec-security-testing-ir-engineer Pen testing, fuzzing, incident response, forensics

All five share a common core of threat modeling (STRIDE/PASTA), secure code review, and vulnerability assessment with CVSS scoring.


Previous: Chapter 25 — Testing | Next: Chapter 27 — Deployment

Table of Contents