New to Kestra?
Use blueprints to kickstart your first workflows.
Reconcile GitHub Actions workflow secret references against configured repository secrets to catch silent breakage before it happens.
GitHub Actions has a quiet footgun: reference secrets.SOME_TOKEN in a workflow step, and if
that secret was renamed, deleted, or never configured, the expression does not fail to parse
and does not error at trigger time, it just silently evaluates to an empty string. The step
runs anyway, often failing somewhere downstream with a confusing error that has nothing
obviously to do with a missing secret. Nobody finds out until that step actually executes,
which can be days after the secret was removed. This blueprint closes that gap by reading
every workflow file's actual content, extracting every secrets.NAME reference with a regex,
and diffing that set against the secrets GitHub's own API says are configured on the
repository, in both directions.
This blueprint was created by Mohit Jeswani.
daily_sweep (io.kestra.plugin.core.trigger.Schedule) runs the reconciliation once a day.search_workflow_files (io.kestra.plugin.github.code.Search) finds every file under
.github/workflows in the target repository, returning each hit's path and
download_url, since GitHub's code search API returns file metadata, not file content.list_configured_secrets (io.kestra.plugin.core.http.Request) calls GitHub's REST API
for the repository's configured Actions secret names (never values, GitHub's API does not
expose those), with allowFailed: true so a token without secrets-read access reports
zero configured secrets instead of crashing the execution.extract_and_reconcile (io.kestra.plugin.scripts.python.Script) downloads every matched
workflow file's raw content, regex-extracts every secrets.NAME reference (recording
which file referenced it), and diffs the referenced-name set against the configured-name
set to classify the repository as HEALTHY, UNUSED_SECRETS_FOUND (configured but never
referenced), or ORPHANED_REFERENCES_FOUND (referenced but not configured, the one that
actually breaks something).route (io.kestra.plugin.core.flow.Switch) alerts Slack for either finding, naming the
exact secret names and, for orphaned references, which workflow file referenced each one.
Filing a GitHub tracking issue is a separate, explicitly authorized action behind
confirm_file_issue in both branches, not tied to the Slack alert.summary logs the final severity and the referenced/configured counts.errors alerts Slack if the flow itself fails, for example a repository that does not exist.GitHub has no built-in check that cross-references a workflow's secrets.* expressions
against what is actually configured; the platform will happily run a workflow with a
reference that resolves to nothing. Kestra runs this reconciliation on a schedule, keeps
repository and confirm_file_issue as flow inputs so the same flow audits any repository
without edits, and keeps a full execution history of every sweep, what it found, and whether
it filed a tracking issue.
repo scope, or fine-grained with Secrets: read-only and Issues: read and write repository permissions) for the target repository.This blueprint only calls GitHub's hosted API; nothing needs to run locally to test it beyond
a repository you can point it at and a token with the permissions above. Point repository
at any public repo with workflows to see search_workflow_files and list_configured_secrets
run end to end (a token without secrets-read access still exercises the orphaned-reference
path, since list_configured_secrets degrades to an empty configured set via allowFailed).
GITHUB_ACCESS_TOKEN: used by search_workflow_files, list_configured_secrets, the
per-file content fetch inside extract_and_reconcile, and issues.Create. In Kestra OSS,
provide it as the base64-encoded environment variable SECRET_GITHUB_ACCESS_TOKEN.SLACK_WEBHOOK_URL: used by both Slack alert cases and the errors block. In Kestra OSS,
provide it as SECRET_SLACK_WEBHOOK_URL.| Input | Default | Purpose |
|---|---|---|
repository |
kestra-io/kestra |
owner/repo to reconcile |
confirm_file_issue |
false |
Authorizes create_unused_issue / create_orphaned_issue to actually fire |
{{ outputs.extract_and_reconcile.vars.severity }}: HEALTHY, UNUSED_SECRETS_FOUND, or
ORPHANED_REFERENCES_FOUND.{{ outputs.extract_and_reconcile.vars.orphaned }} / .orphaned_detail: the secret names
referenced with no matching configured secret, and which workflow file referenced each one.{{ outputs.extract_and_reconcile.vars.unused }}: configured secret names no workflow
references at all.secrets.NAME. A workflow using the bracket form
(secrets['NAME']) or building the secret name dynamically from an expression is not
detected; this covers the overwhelmingly common case, not every syntactically valid one.list_configured_secrets requires a token with permission to read repository secret
metadata; without it the call still returns 200 with zero secrets for personal-repo tokens
that lack the scope, which this blueprint cannot tell apart from a repository that
genuinely has none configured, and reports every reference as orphaned in that case.actions/secrets endpoint used here, so a reference to
one of those will be reported as an orphaned reference even though it resolves correctly
at runtime. If your workflows lean on org or environment secrets, treat orphaned as a
shortlist to check manually rather than a final verdict.GITHUB_ACCESS_TOKEN and SLACK_WEBHOOK_URL secrets.repository at a repo you own with at least one workflow under .github/workflows.HEALTHY or correctly names any
existing drift.alert_orphaned names it.confirm_file_issue: true and re-run; confirm a tracking issue is opened with the
same detail.secrets['NAME'] and interpolated names, trading simplicity
for completeness.uses: ./.github/workflows/x.yml with
secrets: inherit or explicit secrets: mappings) to catch references passed through a
caller workflow rather than read directly.