Security
Managing secrets
Keep credentials out of your config files using environment variables, env.conf, or the secrets manager
When defining either connections or authentication tokens, you’ll need to store sensitive values.
Orbital is built around file-based and git-based config, so these are values you generally don’t want checked in.
There are three ways to keep secrets out of your config files:
- Environment variables — inject secrets at the OS level, common with Kubernetes and cloud orchestrators.
env.conf— a separate file alongside your config, typical for local development.- Secrets manager — a managed store (file-backed, Infisical, etc.) that Orbital looks up on demand.
You can mix all three. See Source precedence for the resolution order.
Environment variables
All Orbital config files are defined in HOCON format, which allows for variable substitution from the environment.
jdbc { my-connection { connectionParams { password = ${POSTGRES_PASSWORD} } }}This is a popular approach when deploying onto Kubernetes, ECS, or similar — the orchestrator injects secrets into the container environment, and Orbital substitutes them at config load.
Using env.conf for sensitive data
In addition to the standard HOCON rules for resolving variables, Orbital supports the use of an env.conf file as a source for substitutions.
This is useful when developing locally, to keep sensitive values out of connections.conf without having to configure environment variables.
The general workflow is:
- Define a
connections.conffile with your connections, using${variables}for placeholders of sensitive data. - Create a local
env.conffile alongside it. The location must match theadditionalSourcespath defined in your taxi.conf file as@orbital/config. - Populate
env.confwith sensitive values.
Eg:
jdbc { my-connection { // ... other params omitted for brevity ... connectionParams { password = ${postgres_password} // Reads the variable "postgres_password" } }}postgres_password=hello123 name: com.foo/myProject version: 0.1.0 sourceRoot: src/ additionalSources: { "@orbital/config" : "orbital/config/*.conf", }Secrets manager
The secrets manager is a backend store that Orbital looks up to resolve ${secrets.NAME} placeholders in your config.
Reference a secret in your config the same way you’d reference an env var:
connections.jdbc.warehouse { url: "jdbc:postgresql://warehouse.example.com:5432/prod" password: ${secrets.DB_PASSWORD}}Secrets are isolated by scope:
| Scope | Visible to |
|---|---|
Server | every workspace on this server |
Organisation | every workspace in that organisation |
Workspace | only that workspace |
Reads cascade — a workspace looks first in Workspace, then Organisation, then Server. The narrower scope wins on a name collision.
Secrets are managed through the Orbital UI under Authentication → Secrets.
Pick a backend by setting vyne.secrets.backend:
| Value | Backend |
|---|---|
InMemory (default) | In-process; loses everything on restart. Tests and demos only. |
File | Local file, age-encrypted. Suitable for dev. |
Infisical | Infisical — cloud or self-hosted. |
Aws | AWS Secrets Manager. |
Vault | HashiCorp Vault — KV v2 mount. |
Gcp | Google Cloud Secret Manager. |
In-memory
The default when no backend is configured. Holds secrets in process memory only — restarts wipe everything, and multi-node clusters don’t share state.
vyne: secrets: backend: InMemory # default; can be omittedFile
A local on-disk store that survives restart, encrypted with age.
vyne: secrets: backend: File file: path: orbital_data/secrets-store # defaultThe path defaults to ${vyne.app.data.path}/secrets-store — ./orbital_data/secrets-store out of the box. Override with vyne.secrets.file.path.
On first start, Orbital generates an identity.txt (private key) and recipients.txt (public key) in that directory. The store layout is:
<path>/├── identity.txt # private key — back this up├── recipients.txt # public key└── secrets/ ├── server/<name>.age ├── organisation/<orgId>/<name>.age └── workspace/<orgId>/<workspaceId>/<name>.ageInfisical
Stores secrets in Infisical — cloud or self-hosted.
You’ll need:
- An Infisical project ID
- An environment slug within that project (e.g.
prod) - A Universal Auth machine-identity client ID and client secret
vyne: secrets: backend: Infisical infisical: site-url: https://app.infisical.com # default; for self-hosted, point at your install project-id: <your-project-id> environment-slug: prod base-path: /orbital # all Orbital secrets live under this folder auth: client-id: ${INFISICAL_CLIENT_ID} client-secret: ${INFISICAL_CLIENT_SECRET}Orbital lays out scopes as folders under base-path:
/orbital/server/<name>/orbital/tenant/<orgId>/<name>/orbital/tenant/<orgId>/<workspaceId>/<name>Missing folders are created automatically on first write — you don’t need to provision the layout in advance.
AWS Secrets Manager
Stores secrets in AWS Secrets Manager.
vyne: secrets: backend: Aws aws: region: eu-west-1 # optional; falls back to the AWS SDK provider chain base-path: /orbital # all Orbital secrets live under this prefix # endpoint-override: ... # optional; for VPC endpoints / localstackAuthentication uses the standard AWS SDK credentials chain — environment variables, shared profile, IAM role on EC2 / ECS / EKS, etc. Provision a principal with secretsmanager:CreateSecret, GetSecretValue, PutSecretValue, UpdateSecret, DescribeSecret, ListSecrets, and DeleteSecret on the base-path’s ARN pattern.
Orbital encodes scopes as a path-style prefix on the secret name:
<base-path>/server/<name><base-path>/tenant/<orgId>/<name><base-path>/tenant/<orgId>/<workspaceId>/<name>Two AWS-specific notes:
- Deletes are immediate. AWS’s default delete schedules removal 7–30 days out, during which the name is reserved. Orbital uses
forceDeleteWithoutRecoveryso the name is freed synchronously — oncedeletereturns, you can recreate a secret with the same name. - Last-write-wins. AWS Secrets Manager has no
expectedVersionhook onPutSecretValue, so concurrent updates do not detect conflicts. If your workflow needs optimistic concurrency, use the File or Infisical backend.
HashiCorp Vault
Stores secrets in a HashiCorp Vault KV v2 mount.
vyne: secrets: backend: Vault vault: address: https://vault.example.com:8200 token: ${VAULT_TOKEN} mount: secret # default; the KV v2 mount path base-path: /orbital # all Orbital secrets live under this prefixAuthentication is via a static Vault token. Provision a periodic token with read, create, update, delete, and list capability on <mount>/data/<base-path>/* and <mount>/metadata/<base-path>/*. Inject the token via the standard env-var / secrets-mount mechanism — Orbital reads it once at startup.
Orbital lays out scopes as KV v2 paths under base-path:
<base-path>/server/<name><base-path>/tenant/<orgId>/<name><base-path>/tenant/<orgId>/<workspaceId>/<name>The secret value is stored under the data key value; an optional description is stored as custom_metadata.description. A folder structure isn’t pre-provisioned — KV v2 creates intermediate paths on first write.
Two notes:
- Delete is permanent. Vault’s standard
deleteis a soft-delete that keeps versions recoverable. To honour the contract (deletemeans delete), Orbital issues aDELETEagainst<mount>/metadata/<path>, which destroys all versions and the metadata key in one call. Recreating a secret at the same name starts a fresh version sequence at 1. - Optimistic concurrency uses CAS. Writes with an
expectedVersionset Vault’scasoption, which performs the version check server-side. Conflicting concurrent writes lose deterministically.
GCP Secret Manager
Stores secrets in Google Cloud Secret Manager.
vyne: secrets: backend: Gcp gcp: project-id: my-gcp-project base-path: orbital # default; encoded into every secret name # endpoint-override: ... # optional; for emulators / VPC endpointsAuthentication uses Google’s standard Application Default Credentials chain — GOOGLE_APPLICATION_CREDENTIALS, attached service account on GCE / GKE / Cloud Run, or gcloud auth application-default login for local development. Provision the principal with the roles/secretmanager.admin role (or a tighter custom role granting secretmanager.secrets.{create,get,list,update,delete} plus secretmanager.versions.{add,access}) on the project.
GCP has a flat per-project namespace and a constrained character set ([A-Za-z0-9_-] only). Orbital encodes the scope hierarchy into the secret id using __ as a segment separator:
<base-path>__server__<name><base-path>__tenant__<orgId>__<name><base-path>__tenant__<orgId>__<workspaceId>__<name>orgId, workspaceId, and the secret name itself must therefore avoid __ and any character outside [A-Za-z0-9_-]. description, when set, is stored on the secret as the description annotation.
Two GCP-specific notes:
- Last-write-wins. GCP
AddSecretVersionhas noetaghook for conditional writes, soexpectedVersionis accepted but not enforced. If your workflow needs optimistic concurrency, use the File or Vault backend. - Deletes are immediate.
deleteSecretremoves the secret and all its versions in one call — there’s no recovery window, so recreating a secret with the same id is allowed and starts a fresh version sequence at 1.
Source precedence
When the same ${name} could be supplied by more than one source, Orbital resolves in this order (last wins):
[OS env] → [env.conf] → [Server secret] → [Organisation secret] → [Workspace secret]If a name is defined in more than one source, Orbital logs a warning at workspace startup naming the source that’s being used. Values are never logged.