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.conf file with your connections, using ${variables} for placeholders of sensitive data.
  • Create a local env.conf file alongside it. The location must match the additionalSources path defined in your taxi.conf file as @orbital/config.
  • Populate env.conf with sensitive values.

Eg:

connections.conf
jdbc {  my-connection {    // ... other params omitted for brevity ...    connectionParams {      password = ${postgres_password} // Reads the variable "postgres_password"    }  }}
env.conf
postgres_password=hello123
taxi.conf
   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:

ScopeVisible to
Serverevery workspace on this server
Organisationevery workspace in that organisation
Workspaceonly 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:

ValueBackend
InMemory (default)In-process; loses everything on restart. Tests and demos only.
FileLocal file, age-encrypted. Suitable for dev.
InfisicalInfisical — cloud or self-hosted.
AwsAWS Secrets Manager.
VaultHashiCorp Vault — KV v2 mount.
GcpGoogle 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 omitted

File

A local on-disk store that survives restart, encrypted with age.

vyne:  secrets:    backend: File    file:      path: orbital_data/secrets-store    # default

The 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>.age

Infisical

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 / localstack

Authentication 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 forceDeleteWithoutRecovery so the name is freed synchronously — once delete returns, you can recreate a secret with the same name.
  • Last-write-wins. AWS Secrets Manager has no expectedVersion hook on PutSecretValue, 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 prefix

Authentication 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 delete is a soft-delete that keeps versions recoverable. To honour the contract (delete means delete), Orbital issues a DELETE against <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 expectedVersion set Vault’s cas option, 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 endpoints

Authentication 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 AddSecretVersion has no etag hook for conditional writes, so expectedVersion is accepted but not enforced. If your workflow needs optimistic concurrency, use the File or Vault backend.
  • Deletes are immediate. deleteSecret removes 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.