# Security

## Encryption

API keys are stored encrypted in the database using the `OptionalEncrypted` cast:

- `agent_settings.openai_api_key` — Encrypted at rest
- `agent_settings.pinecone_api_key` — Encrypted at rest
- `ai_chat_history.messages` — Encrypted at rest

The cast uses Laravel's encryption facilities (`APP_KEY`). The "Optional" variant handles null/empty values gracefully without throwing decryption errors.

API key values are never exposed in API responses. The `GET /admin/agent/settings` endpoint returns only boolean flags (`has_openai_key`, `has_pinecone_key`) to indicate whether keys are configured.

## Rate Limiting

The `POST /agent/chat` endpoint is protected by a rate limiter configured in `AgentServiceProvider`:

```php
RateLimiter::for('agent-chat', function ($request) {
    return Limit::perMinute(10)->by($request->ip());
});
```

- **Limit:** 10 requests per minute
- **Key:** Client IP address
- Exceeding the limit returns a `429 Too Many Requests` response

## SQL Injection Prevention

The `GetProducts` and `GetCategory` tools escape SQL wildcards in user-supplied parameters before passing them to `LIKE` queries:

```php
$escapedName = str_replace(['%', '_'], ['\\%', '\\_'], $name);
$query->where('name', 'like', '%' . $escapedName . '%');
```

This prevents users from injecting wildcard patterns (`%`, `_`) to manipulate query behavior. All database queries use Eloquent's parameterized bindings.

## CSRF Protection

CSRF verification is **disabled platform-wide** by project convention — `VerifyCsrfToken` is commented out of the `web` middleware group in `app/Http/Kernel.php`, so the agent routes do not enforce CSRF tokens. (The chat widget and admin SPA still send an `X-CSRF-TOKEN` header when a `csrf-token` meta tag is present, but it is not validated server-side.)

Request authenticity for the chat endpoints instead relies on the encrypted, HttpOnly `agent_guest_id` cookie (see [Guest Authentication](#guest-authentication)) and the per-IP rate limiter; admin endpoints are protected by the `auth` middleware.

## Guest Authentication

The `AgentAnonymous` middleware (`Http/Middlewares/AgentAnonymous.php`) provides session identity for unauthenticated users:

1. Reads the `agent_guest_id` cookie from the request (automatically decrypted by Laravel's `EncryptCookies` middleware)
2. If the cookie is missing or its decrypted value is not a valid UUID, generates a fresh UUID server-side and queues it back on the response as an HttpOnly, SameSite=Lax, encrypted cookie
3. Creates an in-memory `ChatGuestUser` (extends `Authenticatable`, no database table) keyed on the cookie value
4. Sets the user via `Auth::setUser()`

The guest ID is **never** sourced from a request header or body field. Any client-supplied `X-Guest-ID` header or `guestId` form field is ignored — only the server-issued encrypted cookie is trusted. This closes a prior vulnerability where a visitor could impersonate another guest by sending their UUID in a header or form field.

Because the cookie is HttpOnly and encrypted with `APP_KEY`, JavaScript cannot read or forge it, and a tampered value decrypts to null and is rejected.

## Input Validation

### Chat Messages

```php
$request->validate([
    'message' => 'required|string|max:2000',
]);
```

Messages are limited to **2,000 characters**.

### Audio Files

```php
$request->validate([
    'audio_file' => 'required|file|mimes:mp3,wav,aac,mp4,webm',
]);
```

Only specific audio formats are accepted.

### Context Uploads

```php
$request->validate([
    'file' => 'required|file|mimes:pdf,txt|max:10240',
]);
```

Files are limited to **PDF** and **TXT** formats with a maximum size of **10 MB** (10,240 KB).

## Daily Token Budget Enforcement

`AgentSettingsController::getTokenUsage()` checks daily token consumption before every chat request:

1. Queries `ai_chat_token_usage` for today's date
2. Compares `input_tokens` and `output_tokens` against the limits in `agent_settings`
3. If either limit is exceeded, the chat returns an unavailability message instead of querying the AI

This prevents runaway API costs. Limits are configurable in the admin panel (defaults: 3,000,000 input / 1,000,000 output tokens per day).

## Channel Authorization

WebSocket channels use Laravel's private channel authorization:

- Channel pattern: `agent-channel.{guestId}`
- The `SocketServiceProvider` applies `['web', 'agent.anonymous']` middleware to broadcasting auth routes
- The `AgentAnonymous` middleware authenticates the guest user from the `agent_guest_id` encrypted cookie (see Guest Authentication above)
- Channel authorization in `routes/channels.php` verifies the authenticated user's ID matches the `{guestId}` in the channel name

Because the authenticated user's ID comes from the server-issued cookie rather than an attacker-controlled header, a visitor can only subscribe to the channel whose name matches their own cookie value, preventing eavesdropping on other sessions.
