# Security and Privacy

## Secret inventory

Treat these values as credentials:

- Every private ICS URL
- Every tokenized calendar-provider URL
- `APP_KEY`
- Database password
- Mirror-target bearer tokens
- SMTP credentials
- Urgent webhook URL when it contains a secret
- Administrative password

Do not place these values in Git, screenshots, ticket comments, shared notes, application source, or documentation.

Every private calendar URL previously pasted into chat should be regenerated before production use. Rotation is important because anyone possessing a private ICS URL may be able to read the calendar without another login prompt.

## Encryption at rest

Calendar source URLs and mirror bearer tokens are encrypted with XChaCha20-Poly1305 and associated-data contexts. The application stores a SHA-256 fingerprint separately for exact duplicate detection and token authentication.

The encryption key is stored only in `.env`. Database access alone should not reveal encrypted URLs or tokens. The application displays a masked hostname and shortened path after a feed is saved.

Backups include `.env` so encrypted values remain recoverable. That also makes every backup highly sensitive.

## Web exposure

Only the `public` directory should be the Apache document root. The `.env`, source, database schema, logs, backups, Apps Script template, and Composer files must remain outside the web root.

The included `.htaccess` disables directory indexes, routes requests through the front controller, and adds basic security headers. Keep `APP_DEBUG=false` in production.

Administrative routes require an authenticated session. State-changing forms use CSRF tokens. Login attempts are rate-limited. Session cookies are secure, HTTP-only, SameSite Lax, and use strict session mode.

## ICS retrieval and SSRF protection

Calendar feeds must use HTTPS. URLs with embedded usernames or passwords are rejected. Localhost, `.local`, private, reserved, and unresolvable destinations are rejected unless `ALLOW_PRIVATE_ICS_HOSTS` is deliberately enabled.

Redirects are limited, must remain HTTPS, and are validated again. Conditional ETag and Last-Modified headers are cleared before following a redirect so resource metadata is not forwarded to another target.

Keep `ALLOW_PRIVATE_ICS_HOSTS=false` unless a specific private feed is intentionally required and the network risk has been reviewed.

## Feed data privacy

The application can store event titles, descriptions, locations, URLs, RSVP state, and organizer information when the source provides them. MariaDB backups and server access should therefore be treated as access to all connected calendar details.

Each source has a display privacy mode:

- Full details
- Category only
- Busy plus category

The selected mode is honored by dashboards, digests, conflict messages, change alerts, and incomplete-event warnings. Stored source details remain in the database so conflict and change logic can function.

## Busy-mirror privacy

The mirror API returns only category-based titles, start/end values, all-day state, source/category identifiers, and synchronization fingerprints. It does not return original titles, descriptions, locations, attendees, conference links, or reminder settings.

Google Busy copies are private and carry application-specific private extended properties. The account-local script edits or removes only events with those private properties.

Exclude destination-account sources from their own mirror targets. This is the clearest loop-prevention policy even though managed copies are also filtered during ICS parsing.

## Least privilege

The VPS does not require Google OAuth tokens. It reads only explicitly supplied ICS feeds. Google write access remains inside each destination account's Apps Script project.

Clarkson monitoring does not imply Clarkson write access. Do not attempt to bypass institutional OAuth, Apps Script, or API restrictions.

Use a dedicated MariaDB user with privileges only on the Calendar Control Center database. Run files and cron as the cPanel user, not root.

## Incident response

When a private ICS URL is exposed:

1. Regenerate or reset the private URL at the provider.
2. Replace the source URL through the Calendar Sources screen.
3. Run a manual source synchronization.
4. Confirm the old URL no longer works.
5. Review access and application logs for unexpected retrieval.

When a mirror token is exposed:

1. Rotate the token in Busy-Mirror Targets.
2. Update the destination Apps Script.
3. Run `testConnection`.
4. Review target poll timestamps and user-agent information.

When `APP_KEY` may be exposed:

1. Restrict server and backup access immediately.
2. Take a protected database backup.
3. Inventory every encrypted value.
4. Re-encrypt calendar URLs, target tokens, and encrypted settings under a new key through a controlled migration.
5. Do not simply replace `APP_KEY`; doing so makes existing encrypted values unreadable.

When the administrative password is exposed, use:

```bash
php bin/console user:password scott@thewilhelms.com
```

## Recommended server controls

- AutoSSL with automatic renewal
- Current cPanel, Apache, PHP 8.2/8.3, MariaDB, and operating-system security updates
- cPanel account multi-factor authentication
- SSH keys and disabled password SSH where practical
- Firewall restrictions on administrative services
- Encrypted off-server backups
- File-integrity or malware monitoring
- Fail2ban or equivalent controls for server login services
- Regular review of cPanel, Apache, PHP-FPM, cron, and application logs
