@bernierllc/validators-ci-workflow
v1.5.0
Published
Primitive validator for CI/CD workflow validation - GitHub Actions cache, secrets, matrix strategies, and triggers
Readme
@bernierllc/validators-ci-workflow
Primitive validator for GitHub Actions CI/CD workflow validation including cache configuration, secrets handling, matrix strategies, triggers, and job dependencies.
Installation
npm install @bernierllc/validators-ci-workflowOverview
This validator provides comprehensive validation for GitHub Actions workflows, ensuring best practices for:
- Cache Configuration - Validates cache keys, paths, and restore strategies
- Secrets Handling - Detects insecure secret usage and potential exposure
- Matrix Strategies - Validates matrix job configurations and limits
- Workflow Triggers - Ensures proper trigger configuration
- Job Dependencies - Detects circular dependencies and validates job chains
Usage
Basic Validation
import { validateCIWorkflow } from '@bernierllc/validators-ci-workflow';
const workflowContent = JSON.stringify({
name: 'CI',
on: { push: { branches: ['main'] } },
jobs: {
test: {
'runs-on': 'ubuntu-latest',
steps: [{ uses: 'actions/checkout@v3' }]
}
}
});
const problems = await validateCIWorkflow(workflowContent);
if (problems.length === 0) {
console.log('✓ Workflow validation passed');
} else {
problems.forEach(p => {
console.log(`${p.severity}: ${p.message} [${p.ruleId}]`);
});
}Validate Specific Aspects
import {
hasValidCacheConfig,
hasSecureSecretsHandling,
validateMatrixStrategy,
validateWorkflowTriggers,
validateJobDependencies,
} from '@bernierllc/validators-ci-workflow';
// Check cache configuration
const workflow = {
jobs: {
build: {
steps: [
{
uses: 'actions/cache@v3',
with: {
key: 'node-${{ hashFiles(\'**/package-lock.json\') }}',
path: 'node_modules'
}
}
]
}
}
};
const hasValidCache = await hasValidCacheConfig(workflow);
const isSecure = await hasSecureSecretsHandling(workflow);
// Validate matrix with custom limits
const matrixProblems = await validateMatrixStrategy(workflow, 50);
// Validate allowed triggers
const triggerProblems = await validateWorkflowTriggers(
workflow,
['push', 'pull_request', 'workflow_dispatch']
);
// Check for dependency issues
const depProblems = await validateJobDependencies(workflow);Custom Validation Options
import { validateCIWorkflow } from '@bernierllc/validators-ci-workflow';
const problems = await validateCIWorkflow(workflowContent, {
// Cache configuration
validateCacheConfig: true,
requireCacheKey: true,
allowDynamicCacheKeys: true,
// Secrets handling
validateSecretsHandling: true,
requireSecretPrefix: 'PROD_',
forbiddenSecretNames: ['PASSWORD', 'SECRET', 'API_KEY'],
// Matrix strategy
validateMatrixStrategy: true,
maxMatrixJobs: 100,
requireFailFast: true,
// Workflow triggers
validateTriggers: true,
allowedTriggers: ['push', 'pull_request'],
requirePullRequestTrigger: true,
// Job dependencies
validateJobDependencies: true,
maxDependencyDepth: 5,
allowCircularDependencies: false,
// General workflow
maxWorkflowDuration: 360, // minutes
requireTimeoutMinutes: true,
allowedRunners: ['ubuntu-latest', 'macos-latest'],
});Validation Rules
Cache Configuration Rules
- cache-config - Validates GitHub Actions cache usage
- Requires cache key and path
- Warns about static cache keys
- Detects secrets in cache keys
- Validates restore-keys format
// ✓ Good - Dynamic cache key with hashFiles
{
uses: 'actions/cache@v3',
with: {
key: 'node-${{ hashFiles(\'**/package-lock.json\') }}',
path: 'node_modules'
}
}
// ✗ Bad - Static cache key
{
uses: 'actions/cache@v3',
with: {
key: 'my-static-key',
path: 'node_modules'
}
}
// ✗ Bad - Secret in cache key
{
uses: 'actions/cache@v3',
with: {
key: 'cache-${{ secrets.API_KEY }}',
path: 'data'
}
}Secrets Handling Rules
- secrets-handling - Validates secure secrets usage
- Detects secrets in echo commands
- Warns about secrets written to files
- Validates secret naming conventions
- Checks for forbidden secret names
// ✓ Good - Secure secrets usage
{
env: {
API_KEY: '${{ secrets.API_KEY }}'
},
steps: [
{ run: 'curl -H "Authorization: Bearer $API_KEY" api.example.com' }
]
}
// ✗ Bad - Echoing secrets
{
steps: [
{ run: 'echo ${{ secrets.API_KEY }}' }
]
}
// ✗ Bad - Writing secrets to file
{
steps: [
{ run: 'echo ${{ secrets.API_KEY }} > key.txt' }
]
}Matrix Strategy Rules
- matrix-strategy - Validates matrix configurations
- Calculates total matrix jobs
- Enforces max matrix jobs limit
- Validates variable names
- Checks include/exclude consistency
// ✓ Good - Valid matrix
{
strategy: {
matrix: {
os: ['ubuntu-latest', 'macos-latest'],
node: [16, 18, 20]
},
'fail-fast': false
}
}
// ✗ Bad - Invalid variable name
{
strategy: {
matrix: {
'Invalid-Name': ['value']
}
}
}
// ✗ Bad - Too many jobs
{
strategy: {
matrix: {
os: [1,2,3,4,5],
node: [1,2,3,4,5],
arch: [1,2,3,4,5]
}
}
// 125 jobs exceeds typical limits
}Workflow Triggers Rules
- workflow-triggers - Validates trigger configuration
- Validates push/pull_request filters
- Checks schedule cron syntax
- Validates workflow_dispatch inputs
- Warns about overly broad triggers
// ✓ Good - Specific triggers
{
on: {
push: {
branches: ['main', 'develop'],
paths: ['src/**']
},
pull_request: {
types: ['opened', 'synchronize']
}
}
}
// ✗ Bad - Overly broad trigger
{
on: {
push: {} // Runs on ALL branches
}
}
// ✗ Bad - Invalid cron syntax
{
on: {
schedule: [
{ cron: 'invalid cron' }
]
}
}Job Dependencies Rules
- job-dependencies - Validates job dependency chains
- Detects circular dependencies
- Validates job references
- Checks dependency depth
- Validates timeout configurations
// ✓ Good - Linear dependency chain
{
jobs: {
build: { steps: [] },
test: { needs: 'build', steps: [] },
deploy: { needs: 'test', steps: [] }
}
}
// ✗ Bad - Circular dependency
{
jobs: {
job1: { needs: 'job2', steps: [] },
job2: { needs: 'job1', steps: [] }
}
}
// ✗ Bad - Non-existent dependency
{
jobs: {
test: { needs: 'build', steps: [] }
// 'build' job doesn't exist
}
}API Reference
validateCIWorkflow(content, options?, utils?)
Main validation function for GitHub Actions workflows.
Parameters:
content(string) - Workflow JSON or YAML stringoptions(Partial) - Optional validation optionsutils(SharedUtils) - Optional shared utilities
Returns: Promise<Problem[]>
hasValidCacheConfig(workflowData)
Checks if workflow has valid cache configuration.
Parameters:
workflowData(unknown) - Workflow object
Returns: Promise<boolean>
hasSecureSecretsHandling(workflowData)
Checks if workflow handles secrets securely.
Parameters:
workflowData(unknown) - Workflow object
Returns: Promise<boolean>
validateMatrixStrategy(workflowData, maxJobs?)
Validates matrix strategy configuration.
Parameters:
workflowData(unknown) - Workflow objectmaxJobs(number) - Optional max matrix jobs limit
Returns: Promise<Problem[]>
validateWorkflowTriggers(workflowData, allowedTriggers?)
Validates workflow trigger configuration.
Parameters:
workflowData(unknown) - Workflow objectallowedTriggers(string[]) - Optional allowed trigger types
Returns: Promise<Problem[]>
validateJobDependencies(workflowData)
Validates job dependencies and detects issues.
Parameters:
workflowData(unknown) - Workflow object
Returns: Promise<Problem[]>
Configuration Options
CIWorkflowOptions
interface CIWorkflowOptions {
// Cache configuration validation
validateCacheConfig?: boolean;
requireCacheKey?: boolean;
allowDynamicCacheKeys?: boolean;
// Secrets management validation
validateSecretsHandling?: boolean;
requireSecretPrefix?: string;
forbiddenSecretNames?: string[];
// Matrix strategy validation
validateMatrixStrategy?: boolean;
maxMatrixJobs?: number;
requireFailFast?: boolean;
// Workflow trigger validation
validateTriggers?: boolean;
allowedTriggers?: string[];
requirePullRequestTrigger?: boolean;
// Job dependency validation
validateJobDependencies?: boolean;
maxDependencyDepth?: number;
allowCircularDependencies?: boolean;
// General workflow validation
maxWorkflowDuration?: number; // minutes
requireTimeoutMinutes?: boolean;
allowedRunners?: string[];
}Integration
With Validators Runner
import { createValidatorRunner } from '@bernierllc/validators-runner';
import { ciWorkflowValidator } from '@bernierllc/validators-ci-workflow';
const runner = createValidatorRunner({
validators: [ciWorkflowValidator],
stopOnError: false,
});
const results = await runner.validate(workflowContent);With CI/CD Pipeline
# .github/workflows/validate-workflows.yml
name: Validate Workflows
on:
pull_request:
paths:
- '.github/workflows/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install -g @bernierllc/validators-cli
- run: validators-cli validate-workflow .github/workflows/*.ymlError Handling
All validation functions return Problem[] with the following structure:
interface Problem {
ruleId: string;
message: string;
severity: 'error' | 'warning' | 'info';
location: {
start: number;
end: number;
};
source?: string;
}Severity Levels:
error- Critical issues that must be fixedwarning- Potential problems that should be reviewedinfo- Informational messages for best practices
Best Practices
- Always validate workflows in CI - Catch issues before they reach production
- Use dynamic cache keys - Include file hashes for better cache invalidation
- Never echo secrets - Secrets should only be used in secure contexts
- Limit matrix jobs - Set reasonable limits to prevent resource exhaustion
- Specify trigger filters - Avoid overly broad triggers that run unnecessarily
- Set job timeouts - Prevent runaway jobs from consuming resources
- Avoid circular dependencies - Keep job dependency chains linear and shallow
License
Copyright (c) 2025 Bernier LLC. All rights reserved.
Integration Status
- Logger: Optional - Can integrate for validation event logging
- Docs-Suite: Ready - Full API documentation with TypeDoc
- NeverHub: Not applicable - Primitive validator
