Skip to main content

Secure by Default

Security in TiCore v2 isn’t an add-on you remember to bolt on — it’s the default posture. The csrf middleware, hardened sessions, base security headers, traversal-guarded routing, and PDO prepared statements are built into the core, with secrets kept well outside the web root and a secrets:scan CLI plus gitleaks CI guarding every commit.

The Security Model

CSRF Protection

State-changing requests (POST/PUT/PATCH/DELETE) are guarded against cross-site request forgery. Attach the csrf middleware (CsrfMiddleware) to a route; it reads the csrf_token field or the X-CSRF-Token header and validates it with Security::csrfValid() — a constant-time hash_equals() check — returning 419 Invalid CSRF token on failure. Tokens are minted with Security::generateCsrfToken() (32 random bytes, session-stored). For a hard stop, Security::verifyCsrfToken() logs the client IP and die()s with 403 instead.

Hardened Sessions

Session cookies are set HttpOnly (no JavaScript access), Secure over HTTPS, and SameSite to blunt CSRF and cross-site leakage, and the session ID is regenerated on login to defeat session fixation. The auth addon builds full accounts, roles, and the guest/auth middleware on top of these defaults.

Base Security Headers

Every dispatch emits defensive headers via Router::sendBaseHeaders() — no manual middleware required: X-Frame-Options: SAMEORIGIN (clickjacking), X-Content-Type-Options: nosniff (MIME sniffing), Referrer-Policy: strict-origin-when-cross-origin, and Permissions-Policy: geolocation=(), microphone=(), camera=().

Traversal-Guarded Routing

The deep view fallback rejects .. and NUL bytes, validates every path segment against ^[a-z0-9][a-z0-9\-]*$, then realpath()-verifies the resolved template stays inside templates/default before rendering. Controller and auto-discovery names are regex-validated the same way, so crafted paths like ../../etc/passwd can never escape the application scope.

PDO Prepared Statements

All database access uses PDO with prepared statements, parameterizing every query to eliminate SQL injection by construction rather than by careful string escaping. Addons such as auth, blog-cms, and rest-api-kit follow the same pattern.

Secrets Outside the Web Root

The docroot is www/; credentials live in TiCore/.env, one level above it. A public/private split keeps config, secrets, controllers, and templates unreachable by the browser — they can never be served as plain text.

Secret Scanning & CI

The bin/ticore secrets:scan command catches committable secrets before they leak — GitHub PATs, Stripe sk_live_/whsec_, AWS AKIA, Google AIza, GCP/PEM private keys, AdSense ca-pub ids, and inline credentials — and exits non-zero on any hit. A gitleaks CI workflow enforces the same check on every push.

XSS-Safe Output

The e() helper wraps htmlspecialchars($s, ENT_QUOTES, 'UTF-8'), so user input rendered into templates can’t inject markup or scripts. Use it for all dynamic output.

API Key Auth & Rate Limiting

The rest-api-kit addon stores API keys hashed with SHA-256 (shown once), verifies them by prefix lookup + hash_equals(), and accepts the X-Api-Key header only (no query fallback). Its apikey middleware (ApiKeyAuth) gates the versioned /api/v2 group, applying per-IP token-bucket rate limiting that returns 429 + Retry-After when exceeded (local IPs exempt). A server-side proxy console keeps keys out of the browser entirely.

Defense in Depth — at a Glance

  • csrf middleware: constant-time hash_equals(), 419 on failure
  • ✅ HttpOnly / Secure / SameSite session cookies + ID regeneration on login
  • X-Frame-Options: SAMEORIGIN, nosniff, Referrer-Policy, Permissions-Policy
  • ✅ XSS-safe output via the e() helper (ENT_QUOTES, UTF-8)
  • ✅ Segment whitelist + realpath traversal guard (rejects .. / NUL)
  • ✅ PDO prepared statements everywhere
  • .env & app code outside the public web root (www/)
  • secrets:scan CLI + gitleaks CI; X-Api-Key + 429 rate limiting

Request Handling Safeguards

Trusted client IP resolution

Request::ip() is Cloudflare-aware: it reads CF-Connecting-IP, then the first X-Forwarded-For hop, then REMOTE_ADDR — so CSRF failure logs and rate limits attribute the real visitor, not the proxy.

Gated HTTP method override

Request::capture() honors a _method field or the X-HTTP-Method-Override header, but only upgrades a POST to PUT/PATCH/DELETE. A safe GET can never be spoofed into a write, and a 405 is returned when a path matches but the method doesn’t.

🔒 Secure forms & routes — the everyday pattern
<?php // In a form template — emit a CSRF token + escape output ?>
<form method="post" action="/contact">
    <input type="hidden" name="csrf_token"
           value="<?= e(\TiCore\Core\Security::generateCsrfToken()) ?>">
    <input name="name" value="<?= e($old['name'] ?? '') ?>">
</form>

<?php // In routes/web.php — require CSRF verification on the POST ?>
$router->post('/contact', 'ContactController@submit', ['csrf']);

<?php // Protect a whole section behind auth ?>
$router->group(['prefix' => 'admin', 'middleware' => ['auth']], function ($router) {
    $router->get('/dashboard', 'Admin\DashboardController@index');
    $router->post('/posts', 'Admin\PostController@store', ['csrf']);
});

Build on a Secure Foundation

Every TiCore site starts hardened. Read the docs for the full routing and middleware model, run the CLI secrets:scan before you push, or browse the addonsauth, payments, and rest-api-kit all inherit these defaults. See how TiCore compares to other PHP frameworks.

Share: 𝕏 Twitter Facebook LinkedIn
Proton VPN — 10 device connections, audited no-logs
Recommended: Proton Unlimited
VPN, encrypted Drive, encrypted Mail, password manager — one bundle. See the full review →