Skip to content

GitHub Actions Security Review

Purpose: Security posture review of all GitHub Actions workflows in bedrock-docs — credential scope, OIDC usage, SHA pinning, and review cadence.

Owner: Rory Review frequency: Quarterly alongside compliance/annual-review-checklist.mdLast reviewed: May 2026


Why GHA Security Matters

Every GitHub Actions workflow is a trusted dependency with production credentials. Workflows can:

  • Push to protected branches (auto-merge)
  • Write to Azure Key Vault, deploy to Container Apps, modify Terraform state
  • Access OIDC tokens that grant subscription-level access to Azure and AWS

A compromised workflow (via supply chain attack on an unpinned Action, or via a malicious PR) is a direct path to production infrastructure.


Security Principles

  1. SHA-pin all third-party Actions — never use a floating version tag (@v4). Use a full commit SHA (@sha256:... or @<full-sha>). Floating tags can be moved to point at malicious code
  2. Minimum necessary permissions — each workflow should declare only the permissions it actually needs
  3. OIDC over stored secrets — use federated OIDC tokens for Azure and AWS auth. Never store cloud credentials as GitHub Secrets
  4. Restrict write access — workflows triggered on pull_request from forks must not have write permissions or access to secrets
  5. Review PR workflowspull_request_target is dangerous if it runs code from the PR branch with write permissions

Workflow Inventory

auto-merge.yml

Trigger: pull_request events, checks completed Purpose: Auto-merges kobe-bot PRs after CI passes Credentials: GitHub token (write: pull-requests, contents) OIDC: No Risk: Medium — can merge to main. Mitigated by requiring CI pass and only triggering on approved bot accounts SHA pinning: Verify all Actions are SHA-pinned

deploy-docs.yml

Trigger: Push to main Purpose: Builds VitePress site and deploys to Cloudflare Pages Credentials: Cloudflare API token (GitHub Secret: CLOUDFLARE_API_TOKEN) OIDC: No (Cloudflare uses token auth, not OIDC) Risk: Medium — Cloudflare token should be scoped to Pages deployment only, not account-level SHA pinning: Verify cloudflare/wrangler-action is SHA-pinned

pr-validation.yml

Trigger: pull_requestPurpose: Validates PR format, runs doc linting Credentials: GitHub read token only OIDC: No Risk: Low — read-only, triggered on PR (not pull_request_target) SHA pinning: Verify

baseline-scan.yml

Trigger: Push to main, schedule Purpose: Checkov HIPAA scan of IaC files Credentials: GitHub token (read) OIDC: No Risk: Low — read-only scan SHA pinning: Verify bridgecrewio/checkov-action

security-scan.yml

Trigger: Push to main, schedule Purpose: Trivy vulnerability scan Credentials: GitHub token (read) OIDC: No Risk: Low — read-only scan SHA pinning: Verify aquasecurity/trivy-action

auto-merge.yml / ingest-changes.yml

Trigger: Push to main Purpose: POST to SecOps /api/changes to create CM ticket after merge Credentials: SECOPS_API_KEY (GitHub Secret) OIDC: No Risk: Low — writes to SecOps only, not infrastructure Scope check: Verify the API key has only changes:write scope, not admin

palo-alto-backup-on-demand.yml / palo-alto-audit-on-demand.yml / palo-alto-quarterly-bpa.yml

Trigger: Manual dispatch, schedule Purpose: Backs up and audits Palo Alto configuration via Panorama Credentials: Panorama API key (GitHub Secret) OIDC: No Risk: Medium — Panorama API key can read all firewall config. Verify it cannot push config (read-only API key) SHA pinning: Verify

aztfexport-quarterly.yml

Trigger: Schedule (quarterly) Purpose: Runs aztfexport to capture unmanaged Azure resources Credentials: Azure OIDC via federated credential OIDC: Yes — Azure Risk: Medium — Reader access on subscriptions. Verify OIDC federated credential scope is Reader only, not Contributor SHA pinning: Verify azure/login

apply-windows-baseline.yml

Trigger: Manual dispatch Purpose: Applies Windows security baseline via Intune Credentials: Azure OIDC OIDC: Yes — Azure Risk: Medium-High — can modify Intune policies. Confirm OIDC role is scoped to Intune-specific permissions only SHA pinning: Verify

refresh-github-ips.yml

Trigger: Schedule Purpose: Updates Palo Alto rules with current GitHub IP ranges Credentials: Panorama API key + GitHub API OIDC: No Risk: Medium — can modify firewall rules. Verify the Panorama key has only rule-update scope

restart-syslog-vm.yml

Trigger: Manual dispatch Purpose: Restarts the syslog VM via AWS or Azure Credentials: OIDC (Azure or AWS) OIDC: Yes Risk: Medium — can restart a VM. Verify the OIDC role is scoped to the syslog VM resource group only

setup-github-runner.yml

Trigger: Manual dispatch Purpose: Provisions a self-hosted GitHub Actions runner Credentials: Azure OIDC (Contributor on runner resource group) OIDC: Yes — Azure Risk: High — Contributor access. Verify scope is limited to the runner resource group only. Self-hosted runners must run in isolated environments


SHA Pinning Audit

Run this check quarterly to verify all third-party Actions are SHA-pinned:

bash
# Check for non-SHA-pinned actions (version tags or branch refs)
grep -rn "uses:" .github/workflows/ | grep -v "@[0-9a-f]\{40\}" | grep -v "# sha-ok"

Any result is a pinning gap. Update the workflow to use the full SHA:

  1. Find the Action on GitHub (actions/checkout, cloudflare/wrangler-action, etc.)
  2. Go to the specific version tag → copy the commit SHA
  3. Replace @v4 with @<full-sha> # v4.x.x in the workflow file

Secrets Inventory

GitHub Secrets in Cirius-Group-Inc/bedrock-docs:

SecretUsed ByScopeRotation
CLOUDFLARE_API_TOKENdeploy-docs.ymlCloudflare Pages deploy onlyAnnual (January)
SECOPS_API_KEYingest-changes.ymlSecOps changes:writeAnnual (January)
PANORAMA_API_KEYpalo-alto-*.ymlPanorama read (verify read-only)Annual (January)

All cloud auth (Azure, AWS) uses OIDC — no cloud credentials stored as GitHub Secrets.


OIDC Federated Credential Audit

Verify OIDC federated credentials in Entra and AWS are correctly scoped:

bash
# Azure — list federated credentials for the github-deploy-main service principal
az ad sp list --display-name "github-deploy-main" --query "[].appId" -o tsv | \
  xargs -I{} az ad app federated-credential list --id {}

Each credential's subject must match the exact repo and branch:

  • Format: repo:Cirius-Group-Inc/bedrock-docs:ref:refs/heads/main
  • A wildcard subject (repo:Cirius-Group-Inc/*) would allow any repo to claim the identity — verify this is not set

Quarterly Review Checklist

  • [ ] Run SHA pinning audit — update any non-pinned Actions
  • [ ] Review secrets inventory — are any secrets unused or over-scoped?
  • [ ] Review OIDC federated credentials — correct repo and branch in subject?
  • [ ] Review workflow permissions declarations — do any workflows have permissions: write-all?
  • [ ] Check for new workflows added since last review — were they reviewed before merge?
  • [ ] Verify Panorama API key is read-only (cannot push config changes)
  • [ ] Verify apply-windows-baseline.yml OIDC role has minimum Intune scope
  • [ ] Check self-hosted runner (if any) is isolated and up to date

  • cicd/oidc-authentication.md — OIDC setup and federated credential configuration
  • cicd/pipeline-overview.md — CI/CD pipeline architecture
  • security/supply-chain/ — supply chain monitoring agents
  • compliance/annual-review-checklist.md — quarterly review schedule

Internal use only — Cirius Group