Defaults & guards
Pressline takes the boring stuff seriously. None of it is optional.
CSRF
Per-session token, generated with random_bytes(32), verified with hash_equals. Required on every POST and AJAX request — submit it in $_POST['csrf_token'] or the X-CSRF-TOKEN header.
// In a form
<?= csrf_field() ?>
// In a fetch()
fetch('/ajax/foo.php', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf]').content },
body: JSON.stringify(payload),
});
Verification failure returns HTTP 403 and a JSON error.
SQL
All queries go through mysqli prepared statements. There is no raw concat anywhere in the codebase; tables are also whitelisted in includes/security.php.
Sessions
Configured in includes/security.php:
cookie_httponly = truesamesite = Laxsecure = truewhen the request is HTTPS- Session regenerated on login
Passwords
Bcrypt (password_hash with PASSWORD_BCRYPT). Verified with password_verify on login. The seed admin password is printed in plain text on the installer's final step — change it.
File uploads
Filename validation strips null bytes and path traversal attempts. MIME and extension whitelists reject the obvious dangerous types — php, phar, sh, aspx, executables, etc. Uploads land in /media/uploads/ outside any include path.
Plugin packages
Plugin zips are statically inspected before extraction. Entries with .., leading /, .git/ or .env are rejected. Sha256 hashes are checked against the registry header.
Roles
Three levels seeded by the installer:
| Level | Role | Can do |
|---|---|---|
| 1 | user | Write articles, upload media |
| 2 | admin | Manage users, categories, settings |
| 3 | dev | Apply schema and self-updates |
The level cutoffs themselves are settings — see admin_level and dev_level in Configuration.
Reporting
Found a vulnerability? Mail security@pressline.app rather than opening a public issue.