Skip to main content

Implementing Security Best Practices

1. Pinning Action Versions

Version Type Description Best Practice Example
SHA-256 Hash Specific commit hash for immutable action runs NEW Recommended for production workflows actions/checkout@b4ff7c50f2d0198322d6a2b40375b93f91b82094
Tagged Release Stable release tags like v2 or v3 Use major version tags for stability actions/checkout@v3
Branch Reference Refers to a branch (e.g., main) Not recommended for production due to instability actions/checkout@main

Example: Using SHA for Pinning

jobs:
  build:
    steps:
      - name: Checkout code
        uses: actions/checkout@b4ff7c50f2d0198322d6a2b40375b93f91b82094
Note: Pinning to a SHA ensures that your workflow will always use the exact same version of an action, even if newer versions are released.

2. Using Verified Actions

Action Repository Verified Description
actions/checkout github.com/actions/checkout VERIFIED Checks out your repository code
actions/setup-node github.com/actions/setup-node VERIFIED Sets up a Node.js environment
aws-actions/configure-aws-credentials github.com/aws-actions/configure-aws-credentials VERIFIED Configures AWS credentials for use in workflows

Example: Using a Verified Action

name: Secure Workflow
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
Note: Always pin your actions to a specific commit or tag to ensure stability and security.

3. Reviewing Third-Party Actions

Property Description Recommendation
action name Name of the third-party action Verify the action is from a trusted source or official GitHub marketplace
version/tag Version or tag used in workflow Pin to a specific commit SHA or stable version (e.g., v1)
source code Public availability of action source Prefer actions with open-source code and active maintenance
permissions Permissions requested by the action Review and limit to only necessary permissions (e.g., contents: read)

Example: Pinning Action Version

jobs:
  build:
    steps:
      - name: Use Node.js
        uses: actions/setup-node@v3.8.1
Note: Using a specific SHA (e.g., actions/setup-node@sha256:abc123...) ensures reproducibility and prevents unexpected behavior from updates.
Warning: Avoid using actions/checkout@master or similar floating tags as they can change without notice.

Best Practices for Action Review

  • Pin all third-party actions to a specific version or SHA
  • Review action permissions before use
  • Verify the action's source and maintainers
  • Use official GitHub Actions when available

4. Limiting Workflow Permissions

Permission Description Default Level Required for
actions Read and write access to GitHub Actions workflows read Reading and modifying workflow files
contents Read and write access to repository contents read Pushing commits, reading files
deployments Read and write access to deployments read Managing deployment environments
discussions Read and write access to discussions read Managing repository discussions
issues Read and write access to issues read Managing issues and comments
packages Read and write access to packages read Managing package publishing and downloads
pages Read and write access to Pages read Managing GitHub Pages settings
pull-requests Read and write access to pull requests read Merging PRs, managing reviews
repository-projects Read and write access to repository projects read Managing project boards
security-events Read and write access to security events read Managing security alerts and findings
statuses Read and write access to commit statuses read Setting and reading status of commits

Example: Setting minimal permissions for a workflow

name: Secure Workflow
on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      actions: read
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
Note: By default, workflows run with contents: read and actions: read. If your workflow does not require write access to repository contents, set contents: read explicitly to reduce the risk of accidental or malicious changes.
Warning: Setting contents: write or actions: write in a workflow increases the risk of code injection or unauthorized changes. Always ensure that these permissions are strictly necessary for the workflow's operation.

5. Using Environment Secrets

Secret Type Description Usage Security Note
Repository Secrets Secrets stored at the repository level secrets.MY_SECRET Accessible to all workflows in the repo
Environment Secrets Secrets tied to a specific environment secrets.MY_SECRET Only accessible to workflows targeting that environment
Organization Secrets Secrets shared across repositories in an organization secrets.MY_SECRET Requires organization-level permissions to manage

Example: Using Environment Secrets in a Workflow

name: Secure Deployment
on:
  push:
    branches: [ main ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
      - name: Deploy to Production
        run: |
          echo "Deploying with secret: ${{ secrets.DEPLOY_KEY }}"
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
Note: Environment secrets are encrypted and stored in GitHub's secure vault, accessible only to workflows that target the specific environment.
Warning: Never log or echo secret values in workflow logs. Use set-output or echo with || true to avoid exposing secrets.
Secret Scope Access Control Visibility Best Practice
Repository Full repo access All workflows Use for project-specific secrets
Environment Environment-specific Only workflows targeting the environment Use for deployment credentials or sensitive config
Organization Org-wide access All repos in the org Use for shared secrets like CI/CD tokens

6. Preventing Script Injection

Attack Vector Description Mitigation
Command Injection Executing OS commands through user-controlled input Validate and sanitize all inputs; use parameterized commands
Code Injection Injecting malicious code into workflows or scripts Avoid dynamic code execution; use static analysis tools

Example: Secure Command Execution

#!/bin/bash
# Safe: Using a whitelist of allowed commands
allowed_commands=("ls" "pwd" "echo")
command="${1}"

if [[ "${allowed_commands[@]}" =~ "${command}" ]]; then
  eval "${command}"
else
  echo "Command not allowed"
fi

Example: Input Validation in Workflow

name: Secure Workflow
on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Validate Input
        run: |
          if [[ "${{ github.event.inputs.branch }}" =~ ^[a-zA-Z0-9\-_]+$ ]]; then
            echo "Valid branch name"
          else
            echo "Invalid branch name"
            exit 1
          fi
Note: Always validate and sanitize inputs before using them in commands or workflows to prevent script injection vulnerabilities.

7. Using CodeQL Analysis

Example: Basic CodeQL workflow configuration

name: "CodeQL Analysis"
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: javascript

      - name: Autobuild
        uses: github/codeql-action/autobuild@v2

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2
Property Description Default
languages List of languages to analyze. Supported: javascript, python, java, go, cpp, csharp, ruby, typescript javascript
queries Path to a custom query pack or security-extended/security-and-quality security-and-quality
config-file Path to a CodeQL configuration file (e.g. .github/codeql/config.yml) None
Note: CodeQL analysis is available for free on public repositories and includes security-and-quality queries by default.
Warning: Ensure security-events: write permission is set to allow CodeQL results to be uploaded.

CodeQL Analysis Workflow Steps

  • Checkout: Clone the repository under test
  • Initialize: Set up CodeQL environment and language-specific tooling
  • Autobuild: Build the project (optional, can be skipped for interpreted languages)
  • Analyze: Run CodeQL queries and upload results to GitHub

CodeQL vs Other Static Analysis Tools

  • CodeQL: Deep semantic analysis, supports multiple languages, integrates with GitHub Security features
  • SonarQube: Broader static analysis, more complex setup, supports many languages
  • ESLint: Linting-focused, JavaScript/TypeScript only, less security-specific

8. Scanning Dependencies

Scanner Language/Platform Features Notes
GitHub Dependabot JavaScript, Python, Ruby, Java, Go, PHP, .NET, Docker Security alerts, version updates, manifest analysis Enabled by default in most repositories
Snyk JavaScript, Python, Java, Go, .NET, Ruby, PHP Vulnerability scanning, license checks, policy enforcement Supports both open source and private dependencies
OWASP Dependency-Check Java, .NET, Python, JavaScript CVE scanning, false positive reduction, report generation Can be integrated into build pipelines
Bandit Python Security issue detection in Python code and dependencies Designed for Python-specific security issues

Example: GitHub Dependabot Configuration

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10

Example: Snyk Configuration

version: 1
dependencies:
  - name: "express"
    version: ">=4.18.0"
  - name: "lodash"
    version: ">=4.17.20"
Note: Dependabot updates are automatically triggered by GitHub Actions workflows when dependencies change.
Warning: Ensure that security scanning tools are configured to scan all dependencies, including transitive ones.

9. Using Secret Scanning

Secret scanning is a security feature in GitHub that helps detect and prevent the accidental exposure of sensitive information such as API keys, passwords, and tokens in your repository.

Feature Description Status
Secret Scanning Automatically scans commits and pull requests for sensitive data patterns. NEW
Supported Secrets GitHub supports scanning for over 100 secret types including AWS, Azure, and API tokens. NEW
Integration Works with GitHub Actions workflows and can be configured to fail builds on secret exposure. NEW

Example: Enabling Secret Scanning in a Workflow

name: Secret Scanning
on:
  push:
    branches: [ main ]
jobs:
  scan-secrets:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Scan for secrets
        uses: github/secret-scanning@v1
Note: Secret scanning is enabled by default in GitHub repositories and can be configured via the repository's security settings.

10. Implementing Branch Protection

Protection Rule Description Configuration
Required Status Checks Ensures that specific checks must pass before merging required_status_checks
Required Pull Request Reviews Requires approval from specified reviewers before merging required_pull_request_reviews
Restrict Push Access Limits who can push to protected branches restrict_pushes
Enforce Admins Applies protection rules to administrators as well enforce_admins

Example: GitHub Actions Workflow with Branch Protection

name: Branch Protection Check
on:
  pull_request:
    branches: [ main ]
jobs:
  check-branch:
    runs-on: ubuntu-latest
    steps:
      - name: Check branch protection rules
        run: |
          echo "Branch protection rules enforced"
Note: Branch protection rules can be configured via GitHub UI or API. These settings help maintain code quality and security.

11. Restricting Workflow Triggers

Trigger Type Description Restriction Example Notes
on.push Triggers on git pushes branches: ['main', 'release/*'] Can restrict to specific branches or tags
on.pull_request Triggers on pull request events branches: ['main'] Use with types to limit actions
on.schedule Triggers on cron schedule schedule: ['0 0 * * *'] Can restrict to specific times or days
on.workflow_dispatch Triggers manually via UI or API inputs: { environment: { required: true } } Use inputs to enforce context

Example: Restricting Push Triggers

on:
  push:
    branches:
      - main
      - release/*
    tags:
      - v1.*

Example: Restricting Pull Request Triggers

on:
  pull_request:
    branches:
      - main
    types:
      - opened
      - synchronize
Note: Always limit triggers to only necessary branches and events to reduce exposure.

12. Using Audit Logs

Example: Enabling audit logging for GitHub Actions

name: Audit Log Setup
on:
  workflow_dispatch:

jobs:
  setup-audit-logging:
    runs-on: ubuntu-latest
    steps:
      - name: Configure audit logging
        run: |
          echo "Audit logging enabled for all GitHub Actions workflows"
          # In practice, this would involve setting up external logging services
Setting Description Value
audit_log.enabled Enables audit logging for GitHub Actions true
audit_log.retention_days Number of days to retain audit logs 90
audit_log.export_format Format of exported audit logs json
Note: Audit logs are automatically enabled for GitHub Actions in GitHub Enterprise Cloud and GitHub Enterprise Server.
Warning: Audit logs are not available in GitHub Free and GitHub Pro plans.

Key Points About Audit Logs

  • Audit logs capture events related to GitHub Actions workflows.
  • They are useful for compliance and security monitoring.
  • Logs can be exported in JSON format for external analysis.

13. Rotating Credentials

Credential Type Rotation Frequency Management Method Notes
GitHub Personal Access Token (PAT) 90 days GitHub UI or API Use fine-grained tokens for better security
Secrets in GitHub Actions Manual Repository Settings & Actions Automate rotation using external secret managers
AWS Access Keys 90 days AWS IAM Console or CLI Rotate keys regularly and disable unused ones
Azure Service Principal 90 days Azure CLI or Portal Use managed identities where possible

Example: Rotating GitHub PAT in Workflow

name: Rotate PAT
on:
  schedule:
    - cron: '0 0 * * 1'
jobs:
  rotate-pat:
    runs-on: ubuntu-latest
    steps:
      - name: Generate new token
        run: |
          echo "NEW_TOKEN=$(gh auth token)" >> $GITHUB_ENV
      - name: Update secret in repo
        run: |
          gh secret set GH_TOKEN -b "$NEW_TOKEN"
Note: Ensure the workflow has appropriate permissions to update repository secrets.
Warning: Never hardcode credentials in workflows or commit them to version control.

Was this article helpful?