Skip to Content
TestingApp Secrets and Environment Variables

App Secrets and Environment Variables

Store encrypted app and account-level secrets, inject them at deploy time, and let managed services (databases, storage, auth) auto-publish their credentials. Values are encrypted at rest with Fernet, masked in listings, and only decrypted on explicit reveal or during a deploy.

Today (M1/M2): The encrypted storage, REST and MCP surfaces, and the managed-service producer API are real and in use by the deploy pipeline. Pending: the frontend Secrets tab and account env page (use MCP or REST, which are the primary interfaces until M3), plus redaction of secret values from live SSE deploy logs.

How it works

  1. Set values with set_app_env (per-app) or set_account_env (shared across all your apps). Values are encrypted with Fernet and persisted under data/app-env/.
  2. Deploy with deploy_app. The deploy flow decrypts every app and account-level var, merges them (app overrides account), writes the result to /etc/openfactory/app.env (root, mode 0600), and starts the app under systemd-run with EnvironmentFile=/etc/openfactory/app.env.
  3. Managed services publish credentials for you. When you call add_app_database or add_app_bucket, the service produces variables like DATABASE_URL or S3_BUCKET straight into the app’s env. They show up in list_app_env with source: "managed" and the producer name.
  4. Reveal on demand. reveal_app_env or reveal_account_env decrypts one variable at a time, and writes an audit entry to data/app-env/audit.jsonl.

Two scopes: app vs. account

  • App env is scoped to one app. Use it for app-specific secrets (STRIPE_SECRET_KEY for the shop app, OPENAI_API_KEY for the chatbot).
  • Account env is shared across every app you own. Use it for values you reuse everywhere (a shared analytics token, a common feature flag). Account vars are merged in first, then app vars override on key collision.

Sources

Every entry returned by list_app_env carries a source tag:

  • user — you set it via set_app_env or set_account_env.
  • managed — a managed service producer (postgres, s3, auth) wrote it. The producer field names which one.

Producer-owned keys are protected: a user set_app_env will not silently overwrite a managed key, and a producer can only delete keys it owns.

MCP tools

list_app_env

List an app’s environment variables. Values are masked; use reveal_app_env for plaintext.

list_app_env(app_id="550e8400-e29b-41d4-a716-446655440000")
{ "entries": [ {"name": "DATABASE_URL", "value": "********", "source": "managed", "producer": "postgres-prod", "updated_at": "2026-06-26T10:30:00+00:00"}, {"name": "API_KEY", "value": "********", "source": "user", "producer": null, "updated_at": "2026-06-26T09:15:00+00:00"} ], "env_version": 3, "applied_env_version": 2 }

env_version increments on every write. applied_env_version is the version the last successful deploy rendered. When they differ, a redeploy is needed to pick up the new values.

set_app_env

Upsert or delete app env vars in one transaction. Pass set for upserts and delete for removals.

set_app_env(app_id="550e8400-e29b-41d4-a716-446655440000", set={"NODE_ENV": "production", "LOG_LEVEL": "info"})
{ "env_version": 4, "redeploy_hint": true }

redeploy_hint: true means the app is currently deployed but the new values have not been written to the VM yet. Call deploy_app again to apply.

reveal_app_env

Decrypt and return one variable’s plaintext. Every call writes an audit entry.

reveal_app_env(app_id="550e8400-e29b-41d4-a716-446655440000", name="DATABASE_URL")
{ "name": "DATABASE_URL", "value": "postgres://user:pass@db.example.com:5432/myapp" }

list_account_env

List account-level env vars shared across all your apps.

list_account_env()
{ "entries": [ {"name": "SHARED_API_KEY", "value": "********", "source": "user", "producer": null, "updated_at": "2026-06-26T08:00:00+00:00"} ] }

set_account_env

Upsert or delete account-level env vars. Changes affect every app you own on the next deploy.

set_account_env(set={"SHARED_API_KEY": "sk-abc123"})
{ "key_count": 1, "changed": true }

reveal_account_env

Decrypt and return one account-level variable. Audited.

reveal_account_env(name="SHARED_API_KEY")
{ "name": "SHARED_API_KEY", "value": "sk-abc123xyz..." }

REST endpoints

All app endpoints require a JWT for the app’s owner. Account endpoints require authentication as the user who owns those account variables (not arbitrary user access). Producer endpoints use a Bearer token of the form <kid>.<secret>, loaded from OPENFACTORY_PRODUCER_TOKENS_FILE.

MethodPathPurpose
GET/api/apps/{app_id}/envList app env vars (masked, with source, updated_at, env_version)
PUT/api/apps/{app_id}/envUpsert/delete in one call. Body {set, delete}; returns env_version, redeploy_hint
POST/api/apps/{app_id}/env/revealDecrypt one app var. Body {name}. Audited
GET/api/account/envList account-level env vars (masked) for the authenticated user
PUT/api/account/envUpsert/delete account vars for the authenticated user. Body {set, delete}; returns key_count, changed
POST/api/account/env/revealDecrypt one account var for the authenticated user. Body {name}. Audited
PUT/api/apps/{app_id}/env/managedProducer publishes managed vars. Body {set}; returns key_count, changed, producer
DELETE/api/apps/{app_id}/env/managed/{name}Producer deletes a managed var it owns. Returns {deleted}

Putting it together

A typical “set a secret, deploy, use it, reveal it later” loop:

  1. Set the value. set_app_env(app_id=..., set={"DATABASE_URL": "postgres://..."}) (or PUT /api/apps/{id}/env). The server encrypts the value with Fernet, persists to data/app-env/apps/{app_id}.json, and records the action in the audit log.
  2. Deploy. deploy_app(app_id=...).
  3. Render. The deploy flow calls AppEnvService.render_env(app_id, owner_user_id=...) in-process to decrypt every app and account-level var and merge them (app vars win).
  4. Inject. The rendered plaintext dict is base64-encoded and written to /etc/openfactory/app.env on the VM via the guest agent (root:root, mode 0600).
  5. Start. systemd-run is invoked with --property=EnvironmentFile=/etc/openfactory/app.env so the app process inherits every variable.
  6. Reveal later. reveal_app_env(app_id=..., name="DATABASE_URL") returns the plaintext on demand, and writes the access to data/app-env/audit.jsonl.

Notes

  • Encrypted at rest. Values are encrypted with Fernet using the server’s app-env key. Listings always return masked values (********).
  • Audit log. Every reveal, set, and delete is appended to data/app-env/audit.jsonl with actor, app, key name, and timestamp.
  • Owner only for app env. App env tools and REST routes require the JWT to match the app’s owner. Account env requires authentication as that user.
  • Redeploy to apply. Writes do not mutate a running app. The deploy flow is the only place env files are rendered onto the VM.
  • Managed key protection. A user write cannot overwrite a source: "managed" key, and a producer cannot delete a key owned by another producer or by the user.
  • MCP only: session_token, api_key. The MCP tool signatures include optional session_token and api_key fields for authentication. These fields are MCP transport only and do not appear in REST responses.
Last updated on