Install
$ agentstack add skill-bcgov-agent-skills-github-actions Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Destructive filesystem operation.
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
GitHub Actions — BC Gov Hardening Patterns
Use When
- Authoring or reviewing any file under
.github/workflows/in a BC Gov repo. - Wiring branch protection / a repository ruleset and need to pick the required status check(s).
- Hardening a public repo's pipeline against fork-PR token exfiltration, action tag hijacks, or script injection from PR titles / branch names / issue bodies.
- Debugging
Resource not accessible by integration,Refusing to allow a Personal Access Token to create workflow, or otherGITHUB_TOKENpermission errors. - Adding or auditing Dependabot — both
.github/dependabot.ymland an auto-merge workflow for low-risk bumps. - Picking a runner OS / version, action pin format (tag vs SHA), or
permissions:scope set for a new job. - Publishing artifacts, npm packages, container images, or GitHub Pages from a workflow and need OIDC / least-privilege guidance.
- Migrating an existing repo onto BC Gov defaults (signed commits, single required check, no force-push).
Don't Use When
- Authoring generic GitHub Actions workflows with no BC Gov / security angle — prefer the upstream GitHub Actions docs or a general CI catalogue.
- Authoring reusable / callable workflows that deploy or validate into the BC Gov Azure Landing Zone (e.g., platform-team-owned deploy templates, OpenShift or AKS deploy callers) — defer to the BC Gov
gha-workflowsskill (when installed in your agent environment); this skill covers only the workflow chassis (permissions, pinning, fork-gate, aggregator, ruleset) around such callers, not their deploy logic. - Designing the workflow's domain logic (Bicep what-if, Terraform plan, npm publish mechanics, Pages build) — those belong to the domain skill: sibling [
azure-networking](../azure-networking/SKILL.md) in this catalogue, or upstream Microsoftazure-prepare/azure-deployskills (if installed). This skill covers only the workflow chassis around them. - Provisioning the Azure infrastructure the workflow deploys to — use the matching domain skill (upstream Microsoft
azure-*skills, if installed). - Defining branch-protection rules in code via a Terraform provider — out of scope; this skill describes the settings a ruleset should carry, not the IaC to apply them.
- Debugging self-hosted runner setup, ARC (Actions Runner Controller), or runner-image customization — out of scope.
Workflow
- Set repository defaults before writing any workflow. In the repo's Settings → Actions → General: set Workflow permissions to Read repository contents and packages permissions (default-deny write); leave Allow GitHub Actions to create and approve pull requests off unless a workflow truly needs it; under Fork pull request workflows from outside collaborators pick Require approval for first-time contributors who are new to GitHub at minimum. These defaults make every later
permissions:block additive instead of subtractive. - Pin the runner OS to a specific version label, never the
*-latestaliases. Useruns-on: ubuntu-24.04for Linux jobs,runs-on: windows-2025(orwindows-2022) for Windows jobs, andruns-on: macos-26(ormacos-15) for macOS jobs — or whichever specific label the repo standardises on. A pinned runner makes the OS upgrade a deliberate PR you review, not a silent rollover during an incident;ubuntu-latest,windows-latest, andmacos-latesteach roll forward on GitHub's own cadence and the breaking-change blast radius differs by platform (Xcode majors and SDK churn on macOS, Visual Studio component updates on Windows, glibc / Python / Node default bumps on Ubuntu). - Pin actions by class: first-party actions owned by GitHub (
actions/*) pin to a major tag (actions/checkout@v6) — GitHub guarantees those tags are forward-only and Dependabot still bumps majors. Third-party actions pin to an immutable commit SHA with the human-readable version in a trailing comment (uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0) so a tag hijack cannot swap the action's code under you. - Declare top-level least-privilege
permissions:— prefer the deny-allpermissions: {}baseline. Start every workflow withpermissions: {}(empty mapping = deny everything) and add only the specific scopes each job needs (e.g.contents: readon the checkout job,packages: writeon the publish job,pages: write+id-token: writeon the deploy job,pull-requests: writeon the bot-comment job). Anything that doesn't appear in the block is implicitly denied. If a workflow legitimately needscontents: readeverywhere (e.g. all jobs check out the repo), set that at workflow scope and keep every elevation per-job. - Harden every non-trivial
run:block withset -euo pipefailas the first line (Bash is the default shell onubuntu-*runners). Chain related commands with&&rather than newlines when you want them to be one logical step that stops on the first failure. Withoutpipefail, a failure on the left side of a|is silently dropped; without-u, an unset variable becomes an empty string that quietly corrupts commands. - Add a fork-gate as the first job on any
pull_requestworkflow in a public repo. The job comparesgithub.event.pull_request.head.repo.full_nametogithub.repositoryandexit 1s when they differ, so maintainers re-land fork PRs from an in-repo branch after review. This keeps CI minutes (and any future token) off untrusted code. - Wire a
resultsaggregator job withif: always(),needs: [every-other-job], and a step that fails when anyneeds.*.resultisfailureorcancelled. Make this single job the only required status check in branch protection — then renaming or splitting upstream jobs never requires touching the ruleset. Treatskippedas a pass so conditional jobs don't false-fail the gate. - Constrain the trigger surface. On
push/pull_request, addpaths:filters so the workflow only runs when files it cares about change (skills/**for the publish workflow,docs/**for Pages, etc.). On scheduled or long-running workflows addconcurrency: { group: "", cancel-in-progress: }—truefor build/test,falsefor deploy / publish to avoid mid-flight cancellation. - Pass untrusted input only through
env:, never via${{ … }}interpolation in arun:block. PR titles, branch names, issue bodies, and PR head SHAs are attacker-controlled. Bind them to environment variables in anenv:map and reference them with shell quoting ("$PR_TITLE") so a; rm -rf /payload becomes a literal string, not a command. - Use
pull_request_targetonly when you must mutate the PR (approve, label, auto-merge) and never combine it withactions/checkoutof the PR head without an explicit reviewer gate —pull_request_targetruns with the base repo's tokens. Limit those workflows toif: github.event.pull_request.user.login == 'dependabot[bot]'(or an equivalent allow-list) and a tightconcurrencygroup to avoid race conditions onsynchronize. - Configure Dependabot in two files.
.github/dependabot.ymlenables thegithub-actionsecosystem (and any others —npm,pip,uv,docker) so every pinned action and runtime gets PR-bumped, and uses a Conventional-Commitcommit-message.prefixkeyed to the ecosystem (deps(actions),deps(npm),deps(pip),deps(uv)). Pair it with a smalldependabot-auto-merge.ymltriggered onpull_request_targetthat approves and enables auto-merge only fordependabot[bot]PRs, with a deny-allpermissions: {}at the workflow scope andpermissions: { contents: write, pull-requests: write }scoped to the single auto-merge job. - Require signed commits on
main. Either via the GitHub branch ruleset (Settings → Rules → Rulesets) or the legacy branch protection page: enable Require signed commits, Require a pull request before merging, point Require status checks at the singleresultsjob, set Require strict status checks, and disable Allow force pushes and Allow deletions. Signed commits + a single required check is the BC Gov default — keep workflow PRs themselves signed so they don't lock you out. - Use Conventional Commits for workflow / action PRs, with the scope derived from the primary directory touched:
ci(workflows): ...for.github/workflows/**edits,ci(scripts): ...for.github/scripts/**,deps(actions): ...for pinned-action bumps. This is what the BC Gov coding standard expects and what Dependabot is already configured to emit — keep human PRs consistent so changelogs and release-notes generation stay clean. - Validate locally before opening a PR. Lint YAML (
yamllint .github/workflows/), shellcheck any non-trivialrun:blocks (or useactionlintwhich extracts and shellchecks them for you), and dry-run withactfor fast iteration on the matrix surface. For this repo specifically, also runmake lint test validateso theresultscheck passes on the first push.
Rules
- Always pin first-party
actions/*to a major tag, third-party actions to an immutable commit SHA + version comment, and runner tools/binaries to a specific release version (never pipe unpinned installer scripts from raw CDN URLs). (Why: GitHub forward-guaranteesactions/*major tags; third-party tags are mutable and a hijacked release can ship an exfil payload to every workflow that uses it; unpinned CDN scripts are a security and rate-limiting risk. Dependabot bumps both actions styles, so you lose nothing.) - Always declare a top-level
permissions:block —permissions: {}(deny-all) as the strongest default, orpermissions: { contents: read }when every job legitimately needs to read the repo — and step up to anywritescope only on the single job that needs it. (Why: workflows inherit the repo's Workflow permissions default; setting it at workflow scope makes least-privilege a code-review artefact instead of a UI checkbox that can drift.permissions: {}is the BC Gov standard baseline.) - Always start non-trivial
run:blocks withset -euo pipefailand chain commands you want short-circuited with&&. (Why: Bash withoutpipefailsilently swallows failures upstream of a|; without-uan unset variable becomes""and a missing secret turns into a quietly successful no-op instead of a loud failure.&&makes the dependency between commands part of the shell script, not assumed.) - Always pin the runner to a specific OS version label —
ubuntu-24.04for Linux,windows-2025(orwindows-2022) for Windows,macos-26(ormacos-15) for macOS — never the*-latestaliases (ubuntu-latest,windows-latest,macos-latest). (Why: every*-latestalias silently rolls to a new major version on GitHub's own cadence — Ubuntu LTS roughly yearly, Windows Server every few years, macOS once a year — and the breaking-change blast radius is real on every platform: Xcode majors and SDK churn on macOS, Visual Studio component updates on Windows, glibc / Python / Node default bumps on Ubuntu. A pinned label makes the upgrade a deliberate, reviewable PR instead of an incident-time surprise. GitHub only supports the latest two GA images per OS, so the pinned label also tells you when the OS itself is approaching deprecation.) - Always make a single
resultsaggregator job the only required status check in branch protection. (Why: required-check names are duplicated into the ruleset by string; renaming or splitting any underlying job is otherwise a coordinated change across two systems. The aggregator decouples them andif: always()lets it run even when an upstream job fails.) - Always add a
fork-gatejob that fails fast on PRs opened from a fork in any public-repopull_requestworkflow. (Why: even withpermissions: read, fork PRs spend CI minutes and any future expansion of the workflow's privileges immediately becomes a token-exfil hole. Maintainers re-land fork work from an in-repo branch.) - Always pass attacker-controlled values (
github.event.pull_request.title,head_ref,issue.body,comment.body) throughenv:and quote them in shell. (Why:${{ … }}is interpolated by the runner before the shell parses, so a payload like"; curl …"becomes a command, not a string.env:+"$VAR"is the only safe pattern.) - Always check the actor on a
pull_request_targetworkflow before doing anything that uses the elevated token. (Why:pull_request_targetruns in the base repo's context with its secrets; without a strict actor gate, a fork PR can trigger a privileged path.) - Never check out the PR's head SHA on a
pull_request_targetworkflow and then run code from it (build/test/install hooks). (Why: that re-introduces the untrusted code into a privileged context — the exact attackpull_request_targetwas designed around.) - Never use a long-lived Personal Access Token where the built-in
GITHUB_TOKENor OIDC will work. (Why: PATs grant their owner's full permissions, don't expire on PR close, and bypass the per-workflowpermissions:ceiling; they also block on the owner leaving the org.) - Always log into a public cloud (Azure or AWS) via OIDC federation, not a stored secret credential. Grant
id-token: writeon the deploy job only, use the official provider action SHA-pinned per Rule #1 (azure/loginoraws-actions/configure-aws-credentials), and pin the cloud-side trust by a subject claim scoped to repo + branch / environment (repo:OWNER/REPO:environment:prodis the strongest shape because it composes with GitHub Environments' required-reviewers gate). (Why: a long-livedAZURE_CLIENT_SECRETor AWS access key insecrets.*is a stealable, non-expiring, environment-blind credential — exactly the blast-radius profile that an action-tag hijack, apull_request_targetslip, or an insider is designed to harvest. OIDC tokens expire in ~5 minutes, are minted only whenid-token: writeis granted on a specific job, and the cloud-side subject pins the GitHub repo + ref so a fork or a non-mainbranch cannot exchange them.) - Never disable signed-commit enforcement on
main, even temporarily. (Why: the BC Gov ruleset treats it as a hard floor; disabling it for one merge requires re-enabling and means any unsigned commit that landed during the gap is permanent.)
Examples
- "Add CI to a new repo so PRs run lint and tests" → workflow with
on: pull_request,permissions: { contents: read }, jobsfork-gate → lint-tests → results,actions/checkout@v6+ a pinned setup action,runs-on: ubuntu-24.04, single required check =results. - "Publish an npm package from
mainon a path change" → workflow withon: push: { branches: [main], paths: ["packages/**"] },permissions: { contents: read, packages: write }on the publish job only,actions/setup-node@v6withregistry-url: https://npm.pkg.github.com,NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}, and a version-skip step that no-ops when the version is already on the registry. - "Deploy GitHub Pages from
docs/" → workflow withpermissions: { contents: read, pages: write, id-token: write }at workflow scope,concurrency: { group: pages, cancel-in-progress: false }, build job → deploy job,actions/configure-pages@v6,actions/upload-pages-artifact@v5,actions/deploy-pages@v5. - "Deploy to Azure / AWS from a workflow without storing cloud secrets" → OIDC federation on the deploy job:
permissions: { contents: read, id-token: write },environment: prod, thenuses: azure/login@ # v3.0.0with onlyclient-id/tenant-id/subscription-id(noclient-secret), oruses: aws-actions/configure-aws-credentials@ # v6withrole-to-assume+aws-region(noaws-access-key-id/aws-secret-access-key). Cloud-side: create a Federated Identity Creden
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: bcgov
- Source: bcgov/agent-skills
- License: Apache-2.0
- Homepage: https://bcgov.github.io/agent-skills/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.