Load secrets from seekrit into your GitHub Actions job — resolved,
decrypted on the runner, masked in the logs, and injected into $GITHUB_ENV so every
subsequent step just sees them as environment variables.
seekrit is end-to-end encrypted: this action decrypts your secrets locally using the private key carried by your service token, and sends the API only the token's separate auth credential. The seekrit API never sees plaintext or your private key.
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: seekritdev/github-action@v2
with:
token: ${{ secrets.SEEKRIT_TOKEN }}
# Later steps see the secrets as environment variables:
- run: ./deploy.sh # e.g. $DATABASE_URL, $API_KEY, … are setStore your service token as an encrypted Actions
secret named SEEKRIT_TOKEN.
A service token is bound to a single application
environment, so there is nothing else to configure — it resolves its own org, app, environment, and
composed groups.
| Input | Default | Description |
|---|---|---|
token |
(required) | seekrit v2 service token (skt_<id>_v2.…). Pass ${{ secrets.SEEKRIT_TOKEN }}. |
api-url |
https://api.seekrit.dev |
seekrit API base URL. |
with |
"" |
Group→environment overrides, one group:env per line (or comma-separated). Mirrors the CLI --with. |
export-env |
true |
Inject each secret into $GITHUB_ENV for subsequent steps. |
set-outputs |
false |
Also expose each secret as a step output (steps.<id>.outputs.<NAME>). |
mask |
true |
Register every value with the runner (::add-mask::) so it is redacted from logs. |
prefix |
"" |
String prepended to every injected variable name. |
include |
"" |
If set, inject only these names (comma/newline separated). |
exclude |
"" |
Names to skip (comma/newline separated). |
| Output | Description |
|---|---|
secrets-count |
Number of secrets injected. |
steps:
- uses: seekritdev/github-action@v2
with:
token: ${{ secrets.SEEKRIT_TOKEN }}
- run: |
echo "connecting to the database..." # $DATABASE_URL is set & masked
./migrate.sh- uses: seekritdev/github-action@v2
with:
token: ${{ secrets.SEEKRIT_TOKEN }}
prefix: "SEEKRIT_" # -> $SEEKRIT_DATABASE_URL, ...
include: DATABASE_URL, API_KEY- uses: seekritdev/github-action@v2
with:
token: ${{ secrets.SEEKRIT_TOKEN }}
with: |
platform:staging
payments:staging- id: seekrit
uses: seekritdev/github-action@v2
with:
token: ${{ secrets.SEEKRIT_TOKEN }}
export-env: "false"
set-outputs: "true"
- run: echo "count=${{ steps.seekrit.outputs.secrets-count }}"
# individual values: ${{ steps.seekrit.outputs.DATABASE_URL }}Prefer masked environment variables (
export-env) over step outputs where you can — outputs are stored in the workflow run and are a slightly larger surface than env masking.
- The action reads your service token,
skt_<id>_v2.<auth>.<private key>, and splits it. Onlyskt_<id>_v2.<auth>is ever sent; the private key never leaves the runner. - It calls
GET /v1/resolve, receiving ciphertext only: the ordered secret layers (composed groups → application environment) plus a per-layer data key wrapped to the token's public key. - It unwraps each data key and decrypts every secret locally (ECDH P-256 → HKDF-SHA256 →
AES-256-GCM), applying the same precedence as
seekrit run— the app environment wins over composed groups — then expands secret references (${OTHER_SECRET}) across the merged set, exactly as every other seekrit client does. - Each value is masked (
::add-mask::) and written to$GITHUB_ENV/ step outputs.
Precedence, formats, and the zero-knowledge model are documented at https://seekrit.dev/docs.
- Zero-knowledge. Decryption happens on the runner. The API only ever holds ciphertext and public keys — never plaintext, private keys, or passphrases.
- Masked by default. Every injected value is registered with the runner so it is redacted from
logs (multi-line values are masked line by line). Leave
maskon. - No third-party runtime dependencies. The published
dist/bundle contains only this repo's own code — nonode_modulesare pulled at action runtime — so the code handling your secrets is small and auditable. - Scope your token. Grant each token only the environment it needs, and rotate/revoke from the seekrit dashboard. A leaked CI token is contained to a single environment.
- Treat injected environment variables like any other CI secret: they are available to every step in the job, including third-party actions you invoke afterward.
Reference a major version so you get non-breaking updates:
- uses: seekritdev/github-action@v2Pin to an exact release (e.g. @v2.0.0) or a commit SHA if you require immutability.
v2 requires a v2 service token (skt_<id>_v2.…). v1 sent the whole token — private key
included — as its API credential; v2 sends only the token's auth half, and refuses a legacy token
before making any request. To upgrade:
- Mint a replacement service token in the seekrit dashboard, with the same environment and grants.
- Update the
SEEKRIT_TOKENsecret and change@v1to@v2in the same change. - Confirm the job resolves, then revoke the old token.
v2 also expands secret references (${OTHER_SECRET}) in values, which v1 passed through literally.
Write $${NAME} for a literal ${NAME}.
npm ci
npm run typecheck
npm test # decrypts golden vectors from @seekrit/crypto (crypto is byte-compatible)
npm run build # bundles src/ -> dist/index.js (esbuild); commit the result
npm run lintThe bundled dist/index.js is committed and must stay in sync with src/ — CI fails if it drifts.
The crypto is a vendored, decrypt-only port of the canonical @seekrit/crypto; the golden-vector
test (test/fixtures/vectors.json) is generated by the real implementation and pins byte-for-byte
compatibility.