GitHub Actions Security Guardrails
Enforces security best practices for GitHub Actions workflows. Detects template injection, dangerous pull_request_target checkouts, missing permissions, credential persistence, and secrets inheritance.
github-actions to your lunar-config.yml:uses: github://earthly/lunar-lib/policies/github-actions@v1.0.5
Included Guardrails
This policy includes 6 guardrails that enforce standards for your security and compliance.
no-script-injection
Flags attacker-controlled ${{ }} expressions used directly in run: blocks and actions/github-script script: fields. These get substituted before the shell executes, enabling arbitrary code injection via PR titles, branch names, commit messages, and other user-controlled inputs.
no-dangerous-trigger-checkout
Flags pull_request_target workflows that check out PR head code. This runs attacker-supplied code with base-branch secrets and write permissions — the pattern behind the tj-actions/changed-files breach (CVE-2025-30066).
permissions-declared
Flags workflows with no permissions: key at workflow or job level. Without explicit permissions, workflows inherit org/repo defaults which may be write-all on repos created before GitHub's Feb 2023 default change.
no-write-all-permissions
Flags explicit permissions: write-all at workflow or job level. Workflows should declare only the specific scopes they need to follow the principle of least privilege.
checkout-no-persist-credentials
Flags actions/checkout steps that don't set persist-credentials: false. The default (true) stores GITHUB_TOKEN in .git/config, which leaks if the checkout directory is uploaded as an artifact (ArtiPACKED attacks, 2024).
no-secrets-inherit
Flags secrets: inherit in reusable workflow calls. This passes ALL repository and org secrets to the called workflow, violating least-privilege. Workflows should explicitly pass only needed secrets.
How Guardrails Fit into Lunar
Lunar guardrails define your engineering standards as code. They evaluate data collected by integrations and produce pass/fail checks with actionable feedback.
Policies support gradual enforcement—from silent scoring to blocking PRs or deployments—letting you roll out standards at your own pace without disrupting existing workflows.
Learn How Lunar Works →Required Integrations
This policy evaluates data gathered by one or more of the following integration(s).
Make sure to enable them in your lunar-config.yml.
Documentation
View on GitHubGitHub Actions Security Guardrails
Enforces GitHub Actions security best practices — injection prevention, least-privilege permissions, and credential hygiene.
Overview
Checks GitHub Actions workflows for six categories of security misconfiguration that have led to real-world supply chain compromises. All checks analyze the parsed workflow data collected by the github-actions collector — no separate security collector needed. Skips gracefully when no GitHub Actions workflow data is available (i.e., component has no .github/workflows/ directory). Complements the general ci policy (which covers vendor-agnostic lint and dependency pinning) with GHA-specific security checks. Different from the sast policy (which covers application code analysis, not CI configuration).
Policies
This plugin provides the following policies (use include to select a subset):
| Policy | Description |
|---|---|
no-script-injection |
Flags attacker-controlled ${{ }} expressions in run: blocks and actions/github-script script: fields |
no-dangerous-trigger-checkout |
Flags pull_request_target workflows that check out PR head code |
permissions-declared |
Flags workflows with no explicit permissions: key |
no-write-all-permissions |
Flags permissions: write-all at workflow or job level |
checkout-no-persist-credentials |
Flags actions/checkout without persist-credentials: false |
no-secrets-inherit |
Flags secrets: inherit in reusable workflow calls |
Required Data
This policy reads from the following Component JSON paths:
| Path | Type | Provided By |
|---|---|---|
.ci.native.github_actions.workflows[] |
array | github-actions collector (workflows sub-collector) |
The policy walks the parsed workflow data (triggers, permissions, jobs, steps, run blocks, with parameters) and applies security rules directly. No pre-processed security data needed — the collector just gathers the raw workflow structure.
Note: Ensure the github-actions collector is configured before enabling this policy.
Installation
collectors:
- uses: github://earthly/lunar-lib/collectors/github-actions@main
on: ["domain:your-domain"]
policies:
- uses: github://earthly/lunar-lib/policies/github-actions@main
on: ["domain:your-domain"]
enforcement: report-pr
# include: [no-script-injection, permissions-declared] # Run specific checks only
Examples
Passing Example
Workflow has explicit permissions, safe env-var indirection for user input, and secure checkout:
{
"ci": {
"native": {
"github_actions": {
"workflows": [
{
"file": ".github/workflows/ci.yml",
"name": "CI",
"triggers": ["push", "pull_request"],
"permissions": { "contents": "read" },
"jobs": {
"build": {
"steps": [
{
"name": "Checkout",
"uses": "actions/checkout@abc123",
"with": { "persist-credentials": false }
},
{
"name": "Run tests",
"run": "echo \"PR: $PR_TITLE\"",
"env": { "PR_TITLE": "${{ github.event.pull_request.title }}" }
}
]
}
}
}
]
}
}
}
}
Failing Example — Script Injection
A workflow uses github.event.pull_request.title directly in a run: block (not via env var):
{
"ci": {
"native": {
"github_actions": {
"workflows": [
{
"file": ".github/workflows/ci.yml",
"triggers": ["pull_request"],
"jobs": {
"greet": {
"steps": [
{
"name": "Echo PR title",
"run": "echo \"PR: ${{ github.event.pull_request.title }}\""
}
]
}
}
}
]
}
}
}
}
Failure message: "1 injectable expression(s) found — .github/workflows/ci.yml: job 'greet', step 'Echo PR title' uses github.event.pull_request.title in run block"
Failing Example — Dangerous Checkout
A pull_request_target workflow checks out PR head code:
{
"ci": {
"native": {
"github_actions": {
"workflows": [
{
"file": ".github/workflows/pr-target.yml",
"triggers": ["pull_request_target"],
"jobs": {
"build": {
"steps": [
{
"name": "Checkout",
"uses": "actions/checkout@v4",
"with": { "ref": "${{ github.event.pull_request.head.sha }}" }
}
]
}
}
}
]
}
}
}
}
Failure message: "1 dangerous checkout(s) found — .github/workflows/pr-target.yml: pull_request_target workflow checks out PR head ref in job 'build'"
Remediation
Script Injection (no-script-injection)
Use intermediate environment variables instead of inline expressions:
# Bad — injectable
- run: echo "PR: ${{ github.event.pull_request.title }}"
# Good — safe
- run: echo "PR: $PR_TITLE"
env:
PR_TITLE: ${{ github.event.pull_request.title }}
For actions/github-script, use context properties instead of template expressions:
# Bad — injectable
- uses: actions/github-script@v7
with:
script: |
const title = "${{ github.event.pull_request.title }}";
# Good — safe
- uses: actions/github-script@v7
with:
script: |
const title = context.payload.pull_request.title;
Dangerous Checkout (no-dangerous-trigger-checkout)
Avoid checking out PR head code in pull_request_target workflows. If you must, use a two-workflow approach where the trusted workflow runs first:
# Use pull_request instead of pull_request_target when possible
on: pull_request
Missing Permissions (permissions-declared)
Add an explicit permissions: block to every workflow:
permissions:
contents: read
Write-All Permissions (no-write-all-permissions)
Replace permissions: write-all with specific scopes:
# Bad
permissions: write-all
# Good
permissions:
contents: read
pull-requests: write
Credential Persistence (checkout-no-persist-credentials)
Set persist-credentials: false on all checkout steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
Secrets Inherit (no-secrets-inherit)
Pass only the secrets the called workflow needs:
# Bad
uses: ./.github/workflows/deploy.yml
secrets: inherit
# Good
uses: ./.github/workflows/deploy.yml
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Open Source
This policy is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.
Common Use Cases
Explore how individual guardrails work with specific integrations.
Ready to Automate Your Standards?
See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 100+ built-in guardrails.