PS Product SecurityKnowledge Base

๐Ÿ™ GitHub Actions for Product Security

Intro: GitHub Actions is not just a CI system. It is also a practical control plane for product security: dependency review, code scanning, secret prevention, provenance, and safer cloud access can all be implemented as policy-bearing workflows.

What this page includes

  • what GitHub Actions is and why it matters to Product Security
  • where GitHub Actions is strong and where teams misuse it
  • 5 practical workflow examples
  • web UI setup notes and defensive defaults

What it is

GitHub Actions is GitHubโ€™s workflow automation system. It runs jobs defined in YAML files under .github/workflows/.

In a Product Security context, GitHub Actions commonly becomes the place where teams:

  • block insecure dependency changes before merge;
  • run SAST or code scanning;
  • generate SBOMs and provenance;
  • authenticate to cloud providers without long-lived secrets;
  • standardize security checks across many repositories.

Why Product Security teams care

GitHub Actions is useful to Product Security because it sits close to:

  • pull requests;
  • branch protection;
  • dependency changes;
  • code scanning results;
  • source provenance;
  • repository and organization policy.
  • ownership and disclosure defaults such as CODEOWNERS, SECURITY.md, and org-level templates.

That means it can enforce security where engineers already work, instead of asking them to leave the repo and use a separate process.

When GitHub Actions is a strong fit

Use GitHub Actions heavily when you want:

  • security checks at pull-request time;
  • organization-wide reusable workflows;
  • GitHub-native CodeQL and dependency review;
  • artifact attestations and provenance;
  • OIDC-based deployments to cloud platforms;
  • consistent guardrails across many repositories.

When not to overload GitHub Actions

GitHub Actions is not the right place for every test.

Keep these outside fast PR checks when needed:

  • long-running DAST against production;
  • full manual penetration testing;
  • complex environment-specific fuzzing;
  • sensitive signing operations that belong in isolated release infrastructure.

Use three speeds:

  1. fast PR checks โ€” dependency review, linting, lightweight SAST;
  2. merge or main-branch checks โ€” deeper scans, SBOM generation, attestations;
  3. out-of-band or release-stage checks โ€” heavyweight or manual validation.

Hardening defaults before you add more workflows

Repository UI steps

Settings โ†’ Actions โ†’ General

Review:

  • who can create and approve workflows;
  • whether local actions and reusable workflows are allowed;
  • whether unverified marketplace actions are restricted;
  • default GITHUB_TOKEN permissions.

Good default: read-only by default, then grant explicit write permissions per workflow.

Settings โ†’ Security / Code security and analysis

Depending on plan and features enabled, review:

  • dependency graph;
  • dependency review;
  • code scanning;
  • secret scanning;
  • push protection.

Settings โ†’ Branches

Make security workflows count:

  • require status checks before merge;
  • require pull request reviews;
  • protect the default branch;
  • block direct pushes for most contributors.

For a repository-baseline view that complements workflow hardening, see Repository Governance โ€” CODEOWNERS, SECURITY.md, and Default Files.

Security pitfalls teams hit first

Pitfall Why it happens Better pattern
default broad GITHUB_TOKEN permissions convenience set minimal permissions per job
unpinned third-party actions fast copy-paste pin to a full commit SHA where possible
long-lived cloud secrets in repo settings easy setup OIDC to cloud provider
one reusable workflow copied into 50 repos manually drift central reusable workflows
self-hosted runner used for untrusted PRs and production deploys poor trust separation split runners by trust, or use GitHub-hosted for low-trust jobs
treating Actions as โ€œjust CIโ€ missed security value use it as policy and evidence plane

Example 1 - Dependency Review gate for pull requests

What it does

Blocks pull requests that introduce vulnerable dependencies.

File

.github/workflows/dependency-review.yml

See: snippets/ci/github-actions/dependency-review.yml

name: dependency-review

on:
  pull_request:
    branches: [ main ]

permissions:
  contents: read

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - name: Dependency Review
        uses: actions/dependency-review-action@v4
        with:
          fail-on-severity: high
          allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause

Why Product Security likes it

  • fast PR feedback;
  • no separate scanner UI needed for basic dependency change review;
  • easy to mark as a required status check.

When to use

  • almost every app repo with lockfiles or package manifests.

Example 2 - CodeQL code scanning workflow

What it does

Runs GitHub-native code scanning with CodeQL and publishes alerts in the Security tab.

File

.github/workflows/codeql.yml

See: snippets/ci/github-actions/codeql.yml

name: codeql

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  schedule:
    - cron: "17 3 * * 1"

permissions:
  contents: read
  security-events: write
  actions: read

jobs:
  analyze:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        language: [ "javascript", "python" ]
    steps:
      - uses: actions/checkout@v4
      - uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
      - uses: github/codeql-action/autobuild@v3
      - uses: github/codeql-action/analyze@v3

Why Product Security likes it

  • first-class GitHub integration;
  • results live where developers already review PRs and alerts;
  • useful as a baseline SAST layer.

Operational note

Default setup can be good enough for many repos. Advanced setup is better when you need explicit languages, build steps, paths, or schedules.


Example 3 - OIDC to AWS instead of static cloud keys

What it does

Lets a workflow obtain short-lived AWS credentials using GitHubโ€™s OIDC integration instead of storing long-lived AWS keys in GitHub secrets.

File

.github/workflows/deploy-oidc-aws.yml

See: snippets/ci/github-actions/aws-oidc-deploy.yml

name: deploy-with-oidc

on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials from OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-prod-deploy
          aws-region: eu-central-1

      - name: Verify identity
        run: aws sts get-caller-identity

      - name: Deploy example
        run: |
          aws s3 sync dist/ s3://example-prod-site/

Why this is better

  • no long-lived AWS access key in GitHub;
  • access is scoped by repository, branch, environment, and role trust policy;
  • easier to rotate because there is no duplicated secret to rotate in GitHub.

Common pitfall

People enable OIDC but make the cloud trust conditions too broad. Restrict by repo, branch, environment, or reusable workflow identity.


Example 4 - Build artifact attestation and verification

What it does

Creates provenance for a build artifact and later verifies it.

Files

  • .github/workflows/attest-build.yml
  • local verification with GitHub CLI

See: snippets/ci/github-actions/artifact-attestation.yml

name: attest-build

on:
  push:
    branches: [ main ]

permissions:
  contents: read
  id-token: write
  attestations: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build binary
        run: |
          mkdir -p dist
          echo "example build output" > dist/app.txt
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: app-artifact
          path: dist/app.txt
      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@v1
        with:
          subject-path: dist/app.txt

Verification example

gh attestation verify dist/app.txt   --owner your-org

Why Product Security likes it

  • gives provenance for โ€œwhat was built, where, and by which workflowโ€;
  • supports stronger software supply chain controls;
  • pairs well with reusable workflows and deployment policies.

Example 5 - Reusable security workflow used across repositories

What it does

Defines one reusable workflow for common security checks, then calls it from many repos.

Reusable workflow file

.github/workflows/reusable-security-gate.yml

See: snippets/ci/github-actions/reusable-security-gate.yml

name: reusable-security-gate

on:
  workflow_call:
    inputs:
      python-version:
        required: false
        type: string
        default: "3.12"

permissions:
  contents: read
  security-events: write

jobs:
  security-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ inputs.python-version }}
      - name: Run Semgrep
        run: |
          pip install semgrep
          semgrep scan --config p/default .

Calling repo workflow

name: pr-security

on:
  pull_request:
    branches: [ main ]

jobs:
  call-shared-gate:
    uses: your-org/security-standards/.github/workflows/reusable-security-gate.yml@main
    permissions:
      contents: read
      security-events: write

Why it matters

  • avoids copy-paste drift;
  • lets Product Security maintain one standard gate;
  • works especially well with OIDC and organization-wide rollout.

Other high-value GitHub-native controls

Secret scanning and push protection

Use the GitHub UI to enable:

  • secret scanning;
  • push protection;
  • validity checks where supported.

Typical UI path

  • Repository โ†’ Settings โ†’ Advanced Security
  • enable Secret Protection
  • enable Push protection

This is not the same as scanning during CI after the secret is already in the repo. Push protection aims to stop the secret before it lands.

Branch protection + required workflows

A workflow only changes behavior if merge policy depends on it. Mark security workflows as required status checks.

Environments

Use protected environments for production deployments:

  • required reviewers;
  • wait timers if justified;
  • environment secrets only where absolutely needed.

What used to be common vs what is stronger now

Older pattern Why teams used it Stronger 2026 pattern
store cloud keys in GitHub secrets simple OIDC federation
every repo owns its own security workflow copy easy at small scale reusable workflows and org templates
run everything on self-hosted runners local control split runner trust levels; prefer GitHub-hosted for low-trust checks
use Actions only for build/test narrow CI mindset use Actions as security control plane
publish artifacts with no provenance common legacy flow generate and verify attestations

How Product Security teams use GitHub Actions in practice

Pattern A - minimum viable security baseline

  • dependency review;
  • CodeQL or another code scanning layer;
  • secret scanning and push protection;
  • branch protection with required checks.

Pattern B - stronger supply chain posture

  • SBOM generation;
  • artifact attestation;
  • signed release workflow;
  • OIDC deploy;
  • reusable workflows.

Pattern C - organization-level governance

  • centrally maintained reusable workflows;
  • enforced dependency review;
  • environment protection;
  • metrics from alerts, blocked merges, and push-protection events.


Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.