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_TOKEN stored 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 production and denied for staging, 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

  1. 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=production

    This (a) re-wraps your wrapping key with VAULT_CI_ESCROW_KEY (held by the lpm.dev server) and stores the result in vault_sync.ciEscrowWrappingKey, and (b) creates an OIDC policy controlling which workflow can pull which environment.

  2. 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=.env

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

  3. 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)

StepSees 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

CapabilityFreeProOrg
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