.github/docs/security/workflow-hardening.md at main · windlasstech/.github · GitHub
Skip to content

Latest commit

 

History

History
272 lines (212 loc) · 13 KB

workflow-hardening.md

File metadata and controls

272 lines (212 loc) · 13 KB

Workflow Hardening

Note

This page is part of the Windlass organization security policy. Start with SECURITY.md.

All GitHub Actions workflows enforce supply-chain protections:

Action References

  • SHA-pinned actions: Every uses: reference is pinned to the full 40-character commit SHA (not a mutable tag like @v4). This prevents supply-chain attacks where a tag is silently repointed to a compromised commit. Dependabot keeps SHAs up to date automatically via weekly PRs.

    Example:

    # Correct - SHA pinned with version comment
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
    
    # Incorrect - mutable tag
    - uses: actions/checkout@v6

Internal Reusable Workflows

Reusable workflows published from windlasstech/.github are exempt from the SHA-pinning requirement, for two distinct reasons:

  • Local references (uses: ./.github/workflows/...): GitHub resolves local reusable workflow paths at the caller workflow's own commit, so there is no separate ref to pin. The reference is structurally immutable, and every change is reviewed in the same commit as its callers.
  • Cross-repository references (uses: windlasstech/.github/...@main): Windlass consumer repositories reference these workflows by branch name as a deliberate risk acceptance, not as a security equivalent of SHA pinning. The .github repository is managed within the same organizational trust boundary, and @main propagates centrally reviewed changes to all consumers immediately, without per-consumer version management. The accepted trade-off is that any change to the default branch (including a transient malicious or erroneous change) can execute in consumer workflow contexts, with the caller's granted permissions and secrets, before consumer-specific adoption. This risk is mitigated, not eliminated, by protected branches, signed commits, required CI, least-privilege permissions, and Harden Runner coverage. Windlass consumers running high-privilege workflows (release or deployment jobs holding id-token: write or similarly sensitive grants) may instead pin to a full-length commit SHA for per-repository rollout control. Callers outside the Windlass organization are outside this trust boundary and must pin a full-length commit SHA rather than @main.

This exception applies only to Windlass-owned reusable workflows in this repository. It does not extend to any third-party action or workflow.

Third-Party Action Selection

Workflow Linting

Lint all workflow files with windlasstech/actionlint-hardened-action. This action runs actionlint from a SHA-pinned Docker image and checks for common workflow mistakes, invalid expressions, missing permissions, and insecure patterns.

Add the action to CI after checkout:

- name: Lint workflow files with actionlint
  uses: windlasstech/actionlint-hardened-action@043a16f2538fe7bee89d8e19bbd5292e925210e0 # v1.0.0
  with:
    paths: |
      .github/workflows/*.yml
      .github/workflows/*.yaml

For repository-wide workflow validation, include this step in the repository lint workflow so every workflow change is checked automatically.

Runner Security

  • Harden Runner (required, all repositories): Windlass uses StepSecurity Enterprise, so every job in every repository, public and private, must start with step-security/harden-runner. The minimum permitted configuration is audit mode (egress-policy: audit), which logs all outbound network calls and provides visibility into unexpected egress from CI runners. Enterprise coverage removes the previous limitation where Harden Runner insights were unavailable on private repositories.

  • Recommended progression to block mode: Once audit mode has established a baseline of a workflow's outbound destinations, switch to egress-policy: block with an allowed-endpoints allow-list so all egress traffic except the approved endpoints is blocked. Audit mode is the floor, not the target state:

    - name: Harden the runner (Block all outbound calls except allowed endpoints)
      uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
      with:
        egress-policy: block
        allowed-endpoints: >
          api.github.com:443  
          github.com:443

Harden Runner in Reusable Workflows

Harden Runner must live inside the reusable workflow itself, as the first step of each of its jobs. Two structural reasons rule out caller-side placement:

  • A caller job that invokes a reusable workflow via jobs.<id>.uses: cannot define steps:, so the caller cannot wrap the call with its own Harden Runner step.
  • Every job executes on a fresh ephemeral runner VM allocated in the caller's context, and Harden Runner monitors only the runner of the job it executes in. A step in any other caller job observes nothing about the called workflow's job.

This repository's reusable workflows (OSV Scanner PR/Full, Dependency Review, Scorecard) therefore ship Harden Runner built in, with egress-policy: audit as the non-removable floor.

  • Caller-configurable policy: The reusable workflows expose an egress-policy input (string, default audit) and an allowed-endpoints input (string, default empty, one endpoint per line such as api.github.com:443, effective only with egress-policy: block) so callers can opt into block mode. No input can disable Harden Runner or drop below audit.

Warning

Block mode fails the job when a required endpoint is missing from the allow-list. Follow the progression recommended above: establish a baseline in audit mode first, then switch to egress-policy: block with a complete allowed-endpoints list.

  • Policy Store: On StepSecurity Enterprise, when a reusable workflow is called, the calling repository's policy applies, not the policy of the repository hosting the reusable workflow. Organization-wide block policies can therefore be managed centrally in the Policy Store without per-workflow input wiring.

  • Callers outside the organization: These reusable workflows are public and may be called from other organizations. Harden Runner executes on the caller's runner in the caller's context, so external callers get audit-mode coverage without any Windlass-side configuration. Note that the Community tier covers public repositories only.

  • Ephemeral environments: All builds use fresh, isolated runners with no persistence between builds

Permission Management

When adding or modifying workflows, contributors must:

  1. Explicit top-level permissions: Set permissions: {} or minimal read-only permissions at the workflow level. This establishes a secure default that prevents accidental privilege escalation.

  2. Job-level elevation: Grant additional permissions at the job level only when required:

    • id-token: write: Required for OIDC token generation
    • attestations: write: Required for artifact attestations
    • artifact-metadata: write: Required to upload storage records to the organization's linked artifacts page
    • packages: write: Required for container registry pushes
    • pull-requests: write: Required for PR comments

    Example:

    name: CI
    
    # Minimal permissions at top level
    permissions:
      contents: read
    
    jobs:
      build:
        runs-on: ubuntu-latest
        # Inherits top-level permissions (read-only)
        steps:
          - name: Harden the runner (Audit all outbound calls)
            uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
            with:
              egress-policy: audit
    
          - name: Checkout
            uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
    
      release:
        runs-on: ubuntu-latest
        # Elevated permissions only for this job
        permissions:
          contents: read
          id-token: write # Required for OIDC
          attestations: write # Required for attestations
          artifact-metadata: write # Required for linked artifact storage records
          packages: write # Required for GHCR
        steps:
          - name: Harden the runner (Audit all outbound calls)
            uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
            with:
              egress-policy: audit
    
          - name: Checkout
            uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
  3. Environment protection: Production deployments must use protected environments with:

    • Required reviewers for approval
    • Deployment branch policies
    • Wait timers where appropriate

OIDC and Cloud Authentication

Use OpenID Connect (OIDC) for authentication to cloud providers instead of long-lived credentials stored as GitHub secrets. OIDC credentials are short-lived, scoped to the workflow identity, and controlled by provider-side trust policies.

Supported providers:

  • AWS: aws-actions/configure-aws-credentials
  • Azure: azure/login
  • Google Cloud: google-github-actions/auth

AWS OIDC Example

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@e3dd6a429c905ace6919b0a7664b96b2b5dc3c81 # v4.0.2
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-deploy-role
          aws-region: us-east-1

      - name: Deploy
        run: aws s3 sync ./dist s3://my-bucket/

Release Artifact Integrity

Release artifact provenance, SBOM attestations, linked artifacts uploads, and consumer verification commands are documented in Artifact Attestations.

Use this workflow hardening guide for GitHub Actions permissions, SHA-pinning, runner security, and OIDC configuration. Use the artifact attestation guide for release artifact signing and verification requirements.

References

GitHub Security

Step Security