PmaControl logo PmaControl
  • Home
  • PmaControl
    • AI Agents 13 on-premise agents
    • Plans Community, Cloud, On-Premise, Premium
    • Documentation Guides, API, architecture
    • Clients 28+ companies
    • FAQ 25 questions / 7 categories
    Databases
    • MariaDB 30 articles
    • MySQL 10 articles
    • Galera Cluster 6 articles
    • MaxScale 3 articles
    • ProxySQL 2 articles
    • Amazon Aurora MySQL 0 article
    • Azure Database 0 article
    • ClickHouse 0 article
    • GCP CloudSQL 0 article
    • Percona Server 0 article
    • SingleStore 0 article
    • TiDB 0 article
    • Vitess 0 article
    Solutions
    • Support 24×7 MariaDB & MySQL emergencies
    • Observabilité SQL Monitoring, alerts, topology
    • Haute disponibilité Replication, failover, Galera
    • Disaster Recovery Backup, restore, RPO/RTO
    • Sécurité & conformité Audit, GDPR, SOC2
    • Migration & upgrade Zero downtime, pt-osc, gh-ost
  • Plans
  • Resources
    • Documentation Technical guides & API
    • FAQ 25 frequently asked questions
    • Testimonials Client feedback & use cases
    • Blog Articles & insights
    • Roadmap Upcoming features
    Areas of expertise
    • Observabilité SQL Monitoring, alerts, Dot3 topology
    • Haute disponibilité Replication, failover, Galera
    • Sécurité & conformité Audit, GDPR, SOC2, ISO 27001
    • Disaster Recovery Backup, restore, RPO/RTO
    • Performance & optimisation Digests, EXPLAIN, tuning
    • Migration & upgrade Zero downtime, pt-osc
    Quick links
    • GitHub Wiki 26 pages — install, engine, plugins
    • Source code Official GitHub repository
    • Support 24×7 MariaDB & MySQL emergencies
    • Book a demo 30 min — real architecture
  • Support 24×7
  • Book a demo
Book a demo
🇫🇷 FR Français 🇬🇧 EN English 🇵🇱 PL Polski 🇷🇺 RU Русский 🇨🇳 ZH 中文
Documentation › Protection CSRF (Origin + jeton scopé)

Protection CSRF (Origin + jeton scopé)

Why protect POST endpoints

Without a session-bound CSRF token, a third-party site can submit a POST to PmaControl while an authenticated user is browsing elsewhere. The browser automatically attaches the session cookies and the action runs without user intent — configuration change, operation trigger, inline update.

Switching from GET to POST blocks passive triggers (preload, crawled link), but it is not enough against an active cross-site CSRF attack.

Shared helper in Glial

From Glial v5.1.40 onwards, CSRF logic and Origin/Referer validation live in the framework and are shared by every PmaControl controller:

  • Glial\Security\Csrf::issueToken() / Csrf::validateToken() — issuance and validation of a token per functional scope.
  • Glial\Http\Request::isSameSite() — compares Origin (priority) then Referer (fallback) against the current origin.
No PmaControl controller duplicates this logic: Worker.php only contains the HTTP orchestration specific to POST /Worker/update.

Flow diagram

CSRF mechanism diagram — server-side token issuance, HTML injection, AJAX request, 4-step validation chain.
CSRF mechanism diagram — server-side token issuance, HTML injection, AJAX request, 4-step validation chain.

Token issuance

Before rendering a view that contains a form or an inline-editable cell, the controller issues a token scoped by feature:

<?php
use Glial\Security\Csrf;

$token = Csrf::issueToken($_SESSION, 'worker.update');
$this->set('csrf_token', $token);
The scope prevents a token issued for worker.update from being replayed on daemon.start. The token is stored in $_SESSION['csrf_tokens'][$scope] and persists until the end of the user session.

Client-side injection

For inline-editable cells (bootstrap-editable), the token is injected via two data-* attributes:

<td class="editable"
    data-name="nb_worker"
    data-csrf-field="csrf_token"
    data-csrf-token="<?= htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8') ?>">
    5
</td>
Values are always escaped via htmlspecialchars(..., ENT_QUOTES, 'UTF-8') to neutralise any HTML/JS injection inside the cell.

The App/Webroot/js/Tree/index.js JS detects the presence of data-csrf-token and automatically appends the token to the POST parameters sent by bootstrap-editable:

$.fn.editable.defaults.params = function (params) {
    var $cell = $(this).closest('[data-csrf-token]');
    if ($cell.length) {
        params[$cell.data('csrf-field')] = $cell.data('csrf-token');
    }
    return params;
};
After an AJAX refresh (for instance via the Daemon), window.pmacontrolInitLineEdit(context) is called again on the reloaded context to bind the new token to the new cells.

Server-side validation chain

Four checks are chained in this exact order, before any SQL is built:

  1. HTTP method — must be POST; any other method returns 405 Method Not Allowed.
  2. Same-site origin — Origin first, then Referer as fallback; null values, protocol-relative URLs and any different scheme/host/port are rejected with 403 Invalid request origin.
  3. CSRF token — hash_equals() comparison between the csrf_token payload field and $_SESSION['csrf_tokens'][$scope]; a missing or invalid token returns 403 Invalid CSRF token.
  4. Payload allowlist — only the fields and types expected for this endpoint; an out-of-list payload returns 400 Invalid worker update payload.
<?php
class Worker
{
    public function update()
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            http_response_code(405);
            return;
        }
        if (!Glial\Http\Request::isSameSite($_SERVER)) {
            http_response_code(403);
            echo 'Invalid request origin';
            return;
        }
        if (!Glial\Security\Csrf::validateToken($_POST, $_SESSION, 'worker.update')) {
            http_response_code(403);
            echo 'Invalid CSRF token';
            return;
        }

        $sql = $this->buildWorkerUpdateSql($_POST);
        if ($sql === null) {
            http_response_code(400);
            echo 'Invalid worker update payload';
            return;
        }

        // SQL exécuté ici uniquement
    }
}
Until all four checks have passed, no SQL is built: defense in depth holds even if one of the locks fails.

Reference — return codes

Code Reason When
405 Method Not Allowed HTTP method other than POST.
403 Invalid request origin Off-site Origin/Referer, Origin: null, protocol-relative URL, or different scheme/host/port.
403 Invalid CSRF token Token missing from payload or not matching $_SESSION['csrf_tokens'][$scope].
400 Invalid worker update payload Field out of allowlist, non-integer value, negative or zero pk.
Never give a mutating endpoint a GET fallback for debugging: it silently disables CSRF protection, which only applies to POST.

Documented exemptions

The CSRF helper covers web POST endpoints only. The following surfaces are explicitly exempted:

  • CLI php glial … — no session cookie, no CSRF surface.
  • Machine-to-machine REST API — authenticated by a separate API token; the Origin/Referer check is disabled for these routes and the exemption is documented per endpoint.
  • Read-only endpoints — converted to GET, therefore outside the CSRF perimeter.

Dedicated tests

CSRF protection is covered by PHPUnit tests that explicitly materialise the historical attack scenario:

./vendor/bin/phpunit tests/Glial/Security/CsrfTest.php
./vendor/bin/phpunit tests/Glial/Http/RequestTest.php
./vendor/bin/phpunit tests/Controller/WorkerUpdateSecurityTest.php
With Origin: https://attacker.test or any other external site, the payload is rejected with 403 Invalid request origin before any write, even when a valid CSRF token is provided — the validation chain stops the request on the Origin check before reaching the token.
On this page
  • Why protect POST endpoints
  • Shared helper in Glial
  • Flow diagram
  • Token issuance
  • Client-side injection
  • Server-side validation chain
  • Reference — return codes
  • Documented exemptions
  • Dedicated tests
← Previous page Next page →
PmaControl
+33 6 63 28 27 47 contact@pmacontrol.com
Legal notice GitHub Contact
Do not wait for an incident to understand your architecture. © 2014-2026 PmaControl — 68Koncept