# Phase 2 security and operations

## Local installation

Phase 2 is enabled for the existing Zentrik tenant. It uses the People & Compliance plan and the employees, documents and compliance entitlements. Existing accounts and MFA enrollment remain intact. No sample employee or document data was added to the working database. All application tables use InnoDB; connection/session and Docker defaults remain enforced.

The running Windows application defaults to the private `vault` disk and Windows CurrentUser DPAPI key protection. The OS protects the wrapping keys; neither MySQL nor source contains a document master key. An individual file key is wrapped with DPAPI using a hash of its tenant/document/version identity as additional entropy. Vault Transit is the production/dedicated Linux adapter. APP_KEY is never used as a document wrapping key.

ClamAV is mandatory for publication. The local daemon address defaults to `127.0.0.1:3310`. A scanner outage produces encrypted, inaccessible quarantine records. It is not treated as a clean scan. No document key or file content is written to notifications, access logs or application error messages.

## Enable security services

For the Windows local application, run a real ClamAV daemon on loopback port 3310 with current official virus definitions. Alternatively, with a working Docker engine:

```sh
docker run -d --name zentrik-clamav -p 127.0.0.1:3310:3310 -v zentrik-clam-definitions:/var/lib/clamav clamav/clamav:1.4
```

Wait for the scanner's definitions and health before retrying quarantine scans. Keep ClamAV patched and its database current. A protocol test fixture exists only under tests; it is not a malware engine and cannot be selected by application configuration.

For full Compose deployment, use the production environment and startup instructions in OPERATIONS.md. The app uses private S3-compatible storage, ClamAV on the private network, and a TLS Vault service. The MinIO bucket must be private and the application key restricted to that bucket. Do not expose the scanning service to an untrusted network.

Provision a derived Vault Transit AES key named `zentrik-documents`. Grant the application's Vault token only encrypt/decrypt for that key and use a renewable short-lived identity/token in deployment. Keep administrative rotate/backup/restore privileges separate. Set:

```dotenv
DOCUMENT_KEY_PROVIDER=vault
DOCUMENT_DISK=s3
VAULT_ADDR=https://vault.example.internal:8200
VAULT_TOKEN=your-runtime-secret
VAULT_TRANSIT_KEY=zentrik-documents
CLAMAV_HOST=clamav
CLAMAV_PORT=3310
```

The adapter refuses plain HTTP Vault URLs. Ciphertext records store the Transit key name and version, allowing historical versions to be unwrapped after master-key rotation while the old key versions remain available.

## OCR

OCR is optional assistance and never marks a document verified. The service accepts only already-scanned PDF/JPEG/PNG data. It limits file size to 10 MB, PDFs to 10 pages, subprocess duration, extracted text size, container CPU/memory/process counts, and temporary storage. Its root filesystem is read-only, capabilities are removed, and it is on a separate internal network. Temporary plaintext exists only inside an ephemeral tmpfs directory and is removed after processing. Retained OCR text is encrypted with an independent key and purpose-bound authenticated context.

Set a strong OCR_TOKEN in `.env.production`, then start the optional service:

```sh
docker compose --env-file .env.production --profile ocr up -d --build ocr
```

The app/worker and OCR service must share the private network. For the local Windows PHP process, an operator may temporarily expose the OCR service on loopback only and set OCR_URL to that local address; no public OCR endpoint is provided. Never direct private files to an untrusted OCR service.

## Background work and retries

Run a queue worker and scheduler for automatic scan retries, expiry reminders, overdue renewal escalations, notification retries and digests:

```text
php artisan queue:work --tries=5 --timeout=400
php artisan schedule:work
```

Set DB_QUEUE_RETRY_AFTER=450 for the local database queue or REDIS_QUEUE_RETRY_AFTER=450 for the production Redis queue. The reservation must exceed the longest worker timeout. Scan batches are bounded to 10 files. Reminders use each tenant's local calendar date, configurable days-before thresholds, recipient roles, overdue escalation days and local digest hour. Database unique delivery keys prevent duplicate bell/reminder records. Email delivery is at-least-once; a crash after SMTP accepts a message but before commit may result in a duplicate email.

Manual operations:

```text
php artisan zentrik:documents:scan
php artisan zentrik:compliance
php artisan zentrik:outbox
php artisan zentrik:documents:rotate zentrik --limit=100
php artisan zentrik:documents:purge zentrik
```

Rotation re-encrypts selected file and OCR content with fresh per-object keys/nonces. It does not delete historical business versions. Retain old Vault master-key versions until recovery is verified. Migration between DPAPI and Vault providers requires a controlled migration using both key services; changing the environment variable alone will not recover old envelopes.

## Retention and deletion

Legal holds block archival and purge. Archival removes download access and starts at least the document type's retention period; verification can extend retention beyond the expiry date. Retention cannot be shortened by replacing a file or verifying newer metadata. Purge operates only on archived records strictly past their retained-through date and clears file/OCR key metadata after private object deletion. Business and access audit tombstones remain. Purging is an explicit operator command, not an automatic destructive schedule.

For versioned object stores, configure noncurrent version retention/deletion and legal holds in the storage layer. Application deletion is not a claim of physical erasure from disks, old bucket versions, snapshots or backups. Backup retention must follow the same legal hold and retention policies.

## Recovery

Back up the MySQL metadata, encrypted object storage, APP_KEY, and wrapping-key service independently. APP_KEY alone cannot recover document files. For Vault, preserve encrypted Vault snapshots, seal/unseal recovery material and all required Transit key versions. For DPAPI, preserve the Windows service identity's recoverable profile/master keys and account/domain recovery material; a SQL/object backup on a different Windows user or machine is insufficient. Test recovery before changing service identities or replacing Windows installations.

Restore into an isolated stack, restore matching key-service material and object versions, verify ciphertext authentication and tenant isolation, then reconcile outbox/notification state before enabling outbound email. Do not use a new empty Vault key with the same name as a recovery substitute.

The pre-Phase-2 SQL backup in the task's work directory was encrypted using real Windows DPAPI and restored through memory into an isolated MySQL recovery database. Its 26 InnoDB tables, tenant and account counts were verified. No plaintext dump was saved. This establishes recovery of that pre-upgrade database on the current identity; it is not a substitute for a production Vault/S3/full-stack disaster-recovery drill.

## Technical references

The implementations follow the [official ClamAV INSTREAM protocol](https://docs.clamav.net/manual/Usage/ClamdProtocol.html), [Vault Transit API](https://developer.hashicorp.com/vault/api-docs/secret/transit), and [Windows DPAPI documentation](https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection).
