Hamburger Cross Icon
GitHub Actions Security Guardrails - Lunar Policy for Security And Compliance

GitHub Actions Security Guardrails

Policy Beta Security And Compliance

Enforces security best practices for GitHub Actions workflows. Detects template injection, dangerous pull_request_target checkouts, missing permissions, credential persistence, and secrets inheritance.

Add 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.

Guardrail

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.

injection run expression template security
View Guardrail
Guardrail

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).

pull-request-target checkout fork security
View Guardrail
Guardrail

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.

permissions least-privilege security
View Guardrail
Guardrail

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.

permissions write-all least-privilege security
View Guardrail
Guardrail

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).

checkout credentials token security
View Guardrail
Guardrail

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.

secrets inherit reusable least-privilege security
View Guardrail

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
1
Integrations Gather Data
Collectors extract metadata from code, CI pipelines, tool outputs, and scans
2
{ } Centralized as JSON
All data merged into each component's unified metadata document
3
Guardrails Enforce Standards This Policy
Real-time feedback in PRs and AI workflows

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 GitHub

GitHub 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.

View Repository

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.

Works with any process
check AI agent rules & prompt files
check Post-mortem action items
check Security & compliance policies
check Testing & quality requirements
Automate Now
Paste your AGENTS.md or manual process doc and get guardrails in minutes
Book a Demo