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
- 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_URLorS3_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 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). - Inject. The rendered plaintext dict is base64-encoded and written to
/etc/openfactory/app.envon the VM via the guest agent (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.