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: The encrypted storage, REST and MCP surfaces, and the managed-service producer API are live and used by the deploy pipeline. Still pending: the console Secrets tab and account env page (use MCP or REST for now), and redaction of secret values from live deploy logs.
How it works
- Set values with
set_app_env(per-app) orset_account_env(shared across all your apps). Values are encrypted with Fernet and persisted underdata/app-env/. - 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, mode0600), and starts the app undersystemd-runwithEnvironmentFile=/etc/openfactory/app.env. - Managed services publish credentials for you. When you call
add_app_databaseoradd_app_bucket, the service produces variables likeDATABASE_URLorOF_S3_BUCKETstraight into the app’s env. They show up inlist_app_envwithsource: "managed"and the producer name. - Reveal on demand.
reveal_app_envorreveal_account_envdecrypts one variable at a time, and writes an audit entry todata/app-env/audit.jsonl.
Two scopes: app vs. account
- App env is scoped to one app. Use it for app-specific secrets
(
STRIPE_SECRET_KEYfor the shop app,OPENAI_API_KEYfor 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 viaset_app_envorset_account_env.managed: a managed service producer (postgres, s3, auth) wrote it. Theproducerfield 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.
| Method | Path | Purpose |
|---|---|---|
GET | /api/apps/{app_id}/env | List app env vars (masked, with source, updated_at, env_version) |
PUT | /api/apps/{app_id}/env | Upsert/delete in one call. Body {set, delete}; returns env_version, redeploy_hint |
POST | /api/apps/{app_id}/env/reveal | Decrypt one app var. Body {name}. Audited |
GET | /api/account/env | List account-level env vars (masked) for the authenticated user |
PUT | /api/account/env | Upsert/delete account vars for the authenticated user. Body {set, delete}; returns key_count, changed |
POST | /api/account/env/reveal | Decrypt one account var for the authenticated user. Body {name}. Audited |
PUT | /api/apps/{app_id}/env/managed | Producer 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:
- Set the value.
set_app_env(app_id=..., set={"DATABASE_URL": "postgres://..."})(orPUT /api/apps/{id}/env). The server encrypts the value with Fernet, persists todata/app-env/apps/{app_id}.json, and records the action in the audit log. - Deploy.
deploy_app(app_id=...). - Render. The deploy flow decrypts every app and account-level var and merges them (app vars win).
- Inject. The rendered values are written to
/etc/openfactory/app.envon the VM (root:root, mode0600). - Start.
systemd-runis invoked with--property=EnvironmentFile=/etc/openfactory/app.envso the app process inherits every variable. - Reveal later.
reveal_app_env(app_id=..., name="DATABASE_URL")returns the plaintext on demand, and writes the access todata/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.jsonlwith 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_tokenandapi_keyfields for authentication. These fields are MCP transport only and do not appear in REST responses.
Related
- App Deployment: the deploy flow that renders and injects the env file onto the VM.
- MCP Integration: set up the OpenFactory MCP server.