GitHub Actions workflow linter for validating action and workflow calls
Project description
๐ GHA Workflow Linter
A comprehensive GitHub Actions workflow linter that validates action and workflow calls against remote repositories. GHA Workflow Linter ensures your GitHub Actions workflows reference valid repositories, branches, tags, and commit SHAs.
Features
- ๐ SHA Pinning Enforcement: Requires actions using commit SHAs for security (configurable)
- ๐ Automatic Authentication: Auto-detects GitHub tokens from GitHub CLI when available
- ๐ฆ Local Caching: Stores validation results locally to improve performance and reduce API calls
- Multi-format Support: Works as CLI tool, pre-commit hook, and GitHub Action
- Comprehensive Validation: Validates repositories, references, and syntax
- Parallel Processing: Multi-threaded validation for faster execution
- Flexible Configuration: YAML/JSON config files with environment overrides
- Rich Output: Clear error reporting with file paths and line numbers
- SSH Support: Respects SSH configuration and agent for private repositories
- Rate Limiting: Built-in throttling to respect API limits
Installation
From PyPI
uv add gha-workflow-linter
From Source
git clone https://github.com/modeseven-lfit/gha-workflow-linter.git
cd gha-workflow-linter
uv pip install -e .
Development Installation
git clone https://github.com/modeseven-lfit/gha-workflow-linter.git
cd gha-workflow-linter
uv pip install -e ".[dev]"
Authentication
GHA Workflow Linter uses the GitHub GraphQL API for efficient validation. Authentication is optional but highly recommended to avoid rate limiting.
Automatic Authentication (Recommended)
If you have GitHub CLI installed and authenticated, the linter will automatically get a token when needed:
# No token setup required if GitHub CLI has authentication!
gha-workflow-linter lint
When no token exists, you'll see:
โ ๏ธ No GitHub token found; attempting to get using GitHub CLI
โ
GitHub token retrieved from GitHub CLI
Manual Token Setup
If you don't use GitHub CLI or prefer manual setup:
-
Create a Personal Access Token:
- Go to GitHub Settings > Developer settings > Personal access tokens
- Click "Generate new token (classic)"
- Select scopes:
public_repo(for public repositories) orrepo(for private repositories) - Copy the generated token
-
Set the token via environment variable:
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx gha-workflow-linter lint
-
Or pass the token via CLI flag:
gha-workflow-linter lint --github-token ghp_xxxxxxxxxxxxxxxxxxxx
Authentication Priority
The linter uses the following priority order:
- CLI flag (
--github-token) - Environment variable (
GITHUB_TOKEN) - GitHub CLI fallback (
gh auth token)
Rate Limits
| Authentication | Requests/Hour | Recommended Use |
|---|---|---|
| With Token | 5,000 | โ Production, CI/CD, large repositories |
| Without Token | 60 | โ ๏ธ Small repositories, testing purposes |
Without any authentication, you'll see:
โ ๏ธ No GitHub token available; API requests may be rate-limited
Usage
Command Line Interface
# Show help with version
gha-workflow-linter --help
# Scan current directory (automatic authentication via GitHub CLI)
gha-workflow-linter lint
# Scan with environment token
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
gha-workflow-linter lint
# Scan specific path with CLI token
gha-workflow-linter lint /path/to/project --github-token ghp_xxxxxxxxxxxxxxxxxxxx
# Use custom configuration
gha-workflow-linter lint --config config.yaml
# JSON output format
gha-workflow-linter lint --format json
# Verbose output with 8 parallel workers
gha-workflow-linter lint --verbose --workers 8
# Exclude patterns
gha-workflow-linter lint --exclude "**/test/**" --exclude "**/docs/**"
# Disable SHA pinning policy (allow tags/branches)
gha-workflow-linter lint --no-require-pinned-sha
# Run without any authentication (limited to 60 requests/hour)
# This happens when GitHub CLI is not installed/authenticated AND no token exists
# Shows: โ ๏ธ No GitHub token available; API requests may be rate-limited
gha-workflow-linter lint
As a Pre-commit Hook
Add to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/modeseven-lfit/gha-workflow-linter
rev: d86993e21bbcddcfa9dac63cd43213b6a58fa6fb # frozen: v0.1.1
hooks:
- id: gha-workflow-linter
As a GitHub Action
name: Check GitHub Actions
on: [push, pull_request]
jobs:
check-actions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a
- name: Check action calls (strict SHA pinning)
uses: modeseven-lfit/gha-workflow-linter@d86993e21bbcddcfa9dac63cd43213b6a58fa6fb
with:
path: .
fail-on-error: true
parallel: true
workers: 4
require-pinned-sha: true # Default: require SHA pinning
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
check-actions-allow-tags:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # This would fail in strict mode above
- name: Check action calls (allow tags/branches)
uses: modeseven-lfit/gha-workflow-linter@d86993e21bbcddcfa9dac63cd43213b6a58fa6fb
with:
path: .
require-pinned-sha: false # Allow @v4, @main, etc.
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Configuration
GHA Workflow Linter is configurable via YAML files, environment variables, or command-line arguments. Configuration loads in this order:
- Command-line arguments (highest priority)
- Environment variables with
GHA_WORKFLOW_LINTER_prefix - Configuration file (lowest priority)
Configuration File
Create ~/.config/gha-workflow-linter/config.yaml or use --config:
# Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
log_level: INFO
# Number of parallel workers (1-32)
parallel_workers: 4
# File extensions to scan
scan_extensions:
- ".yml"
- ".yaml"
# Patterns to exclude from scanning
exclude_patterns:
- "**/node_modules/**"
- "**/vendor/**"
# Require actions using commit SHAs (default: true)
require_pinned_sha: true
# Git configuration
git:
timeout_seconds: 30
use_ssh_agent: true
# Network configuration
network:
timeout_seconds: 30
max_retries: 3
retry_delay_seconds: 1.0
rate_limit_delay_seconds: 0.1
# Local caching configuration
cache:
enabled: true
cache_dir: ~/.cache/gha-workflow-linter
cache_file: validation_cache.json
default_ttl_seconds: 604800 # 7 days
max_cache_size: 10000
cleanup_on_startup: true
Environment Variables
export GHA_WORKFLOW_LINTER_LOG_LEVEL=DEBUG
export GHA_WORKFLOW_LINTER_PARALLEL_WORKERS=8
export GHA_WORKFLOW_LINTER_REQUIRE_PINNED_SHA=false
export GHA_WORKFLOW_LINTER_GIT__TIMEOUT_SECONDS=60
export GHA_WORKFLOW_LINTER_CACHE__ENABLED=true
export GHA_WORKFLOW_LINTER_CACHE__DEFAULT_TTL_SECONDS=86400
Local Caching
GHA Workflow Linter includes a local caching system that stores validation results to improve performance and reduce API calls for later runs.
Cache Features
- Automatic Caching: Validation results are automatically cached locally
- Version-Based Invalidation: Tool purges cache when version changes
- Configurable TTL: Cache entries expire after seven days by default
- Size Limits: Cache size limits prevent excessive disk usage
- Persistence: Cache survives between CLI invocations and system restarts
- Smart Cleanup: Expired entries are automatically removed
Cache Commands
# Show cache information
gha-workflow-linter cache --info
# Remove expired cache entries
gha-workflow-linter cache --cleanup
# Clear all cache entries
gha-workflow-linter cache --purge
Cache Options
# Bypass cache for a single run
gha-workflow-linter lint --no-cache
# Clear cache and exit
gha-workflow-linter lint --purge-cache
# Override default cache TTL (in seconds)
gha-workflow-linter lint --cache-ttl 3600 # 1 hour
Cache Benefits
- Performance: Later runs are faster for validated actions
- API Efficiency: Reduces GitHub API calls and respects rate limits better
- Offline Support: Validated actions work without network access
- Bandwidth Savings: Useful in CI/CD environments with repeated workflows
Cache Location
By default, cache files go in:
- Linux/macOS:
~/.cache/gha-workflow-linter/validation_cache.json - Windows:
%LOCALAPPDATA%\gha-workflow-linter\validation_cache.json
You can customize the cache location via configuration file or environment variables.
Version-Based Cache Invalidation
The cache system automatically detects when you've upgraded to a new version of the tool and purges all cached entries to ensure consistency. This prevents issues where validation logic changes between versions could result in stale cached data.
When the tool detects a version mismatch, you'll see a message like:
INFO Cache version mismatch (cache: 0.1.3, current: 0.1.4). Purging cache.
This ensures that:
- Validation logic improvements are always applied
- Bug fixes in validation don't get masked by old cache entries
- New validation features work properly from the first run
Note: Caching works for CLI and pre-commit hook usage. GitHub Actions runners use ephemeral containers, so caching provides no benefit in that environment.
Validation Rules
GHA Workflow Linter validates GitHub Actions workflow calls using these rules:
Action Call Format
Valid action call patterns:
# Standard action with version tag
- uses: actions/checkout@v4
# Action with commit SHA
- uses: actions/checkout@8f4d7d2c3f1b2a9d8e5c6a7b4f3e2d1c0b9a8f7e
# Action with branch reference
- uses: actions/checkout@main
# Reusable workflow call
- uses: org/repo/.github/workflows/workflow.yaml@v1.0.0
# With trailing comment
- uses: actions/setup-python@v5.0.0 # Latest stable
Repository Validation
- Organization names: 1-39 characters, alphanumeric and hyphens
- Cannot start/end with hyphen or contain consecutive hyphens
- Repository names: alphanumeric, dots, underscores, hyphens, slashes
Reference Validation
GHA Workflow Linter validates that references exist using GitHub's GraphQL API:
- Commit SHAs: 40-character hexadecimal strings
- Tags: Semantic versions (v1.0.0) and other tag formats
- Branches: main, master, develop, feature branches
Supported Reference Types
| Type | Example | Validation Method | SHA Pinning |
|---|---|---|---|
| Commit SHA | f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a |
GitHub GraphQL API | โ Required by default |
| Semantic Version | v1.2.3, 1.0.0 |
GitHub GraphQL API | โ Fails unless --no-require-pinned-sha |
| Branch | main, develop |
GitHub GraphQL API | โ Fails unless --no-require-pinned-sha |
SHA Pinning Enforcement
By default, gha-workflow-linter requires all action calls use commit SHAs for security best practices. This helps prevent supply chain attacks and ensures reproducible builds.
# Default behavior - fails on non-SHA references
gha-workflow-linter lint # Fails on @v4, @main, etc.
# Disable SHA pinning policy
gha-workflow-linter lint --no-require-pinned-sha # Allows @v4, @main, etc.
Security Recommendation: Keep SHA pinning enabled in production environments and use automated tools like Dependabot to keep SHA references updated.
Security Considerations
SHA Pinning Benefits
- Supply Chain Security: Prevents malicious code injection through compromised action versions
- Reproducible Builds: Ensures consistent behavior across builds and environments
- Immutable References: SHA references stay fixed, unlike tags and branches
- Audit Trail: Clear tracking of exact code versions used in workflows
Migration Strategy
# Step 1: Identify unpinned actions
gha-workflow-linter lint --format json | jq '.errors[] | \
select(.validation_result == "not_pinned_to_sha")'
# Step 2: Temporarily allow unpinned actions during migration
gha-workflow-linter lint --no-require-pinned-sha
# Step 3: Use tools like Dependabot to pin and update SHA references automatically
Dependabot Configuration
Add to .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "ci"
include: "scope"
Output Formats
Text Output (Default)
๐ท๏ธ gha-workflow-linter version 1.0.0
Scan Summary
โโโโโโโโโโโโโโโโโโโโโโโโโณโโโโโโโโ
โ Metric โ Count โ
โกโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฉ
โ Workflow files โ 12 โ
โ Total action calls โ 45 โ
โ Action calls โ 38 โ
โ Workflow calls โ 7 โ
โ SHA references โ 35 โ
โ Tag references โ 8 โ
โ Branch references โ 2 โ
โโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโ
โ Found 8 validation errors
- 8 actions not pinned to SHA
Validation Errors:
โ Invalid action call in workflow: .github/workflows/test.yaml
- uses: actions/checkout@v4 [not_pinned_to_sha]
โ Invalid action call in workflow: .github/workflows/test.yaml
- uses: actions/setup-python@v5 [not_pinned_to_sha]
JSON Output
gha-workflow-linter lint --format json
{
"scan_summary": {
"total_files": 12,
"total_calls": 45,
"action_calls": 38,
"workflow_calls": 7,
"sha_references": 42,
"tag_references": 2,
"branch_references": 1
},
"validation_summary": {
"total_errors": 8,
"invalid_repositories": 0,
"invalid_references": 0,
"syntax_errors": 0,
"network_errors": 0,
"timeouts": 0,
"not_pinned_to_sha": 8
},
"errors": [
{
"file_path": ".github/workflows/test.yaml",
"line_number": 8,
"raw_line": " - uses: actions/checkout@v4",
"organization": "actions",
"repository": "checkout",
"reference": "v4",
"call_type": "action",
"reference_type": "tag",
"validation_result": "not_pinned_to_sha",
"error_message": "Action not pinned to commit SHA"
}
]
}
GitHub Action Inputs
| Input | Description | Required | Default |
|---|---|---|---|
path |
Path to scan for workflows | No | . |
config-file |
Path to configuration file | No | |
github-token |
GitHub API token | No | |
log-level |
Logging level | No | INFO |
output-format |
Output format (text, json) | No | text |
fail-on-error |
Exit with error on failures | No | true |
parallel |
Enable parallel processing | No | true |
workers |
Number of parallel workers | No | 4 |
exclude |
Comma-separated exclude patterns | No | |
require-pinned-sha |
Require actions pinned to commit SHAs | No | true |
GitHub Action Outputs
| Output | Description |
|---|---|
errors-found |
Number of validation errors |
total-calls |
Total action calls scanned |
scan-summary |
JSON summary of results |
CLI Options
Usage: gha-workflow-linter lint [OPTIONS] [PATH]
Scan GitHub Actions workflows for invalid action and workflow calls.
Arguments:
[PATH] Path to scan for workflows (default: current directory)
Options:
-c, --config FILE Configuration file path
--github-token TEXT GitHub API token (auto-detects from GitHub CLI)
-v, --verbose Enable verbose output
-q, --quiet Suppress all output except errors
--log-level LEVEL Set logging level
-f, --format FORMAT Output format (text, json)
--fail-on-error Exit with error code if failures found
--no-fail-on-error Don't exit with error code
--parallel Enable parallel processing
--no-parallel Disable parallel processing
-j, --workers INTEGER Number of parallel workers (1-32)
-e, --exclude PATTERN Patterns to exclude (multiples accepted)
--require-pinned-sha Require actions pinned to commit SHAs (default)
--no-require-pinned-sha Allow actions with tags/branches
--version Show version and exit
--help Show this message and exit
Integration Examples
Jenkins Pipeline
pipeline {
agent any
stages {
stage('Check Actions') {
steps {
sh 'pip install gha-workflow-linter'
sh 'gha-workflow-linter lint --format json > results.json'
archiveArtifacts artifacts: 'results.json'
}
}
}
}
Docker Usage
# Using published image
docker run --rm -v "$(pwd):/workspace" \
-e GITHUB_TOKEN=$GITHUB_TOKEN \
ghcr.io/modeseven-lfit/gha-workflow-linter:latest lint /workspace
# Build local image
docker build -t gha-workflow-linter .
docker run --rm -v "$(pwd):/workspace" \
-e GITHUB_TOKEN=$GITHUB_TOKEN \
gha-workflow-linter lint /workspace
Error Types
| Error Type | Description | Resolution |
|---|---|---|
invalid_repository |
Repository not found | Check org/repo name spelling |
invalid_reference |
Branch/tag/SHA not found | Verify reference exists |
invalid_syntax |
Malformed action call | Fix YAML syntax |
network_error |
Connection failed | Check network/credentials |
timeout |
Validation timed out | Increase timeout settings |
not_pinned_to_sha |
Action not using SHA | Pin to commit SHA or use --no-require-pinned-sha |
Development
Setup Development Environment
git clone https://github.com/modeseven-lfit/gha-workflow-linter.git
cd gha-workflow-linter
uv pip install -e ".[dev]"
Running Tests
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=gha_workflow_linter
# Run specific test categories
uv run pytest -m unit
uv run pytest -m integration
uv run pytest -m "not slow"
Code Quality
# Format code
ruff format .
# Lint code
ruff check .
# Type checking
mypy src/gha_workflow_linter
# Pre-commit hooks
pre-commit run --all-files
Building and Publishing
Local Builds:
uv build
This project uses automated CI/CD workflows for building and publishing:
Development/Testing:
- Pull requests trigger the
build-test.yamlworkflow - Automatically runs tests, audits, linting
- Validates the package builds without errors
Releases:
- The
build-test-release.yamlworkflow performs publishing/releasing - Triggered by pushing a git tag to the repository
- Automatically builds, tests and publishes the package
Architecture
GHA Workflow Linter follows a modular architecture with clear separation of concerns:
- CLI Interface: Typer-based command-line interface
- Configuration: Pydantic models with YAML/env support
- Scanner: Workflow file discovery and parsing
- Patterns: Regex-based action call extraction
- Validator: Git-based remote validation
- Models: Type-safe data structures
Performance
GHA Workflow Linter performance optimizations:
- Parallel Processing: Multi-threaded validation
- Caching: Repository and reference validation caching
- Rate Limiting: Configurable delays to respect API limits
- Efficient API Operations: Uses GitHub GraphQL API
Typical performance on a repository with 50 workflows and 200 action calls:
- Serial: ~60 seconds
- Parallel (4 workers): ~15 seconds
- Cached: ~2 seconds (follow-up runs)
Security Notes
- Token Security: Tokens are never logged or stored permanently
- Environment Variables: Recommended method for token management
- Private Repositories: Requires appropriate token permissions
- Rate Limiting: Proactive management prevents API abuse
- API Efficiency: Batch queries reduce API surface area
Pre-commit Hook
The repository includes a pre-commit hook that runs gha-workflow-linter on its own workflows:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/modeseven-lfit/gha-workflow-linter
rev: d86993e21bbcddcfa9dac63cd43213b6a58fa6fb # frozen: v0.1.1
hooks:
- id: gha-workflow-linter
Development Setup
For contributors, use the development setup script:
# Install development environment with self-linting
./scripts/setup-dev.sh
# This sets up:
# - Development dependencies
# - Pre-commit hooks (including gha-workflow-linter)
# - GitHub authentication (GitHub CLI recommended)
# - Self-linting test
Troubleshooting
Authentication Issues
GitHub CLI not found:
โ Unable to get GitHub token from any source
๐ก Authentication options:
โข Install GitHub CLI: https://cli.github.com/
โข Or set environment variable: export GITHUB_TOKEN=ghp_xxx
โข Or use --github-token flag with your personal access token
GitHub CLI not authenticated:
# Check authentication status
gh auth status
# Login if not authenticated
gh auth login
Token permissions:
- Ensure your token has
public_reposcope for public repositories - Use
reposcope for private repositories - Check token validity:
gh auth tokenshould return a valid token
Rate limiting:
- Without authentication: 60 requests/hour
- With GitHub token: 5,000 requests/hour
- Large repositories may require authentication to avoid limits
Contributing
We welcome contributions! Please see our contributing guidelines:
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Run the test suite and linting (including self-linting)
- Submit a pull request
License
Licensed under the Apache License 2.0. See the LICENSE file for details.
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Read the Docs
Acknowledgments
Built with modern Python tooling:
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gha_workflow_linter-0.1.8.tar.gz.
File metadata
- Download URL: gha_workflow_linter-0.1.8.tar.gz
- Upload date:
- Size: 178.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42563e4584e00f7a88371fbc4fefa9fc8520a3b60df4620ca9ffa749a2ad391e
|
|
| MD5 |
d62bf3d16d4c52a7fa0d10d650d2146d
|
|
| BLAKE2b-256 |
647770c77a090591c8ef60c95804fd8dace827670ab4d8418e79b040aa06d863
|
Provenance
The following attestation bundles were made for gha_workflow_linter-0.1.8.tar.gz:
Publisher:
build-test-release.yaml on modeseven-lfit/gha-workflow-linter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gha_workflow_linter-0.1.8.tar.gz -
Subject digest:
42563e4584e00f7a88371fbc4fefa9fc8520a3b60df4620ca9ffa749a2ad391e - Sigstore transparency entry: 658806623
- Sigstore integration time:
-
Permalink:
modeseven-lfit/gha-workflow-linter@0ee942c14dab98c17c831e74502e44ba2585e9a4 -
Branch / Tag:
refs/tags/v0.1.8 - Owner: https://github.com/modeseven-lfit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-test-release.yaml@0ee942c14dab98c17c831e74502e44ba2585e9a4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file gha_workflow_linter-0.1.8-py3-none-any.whl.
File metadata
- Download URL: gha_workflow_linter-0.1.8-py3-none-any.whl
- Upload date:
- Size: 65.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58c172454fffcd0da253664c58d860ec9dd67bd3df41503c7c7df1f20c833a99
|
|
| MD5 |
183f49a7dc315bd131df7b9a1c9e325c
|
|
| BLAKE2b-256 |
8e142224b23ae44a6aec9b889c50145651b04b439e5bb7e0b339e8af1f526719
|
Provenance
The following attestation bundles were made for gha_workflow_linter-0.1.8-py3-none-any.whl:
Publisher:
build-test-release.yaml on modeseven-lfit/gha-workflow-linter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gha_workflow_linter-0.1.8-py3-none-any.whl -
Subject digest:
58c172454fffcd0da253664c58d860ec9dd67bd3df41503c7c7df1f20c833a99 - Sigstore transparency entry: 658806629
- Sigstore integration time:
-
Permalink:
modeseven-lfit/gha-workflow-linter@0ee942c14dab98c17c831e74502e44ba2585e9a4 -
Branch / Tag:
refs/tags/v0.1.8 - Owner: https://github.com/modeseven-lfit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-test-release.yaml@0ee942c14dab98c17c831e74502e44ba2585e9a4 -
Trigger Event:
push
-
Statement type: