Skip to content

Secrets management

Security

Sensitive app configuration, integration credentials, signing keys, and app environment variable values sit in the customer’s Key Vault. The Greenlight control plane stores references and metadata needed to bind them to apps, but never the values themselves.

What’s on disk on the control plane, in a database backup, in an exfiltrated control-plane dump? A reference, not a secret.

MaterialWhere it livesWhat the control plane knows
Integration credentialsKey VaultReference + which apps are granted
App env vars, plain and sensitiveKey Vault, injected into app pods through the app’s Kubernetes SecretReference + variable name + sensitivity flag
Workload identityCluster identity service, never persistedThe mapping from workload to app
End-user passwordsCustomer’s IdPNothing
Upstream user tokensIn-memory at the broker during a requestNothing after the request completes
Audit log recordsControl plane databaseThe structured event (not request bodies)

Plain env vars can be revealed through the dashboard or MCP when the caller is authorized, but the value still comes from Key Vault. Sensitive env vars are write-only after creation: admins can see the name and metadata, not the value.

Every integration credential is registered by IT and stored in Key Vault. How it reaches a granted app is a deliberate choice IT makes when registering the integration, and it changes the exposure profile:

  • Proxied — the app holds only a short-lived workload key and calls GREENLIGHT_PROXY_URL. The broker resolves the grant, swaps in the real credential server-side, and forwards the request. The upstream secret never reaches the app. Every call is audited and rotation takes effect on the very next request.
  • Injected — at deploy time Greenlight injects the bound credential into the app’s Kubernetes Secret under an environment variable name IT chose, and the app calls the upstream directly. This is the only option for wire-protocol sources a proxy can’t carry, such as a database connection string. Here the raw upstream credential does live in the app’s environment — the deliberate tradeoff for sources the broker can’t front. There’s no per-call broker audit, and rotation takes effect only at the app’s next deploy.

Both modes keep the credential out of the control-plane database and out of git. The difference is whether the secret reaches the running app process: proxied keeps it server-side; injected places it in the pod’s environment, the same mechanism that delivers a managed DATABASE_URL.

Greenlight admins can see that a secret exists. They can see which app it is bound to, which integration uses it, when it was last rotated. They cannot see the value through the dashboard, the API, or any MCP tool.

The only path to a secret value is to read it through the Key Vault directly using the cloud provider’s own access controls. An audit event is written for every Greenlight-mediated secret reference change; reads of the Key Vault are audited by the cloud provider’s native logging.

When a developer’s coding agent works on an app locally, no secret value ever passes through the agent or its MCP session — that would put credentials in agent transcripts and inference logs. Local development is built so that nothing on the laptop is a long-lived, replayable secret:

  • The agent pairs a bundled greenlight CLI once over its session. To run the app, greenlight run --app <id> -- <command> resolves the app’s configuration server-side and injects values straight into the running process — there is no .env.local file and nothing written to disk. (A personal, no-app run — greenlight run without --app, against the developer’s own granted integrations — uses the same handling: a short-lived, user-scoped token, nothing on disk.)
  • For proxied integrations the laptop never holds the upstream secret. The CLI mints a short-lived, principal-scoped token and the process calls the same broker URL the deployed app uses; the broker swaps in the credential exactly as it does in production.
  • For injected credentials, a granted developer’s process receives the raw value — granting an injected credential is disclosing it. Because a value handed to a developer’s machine can’t be time-bounded or remotely revoked, the dashboard says so at the two moments that decide it — choosing injected delivery at registration, and approving a grant — and recommends a dedicated least-privilege upstream account. Every raw delivery is audited under the developer’s name.
  • To investigate real data without delivering a credential, the agent uses data-inspection tools that run inside the control plane and return only results — never the credential. They cover the app’s own database, its blob storage, and a single request through a granted proxied integration. Blob access uses a short-lived signed URL.

Sensitive env-var values are excluded from local delivery entirely. The CLI’s own credential rotates and is revocable — deactivating the developer immediately kills it and any token minted from it. A lost laptop costs at most one revocable session, never an upstream secret.

Integration credentials are rotated through the dashboard: paste the new value, click rotate, and it lands in Key Vault under a new version.

How fast running apps see the new value depends on how the credential reaches them:

  • Proxied — the broker pulls whichever version is current on the very next call. There’s no overlap window where both are valid, and no app restart.
  • Injected — the value is baked into each app’s environment at deploy time, so apps already running keep the old value until their next deploy. The replace-secret dialog warns IT and lists the affected apps.

Either way there is no app-side change required during rotation. Every rotation writes an audit event recording the integration, credential slug, and delivery mode — never the secret value.

Workload identity rotates automatically on a cadence the cluster controls. App pods see this as transparent; they reauthenticate to the broker on each call.

When IT deletes a credential or a whole integration, the underlying Key Vault secret is destroyed and the credential is excluded from every path that could resolve it. Apps still holding a grant against it stop being served rather than reaching a stale value.

The platform does not have a “but for this one integration we needed plaintext on disk” exception. Every secret routes through Key Vault. If a future provider integration can’t be done that way, it doesn’t ship until it can.