OIDC for CI
CI is the one place where server-side decryption is allowed — by design. CI has no keychain, no persistent state between runs, and no interactive way to pair a wrapping key. So LPM supports an opt-in CI escrow: your wrapping key is re-wrapped with a key the lpm.dev server holds, decrypt happens on the server, and access is gated by OIDC tokens that prove the CI run's identity.
OIDC pulls require a Pro or Org plan.
What you get
- No long-lived
LPM_TOKENstored as a CI secret for vault decrypt — the CI's signed identity token is enough. - Per-environment policies: a workflow can be allowed to pull
productionand denied forstaging, even though both ship in the same blob. - Per-repo, per-branch, fork-aware policies.
- Audit log entries tagged with the CI provider, repo, branch, and workflow.
The flow
-
From a machine that has the wrapping key, you opt the vault into CI escrow once:
lpm env oidc allow --provider=github --repo=owner/repo --branch=main --env=productionThis (a) re-wraps your wrapping key with
VAULT_CI_ESCROW_KEY(held by the lpm.dev server) and stores the result invault_sync.ciEscrowWrappingKey, and (b) creates an OIDC policy controlling which workflow can pull which environment. -
In CI, the workflow requests an identity token from its provider, exchanges it for short-lived access, and pulls the env:
lpm env pull --oidc --env=production --output=.envThe server verifies the OIDC token signature, matches the claims against the policy, unwraps the escrowed wrapping key, decrypts the blob, filters by environment, and returns plaintext over TLS.
-
Your build/test steps read from the resulting
.env(or have the values injected directly into the process environment with--output=-).
The escrow is opt-in per vault. Vaults without ciEscrowWrappingKey set can never be CI-pulled, period.
Policy shape
provider: "github" | "gitlab"
subject: "repo:owner/repo" (GitHub) | "project:id" (GitLab)
allowedBranches: ["main", "release/*"]
allowedEnvironments: ["production", "staging"]
allowForks: false
Validation, the dashboard form, and the exchange handler describe the same two-provider surface — there are no advertised-but-unwired providers. The exchange validates the OIDC token's claims against the policy: a workflow on feature/x when the policy only allows ["main"] returns 403; a fork build when allowForks: false returns 403.
CLI
lpm env oidc allow \
--provider=github \
--repo=owner/repo \
--branch=main \
--env=production # create a policy
lpm env oidc list # list policies for this vault
lpm env oidc revoke <policy-id> # remove a policy
lpm env pull --oidc --env=production \
--output=.env # CI-side decrypt
You can also manage policies from the dashboard under each vault's OIDC tab — same surface, GUI form.
GitHub Actions
GitHub Actions exposes ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN to any job that declares permissions: id-token: write. The CLI fetches the JWT at run time — you don't have to handle it yourself:
name: Build
on: push
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install LPM
run: npm install -g @lpm-registry/cli
- name: Decrypt secrets
run: lpm env pull --oidc --env=production --output=.env
- name: Build
run: npm run build
lpm ci setup github-actions can emit this snippet pre-wired against your current project — no manual edits required.
GitLab CI
GitLab CI has no runtime token-fetch endpoint. Instead, the workflow mints an LPM_OIDC_TOKEN via the id_tokens block with aud: https://lpm.dev and the CLI uses it directly:
build:
stage: build
image: node:22
id_tokens:
LPM_OIDC_TOKEN:
aud: https://lpm.dev
script:
- npm install -g @lpm-registry/cli
- lpm env pull --oidc --env=production --output=.env
- npm run build
You can also set LPM_OIDC_TOKEN by hand on either provider — for self-hosted GitHub runners that lack the runtime endpoint, or for any non-standard CI where you've fetched a https://lpm.dev-audience JWT some other way.
What the server sees (and doesn't)
| Step | Sees plaintext? |
|---|---|
lpm env push (developer machine) | No — ciphertext only |
lpm env oidc allow (developer machine) | No — escrowed wrapping key is encrypted with VAULT_CI_ESCROW_KEY |
lpm env pull --oidc (CI runner) | Yes — the server decrypts and returns plaintext over TLS |
That's the deliberate trade: CI can't hold a wrapping key, so the server holds an escrowed copy. The blast radius is scoped by the policy (provider + repo + branch + environment + fork rule). If you don't want server-side decrypt at all, don't enable CI escrow — lpm env pull from a developer machine (which holds the wrapping key) keeps the server out of the loop entirely.
Other CI providers
Bitbucket Pipelines is not supported for OIDC because Bitbucket's OIDC tokens don't include user identity claims needed to bind the exchange to an LPM account. Bitbucket workflows can still use a regular LPM_TOKEN secret to authenticate the CLI for non-vault operations — see CI/CD.
For other providers, you can supply LPM_OIDC_TOKEN directly if the CI environment can mint a JWT with aud: https://lpm.dev.
Plan
| Capability | Free | Pro | Org |
|---|---|---|---|
| OIDC policy creation | — | ✓ | ✓ |
pull --oidc from CI | — | ✓ | ✓ |
| Org-scoped policies | — | — | ✓ |
A free account that calls lpm env oidc allow or lpm env pull --oidc gets 403 CI escrow requires a Pro or Org plan (for setup) or 403 Vault CI pull requires a Pro or Org plan (at pull time).
Next
- Cloud Sync — the same blob CI decrypts
- Org Sharing — multi-member access alongside CI
- CI/CD — combine OIDC env pulls with OIDC package installs and publishes