# Security

Security in Celena is not optional — it's a set of mandatory practices. Here's what to use and what not to do.

## Input validation

Any user input goes through `Celena\Core\Security\Filter`:

```php
use Celena\Core\Security\Filter;

$name  = Filter::string((string) $request->input('name'));
$slug  = Filter::urlSlug((string) $request->input('slug'));
$email = Filter::email((string) $request->input('email'));
$id    = (int) $request->input('id');
```

Don't trust `$_GET`/`$_POST` directly — read via `$request->input()`/`query()` and filter.

## SQL injection

Prepared statements and `QueryBuilder` only. **Concatenating values into SQL is forbidden.**

```php
// CORRECT
$conn->builder('users')->where('email', '=', $email)->first();
$conn->run('SELECT * FROM ' . $conn->table('users') . ' WHERE id = ?', [$id]);

// DANGEROUS — never do this
$conn->run("SELECT * FROM users WHERE email = '$email'");
```

## XSS — HTML output

In templates, variables are escaped automatically (`{var}`). Raw output (`{var|raw}`) is only for trusted HTML. In PHP use the `e()` helper:

```php
echo e($userText);          // safe
echo $userText;             // DANGEROUS if it's user input
```

The core's Markdown renderer escapes source HTML — user markdown cannot execute `<script>`.

## CSRF

Forms and AJAX requests are protected with a token. In a template:

```html
<form method="post">{csrf} … </form>     <!-- hidden _csrf field -->
{csrf format="meta"}                       <!-- meta tag for fetch/AJAX -->
```

On the server:

```php
use Celena\Core\Security\Csrf;
if (!Csrf::validate((string) $request->input('_csrf'))) {
    return Response::json(['error' => 'csrf'], 419);
}
```

## Authentication and roles

- Passwords are stored as Argon2id (`Celena\Core\Security\Password`). They are never stored in plain text.
- Login — `/admin/login`. Users live in the `cl_celena_users` table.
- Roles: `admin` (everything), `editor`, `author`, `user`, `customer`. Permission check:

```php
use Celena\Core\Security\Permission;
if (!Permission::can($user, 'news.update')) {
    return Response::redirect('/admin/login');
}
```

Admin controllers check `$this->auth->user()` at the start of an action.

## Rate limiting

`Celena\Core\Security\RateLimiter` protects sensitive endpoints (login, lead intake, public API) from brute force:

```php
$limiter = $container->make(RateLimiter::class);
if (!$limiter->attempt('login_' . $request->ip(), 5, 60)) {  // 5 attempts per minute
    return Response::json(['error' => 'rate_limited'], 429);
}
```

## File and zip uploads

- Check the MIME/extension of uploaded files; don't serve them as executable.
- The plugin installer checks archives for ZIP-Slip (escaping the folder), blocks `.phar`, and validates `manifest.json`. Repeat the same checks when unpacking any user archive.

## CSP and external resources

The Content Security Policy allows scripts only from `'self'` and trusted domains (Google services). **External CDNs (jQuery, fonts, widgets) are blocked** — host assets locally. There is no jQuery on the site.

## .htaccess and the server

Don't use `<Directory>` or `php_flag` directives in `.htaccess` with PHP-FPM — it breaks Apache (a 500 error). Static-file cache headers are configured at the `nginx` level, not in `.htaccess`.
