File storage
The encrypted file store for files and batches, on Amazon S3, MinIO, RustFS, other S3-compatible stores or local disk, and its encryption keys.
The gateway keeps the files it owns (Files API uploads, batch inputs and results) in an encrypted object store. It is off by default. While it is off, /v1/files and batches don't work, and the kinds of file that hold customer content can't be turned on in Admin.
Every object is encrypted by the gateway before the backend sees it, whatever the backend, and the database holds metadata only.
Choose a backend
GATEWAY_FILE_STORE=s3
GATEWAY_S3_BUCKET=example-university-ai-files
GATEWAY_S3_PREFIX=prod
GATEWAY_S3_REGION=eu-west-1
GATEWAY_S3_AUTH=aws:role:arn:aws:iam::123456789012:role/ai-gateway-files
GATEWAY_FILE_ENCRYPTION_KEYS_ENV=GATEWAY_FILE_KEYS
GATEWAY_FILE_KEYS=k2026a:<base64 of 32 random bytes>The identity needs s3:PutObject, s3:GetObject, s3:DeleteObject and s3:AbortMultipartUpload on the prefix. Turn on Block Public Access, and add a lifecycle rule that aborts incomplete multipart uploads after a day. With bucket versioning, expire noncurrent versions quickly, or deleted files linger.
Other S3-compatible stores (Cloudflare R2 with region auto, Wasabi, Ceph RGW, Google Cloud Storage's interoperability API) are configured the same way, with an allowlisted endpoint and static keys. MinIO and RustFS are exercised by the project's automated tests; the others are not. See Configuration for every variable.
The S3 client uses one attempt per request, follows no redirects, ignores proxy variables and AWS_ENDPOINT_URL*, and avoids newer AWS-only checksum and session features that older S3-compatible servers reject.
Encryption keys
GATEWAY_FILE_ENCRYPTION_KEYS_ENV names a variable whose value is a list of master keys:
GATEWAY_FILE_KEYS=k2027a:<new key>,k2026a:<old key>- Each key id is 1 to 32 characters of
A-Z a-z 0-9 . _ -; each key is base64 of exactly 32 random bytes (openssl rand -base64 32). Up to 16 keys. - The first key encrypts new objects; the others only decrypt.
- Each object gets its own random data key, wrapped by the master key, and is sealed with AES-256-GCM in 64 KiB chunks bound to the object's name. Any tampering, truncation or reordering is detected.
Losing a key loses its files
Keep the key list in your secret manager and your recovery plan, separately from database and object backups. Without the keys, stored objects can't be read by anyone.
Rotate a key
- Generate a new key and put it first; keep the old one after it. Restart every replica.
- Run
open-model-gateway files verify. Itsby_key_idcounts show how many live files still use the old key. Files expire with their retention (365 days at most; branding files never expire). - Remove the old key only when its count reaches 0. Removing it earlier makes those files unreadable (
key_unavailable).
If a master key leaks, add a new first key at once, then shorten retention and run files sweep --once to remove files under the old key before removing it.
Retention and the sweeper
What may be stored, and for how long, is set in Admin › Settings › Data & privacy › Storage (see Settings). serve sweeps every minute: it deletes expired files and abandoned uploads (after a day), at most 200 per run, safely across replicas. Database rows are never deleted: a deleted file keeps its metadata, without its name.
open-model-gateway files sweep --once [--limit 1000] # one sweep now
open-model-gateway files verify [--limit 100000] # read-only check; non-zero exit on problemsfiles verify reports missing objects, size mismatches, files under another backend, key ids no longer configured, stale uploads and failed deletions. It doesn't list the bucket, so stray objects without a row aren't found: use a bucket lifecycle rule or inventory for those.
Test it
In Settings › Data & privacy, Test storage writes, reads and deletes a small object. Run it after any change, then allow the kinds of file you need.