Security

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 = true
  • samesite = Lax
  • secure = true when 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:

LevelRoleCan do
1userWrite articles, upload media
2adminManage users, categories, settings
3devApply 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.