Skip to content

Outpost software updates

The Arbitex Outpost supports two update methods:

  1. Signed bundle update API (shipped in outpost-0034) — A built-in update manager that downloads, verifies, and stages update bundles via the outpost admin API. Bundles are Ed25519-signed tarballs. Recommended for connected deployments.

  2. Docker image update — Pull and recreate the outpost container with a new image tag. Used for major upgrades, Kubernetes deployments, and environments where bundle-based updates are not configured.

Source: outpost/services/software_update.py, outpost/admin/routes.py


Starting with outpost-0034, the outpost includes a SoftwareUpdateManager service that manages the full update lifecycle: version check → bundle download → signature verification → staging. Updates are applied by restarting the outpost process — there is no auto-apply.

Variable Required Description
SOFTWARE_UPDATE_RELEASE_URL Yes URL to the JSON release manifest. When set, the update manager is initialized.
SOFTWARE_UPDATE_SIGNING_KEY Yes Base64url-encoded 32-byte Ed25519 public key used to verify bundle signatures. Fail-closed: updates are rejected if this key is absent or the signature does not match.
UPDATE_STAGE_DIR No Directory for staged bundle files (default: /tmp/outpost-update-stage).
OUTPOST_VERSION No Current running version, for version comparison in the manifest check.

If SOFTWARE_UPDATE_RELEASE_URL is not set, all update API endpoints return a 503 with "Software update not configured".

The update process has three explicit steps, each triggered by an admin API call:

IDLE → CHECKING → AVAILABLE → DOWNLOADING → STAGED
↓ (restart)
APPLIED

If any step fails, the status transitions to ERROR. Re-trigger from the beginning (/check first).

Terminal window
POST /admin/api/updates/check
Authorization: Basic admin:<OUTPOST_EMERGENCY_ADMIN_KEY>

Fetches the release manifest from SOFTWARE_UPDATE_RELEASE_URL. The manifest must include version, download_url, and signature_url. Compares the manifest version against OUTPOST_VERSION.

Response:

{
"status": "available",
"current_version": "0.33.0",
"latest_version": "0.34.0",
"release_notes": "outpost-0034: signed bundle update API, policy simulator improvements",
"download_progress": null,
"staged_at": null,
"staged_version": null,
"staged_path": null,
"signature_verified": null,
"error": null
}

If already at the latest version, status is "idle".

Terminal window
POST /admin/api/updates/download
Authorization: Basic admin:<OUTPOST_EMERGENCY_ADMIN_KEY>

Downloads the bundle tarball and its Ed25519 signature file concurrently from the URLs in the manifest. Verifies the signature using the SOFTWARE_UPDATE_SIGNING_KEY public key.

Signature verification:

  • Uses the cryptography library’s Ed25519PublicKey.verify().
  • Fail-closed: if SOFTWARE_UPDATE_SIGNING_KEY is missing, the key is invalid, or the signature does not match, the download is rejected and status is set to "error". The bundle is never staged with a failed or missing signature.

Response on success:

{
"status": "staged",
"current_version": "0.33.0",
"latest_version": "0.34.0",
"download_progress": 1.0,
"staged_at": 1741824000.0,
"staged_version": "0.34.0",
"staged_path": "/tmp/outpost-update-stage/outpost-0.34.0.tar.gz",
"signature_verified": true,
"error": null
}

Response on signature failure:

{
"status": "error",
"error": "Signature verification failed — bundle rejected (fail-closed)",
"signature_verified": null
}
Terminal window
GET /admin/api/updates/status
Authorization: Basic admin:<OUTPOST_EMERGENCY_ADMIN_KEY>

Returns the current update manager state. Use this to poll during a download or to confirm staging before applying.

Field Description
status idle / checking / available / downloading / staged / error
current_version Running outpost version
latest_version Version from the last manifest fetch
release_notes Release notes from the manifest
download_progress Float 0.0–1.0 during download, 1.0 when staged
staged_at UNIX timestamp when the bundle was staged
staged_version Version of the staged bundle
staged_path Filesystem path to the staged .tar.gz
signature_verified true if the staged bundle passed signature verification
error Error message if status == "error"

No auto-apply: once status == "staged" and signature_verified == true, apply the update by restarting the outpost process:

Terminal window
# Docker Compose
docker compose -f docker-compose.outpost.yml restart outpost
# Kubernetes
kubectl rollout restart deployment/arbitex-outpost -n arbitex

On startup, the outpost extracts the staged bundle from staged_path and loads the new binary. After restart, verify with GET /admin/api/updates/statuscurrent_version should match staged_version and status should be "idle".

Air-gap update procedure (manual bundle placement)

Section titled “Air-gap update procedure (manual bundle placement)”

In air-gap deployments (OUTPOST_AIRGAP=true) or networks where the outpost cannot reach the release manifest URL, place bundles manually:

  1. Obtain the bundle from your Arbitex account team. The bundle package contains:

    • outpost-{version}.tar.gz — the update tarball
    • outpost-{version}.tar.gz.sig — the Ed25519 signature file
  2. Copy the bundle to the stage directory:

    Terminal window
    # Default stage dir
    cp outpost-0.34.0.tar.gz /tmp/outpost-update-stage/
    cp outpost-0.34.0.tar.gz.sig /tmp/outpost-update-stage/
  3. Trigger download (the download step also performs signature verification even for manually placed bundles, as long as the files exist at the manifest URLs or in the stage directory):

    For pure air-gap with no manifest server, use the direct bundle injection endpoint if available, or restart the outpost with the UPDATE_STAGE_DIR environment pointing to the bundle directory. The update manager will pick up the pre-placed bundle on startup.

  4. Verify signature before restart:

    Terminal window
    # Manually verify using the same Ed25519 key
    # (requires cryptography package)
    python3 - <<'EOF'
    import base64
    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    key_b64 = "<SOFTWARE_UPDATE_SIGNING_KEY>"
    raw_key = base64.b64decode(key_b64 + "==")
    public_key = Ed25519PublicKey.from_public_bytes(raw_key)
    bundle = open("outpost-0.34.0.tar.gz", "rb").read()
    sig = open("outpost-0.34.0.tar.gz.sig", "rb").read()
    public_key.verify(sig, bundle)
    print("Signature valid")
    EOF
  5. Restart the outpost to apply the staged bundle.



The outpost sends a heartbeat to the Platform management plane every 120 seconds via mTLS. The heartbeat payload includes the current running version:

{
"version": "0.1.0",
"uptime": 3600,
"policy_version": "abc123",
...
}

The Platform heartbeat response includes a latest_version field:

{
"latest_version": "0.2.0"
}

When the running version differs from latest_version, the outpost logs a warning:

WARNING outpost.heartbeat: Outpost version outdated: running=0.1.0 latest=0.2.0 — update recommended

The outpost admin status API also exposes this signal:

Terminal window
GET /admin/api/status

Response:

{
"outpost_id": "op_01abc123",
"policy_version": "abc123",
"update_available": true,
"latest_version": "0.2.0",
"uptime_seconds": 7200,
...
}

update_available: true means the Platform has signaled that a newer version is available. The admin panel displays an update notification banner when this field is true.


Open the outpost admin panel at http://localhost:8301 (or your outpost admin URL). If an update is available, a notification banner appears at the top of the Status page:

Update available: Outpost 0.2.0 is available (running 0.1.0). See the update guide to apply.

Poll the status endpoint directly:

Terminal window
curl -s \
-u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \
http://localhost:8301/admin/api/status \
| jq '{update_available, latest_version, policy_version}'

Policy sync status and signature verification

Section titled “Policy sync status and signature verification”

The sync-status endpoint shows the current policy bundle signature state alongside version information:

Terminal window
curl -s \
-u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \
http://localhost:8301/admin/api/sync-status \
| jq '{bundle_version, latest_version, bundle_signature_valid, bundle_verified_at}'
Field Description
bundle_version Policy bundle version currently loaded
latest_version Latest outpost software version reported by Platform
bundle_signature_valid Whether the loaded policy bundle passed signature verification
bundle_verified_at UNIX timestamp of the most recent signature check

bundle_signature_valid: true confirms the policy bundle was verified against the Platform’s signing key. This is separate from the software update — it verifies the policy configuration, not the container image.


The outpost runs as Docker Compose services. A software update involves pulling the new image and recreating the containers.

Step 1 — Back up the current configuration

Terminal window
cp .env .env.bak

Step 2 — Pull the new image

Terminal window
docker compose -f docker-compose.outpost.yml pull

This pulls arbitex-outpost:latest (or the pinned tag in your compose file) without stopping the running containers.

Step 3 — Verify the image signature

Arbitex signs all release images with Docker Content Trust (DCT). Verify before applying:

Terminal window
# Enable content trust for this command
DOCKER_CONTENT_TRUST=1 docker pull arbitex/outpost:0.2.0

If the image has not been signed or the signature does not match, Docker will refuse to pull it and print an error. Do not proceed if signature verification fails — contact Arbitex support.

For air-gapped deployments, see Signature verification in air-gap mode below.

Step 4 — Apply the update with a rolling restart

Terminal window
docker compose -f docker-compose.outpost.yml up -d --force-recreate

This recreates the containers using the newly pulled image. Existing connections to the proxy are gracefully terminated; new connections are handled by the updated container.

Step 5 — Verify the update

Wait 30 seconds for the outpost to fully initialize, then confirm the running version:

Terminal window
curl -s \
-u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \
http://localhost:8301/admin/api/status \
| jq '{update_available, latest_version}'

Expected after a successful update:

{
"update_available": false,
"latest_version": "0.2.0"
}

Also check the health endpoint:

Terminal window
curl -sf http://localhost:8300/health && echo "healthy"

If you run the outpost on Kubernetes via the Arbitex Helm chart:

Step 1 — Update the image tag in values

values-prod.yaml
image:
tag: "0.2.0"

Step 2 — Apply the Helm upgrade

Terminal window
helm upgrade arbitex-outpost arbitex/outpost \
-f values-prod.yaml \
--namespace arbitex

Helm performs a rolling update by default — old pods are kept running while new pods start and pass readiness checks.

Step 3 — Verify

Terminal window
kubectl rollout status deployment/arbitex-outpost -n arbitex
kubectl exec -n arbitex deployment/arbitex-outpost -- \
curl -s -u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} http://localhost:8301/admin/api/status \
| jq .latest_version

In air-gap deployments (OUTPOST_AIRGAP=true), the outpost does not connect to the Platform management plane and cannot receive heartbeat responses. Update detection and application must be done manually.

Contact Arbitex support or your account team to receive the air-gap update bundle for the target version. The bundle contains:

  • arbitex-outpost-{version}.tar — the Docker image archive
  • arbitex-outpost-{version}.sha256 — SHA-256 checksum file
  • arbitex-outpost-{version}.sig — Ed25519 image signature
  1. Import the Arbitex release signing public key (provided by your Arbitex account team):

    Terminal window
    # Import the public key
    gpg --import arbitex-release-signing.pub
  2. Verify the signature on the image archive:

    Terminal window
    gpg --verify arbitex-outpost-0.2.0.sig arbitex-outpost-0.2.0.tar

    A successful verification prints:

    gpg: Good signature from "Arbitex Release Signing Key <[email protected]>"

    Do not proceed if the signature is invalid or the key cannot be verified.

  3. Verify the checksum:

    Terminal window
    sha256sum -c arbitex-outpost-0.2.0.sha256

    Expected output: arbitex-outpost-0.2.0.tar: OK

Terminal window
docker load < arbitex-outpost-0.2.0.tar

Updating the policy bundle (air-gap policy sideload)

Section titled “Updating the policy bundle (air-gap policy sideload)”

Air-gap outposts load the policy bundle from a local volume rather than syncing from the Platform. To update the policy bundle:

  1. Obtain the new policy_bundle.json from your Arbitex account team (delivered as a signed archive using the same GPG key).

  2. Verify the signature on the bundle archive.

  3. Place the new bundle at the configured path:

    Terminal window
    # Default path (override with AIRGAP_POLICY_PATH)
    cp policy_bundle.json /opt/arbitex/policies/policy_bundle.json
  4. Restart the outpost to load the new bundle:

    Terminal window
    docker compose -f docker-compose.outpost.yml restart
  5. Verify the bundle loaded:

    Terminal window
    curl -s \
    -u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \
    http://localhost:8301/admin/api/sync-status \
    | jq '{bundle_version, bundle_signature_valid}'
  1. Tag the loaded image to match the compose file’s image reference:

    Terminal window
    docker tag arbitex-outpost:0.2.0 arbitex-outpost:latest
  2. Recreate the containers:

    Terminal window
    docker compose -f docker-compose.outpost.yml up -d --force-recreate
  3. Verify as in the standard update steps above.


The outpost verifies policy bundle signatures using a trusted Platform public key embedded at build time. In connected mode, the Platform signs bundles and the outpost verifies on each sync. In air-gap mode, the same verification runs when a new bundle is loaded.

bundle_signature_valid in the sync-status response reflects the most recent verification result. If this field is false after loading a new bundle, the bundle was not signed with the expected key — do not use it. Contact Arbitex support.


If the updated outpost is unhealthy or causing errors, roll back by specifying the previous image tag.

Step 1 — Edit the compose file to pin the previous version

services:
outpost:
image: arbitex/outpost:0.1.0 # previous version

Step 2 — Pull and recreate

Terminal window
docker compose -f docker-compose.outpost.yml pull
docker compose -f docker-compose.outpost.yml up -d --force-recreate

Step 3 — Verify

Terminal window
curl -s \
-u admin:${OUTPOST_EMERGENCY_ADMIN_KEY} \
http://localhost:8301/admin/api/status \
| jq .update_available
# Expected: true (since the rolled-back version is older than latest)

After rollback, update_available will be true again — that is expected. Investigate the issue before re-applying the update.

Terminal window
kubectl rollout undo deployment/arbitex-outpost -n arbitex
kubectl rollout status deployment/arbitex-outpost -n arbitex

This rolls back to the previous ReplicaSet. Helm history is also available:

Terminal window
helm history arbitex-outpost -n arbitex
helm rollback arbitex-outpost <revision> -n arbitex

Before applying any update:

  • Read the release notes for the target version (available at https://docs.arbitex.ai/changelog/outpost).
  • Verify the image signature before loading.
  • Back up .env and any local configuration.
  • Test the update in a non-production outpost first if possible.
  • Confirm bundle_signature_valid: true after restart.
  • Confirm update_available: false after restart (connected mode) or check the version field directly (air-gap mode).
  • Run a test request through the proxy to confirm DLP and policy evaluation are functioning.


The Outpost manages its own update lifecycle through one of three update modes:

For most deployments, the safest Outpost is the one that is already patched. When a Critical or High severity CVE is fixed in an Outpost release, the gap between “a fix exists” and “your Outpost is running it” is pure exposure. auto mode closes that gap on your behalf — verified, health-gated, and confined to a window you choose — so you get SaaS-equivalent patch latency without giving up control of your data plane.

Every mode applies the same signature verification before any new version runs. The difference between the modes is who triggers the rollout and when, not whether patches are verified.

Mode Behavior Best for
auto (default) The embedded Updater daemon polls the signed Arbitex release feed. When a signed Critical/High CVE patch is published, it pulls the new image digest and rolls the Outpost’s own pods within your configured maintenance window. The rollout is a single-pod canary first, health-gated, with automatic rollback on failure. SaaS-equivalent and standard-compliance customers; most self-hosted enterprises.
notify The Updater polls the feed and flags available updates in the admin UI plus an email or Slack alert — but applies nothing on its own. You click Apply to trigger the same canary → rollout flow used by auto. Regulated customers who require a change-control review before any production change.
manual No polling and no telemetry. You pull signed patches yourself and apply them with the Outpost CLI. The patch goes through the same signature verification; only the transport is human-driven. Air-gapped deployments and strict-compliance regimes.

In auto mode, updates apply only inside a maintenance window you configure. Outside the window, an available patch is staged and waits — it is never applied to a running pod at an unexpected time.

auto_update:
window: "Sun 02:00-04:00 UTC"

The window is expressed in UTC. Choose a low-traffic period that suits your operations.

When an update is applied — whether triggered automatically in auto mode or by clicking Apply in notify mode — the Outpost rolls out progressively:

  1. Canary — a single pod is updated to the new digest first.
  2. Health gate — the canary pod must pass its health and readiness checks for a defined period before the rollout continues.
  3. Fleet rollout — once the canary is healthy, the remaining pods are updated.
  4. Automatic rollback — if the canary fails its health gate, the rollout halts and the Outpost automatically reverts to the previous known-good version.

In auto mode, the outcome of every rollout (success or failure) is reported back to Arbitex so your account team has visibility into fleet patch health.

You can change the update mode at any time from the Outpost admin UI. The customer dashboard shows your current and available versions, update history, last patch latency, and the active mode setting.

Air-gapped Outposts run in manual mode: the Outpost does not poll the release feed and emits no update telemetry. Retrieve signed patches yourself and apply them with the CLI. Verification is identical to the connected modes — only the transport differs.

  1. Download the signed patch for the target version from download.arbitex.ai onto an internet-connected workstation.

  2. Transfer the patch to the Outpost host through your approved channel (SCP, USB media, etc.).

  3. Apply the patch with the Outpost CLI:

    Terminal window
    arbitex-outpost update --apply ./outpost-<ver>.tar.gz.sig

If the signature does not verify against the embedded trust root, the apply step fails and the current version keeps running — the same fail-closed behavior as the connected modes.


All upgrade operations are orchestrated through the Outpost admin API with Ed25519 signature verification and automatic rollback on failure.

Before upgrading, verify that your runtime environment meets the minimum requirements. The Outpost checks these automatically during the upgrade preflight step.

Component Min version Recommended Required Notes
Python 3.12.0 3.12.4 Yes Python runtime
FastAPI 0.115.0 0.115.0 Yes ASGI web framework
Uvicorn 0.30.0 0.30.0 Yes ASGI server
Pydantic 2.5.0 2.9.0 Yes Config validation (v2)
httpx 0.27.0 0.27.0 Yes Async HTTP client for platform communication
cryptography 43.0.0 44.0.0 Yes TLS, Ed25519 signatures, cert verification
PyJWT 2.12.0 2.12.1 Yes JWT token verification
opentelemetry-api 1.27.0 1.27.0 No OpenTelemetry tracing and metrics
prometheus-client 0.21.0 0.21.0 No Prometheus metrics export
spaCy 3.7.0 3.8.0 No NER scanning (Tier 2 DLP)
onnxruntime 1.18.0 1.19.0 No DeBERTa ONNX inference (Tier 3 DLP)

Check compatibility at any time:

GET /admin/api/compat
Authorization: Bearer <admin-token>

The response includes per-component results with pass / warn / fail / skip statuses. An overall: "fail" result means the Outpost may not function correctly after upgrade — resolve all failing components before proceeding.

Before starting any upgrade:

  1. Verify configuration — ensure SOFTWARE_UPDATE_RELEASE_URL and SOFTWARE_UPDATE_ED25519_KEY are set. (SOFTWARE_UPDATE_SIGNING_KEY is a legacy alias for SOFTWARE_UPDATE_ED25519_KEY.)
  2. Check disk space — the preflight step requires at least 500 MB free in the staging directory:
    Terminal window
    df -h /tmp/outpost-update-stage
  3. Verify current healthGET /admin/api/upgrade/status should show phase idle with no previous failed upgrade.
  4. Drain active connections — if your deployment uses a load balancer, drain the Outpost instance from the pool before upgrading.
  5. Back up configuration:
    Terminal window
    cp .env .env.pre-upgrade-backup
  6. Run compatibility checkGET /admin/api/compat should return overall: "pass" or warn.

The upgrade orchestrator runs a 7-step workflow. Each step must succeed before the next begins. If any step fails, automatic rollback is triggered.

Step Phase What happens
1. Preflight preflight Verifies update manager is configured, checks disk space (min 500 MB)
2. Check checking Fetches release manifest, compares current vs. latest version
3. Download & verify downloading Downloads bundle, verifies Ed25519 signature (or HMAC-SHA256 fallback), extracts
4. Backup backing_up Snapshots existing staged files to _upgrade_backup/
5. Apply applying Validates extracted files, writes _upgrade_pending.json marker
6. Post-verify verifying Re-checks extracted directory integrity, computes SHA-256 of staged tarball
7. Rollback (on failure) rolling_back Removes pending marker, restores from backup

Trigger the full orchestration with POST /admin/api/upgrade/run. If a step fails, rollback is automatic: the _upgrade_pending.json marker is removed, the backup is restored, and phase is set to failed with rollback_performed: true. Use GET /admin/api/upgrade/history to review the last 10 upgrade run results.

The orchestrator enforces single-execution safety via an asyncio.Lock — a second run call while an upgrade is in progress returns immediately with an error.


The Outpost update procedures above cover the Outpost data plane. For a full deployment upgrade that includes the Platform backend and Cloud Portal, use the following procedures.

  • Admin access — platform admin credentials and shell access to the host running the deployment.
  • Maintenance window — schedule downtime. Migrations lock tables; production traffic will fail during the migration step.
  • Read the changelog — review CHANGELOG.md in the platform repository before upgrading.

All Arbitex components are currently at 0.1.0 pre-release. All three components must be upgraded together — mixed-version deployments are not supported. Version drift between Platform and Outpost is detected automatically via the heartbeat mechanism.

Database (PostgreSQL):

Terminal window
pg_dump -Fc -d arbitex_platform -f backup-platform-$(date +%Y%m%d).dump
pg_dump -Fc -d arbitex_cloud -f backup-cloud-$(date +%Y%m%d).dump

Verify the dumps are non-zero before continuing.

Application config export — export the full configuration for each org before upgrading (covers DLP rules, compliance bundles, policy templates, routing rules, enterprise entitlements, and 15 other domains):

Terminal window
curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \
"https://platform.example.com/api/v1/admin/orgs/${ORG_ID}/config/export" \
-o config-backup-$(date +%Y%m%d).json

Audit log archive:

Terminal window
curl -s -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"window_days": 90}' \
"https://platform.example.com/api/v1/admin/audit/export" \
-o audit-backup-$(date +%Y%m%d).json

Verify the chain_status field in the response is "intact".

Outpost policy cache:

Terminal window
cp -r "${POLICY_CACHE_PATH:-policy_cache/}" policy-cache-backup-$(date +%Y%m%d)/
# For air-gap deployments also back up:
cp -r "$AIRGAP_POLICY_PATH" airgap-policy-backup-$(date +%Y%m%d)/
cp -r "$AIRGAP_MODEL_PATH" airgap-model-backup-$(date +%Y%m%d)/
Terminal window
docker compose down
docker compose pull
docker compose run --rm backend python scripts/run-migrations.py
docker compose up -d
curl http://localhost:8000/health # expected: {"status": "ok"}
Terminal window
helm upgrade arbitex-platform ./deploy/helm/arbitex-platform -f values.yaml
kubectl rollout status deployment/arbitex-platform

Alembic runs automatically as an init container — no manual migration step is needed.

Terminal window
pg_restore -d arbitex_platform --clean --if-exists backup-platform-YYYYMMDD.dump
pg_restore -d arbitex_cloud --clean --if-exists backup-cloud-YYYYMMDD.dump

Alternatively, use Alembic to downgrade to a specific revision: alembic downgrade <target_revision>.

Check Command Expected result
API health curl /health {"status": "ok"}
Database migrations python scripts/run-migrations.py --check No pending migrations
Audit chain integrity POST /api/v1/admin/audit/export chain_status: "intact"
Outpost heartbeat Check platform logs Heartbeat received, version matches platform
DLP rules loaded GET /api/v1/admin/dlp-rules Rules returned, count matches pre-upgrade
Config export completeness GET /api/v1/admin/orgs/{org_id}/config/export 20 domains present in response

Review the notes for every version between your current version and the target version. Apply changes cumulatively.

Initial schema — creates base tables: users, conversations, messages, audit_logs, model_configs.

Required env vars: DATABASE_URL, JWT_SECRET_KEY.

First-time setup: call POST /api/setup to create the initial admin account. This endpoint disables itself after first use.

Migrations 002–004: model_instructions column on model_configs; share_token on conversations; performance indexes on messages and audit_logs. No new required env vars.

Migrations 005–006: model_rate_limits table; system_configs key-value table.

New optional: REDIS_URL — Redis connection string for rate limiting (falls back to PostgreSQL advisory locks if unset).

Migrations 007–008: dlp_rules and dlp_rule_versions tables; HMAC chain columns on audit_logs (hmac_signature, prev_hmac, hmac_key_id).

New required: AUDIT_HMAC_KEY — HMAC-SHA256 key for the tamper-evident audit chain. The audit log will not write if this is unset.

New optional: AUDIT_HMAC_KEY_ID (default "default") — key identifier stored in each audit record.

Migrations 009–010: api_keys table with hashed key storage and per-key rate limits; usage_records table for per-request token and cost tracking. No new required env vars.

Migrations 011–015: alert_rules and alert_events tables; versioning columns on dlp_rules; dlp_events table; retention_policies table; user_groups and group_memberships tables.

New optional: AUDIT_SINKS (default "db") — comma-separated list: db, jsonl, webhook, splunk_hec.

Migration 026: MFA columns on users (totp_secret, mfa_enabled, backup_codes); saml_idp_configs, webauthn_credentials, ip_allowlist_entries tables.

New env vars:

  • WEBAUTHN_RP_ID (default "localhost") — must match your actual domain before enabling passkeys in production. WebAuthn credentials are bound to the RP ID — changing it after credentials are registered invalidates all existing passkeys.
  • CLOUD_CA_CERT_PATH — CA certificate for verifying Cloud Portal mTLS connections.
  • MTLS_CA_BUNDLE — CA bundle path for outbound mTLS connections from Platform to Cloud.

Migrations through 072: model_registry and model_registry_versions; passkey_policy columns on organizations; org_recovery_codes; content_categories and content_filter_rules.

New env vars (selected):

Variable Default Description
OAUTH_JWT_PRIVATE_KEY (required if OAuth enabled) PEM-encoded RSA private key for signing OAuth JWTs
POLICY_SIGNING_KEY (required) Ed25519 private key for signing policy packages distributed to Outpost
SECRETS_BACKEND "env" Secrets storage backend: env, vault, file
DLP_INFERENCE_FAIL_MODE "closed" closed = block request on inference failure; open = allow
LOG_FORMAT "text" json recommended for production log aggregation