GitHub Actions
Before you begin
CI authenticates with an Access service token, not with prk login. Read
Authentication first.
Both routes install from npm
Both routes below install @yashau/prick at run time. The action lives in this
repository at action/ with its own test suite (mise run test:action).
Two secrets in GitHub, none in the workflow
The only thing your repository stores is the service token. Everything else lives in prick.
- Create a service token under Zero Trust → Access → Service auth → Service
Tokens. The client secret is shown once; the client id ends in
.access. - Add it to the Access policy for your prick hostname — an include rule of type Service Auth, with the policy’s action set to Service Auth rather than Allow. A policy listing only human identities rejects the token at the edge, before prick ever sees the request.
- Store the pair as GitHub Actions secrets, e.g.
PRICK_CLIENT_IDandPRICK_CLIENT_SECRET.
The action
- uses: yashau/prick/action@v2026.815.0 with: url: ${{ secrets.PRICK_URL }} client-id: ${{ secrets.PRICK_CLIENT_ID }} client-secret: ${{ secrets.PRICK_CLIENT_SECRET }} project: api environment: production
- run: ./deploy.sh # DATABASE_URL, STRIPE_SECRET_KEY, … are all just there| Input | Required | Default | Meaning |
|---|---|---|---|
url |
yes | — | Base URL of the server. Must be https: the service token is a request header |
client-id |
yes | — | Access service token client id, the one ending in .access |
client-secret |
yes | — | Access service token client secret |
project |
yes | — | Project to read. Matched exactly, case-sensitively |
environment |
no | production |
Environment to read. Matched exactly, case-sensitively |
keys |
no | (all) | Allowlist of secret names, newline- or comma-separated |
prefix |
no | (none) | Prepended to every variable name, e.g. APP_ |
export-to |
no | env |
env appends to $GITHUB_ENV; outputs sets one JSON output. Prefer env |
version |
no | (the action’s ref) | Version of @yashau/prick to install |
mask |
no | true |
Register values with the log masker. Setting this to false prints your secrets |
allow-unsafe-names |
no | false |
Permit PATH, NODE_OPTIONS, LD_*, GITHUB_* and friends |
Two outputs: keys, the newline-separated variable names that were injected after
any prefix — names only, so it is safe to print — and secrets, a JSON object set
only under export-to: outputs.
Naming keys is worth doing. A job that asks for two variables cannot leak the
other thirty, and a name that is not in the environment fails the step rather
than starting the job without it.
allow-unsafe-names defaults to false for the same reason prk run refuses
those names, only more so: a value written to $GITHUB_ENV applies to every later
step in the job, so a secret store that can set PATH controls the whole job.
It is a composite action rather than a bundled JavaScript one. A JavaScript
action has to commit its own dist/ — a build artefact no reviewer reads and that
nothing can prove was built from the sources beside it. For the thing holding
access to every secret in an environment, that is exactly the wrong shape. The
version input defaults to the action’s own ref when that ref is a release tag, so
the action and the CLI cannot drift.
Or drive the CLI yourself
name: Deploy
on: push: branches: [main]
permissions: contents: read
jobs: deploy: runs-on: ubuntu-24.04 env: PRK_API_URL: https://prick.example.com PRK_PROJECT: api PRK_ENV: production PRK_ACCESS_CLIENT_ID: ${{ secrets.PRICK_CLIENT_ID }} PRK_ACCESS_CLIENT_SECRET: ${{ secrets.PRICK_CLIENT_SECRET }} steps: - uses: actions/checkout@v5 with: persist-credentials: false
- run: npm install -g @yashau/prick
- run: prk run --no-input -- ./deploy.shPRK_ACCESS_CLIENT_ID / PRK_ACCESS_CLIENT_SECRET are prick’s names. If your
runner already sets CF_ACCESS_CLIENT_ID / CF_ACCESS_CLIENT_SECRET for
cloudflared, those are read as a fallback and the job works unchanged.
--no-input matters: it makes a missing or rejected credential fail immediately
instead of blocking on a prompt no CI job can answer.
The first run will 403, and that is the flow
Access will let the token through the edge, and prick will refuse it, because authentication is not authorization and a new service token has no grant.
That denial is recorded. The subject then appears in the “seen but not granted”
list, so an administrator can grant it without anyone copying an opaque
e367826f93b8d71185e03fe518aff3b4.access string between two consoles:
prk access identities --deniedprk access grant e367826f93b8d71185e03fe518aff3b4.access --role reader --scope api:productionGive CI the narrowest role that works. A deploy job that only reads secrets
is a reader on one environment, not a global admin.
Masking
If you must put a value into the workflow environment rather than into a child process, mask it so GitHub redacts it from logs:
echo "::add-mask::$SECRET_VALUE"Prefer prk run — a value injected straight into the child never passes through
a step’s stdout at all.
Do not write the file into the workspace
prk secrets download --format env --output "$RUNNER_TEMP/.env"Anything in the checkout directory risks being picked up by an upload-artifact
step or a build context. $RUNNER_TEMP is discarded with the runner.