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