Penv Cloud
Start free
Docs
Guidesince cloud@2026-09-10

GitHub Actions

Exchange your GitHub Actions OIDC token for a short-lived credential from us and run one step with the values.

Not verified yet

This page is an outline. Nothing here has been confirmed against source, so do not follow it as product behavior.

Your workflow exchanges a GitHub-signed OIDC token for a credential we mint. You store no long-lived value in repository settings. See machine identity and values in a CI build.

Trust a GitHub repository

Open Machine Identities then Connect a Platform and pick GitHub Actions. The dialog has no subject field: we read the subject from the GitHub API, because repositories created or renamed since July 2026 embed numeric ids and a typed subject would never match.

The first time, Connect sends you to GitHub to install the Penv Cloud app, then brings you back to pick a repository. Installing the app creates no identity; picking a repository does. If the app was already installed on your account, GitHub has nothing to change and doesn't send you back: click Already installed? Check again on the card instead. Repositories the app covers with no identity yet are listed under Ready to connect on All Identities.

Connect a platform and the GitHub Actions trust reference list what the trust holds.

The audience is your workspace id, shown in the console.

Run penv in the workflow

.github/workflows/deploy.yml
permissions:
  id-token: write

steps:
  - name: Install penv
    run: |
      curl -fsSL https://penv.cloud/install | sh
      echo "$HOME/.penv/bin" >> "$GITHUB_PATH"

  - name: Deploy
    run: |
      T=$(curl -sS \
        -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
        "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=YOUR_WORKSPACE_ID" | jq -r .value)
      C=$(T="$T" jq -n '{token:env.T}' | curl -sS "https://penv.cloud/api/v1/auth/oidc" \
        -H 'content-type: application/json' --data-binary @- | jq -r .credential)
      PENV_TOKEN="$C" penv run -- npm run deploy

permissions and the token request are GitHub's OIDC mechanism.

LineNote
>> "$GITHUB_PATH"The installer edits no startup file; this puts penv on PATH for later steps
jqBoth curl calls parse with jq; install it on your runners that lack it
PENV_TOKEN="$C"Takes precedence over the keychain. A runner has no keychain, so every run is online and nothing is cached
CredentialLasts fifteen minutes, capped by the trust's expiry. Keep both tokens inside one step

For a host that needs a file, PENV_TOKEN="$C" penv pull writes a plain .env in the build step. See penv run, penv pull and environment variables.

Published action

The action has no uses: coordinate yet. Use the workflow above until it is published; this page stays needs-source until then. Once published, reference it by full commit SHA (why).

The action does the same exchange without installing penv. It is a JavaScript action on node24 with a main step and a post step on always(), so a failed build revokes the credential too. It reads none of your repository secrets and mints its token per run.

InputRequiredValue
audienceyesThe workspace id
runyesThe command that receives the values; no other step does
api-originnoDefaults to https://penv.cloud
filesnoOne NAME=path/name per line, for a tool that cannot read a variable

A files entry writes the value to a 0600 file under $RUNNER_TEMP, sets NAME to its path, and removes the file before the step ends, including on failure.

The action fails the step, naming the parameter, when a derived name or a files name would set a variable that changes how your command runs rather than what it reads. Rename the parameter.

RefusedExamples
Loader and libcLD_*, DYLD_*, GCONV_PATH, GLIBC_TUNABLES, MALLOC_*, TMPDIR
ShellPATH, HOME, SHELL, ENV, BASH_*, SHELLOPTS, BASHOPTS, PS1-PS4, PROMPT_COMMAND, IFS, CDPATH
InterpretersNODE_OPTIONS, NODE_PATH, PYTHON*, PERL*, RUBYOPT, RUBYLIB, JAVA_TOOL_OPTIONS, _JAVA_OPTIONS, JDK_JAVA_OPTIONS, *_OPTS of Java build tools, DOTNET_STARTUP_HOOKS, CORECLR_*
ToolchainsGIT_*, NPM_CONFIG_*, GOFLAGS, GOPROXY, RUSTC_WRAPPER, CARGO_*, CC, CXX, MAKEFLAGS, OPENSSL_CONF
Package managersPIP_*, PIPX_*, UV_*, POETRY_*, CONDA_*, YARN_*, PNPM_*, BUNDLE_*, GEM_*, COMPOSER_*, NUGET_*, RUSTUP_*
Launched programsEDITOR, VISUAL, PAGER, LESSOPEN, SSH_ASKPASS, SSH_AUTH_SOCK, GH_HOST
Runner and penvGITHUB_*, RUNNER_*, ACTIONS_*, INPUT_*, STATE_*, PENV_*

A name that is not a valid variable name is refused too. The command also does not inherit ACTIONS_ID_TOKEN_REQUEST_*, ACTIONS_RUNTIME_TOKEN, GITHUB_STATE, GITHUB_ENV, GITHUB_OUTPUT or GITHUB_PATH: it cannot mint another OIDC token or pass a value to a later step.

BehaviorDetail
Credential lifetimeFive minutes, bound to the repository, workflow ref, executing job's workflow ref, run id, run attempt, and actor (audit only)
pull_request_targetWe refuse it
pull_request, workflow_runAllowed
RunnersLinux and macOS. The action refuses Windows: neither 0600 nor bash holds there

Values in a CI build covers trigger rules, closed output channels, masking coverage, and what penv cannot promise inside your shared job.

Troubleshooting

AnswerMeaning
401 unauthorizedYour trust does not name this subject

All codes: our API answers.

Machine identity