Compare commits

...

24 Commits

Author SHA1 Message Date
renovate[bot]
f6ed49a6dd chore(deps): update astral-sh/setup-uv action (v7.1.5 → v7.1.6) (#398)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 13:06:42 +02:00
renovate[bot]
23ac5dbca3 chore(deps): update github/codeql-action action (v4.31.7 → v4.31.8) (#399)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 13:05:57 +02:00
renovate[bot]
a8031d3922 chore(deps): update raven-actions/actionlint action (v2.0.1 → v2.1.0) (#400)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 13:05:24 +02:00
renovate[bot]
30149dd950 chore(deps): update image python to v3.14.2 (#401)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 09:41:53 +02:00
renovate[bot]
3a3cdcdefe chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.14.8 → v0.14.9) (#402)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 09:36:11 +02:00
renovate[bot]
7d28006a83 chore(deps): update pre-commit hook astral-sh/uv-pre-commit (0.9.16 → 0.9.17) (#403)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 09:34:05 +02:00
renovate[bot]
4008db6517 chore(deps): update markdownlint-cli2 (0.19.0 → 0.20.0) (#404)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 09:18:09 +02:00
renovate[bot]
7aa206a02a chore(deps): update image python to v3.14.1 (#391)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 15:03:07 +02:00
renovate[bot]
8481bbb5cd chore(deps): update pre-commit hook astral-sh/uv-pre-commit (0.9.13 → 0.9.16) (#393)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 15:02:40 +02:00
renovate[bot]
4c0068e6e7 chore(deps): update pre-commit hook davidanson/markdownlint-cli2 (v0.19.1 → v0.20.0) (#394)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 15:02:19 +02:00
renovate[bot]
5cecfe7cbe chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.14.7 → v0.14.8) (#392)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 15:02:01 +02:00
renovate[bot]
0288a1c8b8 chore(deps): update astral-sh/setup-uv action (v7.1.4 → v7.1.5) (#390)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 07:39:13 +02:00
44a11e9773 chore: update actions, cleanup pr-lint and pre-commit (#389)
* chore: update actions, cleanup pr-lint

* chore: cleanup pre-commit config, formatting

* chore: revert sigstore/cosign-installer downgrade

* chore: formatting
2025-12-07 02:24:33 +02:00
renovate[bot]
a52399cf74 chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.14.6 → v0.14.7) (#385)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 18:45:39 +02:00
renovate[bot]
803165db8f chore(deps): update docker/metadata-action action (v5.9.0 → v5.10.0) (#387)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 18:45:19 +02:00
renovate[bot]
d69ed9e999 chore(deps): update pre-commit hook astral-sh/uv-pre-commit (0.9.11 → 0.9.13) (#386)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 18:44:55 +02:00
renovate[bot]
8eea6f781b chore(deps): update pre-commit hook gitleaks/gitleaks (v8.29.1 → v8.30.0) (#388)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 18:44:21 +02:00
renovate[bot]
4889586a94 chore(deps): update python (3.11.14 → 3.14.0) (#382)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 09:12:42 +02:00
renovate[bot]
e02ca4d843 chore(deps): update shivammathur/setup-php action (2.35.5 → 2.36.0) (#383)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 09:10:48 +02:00
renovate[bot]
13ef0db9ba chore(deps): update oxsecurity/megalinter action (v9.1.0 → v9.2.0) (#381)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 09:10:23 +02:00
renovate[bot]
c366e99ee3 chore(deps)!: update node (22.21.1 → 24.11.1) (#380)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 07:50:44 +02:00
fbbb487332 fix(pr-lint): corepack detection, tweaks and yarn fix (#379) 2025-11-30 13:46:16 +02:00
abe24f8570 feat(ci): versioning change (#378)
* chore: remove bylines from actions

* feat: new daily release action

* chore(ci): ignore false positive in codeql, fix others

* fix: cr comments
2025-11-28 10:56:52 +02:00
9aa16a8164 feat: use our own actions in our workflows (#377)
* feat: use our own actions in our workflows

* fix: add missing inputs to validate-inputs, refactor node

* chore: cr comment fixes

* fix: update-validators formatting

* chore: update validators, add tests, conventions

* feat: validate severity with severity_enum

* feat: add 10 generic validators to improve input validation coverage

Add comprehensive validation system improvements across multiple phases:

Phase 2A - Quick Wins:
- Add multi_value_enum validator for 2-10 value enumerations
- Add exit_code_list validator for Unix/Linux exit codes (0-255)
- Refactor coverage_driver to use multi_value_enum

Phase 2B - High-Value Validators:
- Add key_value_list validator with shell injection prevention
- Add path_list validator with path traversal and glob support

Quick Wins - Additional Enums:
- Add network_mode validator for Docker network modes
- Add language_enum validator for language detection
- Add framework_mode validator for PHP framework modes
- Update boolean pattern to include 'push'

Phase 2C - Specialized Validators:
- Add json_format validator for JSON syntax validation
- Add cache_config validator for Docker BuildKit cache configs

Improvements:
- All validators include comprehensive security checks
- Pattern-based validation with clear error messages
- 23 new test methods with edge case coverage
- Update special case mappings for 20+ inputs
- Fix build-args mapping test expectation

Coverage impact: 22 actions now at 100% validation (88% → 92%)
Test suite: 762 → 785 tests (+23 tests, all passing)

* chore: regenerate rules.yml with improved validator coverage

Regenerate validation rules for all actions with new validators:

- compress-images: 86% → 100% (+1 input: ignore-paths)
- docker-build: 63% → 100% (+4 inputs: cache configs, platform-build-args)
- docker-publish: 73% → 100% (+1 input: build-args)
- language-version-detect: 67% → 100% (+1 input: language)
- php-tests: 89% (fixed framework→framework_mode mapping)
- prettier-lint: 86% → 100% (+2 inputs: file-pattern, plugins)
- security-scan: 86% (maintained coverage)

Overall: 23 of 25 actions now at 100% validation coverage (92%)

* fix: address PR #377 review comments

- Add | None type annotations to 6 optional parameters (PEP 604)
- Standardize injection pattern: remove @# from comma_separated_list validator
  (@ and # are not shell injection vectors, allows npm scoped packages)
- Remove dead code: unused value expression in key_value_list validator
- Update tests to reflect injection pattern changes
2025-11-25 23:51:03 +02:00
62 changed files with 3040 additions and 763 deletions

View File

@@ -17,7 +17,7 @@ runs:
using: composite
steps:
- name: Install uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
enable-cache: true
@@ -31,7 +31,7 @@ runs:
run: uv sync --frozen
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '24'
cache: npm

View File

@@ -15,3 +15,19 @@ paths-ignore:
# Use security and quality query suite
queries:
- uses: security-and-quality
# Suppress specific false positives
# These findings have been manually reviewed and determined to be false positives
# with appropriate security controls in place
query-filters:
# docker-publish: Code injection in validated context
# False positive: User input is validated and sanitized before use
# - Only relative paths and trusted git URLs are allowed
# - Absolute paths and arbitrary URLs are rejected
# - Path traversal attempts are blocked
# - Custom contexts require explicit opt-in via use-custom-context: true
# - Wraps docker/build-push-action (trusted Docker-maintained action)
# - Action is designed for trusted workflows only (documented in action.yml)
- exclude:
id: js/actions/code-injection
kind: problem

View File

@@ -39,212 +39,30 @@ jobs:
with:
fetch-depth: 0
- name: Check Required Configurations
id: check-configs
shell: sh
run: |
# Initialize all flags as false
{
echo "run_gitleaks=false"
echo "run_trivy=true"
} >> "$GITHUB_OUTPUT"
# Check Gitleaks configuration and license
if [ -f ".gitleaks.toml" ] && [ -n "${{ secrets.GITLEAKS_LICENSE }}" ]; then
echo "Gitleaks config and license found"
printf '%s\n' "run_gitleaks=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::Gitleaks config or license missing - skipping Gitleaks scan"
fi
- name: Run actionlint
uses: raven-actions/actionlint@3a24062651993d40fed1019b58ac6fbdfbf276cc # v2.0.1
- name: Run Security Scan
id: security-scan
uses: ./security-scan
with:
cache: true
fail-on-error: true
shellcheck: false
- name: Run Gitleaks
if: steps.check-configs.outputs.run_gitleaks == 'true'
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
with:
config-path: .gitleaks.toml
report-format: sarif
report-path: gitleaks-report.sarif
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@a11da62073708815958ea6d84f5650c78a3ef85b # master
with:
scan-type: 'fs'
scanners: 'vuln,config,secret'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
timeout: '10m'
- name: Verify SARIF files
id: verify-sarif
shell: sh
run: |
# Initialize outputs
{
echo "has_trivy=false"
echo "has_gitleaks=false"
} >> "$GITHUB_OUTPUT"
# Check Trivy results
if [ -f "trivy-results.sarif" ]; then
if jq -e . </dev/null 2>&1 <"trivy-results.sarif"; then
printf '%s\n' "has_trivy=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::Trivy SARIF file exists but is not valid JSON"
fi
fi
# Check Gitleaks results if it ran
if [ "${{ steps.check-configs.outputs.run_gitleaks }}" = "true" ]; then
if [ -f "gitleaks-report.sarif" ]; then
if jq -e . </dev/null 2>&1 <"gitleaks-report.sarif"; then
printf '%s\n' "has_gitleaks=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::Gitleaks SARIF file exists but is not valid JSON"
fi
fi
fi
- name: Upload Trivy results
if: steps.verify-sarif.outputs.has_trivy == 'true'
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
with:
sarif_file: 'trivy-results.sarif'
category: 'trivy'
- name: Upload Gitleaks results
if: steps.verify-sarif.outputs.has_gitleaks == 'true'
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
with:
sarif_file: 'gitleaks-report.sarif'
category: 'gitleaks'
- name: Archive security reports
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: security-reports-${{ github.run_id }}
path: |
${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'trivy-results.sarif' || '' }}
${{ steps.verify-sarif.outputs.has_gitleaks == 'true' && 'gitleaks-report.sarif' || '' }}
retention-days: 30
- name: Analyze Results
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const fs = require('fs');
try {
let totalIssues = 0;
let criticalIssues = 0;
const analyzeSarif = (file, tool) => {
if (!fs.existsSync(file)) {
console.log(`No results file found for ${tool}`);
return null;
}
try {
const sarif = JSON.parse(fs.readFileSync(file, 'utf8'));
return sarif.runs.reduce((acc, run) => {
if (!run.results) return acc;
const critical = run.results.filter(r =>
r.level === 'error' ||
r.level === 'critical' ||
(r.ruleId || '').toLowerCase().includes('critical')
).length;
return {
total: acc.total + run.results.length,
critical: acc.critical + critical
};
}, { total: 0, critical: 0 });
} catch (error) {
console.log(`Error analyzing ${tool} results: ${error.message}`);
return null;
}
};
// Only analyze results from tools that ran successfully
const results = {
trivy: ${{ steps.verify-sarif.outputs.has_trivy }} ?
analyzeSarif('trivy-results.sarif', 'trivy') : null,
gitleaks: ${{ steps.verify-sarif.outputs.has_gitleaks }} ?
analyzeSarif('gitleaks-report.sarif', 'gitleaks') : null
};
// Aggregate results
Object.entries(results).forEach(([tool, result]) => {
if (result) {
totalIssues += result.total;
criticalIssues += result.critical;
console.log(`${tool}: ${result.total} total, ${result.critical} critical issues`);
}
});
// Create summary
const summary = `## Security Scan Summary
- Total Issues Found: ${totalIssues}
- Critical Issues: ${criticalIssues}
### Tool Breakdown
${Object.entries(results)
.filter(([_, r]) => r)
.map(([tool, r]) =>
`- ${tool}: ${r.total} total, ${r.critical} critical`
).join('\n')}
### Tools Run Status
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy }}
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks }}
`;
// Set output
core.setOutput('total_issues', totalIssues);
core.setOutput('critical_issues', criticalIssues);
// Add job summary
await core.summary
.addRaw(summary)
.write();
// Fail if critical issues found
if (criticalIssues > 0) {
core.setFailed(`Found ${criticalIssues} critical security issues`);
}
} catch (error) {
core.setFailed(`Analysis failed: ${error.message}`);
}
gitleaks-license: ${{ secrets.GITLEAKS_LICENSE }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Notify on Critical Issues
if: failure()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
if: failure() && steps.security-scan.outputs.critical_issues != '0'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |-
const { repo, owner } = context.repo;
const critical = core.getInput('critical_issues');
const critical = '${{ steps.security-scan.outputs.critical_issues }}';
const total = '${{ steps.security-scan.outputs.total_issues }}';
const body = `🚨 Critical security issues found in GitHub Actions
${critical} critical security issues were found during the security scan.
${critical} critical security issues (out of ${total} total) were found during the security scan.
### Scan Results
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'Completed' || 'Skipped/Failed' }}
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks == 'true' && 'Completed' || 'Skipped' }}
- Actionlint: Completed
- Trivy: ${{ steps.security-scan.outputs.has_trivy_results == 'true' && 'Completed' || 'Skipped/Failed' }}
- Gitleaks: ${{ steps.security-scan.outputs.has_gitleaks_results == 'true' && 'Completed' || 'Skipped' }}
[View detailed scan results](https://github.com/${owner}/${repo}/actions/runs/${context.runId})

View File

@@ -49,7 +49,7 @@ jobs:
- name: Extract metadata
id: meta
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: ghcr.io/${{ github.repository_owner }}/actions
tags: |

View File

@@ -42,4 +42,5 @@ jobs:
with:
language: ${{ matrix.language }}
queries: security-and-quality
config-file: .github/codeql/codeql-config.yml
token: ${{ github.token }}

View File

@@ -1,51 +0,0 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: 'CodeQL'
on:
push:
branches:
- 'main'
pull_request:
branches:
- 'main'
schedule:
- cron: '30 1 * * 0' # Run at 1:30 AM UTC every Sunday
merge_group:
permissions:
actions: read
contents: read
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
fail-fast: false
matrix:
language:
- 'actions'
- 'javascript'
- 'python'
steps: # Add languages used in your actions
- name: Checkout repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Initialize CodeQL
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
with:
languages: ${{ matrix.language }}
queries: security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
with:
category: '/language:${{matrix.language}}'

View File

@@ -30,7 +30,7 @@ jobs:
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@78b1d469a1b1c94945b15bd71dedcb1928667f49 # v3.25.3
uses: github/issue-metrics@55bb0b704982057a101ab7515fb72b2293927c8a # v3.25.4
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:ivuorinen/actions is:issue created:${{ env.last_month }} -reason:"not planned"'

View File

@@ -22,27 +22,28 @@ jobs:
steps:
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Create tag if necessary
uses: fregante/daily-version-action@fb1a60b7c4daf1410cd755e360ebec3901e58588 # v2.1.3
- name: Create daily release
id: daily-version
with:
prefix: v
run: |
set -eu
- name: Create changelog text
if: steps.daily-version.outputs.created
id: changelog
uses: loopwerk/tag-changelog@941366edb8920e2071eae0449031830984b9f26e # v1.3.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
config_file: .github/tag-changelog-config.js
VERSION="v$(date '+%Y.%m.%d')"
printf '%s\n' "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Create release
if: steps.daily-version.outputs.created
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
# Check if release already exists
if gh release view "$VERSION" >/dev/null 2>&1; then
printf '%s\n' "created=false" >> "$GITHUB_OUTPUT"
printf '%s\n' "Release $VERSION already exists - skipping"
exit 0
fi
# Create release with auto-generated changelog (also creates tag)
gh release create "$VERSION" \
--title "Release $VERSION" \
--generate-notes \
--target main
printf '%s\n' "created=true" >> "$GITHUB_OUTPUT"
printf '%s\n' "Created release $VERSION"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag: ${{ steps.daily-version.outputs.version }}
name: Release ${{ steps.daily-version.outputs.version }}
body: ${{ steps.changelog.outputs.changes }}
allowUpdates: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -24,17 +24,9 @@ on:
merge_group:
env:
# Apply linter fixes configuration
APPLY_FIXES: none
APPLY_FIXES_EVENT: pull_request
APPLY_FIXES_MODE: commit
# Disable linters that do not work or conflict
# MegaLinter configuration - these override the action's defaults
DISABLE_LINTERS: REPOSITORY_DEVSKIM
# Additional settings
VALIDATE_ALL_CODEBASE: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
GITHUB_TOKEN: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
# Report configuration
REPORT_OUTPUT_FOLDER: megalinter-reports
@@ -72,111 +64,27 @@ jobs:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: MegaLinter
id: ml
uses: oxsecurity/megalinter/flavors/cupcake@62c799d895af9bcbca5eacfebca29d527f125a57 # v9.1.0
- name: Check MegaLinter Results
id: check-results
if: always()
shell: sh
run: |
printf '%s\n' "status=success" >> "$GITHUB_OUTPUT"
if [ -f "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log" ]; then
if grep -q "ERROR\|CRITICAL" "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log"; then
echo "Linting errors found"
printf '%s\n' "status=failure" >> "$GITHUB_OUTPUT"
fi
else
echo "::warning::MegaLinter log file not found"
fi
- name: Upload Reports
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
- name: Run MegaLinter
id: pr-lint
uses: ./pr-lint
with:
name: MegaLinter reports
path: |
megalinter-reports
mega-linter.log
retention-days: 30
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
username: fiximus
email: github-bot@ivuorinen.net
- name: Upload SARIF Report
if: always() && hashFiles('megalinter-reports/sarif/*.sarif')
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: megalinter-reports/sarif
category: megalinter
- name: Prepare Git for Fixes
if: steps.ml.outputs.has_updated_sources == 1
shell: sh
run: |
sudo chown -Rc $(id -u) .git/
git config --global user.name "fiximus"
git config --global user.email "github-bot@ivuorinen.net"
- name: Create Pull Request
if: |
steps.ml.outputs.has_updated_sources == 1 &&
(env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) &&
env.APPLY_FIXES_MODE == 'pull_request' &&
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) &&
!contains(github.event.head_commit.message, 'skip fix')
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
id: cpr
with:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
commit-message: '[MegaLinter] Apply linters automatic fixes'
title: '[MegaLinter] Apply linters automatic fixes'
labels: bot
branch: megalinter/fixes-${{ github.ref_name }}
branch-suffix: timestamp
delete-branch: true
body: |
## MegaLinter Fixes
MegaLinter has identified and fixed code style issues.
### 🔍 Changes Made
- Automated code style fixes
- Formatting improvements
- Lint error corrections
### 📝 Notes
- Please review the changes carefully
- Run tests before merging
- Verify formatting matches project standards
> Generated automatically by MegaLinter
- name: Commit Fixes
if: |
steps.ml.outputs.has_updated_sources == 1 &&
(env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) &&
env.APPLY_FIXES_MODE == 'commit' &&
github.ref != 'refs/heads/main' &&
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) &&
!contains(github.event.head_commit.message, 'skip fix')
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
with:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }}
commit_message: |
style: apply MegaLinter fixes
[skip ci]
commit_user_name: fiximus
commit_user_email: github-bot@ivuorinen.net
push_options: --force
- name: Create Status Check
- name: Check Results
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const status = '${{ steps.check-results.outputs.status }}';
const status = '${{ steps.pr-lint.outputs.validation_status }}';
const conclusion = status === 'success' ? 'success' : 'failure';
const summary = `## MegaLinter Results

View File

@@ -17,6 +17,6 @@ jobs:
contents: write
steps:
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
- uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
generate_release_notes: true

View File

@@ -53,7 +53,7 @@ jobs:
# Record the base commit for diffing without checking it out
# Keep PR head checked out so scanners analyze the new changes
BASE_REF="refs/remotes/origin-base/${{ github.event.pull_request.base.ref }}"
echo "BASE_REF=${BASE_REF}" >> $GITHUB_ENV
echo "BASE_REF=${BASE_REF}" >> "$GITHUB_ENV"
echo "Base ref: ${BASE_REF}"
git log -1 --oneline "${BASE_REF}"

View File

@@ -25,7 +25,7 @@ jobs:
steps:
- name: 🚀 Run stale
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30

View File

@@ -73,7 +73,7 @@ jobs:
if: always()
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
if: always() && hashFiles('_tests/reports/test-results.sarif') != ''
with:
sarif_file: _tests/reports/test-results.sarif
@@ -125,10 +125,10 @@ jobs:
shell: sh
run: |
if [ -d "_tests/reports/integration" ] && [ -n "$(find _tests/reports/integration -type f 2>/dev/null)" ]; then
printf '%s\n' "reports-found=true" >> $GITHUB_OUTPUT
printf '%s\n' "reports-found=true" >> "$GITHUB_OUTPUT"
echo "Integration test reports found"
else
printf '%s\n' "reports-found=false" >> $GITHUB_OUTPUT
printf '%s\n' "reports-found=false" >> "$GITHUB_OUTPUT"
echo "No integration test reports found"
fi

View File

@@ -49,7 +49,7 @@ jobs:
- name: Create Pull Request
if: steps.action-versioning.outputs.updated == 'true'
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}'
@@ -68,8 +68,6 @@ jobs:
```bash
make check-version-refs
```
🤖 Auto-generated by version-maintenance workflow
branch: automated/version-update-${{ steps.version.outputs.major }}
delete-branch: true
labels: |
@@ -78,7 +76,7 @@ jobs:
- name: Check for Annual Bump
if: steps.action-versioning.outputs.needs-annual-bump == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const currentYear = new Date().getFullYear();
@@ -120,8 +118,6 @@ jobs:
\`\`\`bash
make check-version-refs
\`\`\`
🤖 Auto-generated by version-maintenance workflow
`,
labels: ['maintenance', 'high-priority']
});

View File

@@ -14,7 +14,7 @@ repos:
types: [markdown, python, yaml]
files: ^(docs/.*|README\.md|CONTRIBUTING\.md|CHANGELOG\.md|.*\.py|.*\.ya?ml)$
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.9.11
rev: 0.9.17
hooks:
- id: uv-lock
- id: uv-sync
@@ -44,7 +44,7 @@ repos:
args: [--autofix, --no-sort-keys]
- repo: https://github.com/DavidAnson/markdownlint-cli2
rev: v0.19.1
rev: v0.20.0
hooks:
- id: markdownlint-cli2
args: [--fix]
@@ -55,7 +55,7 @@ repos:
- id: yamllint
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.6
rev: v0.14.9
hooks:
# Run the linter with auto-fix
- id: ruff-check
@@ -83,11 +83,6 @@ repos:
- id: actionlint
args: ['-shellcheck=']
- repo: https://github.com/renovatebot/pre-commit-hooks
rev: 42.19.3
hooks:
- id: renovate-config-validator
- repo: https://github.com/bridgecrewio/checkov.git
rev: '3.2.495'
hooks:
@@ -96,6 +91,6 @@ repos:
- '--quiet'
- repo: https://github.com/gitleaks/gitleaks
rev: v8.29.1
rev: v8.30.0
hooks:
- id: gitleaks

View File

@@ -1 +1 @@
3.14.0
3.14.2

View File

@@ -5,13 +5,14 @@
- **Path**: /Users/ivuorinen/Code/ivuorinen/actions
- **Branch**: main
- **External Usage**: `ivuorinen/actions/<action-name>@main`
- **Total Actions**: 43 self-contained actions
- **Total Actions**: 44 self-contained actions
- **Dogfooding**: Workflows use local actions (pr-lint, codeql-analysis, security-scan)
## Structure
```text
/
├── <action-dirs>/ # 43 self-contained actions
├── <action-dirs>/ # 44 self-contained actions
│ ├── action.yml # Action definition
│ ├── README.md # Auto-generated
│ └── CustomValidator.py # Optional validator
@@ -25,12 +26,14 @@
└── Makefile # Build automation
```
## Action Categories (43 total)
## Action Categories (44 total)
**Setup (7)**: node-setup, set-git-config, php-version-detect, python-version-detect, python-version-detect-v2, go-version-detect, dotnet-version-detect
**Linting (13)**: ansible-lint-fix, biome-check/fix, csharp-lint-check, eslint-check/fix, go-lint, pr-lint, pre-commit, prettier-check/fix, python-lint-fix, terraform-lint-fix
**Security (1)**: security-scan (actionlint, Gitleaks, Trivy scanning)
**Build (3)**: csharp-build, go-build, docker-build
**Publishing (5)**: npm-publish, docker-publish, docker-publish-gh, docker-publish-hub, csharp-publish
@@ -85,3 +88,28 @@ make test # All tests (pytest + ShellSpec)
- ✅ Convention-based validation
- ✅ Test generation system
- ✅ Full backward compatibility
## Dogfooding Strategy
The repository actively dogfoods its own actions in workflows:
**Fully Dogfooded Workflows**:
- **pr-lint.yml**: Uses `./pr-lint` (was 204 lines, now 112 lines - 45% reduction)
- **action-security.yml**: Uses `./security-scan` (was 264 lines, now 82 lines - 69% reduction)
- **codeql-new.yml**: Uses `./codeql-analysis`
- **sync-labels.yml**: Uses `./sync-labels`
- **version-maintenance.yml**: Uses `./action-versioning`
**Intentionally External**:
- **build-testing-image.yml**: Uses docker/\* actions directly (needs metadata extraction)
- Core GitHub actions (checkout, upload-artifact, setup-\*) kept for standardization
**Benefits**:
- Early detection of action issues
- Real-world testing of actions
- Reduced workflow duplication
- Improved maintainability
- Better documentation through usage examples

View File

@@ -22,9 +22,9 @@ Each action is fully self-contained and can be used independently in any GitHub
## 📚 Action Catalog
This repository contains **25 reusable GitHub Actions** for CI/CD automation.
This repository contains **26 reusable GitHub Actions** for CI/CD automation.
### Quick Reference (25 Actions)
### Quick Reference (26 Actions)
| Icon | Action | Category | Description | Key Features |
|:----:|:-----------------------------------------------------|:-----------|:----------------------------------------------------------------|:---------------------------------------------|
@@ -34,7 +34,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
| 🛡️ | [`codeql-analysis`][codeql-analysis] | Repository | Run CodeQL security analysis for a single language with conf... | Auto-detection, Token auth, Outputs |
| 🖼️ | [`compress-images`][compress-images] | Repository | Compress images on demand (workflow_dispatch), and at 11pm e... | Token auth, Outputs |
| 📝 | [`csharp-build`][csharp-build] | Build | Builds and tests C# projects. | Caching, Auto-detection, Token auth, Outputs |
| 📝 | [`csharp-lint-check`][csharp-lint-check] | Linting | Runs linters like StyleCop or dotnet-format for C# code styl... | Auto-detection, Token auth, Outputs |
| 📝 | [`csharp-lint-check`][csharp-lint-check] | Linting | Runs linters like StyleCop or dotnet-format for C# code styl... | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`csharp-publish`][csharp-publish] | Publishing | Publishes a C# project to GitHub Packages. | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`docker-build`][docker-build] | Build | Builds a Docker image for multiple architectures with enhanc... | Caching, Auto-detection, Token auth, Outputs |
| ☁️ | [`docker-publish`][docker-publish] | Publishing | Simple wrapper to publish Docker images to GitHub Packages a... | Token auth, Outputs |
@@ -49,6 +49,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
| ✅ | [`prettier-lint`][prettier-lint] | Linting | Run Prettier in check or fix mode with advanced configuratio... | Caching, Auto-detection, Token auth, Outputs |
| 📝 | [`python-lint-fix`][python-lint-fix] | Linting | Lints and fixes Python files, commits changes, and uploads S... | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`release-monthly`][release-monthly] | Repository | Creates a release for the current month, incrementing patch ... | Token auth, Outputs |
| 🛡️ | [`security-scan`][security-scan] | Security | Comprehensive security scanning for GitHub Actions including... | Caching, Token auth, Outputs |
| 📦 | [`stale`][stale] | Repository | A GitHub Action to close stale issues and pull requests. | Token auth, Outputs |
| 🏷️ | [`sync-labels`][sync-labels] | Repository | Sync labels from a YAML file to a GitHub repository | Token auth, Outputs |
| 🖥️ | [`terraform-lint-fix`][terraform-lint-fix] | Linting | Lints and fixes Terraform files with advanced validation and... | Token auth, Outputs |
@@ -74,7 +75,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
|:-----------------------------------------------|:------------------------------------------------------|:---------------------------------------------|:---------------------------------------------|
| 📦 [`ansible-lint-fix`][ansible-lint-fix] | Lints and fixes Ansible playbooks, commits changes... | Ansible, YAML | Caching, Token auth, Outputs |
| ✅ [`biome-lint`][biome-lint] | Run Biome linter in check or fix mode | JavaScript, TypeScript, JSON | Caching, Auto-detection, Token auth, Outputs |
| 📝 [`csharp-lint-check`][csharp-lint-check] | Runs linters like StyleCop or dotnet-format for C#... | C#, .NET | Auto-detection, Token auth, Outputs |
| 📝 [`csharp-lint-check`][csharp-lint-check] | Runs linters like StyleCop or dotnet-format for C#... | C#, .NET | Caching, Auto-detection, Token auth, Outputs |
| ✅ [`eslint-lint`][eslint-lint] | Run ESLint in check or fix mode with advanced conf... | JavaScript, TypeScript | Caching, Auto-detection, Token auth, Outputs |
| 📝 [`go-lint`][go-lint] | Run golangci-lint with advanced configuration, cac... | Go | Caching, Token auth, Outputs |
| ✅ [`pr-lint`][pr-lint] | Runs MegaLinter against pull requests | Conventional Commits | Caching, Auto-detection, Token auth, Outputs |
@@ -115,6 +116,12 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
| 📦 [`stale`][stale] | A GitHub Action to close stale issues and pull req... | GitHub Actions | Token auth, Outputs |
| 🏷️ [`sync-labels`][sync-labels] | Sync labels from a YAML file to a GitHub repositor... | YAML, GitHub | Token auth, Outputs |
#### 🛡️ Security (1 action)
| Action | Description | Languages | Features |
|:-------------------------------------|:------------------------------------------------------|:----------|:-----------------------------|
| 🛡️ [`security-scan`][security-scan] | Comprehensive security scanning for GitHub Actions... | - | Caching, Token auth, Outputs |
#### ✅ Validation (1 action)
| Action | Description | Languages | Features |
@@ -131,7 +138,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
| [`codeql-analysis`][codeql-analysis] | - | ✅ | ✅ | ✅ |
| [`compress-images`][compress-images] | - | - | ✅ | ✅ |
| [`csharp-build`][csharp-build] | ✅ | ✅ | ✅ | ✅ |
| [`csharp-lint-check`][csharp-lint-check] | - | ✅ | ✅ | ✅ |
| [`csharp-lint-check`][csharp-lint-check] | | ✅ | ✅ | ✅ |
| [`csharp-publish`][csharp-publish] | ✅ | ✅ | ✅ | ✅ |
| [`docker-build`][docker-build] | ✅ | ✅ | ✅ | ✅ |
| [`docker-publish`][docker-publish] | - | - | ✅ | ✅ |
@@ -146,6 +153,7 @@ This repository contains **25 reusable GitHub Actions** for CI/CD automation.
| [`prettier-lint`][prettier-lint] | ✅ | ✅ | ✅ | ✅ |
| [`python-lint-fix`][python-lint-fix] | ✅ | ✅ | ✅ | ✅ |
| [`release-monthly`][release-monthly] | - | - | ✅ | ✅ |
| [`security-scan`][security-scan] | ✅ | - | ✅ | ✅ |
| [`stale`][stale] | - | - | ✅ | ✅ |
| [`sync-labels`][sync-labels] | - | - | ✅ | ✅ |
| [`terraform-lint-fix`][terraform-lint-fix] | - | - | ✅ | ✅ |
@@ -224,6 +232,7 @@ All actions can be used independently in your workflows:
[prettier-lint]: prettier-lint/README.md
[python-lint-fix]: python-lint-fix/README.md
[release-monthly]: release-monthly/README.md
[security-scan]: security-scan/README.md
[stale]: stale/README.md
[sync-labels]: sync-labels/README.md
[terraform-lint-fix]: terraform-lint-fix/README.md

View File

@@ -6,8 +6,8 @@ set -euo pipefail
# Source setup utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# shellcheck source=_tests/framework/setup.sh
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/setup.sh"
# Action testing utilities
@@ -57,6 +57,13 @@ get_action_name() {
uv run "$script_dir/../shared/validation_core.py" --name "$action_file"
}
get_action_runs_using() {
local action_file="$1"
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
uv run "$script_dir/../shared/validation_core.py" --runs-using "$action_file"
}
# Check if an input is required in an action.yml file
is_input_required() {
local action_file="$1"
@@ -69,7 +76,7 @@ is_input_required() {
required_status=$(uv run "$script_dir/../shared/validation_core.py" --property "$action_file" "$input_name" "required")
# Return 0 (success) if input is required, 1 (failure) if optional
[[ $required_status == "required" ]]
[[ "$required_status" == "required" ]]
}
# Test input validation using Python validation module
@@ -363,5 +370,5 @@ run_action_tests() {
}
# Export all functions
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name is_input_required
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name get_action_runs_using is_input_required
export -f test_input_validation test_action_outputs test_external_usage measure_action_time run_action_tests

View File

@@ -521,6 +521,16 @@ class ActionFileParser:
except (OSError, ValueError, yaml.YAMLError, AttributeError):
return []
@staticmethod
def get_action_runs_using(action_file: str) -> str:
"""Get the runs.using value from an action.yml file."""
try:
data = ActionFileParser.load_action_file(action_file)
runs = data.get("runs", {})
return runs.get("using", "unknown")
except (OSError, ValueError, yaml.YAMLError, AttributeError):
return "unknown"
@staticmethod
def _get_required_property(input_data: dict, property_name: str) -> str:
"""Get the required/optional property."""
@@ -787,6 +797,11 @@ Examples:
mode_group.add_argument("--inputs", metavar="ACTION_FILE", help="List action inputs")
mode_group.add_argument("--outputs", metavar="ACTION_FILE", help="List action outputs")
mode_group.add_argument("--name", metavar="ACTION_FILE", help="Get action name")
mode_group.add_argument(
"--runs-using",
metavar="ACTION_FILE",
help="Get action runs.using value",
)
mode_group.add_argument(
"--validate-yaml",
metavar="YAML_FILE",
@@ -834,6 +849,12 @@ def _handle_name_command(args):
print(name)
def _handle_runs_using_command(args):
"""Handle the runs-using command."""
runs_using = ActionFileParser.get_action_runs_using(args.runs_using)
print(runs_using)
def _handle_validate_yaml_command(args):
"""Handle the validate-yaml command."""
try:
@@ -853,6 +874,7 @@ def _execute_command(args):
"inputs": _handle_inputs_command,
"outputs": _handle_outputs_command,
"name": _handle_name_command,
"runs_using": _handle_runs_using_command,
"validate_yaml": _handle_validate_yaml_command,
}

View File

@@ -0,0 +1,116 @@
#!/usr/bin/env shellspec
# Unit tests for security-scan action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "security-scan action"
ACTION_DIR="security-scan"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating token input"
It "accepts valid GitHub token"
When call validate_input_python "security-scan" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "rejects injection in token"
When call validate_input_python "security-scan" "token" "token; rm -rf /"
The status should be failure
End
It "accepts empty token (optional)"
When call validate_input_python "security-scan" "token" ""
The status should be success
End
End
Context "when validating actionlint-enabled input"
It "accepts true value"
When call validate_input_python "security-scan" "actionlint-enabled" "true"
The status should be success
End
It "accepts false value"
When call validate_input_python "security-scan" "actionlint-enabled" "false"
The status should be success
End
It "rejects non-boolean value"
When call validate_input_python "security-scan" "actionlint-enabled" "maybe"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Security Scan"
End
It "defines all expected inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "gitleaks-license"
The output should include "gitleaks-config"
The output should include "trivy-severity"
The output should include "trivy-scanners"
The output should include "trivy-timeout"
The output should include "actionlint-enabled"
The output should include "token"
End
It "defines all expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "has_trivy_results"
The output should include "has_gitleaks_results"
The output should include "total_issues"
The output should include "critical_issues"
End
It "uses composite run type"
run_type=$(get_action_runs_using "$ACTION_FILE")
When call echo "$run_type"
The output should equal "composite"
End
End
Context "when validating inputs per conventions"
It "validates token against github_token convention"
When call validate_input_python "security-scan" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "validates actionlint-enabled as boolean"
When call validate_input_python "security-scan" "actionlint-enabled" "true"
The status should be success
End
It "rejects invalid boolean for actionlint-enabled"
When call validate_input_python "security-scan" "actionlint-enabled" "1"
The status should be failure
End
End
Context "when testing optional inputs"
It "accepts empty gitleaks-license"
When call validate_input_python "security-scan" "gitleaks-license" ""
The status should be success
End
It "accepts empty token"
When call validate_input_python "security-scan" "token" ""
The status should be success
End
It "accepts valid gitleaks-license value"
When call validate_input_python "security-scan" "gitleaks-license" "license-key-123"
The status should be success
End
End
End

View File

@@ -76,11 +76,7 @@ if ! git diff --quiet; then
git commit -m "chore: bump major version from $OLD_VERSION to $NEW_VERSION
This commit updates all internal action references from $OLD_VERSION
to $NEW_VERSION.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>"
to $NEW_VERSION."
printf '%b' "${GREEN}✅ Committed version bump${NC}\n"
else

View File

@@ -95,7 +95,7 @@ runs:
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_file"
while IFS= read -r line; do
current_sha=$(echo "$line" | grep -oE '@[a-f0-9]{40}' | sed 's/@//')
current_sha=$(printf '%s' "$line" | grep -oE '@[a-f0-9]{40}' | sed 's/@//')
if [ "$current_sha" != "$TAG_SHA" ]; then
echo "Found outdated reference: $current_sha (should be $TAG_SHA)"
@@ -153,11 +153,7 @@ runs:
git commit -m "chore: update action references to $MAJOR_VERSION ($TAG_SHA)" \
-m "" \
-m "This commit updates all internal action references to point to the latest" \
-m "$MAJOR_VERSION tag SHA." \
-m "" \
-m "🤖 Generated with [Claude Code](https://claude.com/claude-code)" \
-m "" \
-m "Co-Authored-By: Claude <noreply@anthropic.com>"
-m "$MAJOR_VERSION tag SHA."
commit_sha=$(git rev-parse HEAD)
printf '%s\n' "sha=$commit_sha" >> "$GITHUB_OUTPUT"

View File

@@ -77,7 +77,7 @@ runs:
if: steps.check-files.outputs.files_found == 'true'
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: '3.11'
python-version: '3.14'
cache: 'pip'
- name: Install ansible-lint
@@ -130,6 +130,6 @@ runs:
- name: Upload SARIF Report
if: steps.check-files.outputs.files_found == 'true'
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: ansible-lint.sarif

View File

@@ -181,9 +181,9 @@ runs:
echo "Detected package manager: $package_manager"
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '22'
node-version: '24'
- name: Enable Corepack
shell: sh
@@ -331,7 +331,7 @@ runs:
- name: Upload SARIF Report
if: inputs.mode == 'check' && always()
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: biome-report.sarif

View File

@@ -28,7 +28,8 @@ conventions:
mode: mode_enum
token: github_token
username: username
overrides: {}
overrides:
mode: mode_enum
statistics:
total_inputs: 6
validated_inputs: 6

View File

@@ -186,7 +186,7 @@ runs:
echo "Using build mode: $build_mode"
- name: Initialize CodeQL
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
languages: ${{ inputs.language }}
queries: ${{ inputs.queries }}
@@ -199,12 +199,12 @@ runs:
threads: ${{ inputs.threads }}
- name: Autobuild
uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/autobuild@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
if: ${{ steps.set-build-mode.outputs.build-mode == 'autobuild' }}
- name: Perform CodeQL Analysis
id: analysis
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
category: ${{ steps.set-category.outputs.category }}
upload: ${{ inputs.upload-results }}

View File

@@ -42,7 +42,7 @@ conventions:
packs: codeql_packs
queries: codeql_queries
ram: numeric_range_256_32768
skip-queries: codeql_queries
skip-queries: boolean
source-root: file_path
threads: numeric_range_1_128
token: github_token
@@ -51,6 +51,7 @@ overrides:
build-mode: codeql_build_mode
category: category_format
config: codeql_config
language: codeql_language
output: file_path
packs: codeql_packs
queries: codeql_queries

View File

@@ -163,7 +163,7 @@ runs:
- name: Create New Pull Request If Needed
if: steps.calibre.outputs.markdown != ''
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
with:
token: ${{ inputs.token }}
title: 'chore: compress images'

View File

@@ -2,7 +2,7 @@
# Validation rules for compress-images action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 86% (6/7 inputs)
# Coverage: 100% (7/7 inputs)
#
# This file defines validation rules for the compress-images GitHub Action.
# Rules are automatically applied by validate-inputs action when this
@@ -24,6 +24,7 @@ optional_inputs:
- working-directory
conventions:
email: email
ignore-paths: path_list
image-quality: numeric_range_0_100
png-quality: numeric_range_0_100
token: github_token
@@ -32,10 +33,10 @@ conventions:
overrides: {}
statistics:
total_inputs: 7
validated_inputs: 6
validated_inputs: 7
skipped_inputs: 0
coverage_percentage: 86
validation_coverage: 86
coverage_percentage: 100
validation_coverage: 100
auto_detected: true
manual_review_required: false
quality_indicators:

View File

@@ -206,6 +206,6 @@ runs:
fi
- name: Upload SARIF Report
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: dotnet-format.sarif

View File

@@ -2,7 +2,7 @@
# Validation rules for docker-build action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 63% (17/27 inputs)
# Coverage: 100% (27/27 inputs)
#
# This file defines validation rules for the docker-build GitHub Action.
# Rules are automatically applied by validate-inputs action when this
@@ -45,17 +45,27 @@ optional_inputs:
conventions:
architectures: docker_architectures
auto-detect-platforms: docker_architectures
build-args: key_value_list
build-contexts: key_value_list
buildkit-version: semantic_version
buildx-version: semantic_version
cache-mode: boolean
cache-export: cache_config
cache-from: cache_config
cache-import: cache_config
cache-mode: cache_mode
context: file_path
dockerfile: file_path
dry-run: boolean
image-name: docker_image_name
max-retries: numeric_range_1_10
network: network_mode
parallel-builds: numeric_range_0_16
platform-build-args: json_format
platform-fallback: docker_architectures
sbom-format: report_format
push: boolean
sbom-format: sbom_format
scan-image: boolean
secrets: key_value_list
sign-image: boolean
tag: docker_tag
token: github_token
@@ -65,12 +75,12 @@ overrides:
sbom-format: sbom_format
statistics:
total_inputs: 27
validated_inputs: 17
validated_inputs: 27
skipped_inputs: 0
coverage_percentage: 63
validation_coverage: 63
coverage_percentage: 100
validation_coverage: 100
auto_detected: true
manual_review_required: true
manual_review_required: false
quality_indicators:
has_required_inputs: true
has_token_validation: true

View File

@@ -112,7 +112,7 @@ runs:
dockerhub|github|both)
;;
*)
echo "::error::Invalid registry value. Must be 'dockerhub', 'github', or 'both'"
printf '%s\n' "::error::Invalid registry value. Must be 'dockerhub', 'github', or 'both'"
exit 1
;;
esac
@@ -120,7 +120,7 @@ runs:
# Validate Docker Hub credentials if needed
if [ "$INPUT_REGISTRY" = "dockerhub" ] || [ "$INPUT_REGISTRY" = "both" ]; then
if [ -z "$INPUT_DOCKERHUB_USERNAME" ] || [ -z "$INPUT_DOCKERHUB_TOKEN" ]; then
echo "::error::Docker Hub username and token are required when publishing to Docker Hub"
printf '%s\n' "::error::Docker Hub username and token are required when publishing to Docker Hub"
exit 1
fi
fi
@@ -129,46 +129,77 @@ runs:
if [ "$INPUT_REGISTRY" = "github" ] || [ "$INPUT_REGISTRY" = "both" ]; then
token="${INPUT_TOKEN:-${GITHUB_TOKEN:-}}"
if [ -z "$token" ]; then
echo "::error::GitHub token is required when publishing to GitHub Packages"
printf '%s\n' "::error::GitHub token is required when publishing to GitHub Packages"
exit 1
fi
fi
# Validate context input for security
INPUT_CONTEXT="${INPUT_CONTEXT:-.}"
case "$INPUT_CONTEXT" in
.|./*|*/*)
# Relative paths are allowed
# Check for path traversal attempts
case "$INPUT_CONTEXT" in
*/../*|../*|*/..)
printf '%s\n' "::error::Context path contains path traversal: '$INPUT_CONTEXT'"
exit 1
;;
esac
;;
/*)
echo "::error::Context cannot be an absolute path: '$INPUT_CONTEXT'"
echo "::error::Use relative paths (e.g., '.', './app') to prevent code injection"
printf '%s\n' "::error::Context cannot be an absolute path: '$INPUT_CONTEXT'"
printf '%s\n' "::error::Use relative paths (e.g., '.', './app')"
exit 1
;;
*://*)
echo "::warning::Context is a remote URL: '$INPUT_CONTEXT'"
echo "::warning::Ensure this URL is from a trusted source to prevent code injection"
git://*|git@*|https://*.git|https://github.com/*|https://gitlab.com/*)
# Allow trusted git repository URLs
printf '%s\n' "::notice::Using git repository URL for context"
;;
http://*|https://*)
printf '%s\n' "::error::Context cannot be an arbitrary HTTP URL: '$INPUT_CONTEXT'"
printf '%s\n' "::error::Only git repository URLs are allowed for remote contexts"
exit 1
;;
*)
printf '%s\n' "::error::Invalid context format: '$INPUT_CONTEXT'"
printf '%s\n' "::error::Must be a relative path or git repository URL"
exit 1
;;
esac
# Validate dockerfile input for security
INPUT_DOCKERFILE="${INPUT_DOCKERFILE:-Dockerfile}"
case "$INPUT_DOCKERFILE" in
Dockerfile|*/Dockerfile|*.dockerfile|*/*.dockerfile)
# Common dockerfile patterns are allowed
# Check for path traversal attempts
case "$INPUT_DOCKERFILE" in
*/../*|../*|*/..)
printf '%s\n' "::error::Dockerfile path contains path traversal: '$INPUT_DOCKERFILE'"
exit 1
;;
esac
;;
/*)
echo "::error::Dockerfile path cannot be absolute: '$INPUT_DOCKERFILE'"
echo "::error::Use relative paths (e.g., 'Dockerfile', './docker/Dockerfile')"
printf '%s\n' "::error::Dockerfile path cannot be absolute: '$INPUT_DOCKERFILE'"
printf '%s\n' "::error::Use relative paths (e.g., 'Dockerfile', './docker/Dockerfile')"
exit 1
;;
*://*)
echo "::error::Dockerfile path cannot be a URL: '$INPUT_DOCKERFILE'"
printf '%s\n' "::error::Dockerfile path cannot be a URL: '$INPUT_DOCKERFILE'"
exit 1
;;
*)
printf '%s\n' "::error::Invalid Dockerfile format: '$INPUT_DOCKERFILE'"
printf '%s\n' "::error::Must be 'Dockerfile', '*/Dockerfile', '*.dockerfile', or '*/*.dockerfile'"
exit 1
;;
esac
echo "Input validation completed successfully"
printf '%s\n' "Input validation completed successfully"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
@@ -223,14 +254,14 @@ runs:
# Output results
printf 'image-name=%s\n' "$base_name" >> "$GITHUB_OUTPUT"
{
echo 'tags<<EOF'
echo "$tags"
echo 'EOF'
printf '%s\n' 'tags<<EOF'
printf '%s\n' "$tags"
printf '%s\n' 'EOF'
} >> "$GITHUB_OUTPUT"
echo "Image name: $base_name"
echo "Tags:"
echo "$tags"
printf 'Image name: %s\n' "$base_name"
printf '%s\n' "Tags:"
printf '%s\n' "$tags"
- name: Login to Docker Hub
if: inputs.registry == 'dockerhub' || inputs.registry == 'both'

View File

@@ -2,7 +2,7 @@
# Validation rules for docker-publish action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 73% (8/11 inputs)
# Coverage: 100% (11/11 inputs)
#
# This file defines validation rules for the docker-publish GitHub Action.
# Rules are automatically applied by validate-inputs action when this
@@ -27,25 +27,27 @@ optional_inputs:
- tags
- token
conventions:
build-args: key_value_list
context: file_path
dockerfile: file_path
dockerhub-token: github_token
dockerhub-username: username
image-name: docker_image_name
platforms: docker_architectures
registry: registry
push: boolean
registry: registry_enum
tags: docker_tag
token: github_token
overrides:
platforms: null
registry: registry_enum
statistics:
total_inputs: 11
validated_inputs: 8
skipped_inputs: 1
coverage_percentage: 73
validation_coverage: 73
validated_inputs: 11
skipped_inputs: 0
coverage_percentage: 100
validation_coverage: 100
auto_detected: true
manual_review_required: true
manual_review_required: false
quality_indicators:
has_required_inputs: false
has_token_validation: true

View File

@@ -288,9 +288,9 @@ runs:
echo "Detected package manager: $package_manager"
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '22'
node-version: '24'
- name: Enable Corepack
shell: sh
@@ -457,7 +457,7 @@ runs:
- name: Upload SARIF Report
if: inputs.mode == 'check' && inputs.report-format == 'sarif' && always()
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: ${{ inputs.working-directory }}/eslint-results.sarif

View File

@@ -44,7 +44,8 @@ conventions:
token: github_token
username: username
working-directory: file_path
overrides: {}
overrides:
mode: mode_enum
statistics:
total_inputs: 14
validated_inputs: 14

View File

@@ -46,6 +46,9 @@ const CATEGORIES = {
'compress-images': 'Repository',
'codeql-analysis': 'Repository',
// Security
'security-scan': 'Security',
// Validation
'validate-inputs': 'Validation',
};
@@ -120,6 +123,7 @@ const CATEGORY_ICONS = {
Build: '🏗️',
Publishing: '🚀',
Repository: '📦',
Security: '🛡️',
Validation: '✅',
};
@@ -232,7 +236,7 @@ function generateCategoryTables(actions) {
let output = '';
// Sort categories by priority
const categoryOrder = ['Setup', 'Utilities', 'Linting', 'Testing', 'Build', 'Publishing', 'Repository', 'Validation'];
const categoryOrder = ['Setup', 'Utilities', 'Linting', 'Testing', 'Build', 'Publishing', 'Repository', 'Security', 'Validation'];
for (const category of categoryOrder) {
if (!categories[category]) continue;

View File

@@ -414,7 +414,7 @@ runs:
- name: Upload Lint Results
if: always() && inputs.report-format == 'sarif'
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: ${{ inputs.working-directory }}/reports/golangci-lint.sarif
category: golangci-lint

View File

@@ -36,15 +36,17 @@ conventions:
disable-linters: linter_list
enable-linters: linter_list
fail-on-error: boolean
go-version: semantic_version
go-version: go_version
golangci-lint-version: semantic_version
max-retries: numeric_range_1_10
only-new-issues: branch_name
only-new-issues: boolean
report-format: report_format
timeout: numeric_range_1_3600
timeout: timeout_with_unit
token: github_token
working-directory: file_path
overrides:
disable-linters: linter_list
enable-linters: linter_list
go-version: go_version
only-new-issues: boolean
timeout: timeout_with_unit

View File

@@ -2,7 +2,7 @@
# Validation rules for language-version-detect action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 67% (2/3 inputs)
# Coverage: 100% (3/3 inputs)
#
# This file defines validation rules for the language-version-detect GitHub Action.
# Rules are automatically applied by validate-inputs action when this
@@ -21,16 +21,17 @@ optional_inputs:
- token
conventions:
default-version: semantic_version
language: language_enum
token: github_token
overrides: {}
statistics:
total_inputs: 3
validated_inputs: 2
validated_inputs: 3
skipped_inputs: 0
coverage_percentage: 67
validation_coverage: 67
coverage_percentage: 100
validation_coverage: 100
auto_detected: true
manual_review_required: true
manual_review_required: false
quality_indicators:
has_required_inputs: true
has_token_validation: true

View File

@@ -121,9 +121,9 @@ runs:
echo "Detected package manager: $package_manager"
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '22'
node-version: '24'
- name: Enable Corepack
shell: sh

View File

@@ -22,7 +22,7 @@ optional_inputs:
- token
conventions:
npm_token: github_token
package-version: semantic_version
package-version: strict_semantic_version
registry-url: url
scope: scope
token: github_token

50
package-lock.json generated
View File

@@ -13,7 +13,7 @@
"js-yaml": "^4.1.0",
"markdown-table": "^3.0.3",
"markdown-table-formatter": "^1.6.0",
"markdownlint-cli2": "^0.19.0",
"markdownlint-cli2": "^0.20.0",
"prettier": "^3.3.3",
"yaml-lint": "^1.7.0"
},
@@ -661,6 +661,19 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-east-asian-width": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
@@ -1051,9 +1064,9 @@
}
},
"node_modules/markdownlint": {
"version": "0.39.0",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.39.0.tgz",
"integrity": "sha512-Xt/oY7bAiHwukL1iru2np5LIkhwD19Y7frlsiDILK62v3jucXCD6JXlZlwMG12HZOR+roHIVuJZrfCkOhp6k3g==",
"version": "0.40.0",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.40.0.tgz",
"integrity": "sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1064,7 +1077,8 @@
"micromark-extension-gfm-footnote": "2.1.0",
"micromark-extension-gfm-table": "2.1.1",
"micromark-extension-math": "3.1.0",
"micromark-util-types": "2.0.2"
"micromark-util-types": "2.0.2",
"string-width": "8.1.0"
},
"engines": {
"node": ">=20"
@@ -1074,17 +1088,18 @@
}
},
"node_modules/markdownlint-cli2": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.19.0.tgz",
"integrity": "sha512-0+g7Fi/Y3qfvwfhJr77CpC/dEEoc4k7SvumlnL1tb68O+7fjKtIUG7aKzNUQIMXTVi8x63jcfXg4swz/ZYKyCw==",
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.20.0.tgz",
"integrity": "sha512-esPk+8Qvx/f0bzI7YelUeZp+jCtFOk3KjZ7s9iBQZ6HlymSXoTtWGiIRZP05/9Oy2ehIoIjenVwndxGtxOIJYQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"globby": "15.0.0",
"js-yaml": "4.1.1",
"jsonc-parser": "3.3.1",
"markdown-it": "14.1.0",
"markdownlint": "0.39.0",
"markdownlint": "0.40.0",
"markdownlint-cli2-formatter-default": "0.0.6",
"micromatch": "4.0.8"
},
@@ -1111,6 +1126,23 @@
"markdownlint-cli2": ">=0.0.4"
}
},
"node_modules/markdownlint/node_modules/string-width": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
"integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.3.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",

View File

@@ -24,7 +24,7 @@
"js-yaml": "^4.1.0",
"markdown-table": "^3.0.3",
"markdown-table-formatter": "^1.6.0",
"markdownlint-cli2": "^0.19.0",
"markdownlint-cli2": "^0.20.0",
"prettier": "^3.3.3",
"yaml-lint": "^1.7.0"
},

View File

@@ -319,7 +319,7 @@ runs:
- name: Setup PHP
id: setup-php
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
php-version: ${{ steps.detect-php-version.outputs.detected-version }}
extensions: ${{ inputs.extensions }}

View File

@@ -2,7 +2,7 @@
# Validation rules for php-tests action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 78% (7/9 inputs)
# Coverage: 89% (8/9 inputs)
#
# This file defines validation rules for the php-tests GitHub Action.
# Rules are automatically applied by validate-inputs action when this
@@ -27,7 +27,8 @@ optional_inputs:
conventions:
coverage: coverage_driver
email: email
framework: boolean
extensions: php_extensions
framework: framework_mode
max-retries: numeric_range_1_10
php-version: semantic_version
token: github_token
@@ -35,12 +36,12 @@ conventions:
overrides: {}
statistics:
total_inputs: 9
validated_inputs: 7
validated_inputs: 8
skipped_inputs: 0
coverage_percentage: 78
validation_coverage: 78
coverage_percentage: 89
validation_coverage: 89
auto_detected: true
manual_review_required: true
manual_review_required: false
quality_indicators:
has_required_inputs: false
has_token_validation: true

View File

@@ -54,13 +54,9 @@ runs:
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
with:
token: ${{ inputs.token || github.token }}
ref: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref_name }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
# If you use VALIDATE_ALL_CODEBASE = true, you can remove this line to
# improve performance
fetch-depth: 0
# ╭──────────────────────────────────────────────────────────╮
# │ Install packages for linting │
# ╰──────────────────────────────────────────────────────────╯
@@ -74,6 +70,29 @@ runs:
if [ -f package.json ]; then
printf '%s\n' "found=true" >> "$GITHUB_OUTPUT"
# Check if packageManager field is set (for corepack)
if command -v jq >/dev/null 2>&1; then
has_package_manager=$(jq -r '.packageManager // empty' package.json 2>/dev/null || printf '')
if [ -n "$has_package_manager" ]; then
printf '%s\n' "has-package-manager=true" >> "$GITHUB_OUTPUT"
printf 'Found packageManager field: %s\n' "$has_package_manager"
else
printf '%s\n' "has-package-manager=false" >> "$GITHUB_OUTPUT"
fi
else
# Fallback: check with grep if jq not available
# Use robust pattern to verify non-empty value
if grep -q '"packageManager"[[:space:]]*:[[:space:]]*"[^"]\+"' package.json 2>/dev/null; then
printf '%s\n' "has-package-manager=true" >> "$GITHUB_OUTPUT"
printf '%s\n' "Found packageManager field in package.json"
else
printf '%s\n' "has-package-manager=false" >> "$GITHUB_OUTPUT"
fi
fi
else
# Explicitly set has-package-manager to false when package.json doesn't exist
printf '%s\n' "has-package-manager=false" >> "$GITHUB_OUTPUT"
fi
- name: Detect Package Manager
@@ -99,30 +118,35 @@ runs:
- name: Setup Node.js
if: steps.detect-node.outputs.found == 'true'
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '22'
node-version: '24'
- name: Enable Corepack
if: steps.detect-node.outputs.found == 'true'
if: steps.detect-node.outputs.found == 'true' && steps.detect-node.outputs.has-package-manager == 'true'
shell: sh
run: |
set -eu
corepack enable
printf '%s\n' "Corepack enabled - package manager will be installed automatically from package.json"
- name: Install Package Manager
if: steps.detect-node.outputs.found == 'true'
- name: Install Package Manager (Fallback)
if: steps.detect-node.outputs.found == 'true' && steps.detect-node.outputs.has-package-manager == 'false'
shell: sh
env:
PACKAGE_MANAGER: ${{ steps.detect-pm.outputs.package-manager }}
run: |
set -eu
printf 'No packageManager field found, using detected package manager: %s\n' "$PACKAGE_MANAGER"
case "$PACKAGE_MANAGER" in
pnpm)
corepack enable
corepack prepare pnpm@latest --activate
;;
yarn)
corepack enable
corepack prepare yarn@stable --activate
;;
bun|npm)
@@ -161,9 +185,14 @@ runs:
pnpm install --frozen-lockfile
;;
"yarn")
if [ -f ".yarnrc.yml" ]; then
# Detect Yarn version by checking actual version output
# Yarn 2+ (Berry) uses --immutable, Yarn 1.x (Classic) uses --frozen-lockfile
yarn_version=$(yarn --version 2>/dev/null || printf '1.0.0')
if printf '%s' "$yarn_version" | grep -q '^[2-9]'; then
# Yarn 2+ (Berry) - use --immutable
yarn install --immutable
else
# Yarn 1.x (Classic) - use --frozen-lockfile
yarn install --frozen-lockfile
fi
;;
@@ -306,7 +335,7 @@ runs:
- name: Setup PHP
if: steps.detect-php.outputs.found == 'true'
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
php-version: ${{ steps.php-version.outputs.detected-version }}
tools: composer
@@ -323,7 +352,7 @@ runs:
set -eu
matcher_path=$(printf '%s' "$RUNNER_TOOL_CACHE/php.json" | tr -d '\n\r')
echo "::add-matcher::$matcher_path"
printf '%s\n' "::add-matcher::$matcher_path"
- name: Install PHP dependencies
if: steps.detect-php.outputs.found == 'true'
@@ -349,7 +378,7 @@ runs:
id: python-version
shell: sh
env:
DEFAULT_VERSION: '3.11'
DEFAULT_VERSION: '3.14'
run: |
set -eu
@@ -486,7 +515,7 @@ runs:
id: go-version
shell: sh
env:
DEFAULT_VERSION: '1.24'
DEFAULT_VERSION: '1.25'
run: |
set -eu
@@ -603,7 +632,7 @@ runs:
- name: MegaLinter
# You can override MegaLinter flavor used to have faster performances
# More info at https://megalinter.io/latest/flavors/
uses: oxsecurity/megalinter/flavors/cupcake@62c799d895af9bcbca5eacfebca29d527f125a57 # v9.1.0
uses: oxsecurity/megalinter/flavors/cupcake@55a59b24a441e0e1943080d4a512d827710d4a9d # v9.2.0
id: ml
# All available variables are described in documentation
@@ -621,11 +650,7 @@ runs:
# github.event_name == 'push' &&
# contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref)
# }}
VALIDATE_ALL_CODEBASE: >-
${{
github.event_name == 'push' &&
contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref)
}}
VALIDATE_ALL_CODEBASE: false
GITHUB_TOKEN: ${{ inputs.token || github.token }}
@@ -649,13 +674,6 @@ runs:
# Uncomment to disable copy-paste and spell checks
DISABLE: COPYPASTE,SPELL
# Export env vars to make them available for subsequent expressions
- name: Export Apply Fixes Variables
shell: sh
run: |
echo "APPLY_FIXES_EVENT=pull_request" >> "$GITHUB_ENV"
echo "APPLY_FIXES_MODE=commit" >> "$GITHUB_ENV"
# Upload MegaLinter artifacts
- name: Archive production artifacts
if: success() || failure()
@@ -666,108 +684,3 @@ runs:
path: |
megalinter-reports
mega-linter.log
# Set APPLY_FIXES_IF var for use in future steps
- name: Set APPLY_FIXES_IF var
shell: sh
env:
APPLY_FIXES_CONDITION: >-
${{
steps.ml.outputs.has_updated_sources == 1 &&
(env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) &&
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository)
}}
run: |
set -eu
# Sanitize by removing newlines to prevent env var injection
sanitized_condition="$(echo "$APPLY_FIXES_CONDITION" | tr -d '\n\r')"
printf 'APPLY_FIXES_IF=%s\n' "$sanitized_condition" >> "${GITHUB_ENV}"
# Set APPLY_FIXES_IF_* vars for use in future steps
- name: Set APPLY_FIXES_IF_* vars
shell: sh
env:
APPLY_FIXES_IF_PR_CONDITION: ${{ env.APPLY_FIXES_IF == 'true' && env.APPLY_FIXES_MODE == 'pull_request' }}
APPLY_FIXES_IF_COMMIT_CONDITION: ${{ env.APPLY_FIXES_IF == 'true' && env.APPLY_FIXES_MODE == 'commit' && (!contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref)) }}
run: |
set -eu
# Sanitize by removing newlines to prevent env var injection
sanitized_pr="$(echo "$APPLY_FIXES_IF_PR_CONDITION" | tr -d '\n\r')"
sanitized_commit="$(echo "$APPLY_FIXES_IF_COMMIT_CONDITION" | tr -d '\n\r')"
printf 'APPLY_FIXES_IF_PR=%s\n' "$sanitized_pr" >> "${GITHUB_ENV}"
printf 'APPLY_FIXES_IF_COMMIT=%s\n' "$sanitized_commit" >> "${GITHUB_ENV}"
# Create pull request if applicable
# (for now works only on PR from same repository, not from forks)
- name: Create Pull Request with applied fixes
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
id: cpr
if: env.APPLY_FIXES_IF_PR == 'true'
with:
token: ${{ inputs.token || github.token }}
commit-message: 'style: apply linter fixes'
title: 'style: apply linter fixes'
labels: bot
- name: Create PR output
if: env.APPLY_FIXES_IF_PR == 'true'
shell: sh
env:
PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }}
PR_URL: ${{ steps.cpr.outputs.pull-request-url }}
run: |
set -eu
printf 'PR Number - %s\n' "$PR_NUMBER"
printf 'PR URL - %s\n' "$PR_URL"
# Push new commit if applicable
# (for now works only on PR from same repository, not from forks)
- name: Prepare commit
if: env.APPLY_FIXES_IF_COMMIT == 'true'
shell: sh
env:
BRANCH_REF: >-
${{
github.event.pull_request.head.ref ||
github.head_ref ||
github.ref_name
}}
run: |
set -eu
# Fix .git directory ownership after MegaLinter container execution
current_uid=$(id -u)
sudo chown -Rc "$current_uid" .git/
# Ensure we're on the correct branch (not in detached HEAD state)
# This is necessary because MegaLinter may leave the repo in a detached HEAD state
current_branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$current_branch" = "HEAD" ]; then
echo "Repository is in detached HEAD state, checking out $BRANCH_REF"
# Validate branch reference to prevent command injection
if ! git check-ref-format --branch "$BRANCH_REF"; then
echo "::error::Invalid branch reference format: $BRANCH_REF"
exit 1
fi
git checkout "$BRANCH_REF"
else
echo "Repository is on branch: $current_branch"
fi
- name: Commit and push applied linter fixes
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
if: env.APPLY_FIXES_IF_COMMIT == 'true'
with:
branch: >-
${{
github.event.pull_request.head.ref ||
github.head_ref ||
github.ref
}}
commit_message: 'style: apply linter fixes'
commit_user_name: ${{ inputs.username }}
commit_user_email: ${{ inputs.email }}

View File

@@ -274,9 +274,9 @@ runs:
echo "Detected package manager: $package_manager"
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '22'
node-version: '24'
- name: Enable Corepack
shell: sh

View File

@@ -2,7 +2,7 @@
# Validation rules for prettier-lint action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 86% (12/14 inputs)
# Coverage: 100% (14/14 inputs)
#
# This file defines validation rules for the prettier-lint GitHub Action.
# Rules are automatically applied by validate-inputs action when this
@@ -34,21 +34,24 @@ conventions:
config-file: file_path
email: email
fail-on-error: boolean
file-pattern: path_list
ignore-file: file_path
max-retries: numeric_range_1_10
mode: mode_enum
plugins: linter_list
prettier-version: semantic_version
report-format: report_format
token: github_token
username: username
working-directory: file_path
overrides: {}
overrides:
mode: mode_enum
statistics:
total_inputs: 14
validated_inputs: 12
validated_inputs: 14
skipped_inputs: 0
coverage_percentage: 86
validation_coverage: 86
coverage_percentage: 100
validation_coverage: 100
auto_detected: true
manual_review_required: false
quality_indicators:

View File

@@ -370,7 +370,7 @@ runs:
- name: Upload SARIF Report
if: steps.check-files.outputs.result == 'found'
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: ${{ inputs.working-directory }}/reports/flake8.sarif
category: 'python-lint'

82
security-scan/README.md Normal file
View File

@@ -0,0 +1,82 @@
# ivuorinen/actions/security-scan
## Security Scan
### Description
Comprehensive security scanning for GitHub Actions including actionlint,
Gitleaks (optional), and Trivy vulnerability scanning. Requires
'security-events: write' and 'contents: read' permissions in the workflow.
### Inputs
| name | description | required | default |
|----------------------|--------------------------------------------------------------|----------|----------------------|
| `gitleaks-license` | <p>Gitleaks license key (required for Gitleaks scanning)</p> | `false` | `""` |
| `gitleaks-config` | <p>Path to Gitleaks config file</p> | `false` | `.gitleaks.toml` |
| `trivy-severity` | <p>Severity levels to scan for (comma-separated)</p> | `false` | `CRITICAL,HIGH` |
| `trivy-scanners` | <p>Types of scanners to run (comma-separated)</p> | `false` | `vuln,config,secret` |
| `trivy-timeout` | <p>Timeout for Trivy scan</p> | `false` | `10m` |
| `actionlint-enabled` | <p>Enable actionlint scanning</p> | `false` | `true` |
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
### Outputs
| name | description |
|------------------------|-----------------------------------------------------|
| `has_trivy_results` | <p>Whether Trivy scan produced valid results</p> |
| `has_gitleaks_results` | <p>Whether Gitleaks scan produced valid results</p> |
| `total_issues` | <p>Total number of security issues found</p> |
| `critical_issues` | <p>Number of critical security issues found</p> |
### Runs
This action is a `composite` action.
### Usage
```yaml
- uses: ivuorinen/actions/security-scan@main
with:
gitleaks-license:
# Gitleaks license key (required for Gitleaks scanning)
#
# Required: false
# Default: ""
gitleaks-config:
# Path to Gitleaks config file
#
# Required: false
# Default: .gitleaks.toml
trivy-severity:
# Severity levels to scan for (comma-separated)
#
# Required: false
# Default: CRITICAL,HIGH
trivy-scanners:
# Types of scanners to run (comma-separated)
#
# Required: false
# Default: vuln,config,secret
trivy-timeout:
# Timeout for Trivy scan
#
# Required: false
# Default: 10m
actionlint-enabled:
# Enable actionlint scanning
#
# Required: false
# Default: true
token:
# GitHub token for authentication
#
# Required: false
# Default: ""
```

282
security-scan/action.yml Normal file
View File

@@ -0,0 +1,282 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
#
# REQUIRED PERMISSIONS (set these in your workflow file):
# permissions:
# security-events: write # Required for SARIF uploads
# contents: read # Required for repository access
#
---
name: Security Scan
description: |
Comprehensive security scanning for GitHub Actions including actionlint,
Gitleaks (optional), and Trivy vulnerability scanning. Requires
'security-events: write' and 'contents: read' permissions in the workflow.
author: Ismo Vuorinen
branding:
icon: shield
color: red
inputs:
gitleaks-license:
description: 'Gitleaks license key (required for Gitleaks scanning)'
required: false
default: ''
gitleaks-config:
description: 'Path to Gitleaks config file'
required: false
default: '.gitleaks.toml'
trivy-severity:
description: 'Severity levels to scan for (comma-separated)'
required: false
default: 'CRITICAL,HIGH'
trivy-scanners:
description: 'Types of scanners to run (comma-separated)'
required: false
default: 'vuln,config,secret'
trivy-timeout:
description: 'Timeout for Trivy scan'
required: false
default: '10m'
actionlint-enabled:
description: 'Enable actionlint scanning'
required: false
default: 'true'
token:
description: 'GitHub token for authentication'
required: false
default: ''
outputs:
has_trivy_results:
description: 'Whether Trivy scan produced valid results'
value: ${{ steps.verify-sarif.outputs.has_trivy }}
has_gitleaks_results:
description: 'Whether Gitleaks scan produced valid results'
value: ${{ steps.verify-sarif.outputs.has_gitleaks }}
total_issues:
description: 'Total number of security issues found'
value: ${{ steps.analyze.outputs.total_issues }}
critical_issues:
description: 'Number of critical security issues found'
value: ${{ steps.analyze.outputs.critical_issues }}
runs:
using: composite
steps:
- name: Validate Inputs
id: validate
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
with:
action-type: security-scan
gitleaks-license: ${{ inputs.gitleaks-license }}
gitleaks-config: ${{ inputs.gitleaks-config }}
trivy-severity: ${{ inputs.trivy-severity }}
trivy-scanners: ${{ inputs.trivy-scanners }}
trivy-timeout: ${{ inputs.trivy-timeout }}
actionlint-enabled: ${{ inputs.actionlint-enabled }}
token: ${{ inputs.token }}
- name: Check Required Configurations
id: check-configs
shell: sh
run: |
set -eu
# Initialize all flags as false
{
printf '%s\n' "run_gitleaks=false"
printf '%s\n' "run_trivy=true"
printf '%s\n' "run_actionlint=${{ inputs.actionlint-enabled }}"
} >> "$GITHUB_OUTPUT"
# Check Gitleaks configuration and license
if [ -f "${{ inputs.gitleaks-config }}" ] && [ -n "${{ inputs.gitleaks-license }}" ]; then
printf 'Gitleaks config and license found\n'
printf '%s\n' "run_gitleaks=true" >> "$GITHUB_OUTPUT"
else
printf '::warning::Gitleaks config or license missing - skipping Gitleaks scan\n'
fi
- name: Run actionlint
if: steps.check-configs.outputs.run_actionlint == 'true'
uses: raven-actions/actionlint@963d4779ef039e217e5d0e6fd73ce9ab7764e493 # v2.1.0
with:
cache: true
fail-on-error: true
shellcheck: false
- name: Run Gitleaks
if: steps.check-configs.outputs.run_gitleaks == 'true'
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
env:
GITHUB_TOKEN: ${{ inputs.token || github.token }}
GITLEAKS_LICENSE: ${{ inputs.gitleaks-license }}
with:
config-path: ${{ inputs.gitleaks-config }}
report-format: sarif
report-path: gitleaks-report.sarif
- name: Run Trivy vulnerability scanner
if: steps.check-configs.outputs.run_trivy == 'true'
uses: aquasecurity/trivy-action@a11da62073708815958ea6d84f5650c78a3ef85b # master
with:
scan-type: 'fs'
scanners: ${{ inputs.trivy-scanners }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: ${{ inputs.trivy-severity }}
timeout: ${{ inputs.trivy-timeout }}
- name: Verify SARIF files
id: verify-sarif
shell: sh
run: |
set -eu
# Initialize outputs
{
printf '%s\n' "has_trivy=false"
printf '%s\n' "has_gitleaks=false"
} >> "$GITHUB_OUTPUT"
# Check Trivy results
if [ -f "trivy-results.sarif" ]; then
if jq -e . <"trivy-results.sarif" >/dev/null 2>&1; then
printf '%s\n' "has_trivy=true" >> "$GITHUB_OUTPUT"
else
printf '::warning::Trivy SARIF file exists but is not valid JSON\n'
fi
fi
# Check Gitleaks results if it ran
if [ "${{ steps.check-configs.outputs.run_gitleaks }}" = "true" ]; then
if [ -f "gitleaks-report.sarif" ]; then
if jq -e . <"gitleaks-report.sarif" >/dev/null 2>&1; then
printf '%s\n' "has_gitleaks=true" >> "$GITHUB_OUTPUT"
else
printf '::warning::Gitleaks SARIF file exists but is not valid JSON\n'
fi
fi
fi
- name: Upload Trivy results
if: steps.verify-sarif.outputs.has_trivy == 'true'
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: 'trivy-results.sarif'
category: 'trivy'
- name: Upload Gitleaks results
if: steps.verify-sarif.outputs.has_gitleaks == 'true'
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: 'gitleaks-report.sarif'
category: 'gitleaks'
- name: Archive security reports
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: security-reports-${{ github.run_id }}
path: |
${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'trivy-results.sarif' || '' }}
${{ steps.verify-sarif.outputs.has_gitleaks == 'true' && 'gitleaks-report.sarif' || '' }}
retention-days: 30
- name: Analyze Results
id: analyze
if: always()
shell: node {0}
run: |
const fs = require('fs');
try {
let totalIssues = 0;
let criticalIssues = 0;
const analyzeSarif = (file, tool) => {
if (!fs.existsSync(file)) {
console.log(`No results file found for ${tool}`);
return null;
}
try {
const sarif = JSON.parse(fs.readFileSync(file, 'utf8'));
return sarif.runs.reduce((acc, run) => {
if (!run.results) return acc;
const critical = run.results.filter(r =>
r.level === 'error' ||
r.level === 'critical' ||
(r.ruleId || '').toLowerCase().includes('critical')
).length;
return {
total: acc.total + run.results.length,
critical: acc.critical + critical
};
}, { total: 0, critical: 0 });
} catch (error) {
console.log(`Error analyzing ${tool} results: ${error.message}`);
return null;
}
};
// Only analyze results from tools that ran successfully
const results = {
trivy: '${{ steps.verify-sarif.outputs.has_trivy }}' === 'true' ?
analyzeSarif('trivy-results.sarif', 'trivy') : null,
gitleaks: '${{ steps.verify-sarif.outputs.has_gitleaks }}' === 'true' ?
analyzeSarif('gitleaks-report.sarif', 'gitleaks') : null
};
// Aggregate results
Object.entries(results).forEach(([tool, result]) => {
if (result) {
totalIssues += result.total;
criticalIssues += result.critical;
console.log(`${tool}: ${result.total} total, ${result.critical} critical issues`);
}
});
// Create summary
const summary = `## Security Scan Summary
- Total Issues Found: ${totalIssues}
- Critical Issues: ${criticalIssues}
### Tool Breakdown
${Object.entries(results)
.filter(([_, r]) => r)
.map(([tool, r]) =>
`- ${tool}: ${r.total} total, ${r.critical} critical`
).join('\n')}
### Tools Run Status
- Actionlint: ${{ steps.check-configs.outputs.run_actionlint }}
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy }}
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks }}
`;
// Set outputs using GITHUB_OUTPUT
const outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
fs.appendFileSync(outputFile, `total_issues=${totalIssues}\n`);
fs.appendFileSync(outputFile, `critical_issues=${criticalIssues}\n`);
}
// Add job summary using GITHUB_STEP_SUMMARY
const summaryFile = process.env.GITHUB_STEP_SUMMARY;
if (summaryFile) {
fs.appendFileSync(summaryFile, summary + '\n');
}
// Fail if critical issues found
if (criticalIssues > 0) {
console.error(`Found ${criticalIssues} critical security issues`);
process.exit(1);
}
} catch (error) {
console.error(`Analysis failed: ${error.message}`);
process.exit(1);
}

55
security-scan/rules.yml Normal file
View File

@@ -0,0 +1,55 @@
---
# Validation rules for security-scan action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 86% (6/7 inputs)
#
# This file defines validation rules for the security-scan GitHub Action.
# Rules are automatically applied by validate-inputs action when this
# action is used.
#
schema_version: '1.0'
action: security-scan
description: |
Comprehensive security scanning for GitHub Actions including actionlint,
Gitleaks (optional), and Trivy vulnerability scanning. Requires
'security-events: write' and 'contents: read' permissions in the workflow.
generator_version: 1.0.0
required_inputs: []
optional_inputs:
- actionlint-enabled
- gitleaks-config
- gitleaks-license
- token
- trivy-scanners
- trivy-severity
- trivy-timeout
conventions:
actionlint-enabled: boolean
gitleaks-config: file_path
token: github_token
trivy-scanners: scanner_list
trivy-severity: severity_enum
trivy-timeout: timeout_with_unit
overrides:
actionlint-enabled: boolean
gitleaks-config: file_path
token: github_token
trivy-scanners: scanner_list
trivy-severity: severity_enum
trivy-timeout: timeout_with_unit
statistics:
total_inputs: 7
validated_inputs: 6
skipped_inputs: 0
coverage_percentage: 86
validation_coverage: 86
auto_detected: true
manual_review_required: false
quality_indicators:
has_required_inputs: false
has_token_validation: true
has_version_validation: false
has_file_validation: true
has_security_validation: true

View File

@@ -52,7 +52,7 @@ runs:
- name: 🚀 Run stale
id: stale
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with:
repo-token: ${{ inputs.token || github.token }}
days-before-stale: ${{ inputs.days-before-stale }}

View File

@@ -256,7 +256,7 @@ runs:
- name: Upload SARIF Report
if: steps.check-files.outputs.found == 'true' && inputs.format == 'sarif'
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: ${{ env.VALIDATED_WORKING_DIR }}/reports/tflint.sarif
category: terraform-lint

View File

@@ -8,56 +8,62 @@ Centralized Python-based input validation for GitHub Actions with PCRE regex sup
### Inputs
| name | description | required | default |
|---------------------|-------------------------------------------------------------------------------------|----------|---------|
| `action` | <p>Action name to validate (alias for action-type)</p> | `false` | `""` |
| `action-type` | <p>Type of action to validate (e.g., csharp-publish, docker-build, eslint-lint)</p> | `false` | `""` |
| `rules-file` | <p>Path to validation rules file</p> | `false` | `""` |
| `fail-on-error` | <p>Whether to fail on validation errors</p> | `false` | `true` |
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
| `namespace` | <p>Namespace/username for validation</p> | `false` | `""` |
| `email` | <p>Email address for validation</p> | `false` | `""` |
| `username` | <p>Username for validation</p> | `false` | `""` |
| `dotnet-version` | <p>.NET version string</p> | `false` | `""` |
| `terraform-version` | <p>Terraform version string</p> | `false` | `""` |
| `tflint-version` | <p>TFLint version string</p> | `false` | `""` |
| `node-version` | <p>Node.js version string</p> | `false` | `""` |
| `force-version` | <p>Force version override</p> | `false` | `""` |
| `default-version` | <p>Default version fallback</p> | `false` | `""` |
| `image-name` | <p>Docker image name</p> | `false` | `""` |
| `tag` | <p>Docker image tag</p> | `false` | `""` |
| `architectures` | <p>Target architectures</p> | `false` | `""` |
| `dockerfile` | <p>Dockerfile path</p> | `false` | `""` |
| `context` | <p>Docker build context</p> | `false` | `""` |
| `build-args` | <p>Docker build arguments</p> | `false` | `""` |
| `buildx-version` | <p>Docker Buildx version</p> | `false` | `""` |
| `max-retries` | <p>Maximum retry attempts</p> | `false` | `""` |
| `image-quality` | <p>Image quality percentage</p> | `false` | `""` |
| `png-quality` | <p>PNG quality percentage</p> | `false` | `""` |
| `parallel-builds` | <p>Number of parallel builds</p> | `false` | `""` |
| `days-before-stale` | <p>Number of days before marking as stale</p> | `false` | `""` |
| `days-before-close` | <p>Number of days before closing stale items</p> | `false` | `""` |
| `pre-commit-config` | <p>Pre-commit configuration file path</p> | `false` | `""` |
| `base-branch` | <p>Base branch name</p> | `false` | `""` |
| `dry-run` | <p>Dry run mode</p> | `false` | `""` |
| `is_fiximus` | <p>Use Fiximus bot</p> | `false` | `""` |
| `prefix` | <p>Release tag prefix</p> | `false` | `""` |
| `language` | <p>Language to analyze (for CodeQL)</p> | `false` | `""` |
| `queries` | <p>CodeQL queries to run</p> | `false` | `""` |
| `packs` | <p>CodeQL query packs</p> | `false` | `""` |
| `config-file` | <p>CodeQL configuration file path</p> | `false` | `""` |
| `config` | <p>CodeQL configuration YAML string</p> | `false` | `""` |
| `build-mode` | <p>Build mode for compiled languages</p> | `false` | `""` |
| `source-root` | <p>Source code root directory</p> | `false` | `""` |
| `category` | <p>Analysis category</p> | `false` | `""` |
| `checkout-ref` | <p>Git reference to checkout</p> | `false` | `""` |
| `working-directory` | <p>Working directory for analysis</p> | `false` | `""` |
| `upload-results` | <p>Upload results to GitHub Security</p> | `false` | `""` |
| `ram` | <p>Memory in MB for CodeQL</p> | `false` | `""` |
| `threads` | <p>Number of threads for CodeQL</p> | `false` | `""` |
| `output` | <p>Output path for SARIF results</p> | `false` | `""` |
| `skip-queries` | <p>Skip running queries</p> | `false` | `""` |
| `add-snippets` | <p>Add code snippets to SARIF</p> | `false` | `""` |
| name | description | required | default |
|----------------------|-------------------------------------------------------------------------------------|----------|---------|
| `action` | <p>Action name to validate (alias for action-type)</p> | `false` | `""` |
| `action-type` | <p>Type of action to validate (e.g., csharp-publish, docker-build, eslint-lint)</p> | `false` | `""` |
| `rules-file` | <p>Path to validation rules file</p> | `false` | `""` |
| `fail-on-error` | <p>Whether to fail on validation errors</p> | `false` | `true` |
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
| `namespace` | <p>Namespace/username for validation</p> | `false` | `""` |
| `email` | <p>Email address for validation</p> | `false` | `""` |
| `username` | <p>Username for validation</p> | `false` | `""` |
| `dotnet-version` | <p>.NET version string</p> | `false` | `""` |
| `terraform-version` | <p>Terraform version string</p> | `false` | `""` |
| `tflint-version` | <p>TFLint version string</p> | `false` | `""` |
| `node-version` | <p>Node.js version string</p> | `false` | `""` |
| `force-version` | <p>Force version override</p> | `false` | `""` |
| `default-version` | <p>Default version fallback</p> | `false` | `""` |
| `image-name` | <p>Docker image name</p> | `false` | `""` |
| `tag` | <p>Docker image tag</p> | `false` | `""` |
| `architectures` | <p>Target architectures</p> | `false` | `""` |
| `dockerfile` | <p>Dockerfile path</p> | `false` | `""` |
| `context` | <p>Docker build context</p> | `false` | `""` |
| `build-args` | <p>Docker build arguments</p> | `false` | `""` |
| `buildx-version` | <p>Docker Buildx version</p> | `false` | `""` |
| `max-retries` | <p>Maximum retry attempts</p> | `false` | `""` |
| `image-quality` | <p>Image quality percentage</p> | `false` | `""` |
| `png-quality` | <p>PNG quality percentage</p> | `false` | `""` |
| `parallel-builds` | <p>Number of parallel builds</p> | `false` | `""` |
| `days-before-stale` | <p>Number of days before marking as stale</p> | `false` | `""` |
| `days-before-close` | <p>Number of days before closing stale items</p> | `false` | `""` |
| `pre-commit-config` | <p>Pre-commit configuration file path</p> | `false` | `""` |
| `base-branch` | <p>Base branch name</p> | `false` | `""` |
| `dry-run` | <p>Dry run mode</p> | `false` | `""` |
| `is_fiximus` | <p>Use Fiximus bot</p> | `false` | `""` |
| `prefix` | <p>Release tag prefix</p> | `false` | `""` |
| `language` | <p>Language to analyze (for CodeQL)</p> | `false` | `""` |
| `queries` | <p>CodeQL queries to run</p> | `false` | `""` |
| `packs` | <p>CodeQL query packs</p> | `false` | `""` |
| `config-file` | <p>CodeQL configuration file path</p> | `false` | `""` |
| `config` | <p>CodeQL configuration YAML string</p> | `false` | `""` |
| `build-mode` | <p>Build mode for compiled languages</p> | `false` | `""` |
| `source-root` | <p>Source code root directory</p> | `false` | `""` |
| `category` | <p>Analysis category</p> | `false` | `""` |
| `checkout-ref` | <p>Git reference to checkout</p> | `false` | `""` |
| `working-directory` | <p>Working directory for analysis</p> | `false` | `""` |
| `upload-results` | <p>Upload results to GitHub Security</p> | `false` | `""` |
| `ram` | <p>Memory in MB for CodeQL</p> | `false` | `""` |
| `threads` | <p>Number of threads for CodeQL</p> | `false` | `""` |
| `output` | <p>Output path for SARIF results</p> | `false` | `""` |
| `skip-queries` | <p>Skip running queries</p> | `false` | `""` |
| `add-snippets` | <p>Add code snippets to SARIF</p> | `false` | `""` |
| `gitleaks-license` | <p>Gitleaks license key</p> | `false` | `""` |
| `gitleaks-config` | <p>Gitleaks configuration file path</p> | `false` | `""` |
| `trivy-severity` | <p>Trivy severity levels to scan</p> | `false` | `""` |
| `trivy-scanners` | <p>Trivy scanner types to run</p> | `false` | `""` |
| `trivy-timeout` | <p>Trivy scan timeout</p> | `false` | `""` |
| `actionlint-enabled` | <p>Enable actionlint scanning</p> | `false` | `""` |
### Outputs
@@ -365,4 +371,40 @@ This action is a `composite` action.
#
# Required: false
# Default: ""
gitleaks-license:
# Gitleaks license key
#
# Required: false
# Default: ""
gitleaks-config:
# Gitleaks configuration file path
#
# Required: false
# Default: ""
trivy-severity:
# Trivy severity levels to scan
#
# Required: false
# Default: ""
trivy-scanners:
# Trivy scanner types to run
#
# Required: false
# Default: ""
trivy-timeout:
# Trivy scan timeout
#
# Required: false
# Default: ""
actionlint-enabled:
# Enable actionlint scanning
#
# Required: false
# Default: ""
```

View File

@@ -173,6 +173,26 @@ inputs:
description: 'Add code snippets to SARIF'
required: false
# Security-scan specific inputs
gitleaks-license:
description: 'Gitleaks license key'
required: false
gitleaks-config:
description: 'Gitleaks configuration file path'
required: false
trivy-severity:
description: 'Trivy severity levels to scan'
required: false
trivy-scanners:
description: 'Trivy scanner types to run'
required: false
trivy-timeout:
description: 'Trivy scan timeout'
required: false
actionlint-enabled:
description: 'Enable actionlint scanning'
required: false
outputs:
validation-status:
description: 'Overall validation status (success/failure)'

View File

@@ -114,7 +114,7 @@ class ValidationRuleGenerator:
"prefix": re.compile(r"\b(prefix|tag[_-]?prefix)\b", re.IGNORECASE),
# Boolean patterns (broad, should be lower priority)
"boolean": re.compile(
r"\b(dry-?run|verbose|enable|disable|auto|skip|force|cache|provenance|sbom|scan|sign|fail[_-]?on[_-]?error|nightly)\b",
r"\b(dry-?run|verbose|enable|disable|auto|skip|force|cache|provenance|sbom|scan|sign|push|fail[_-]?on[_-]?error|nightly)\b",
re.IGNORECASE,
),
# File extensions pattern
@@ -160,36 +160,36 @@ class ValidationRuleGenerator:
"npm_token": "github_token",
"password": "github_token",
# Complex fields that should skip validation
"build-args": None, # Can be empty
"context": None, # Default handled
"cache-from": None, # Complex cache syntax
"cache-export": None, # Complex cache syntax
"cache-import": None, # Complex cache syntax
"build-contexts": None, # Complex syntax
"secrets": None, # Complex syntax
"platform-build-args": None, # JSON format
"extensions": None, # PHP extensions list
"tools": None, # PHP tools list
"build-args": "key_value_list", # Docker build arguments (KEY=VALUE format)
"context": "file_path", # Build context path
"cache-from": "cache_config", # Docker cache configuration
"cache-export": "cache_config", # Docker cache configuration
"cache-import": "cache_config", # Docker cache configuration
"build-contexts": "key_value_list", # Docker build contexts (KEY=VALUE format)
"secrets": "key_value_list", # Docker secrets (KEY=VALUE format)
"platform-build-args": "json_format", # JSON format for platform-specific args
"extensions": "php_extensions", # PHP extensions list
"tools": "linter_list", # PHP tools list - same pattern as linters
"framework": "framework_mode", # PHP framework mode (auto, laravel, generic)
"args": None, # Composer args
"stability": None, # Composer stability
"registry-url": "url", # URL format
"scope": "scope", # NPM scope
"plugins": None, # Prettier plugins
"plugins": "linter_list", # Prettier plugins - same pattern as linters
"file-extensions": "file_extensions", # File extension list
"file-pattern": None, # Glob pattern
"enable-linters": None, # Linter list
"disable-linters": None, # Linter list
"success-codes": None, # Exit code list
"retry-codes": None, # Exit code list
"ignore-paths": None, # Path patterns
"key-files": None, # Cache key files
"restore-keys": None, # Cache restore keys
"env-vars": None, # Environment variables
"file-pattern": "path_list", # Glob pattern for file paths
"enable-linters": "linter_list", # Linter list
"disable-linters": "linter_list", # Linter list
"success-codes": "exit_code_list", # Exit code list
"retry-codes": "exit_code_list", # Exit code list
"ignore-paths": "path_list", # Path patterns to ignore
"key-files": "path_list", # Cache key files (paths)
"restore-keys": "path_list", # Cache restore keys (paths)
"env-vars": "key_value_list", # Environment variables (KEY=VALUE format)
# Action-specific fields that need special handling
"type": None, # Cache type enum (npm, composer, go, etc.) - complex enum,
# skip validation
"paths": None, # File paths for caching (comma-separated) - complex format,
# skip validation
"paths": "path_list", # File paths for caching (comma-separated)
"command": None, # Shell command - complex format, skip validation for safety
"backoff-strategy": None, # Retry strategy enum - complex enum, skip validation
"shell": None, # Shell type enum - simple enum, skip validation
@@ -199,10 +199,13 @@ class ValidationRuleGenerator:
"retry-delay": "numeric_range_1_300", # Retry delay should support higher values
"max-warnings": "numeric_range_0_10000",
# version-file-parser specific fields
"language": None, # Simple enum (node, php, python, go, dotnet)
"tool-versions-key": None, # Simple string (nodejs, python, php, golang, dotnet)
"dockerfile-image": None, # Simple string (node, python, php, golang, dotnet)
"validation-regex": "regex_pattern", # Regex pattern - validate for ReDoS
# Docker network mode
"network": "network_mode", # Docker network mode (host, none, default)
# Language enum for version detection
"language": "language_enum", # Language type (php, python, go, dotnet)
}
def get_action_directories(self) -> list[str]:
@@ -314,7 +317,6 @@ class ValidationRuleGenerator:
"docker-publish": {
"registry": "registry_enum",
"cache-mode": "cache_mode",
"platforms": None, # Skip validation - complex platform format
},
"docker-publish-hub": {
"password": "docker_password",
@@ -354,26 +356,28 @@ class ValidationRuleGenerator:
"prettier-lint": {
"mode": "mode_enum",
},
"security-scan": {
"gitleaks-config": "file_path",
"trivy-severity": "severity_enum",
"trivy-scanners": "scanner_list",
"trivy-timeout": "timeout_with_unit",
"actionlint-enabled": "boolean",
"token": "github_token",
},
}
if action_name in action_overrides:
# Apply overrides for existing conventions
overrides.update(
{
input_name: override_value
for input_name, override_value in action_overrides[action_name].items()
if input_name in conventions
},
)
# Add missing inputs from overrides to conventions
for input_name, override_value in action_overrides[action_name].items():
if input_name not in conventions and input_name in action_data["inputs"]:
if input_name in action_data["inputs"]:
overrides[input_name] = override_value
# Update conventions to match override (or set to None if skipped)
conventions[input_name] = override_value
# Calculate statistics
total_inputs = len(action_data["inputs"])
validated_inputs = len(conventions)
skipped_inputs = sum(1 for v in overrides.values() if v is None)
validated_inputs = sum(1 for v in conventions.values() if v is not None)
skipped_inputs = sum(1 for v in conventions.values() if v is None)
coverage = round((validated_inputs / total_inputs) * 100) if total_inputs > 0 else 0
# Generate rules object with enhanced metadata
@@ -432,8 +436,20 @@ class ValidationRuleGenerator:
# Use a custom yaml dumper to ensure proper indentation
class CustomYamlDumper(yaml.SafeDumper):
def increase_indent(self, flow: bool = False, *, indentless: bool = False) -> None: # noqa: FBT001, FBT002
return super().increase_indent(flow, indentless=indentless)
def increase_indent(self, flow: bool = False, *, indentless: bool = False) -> None: # noqa: FBT001, FBT002, ARG002 # type: ignore[override]
return super().increase_indent(flow, False)
def choose_scalar_style(self):
"""Choose appropriate quote style based on string content."""
if hasattr(self, "event") and hasattr(self.event, "value") and self.event.value: # type: ignore[attr-defined]
value = self.event.value # type: ignore[attr-defined]
# Use literal block style for multiline strings
if "\n" in value:
return "|"
# Use double quotes for strings with single quotes
if "'" in value:
return '"'
return super().choose_scalar_style()
yaml_content = yaml.dump(
rules,

File diff suppressed because it is too large Load Diff

View File

@@ -274,6 +274,71 @@ class TestDockerValidator:
result = self.validator.validate_inputs(inputs)
assert isinstance(result, bool)
def test_validate_registry_valid(self):
"""Test registry enum validation with valid values."""
valid_registries = [
"dockerhub",
"github",
"both",
]
for registry in valid_registries:
self.validator.errors = []
result = self.validator.validate_registry(registry)
assert result is True, f"Should accept registry: {registry}"
def test_validate_registry_invalid(self):
"""Test registry enum validation with invalid values."""
invalid_registries = [
"", # Empty
" ", # Whitespace only
"docker", # Wrong value (should be dockerhub)
"hub", # Wrong value
"ghcr", # Wrong value
"gcr", # Wrong value
"both,github", # Comma-separated not allowed
"DOCKERHUB", # Uppercase
"DockerHub", # Mixed case
"docker hub", # Space
"github.com", # Full URL not allowed
]
for registry in invalid_registries:
self.validator.errors = []
result = self.validator.validate_registry(registry)
assert result is False, f"Should reject registry: {registry}"
def test_validate_sbom_format_valid(self):
"""Test SBOM format validation with valid values."""
valid_formats = [
"spdx-json",
"cyclonedx-json",
"", # Empty is optional
]
for sbom_format in valid_formats:
self.validator.errors = []
result = self.validator.validate_sbom_format(sbom_format)
assert result is True, f"Should accept SBOM format: {sbom_format}"
def test_validate_sbom_format_invalid(self):
"""Test SBOM format validation with invalid values."""
invalid_formats = [
"spdx", # Missing -json suffix
"cyclonedx", # Missing -json suffix
"json", # Just json
"spdx-xml", # Wrong format
"cyclonedx-xml", # Wrong format
"SPDX-JSON", # Uppercase
"spdx json", # Space
"invalid", # Invalid value
]
for sbom_format in invalid_formats:
self.validator.errors = []
result = self.validator.validate_sbom_format(sbom_format)
assert result is False, f"Should reject SBOM format: {sbom_format}"
def test_empty_values_handling(self):
"""Test that empty values are handled appropriately."""
# Some Docker fields might be required, others optional
@@ -281,3 +346,5 @@ class TestDockerValidator:
assert isinstance(self.validator.validate_docker_tag(""), bool)
assert isinstance(self.validator.validate_architectures(""), bool)
assert isinstance(self.validator.validate_prefix(""), bool)
# Registry should reject empty values
assert self.validator.validate_registry("") is False

View File

@@ -151,7 +151,7 @@ class TestValidationRuleGenerator:
generator = ValidationRuleGenerator()
# Test special cases from the mapping
assert generator.detect_validation_type("build-args", {}) is None
assert generator.detect_validation_type("build-args", {}) == "key_value_list"
assert generator.detect_validation_type("version", {}) == "flexible_version"
assert (
generator.detect_validation_type("dotnet-version", {}) == "dotnet_version"

View File

@@ -556,13 +556,33 @@ class ConventionBasedValidator(BaseValidator):
self._validator_modules["codeql"] = codeql.CodeQLValidator()
return self._validator_modules["codeql"], f"validate_{validator_type}"
# PHP-specific validators
if validator_type in ["php_extensions", "coverage_driver", "mode_enum"]:
# Return self for PHP-specific validation methods
# Convention-based validators
if validator_type in [
"php_extensions",
"coverage_driver",
"mode_enum",
"binary_enum",
"multi_value_enum",
"report_format",
"format_enum",
"linter_list",
"timeout_with_unit",
"severity_enum",
"scanner_list",
"exit_code_list",
"key_value_list",
"path_list",
"network_mode",
"language_enum",
"framework_mode",
"json_format",
"cache_config",
]:
# Return self for validation methods implemented in this class
return self, f"_validate_{validator_type}"
# Package manager and report format validators
if validator_type in ["package_manager_enum", "report_format"]:
# Package manager validators
if validator_type in ["package_manager_enum"]:
# These could be in a separate module, but for now we'll put them in file validator
if "file" not in self._validator_modules:
from . import file
@@ -592,9 +612,104 @@ class ConventionBasedValidator(BaseValidator):
# Default range
return 0, 100
def _validate_comma_separated_list(
self,
value: str,
input_name: str,
item_pattern: str | None = None,
valid_items: list | None = None,
check_injection: bool = False,
item_name: str = "item",
) -> bool:
"""Validate comma-separated list of items (generic validator).
This is a generic validator that can be used for any comma-separated list
with either pattern-based or enum-based validation.
Args:
value: The comma-separated list value
input_name: The input name for error messages
item_pattern: Regex pattern each item must match (default: alphanumeric+hyphens+underscores)
valid_items: Optional list of valid items for enum-style validation
check_injection: Whether to check for shell injection patterns
item_name: Descriptive name for items in error messages (e.g., "linter", "extension")
Returns:
True if valid, False otherwise
Examples:
>>> # Pattern-based validation
>>> validator._validate_comma_separated_list(
... "gosec,govet", "enable-linters",
... item_pattern=r'^[a-zA-Z0-9_-]+$',
... item_name="linter"
... )
True
>>> # Enum-based validation
>>> validator._validate_comma_separated_list(
... "vuln,config", "scanners",
... valid_items=["vuln", "config", "secret", "license"],
... item_name="scanner"
... )
True
"""
import re
if not value or value.strip() == "":
return True # Optional
# Security check for injection patterns
if check_injection and re.search(r"[;&|`$()]", value):
self.add_error(
f"Potential injection detected in {input_name}: {value}. "
f"Avoid using shell metacharacters (;, &, |, `, $, parentheses)"
)
return False
# Split by comma and validate each item
items = [item.strip() for item in value.split(",")]
for item in items:
if not item: # Empty after strip
self.add_error(f"Invalid {input_name}: {value}. Contains empty {item_name}")
return False
# Enum-based validation (if valid_items provided)
if valid_items is not None:
if item not in valid_items:
self.add_error(
f"Invalid {item_name} '{item}' in {input_name}. "
f"Must be one of: {', '.join(valid_items)}"
)
return False
# Pattern-based validation (if no valid_items and pattern provided)
elif item_pattern is not None:
if not re.match(item_pattern, item):
self.add_error(
f"Invalid {item_name} '{item}' in {input_name}. "
f"Must match pattern: alphanumeric with hyphens/underscores"
)
return False
# Default pattern if neither valid_items nor item_pattern provided
elif not re.match(r"^[a-zA-Z0-9_-]+$", item):
self.add_error(
f"Invalid {item_name} '{item}' in {input_name}. "
f"Must be alphanumeric with hyphens/underscores"
)
return False
return True
def _validate_php_extensions(self, value: str, input_name: str) -> bool:
"""Validate PHP extensions format.
Wrapper for comma-separated list validator with PHP extension-specific rules.
Allows alphanumeric characters, underscores, and spaces.
Checks for shell injection patterns.
Args:
value: The extensions value (comma-separated list)
input_name: The input name for error messages
@@ -602,59 +717,736 @@ class ConventionBasedValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
import re
return self._validate_comma_separated_list(
value,
input_name,
item_pattern=r"^[a-zA-Z0-9_\s]+$",
check_injection=True,
item_name="extension",
)
if not value:
return True
def _validate_binary_enum(
self,
value: str,
input_name: str,
valid_values: list | None = None,
case_sensitive: bool = True,
) -> bool:
"""Validate binary enum (two-value choice) (generic validator).
# Check for injection patterns
if re.search(r"[;&|`$()@#]", value):
self.add_error(f"Potential injection detected in {input_name}: {value}")
This is a generic validator for two-value enums (e.g., check/fix, enabled/disabled).
Args:
value: The enum value
input_name: The input name for error messages
valid_values: List of exactly 2 valid values (default: ["check", "fix"])
case_sensitive: Whether validation is case-sensitive (default: True)
Returns:
True if valid, False otherwise
Examples:
>>> # Default check/fix mode
>>> validator._validate_binary_enum("check", "mode")
True
>>> # Custom binary enum
>>> validator._validate_binary_enum(
... "enabled", "status",
... valid_values=["enabled", "disabled"]
... )
True
"""
if valid_values is None:
valid_values = ["check", "fix"]
if len(valid_values) != 2:
raise ValueError(
f"Binary enum requires exactly 2 valid values, got {len(valid_values)}"
)
if not value or value.strip() == "":
return True # Optional
# Case-insensitive comparison if needed
if not case_sensitive:
value_lower = value.lower()
valid_values_lower = [v.lower() for v in valid_values]
if value_lower not in valid_values_lower:
self.add_error(
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_values)}"
)
return False
else:
if value not in valid_values:
self.add_error(
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_values)}"
)
return False
return True
def _validate_format_enum(
self,
value: str,
input_name: str,
valid_formats: list | None = None,
allow_custom: bool = False,
) -> bool:
"""Validate output format enum (generic validator).
Generic validator for tool output formats (SARIF, JSON, XML, etc.).
Supports common formats across linting/analysis tools.
Args:
value: The format value
input_name: The input name for error messages
valid_formats: List of valid formats (default: comprehensive list)
allow_custom: Whether to allow formats not in the predefined list (default: False)
Returns:
True if valid, False otherwise
Examples:
>>> # Default comprehensive format list
>>> validator._validate_format_enum("json", "format")
True
>>> # Tool-specific format list
>>> validator._validate_format_enum(
... "sarif", "output-format",
... valid_formats=["json", "sarif", "text"]
... )
True
"""
if valid_formats is None:
# Comprehensive list of common formats across all tools
valid_formats = [
"checkstyle",
"colored-line-number",
"compact",
"github-actions",
"html",
"json",
"junit",
"junit-xml",
"line-number",
"sarif",
"stylish",
"tab",
"teamcity",
"xml",
]
if not value or value.strip() == "":
return True # Optional
# Check if format is valid
if value not in valid_formats and not allow_custom:
self.add_error(
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_formats)}"
)
return False
# Check format - should be alphanumeric, underscores, commas, spaces only
if not re.match(r"^[a-zA-Z0-9_,\s]+$", value):
self.add_error(f"Invalid format for {input_name}: {value}")
return False
return True
def _validate_multi_value_enum(
self,
value: str,
input_name: str,
valid_values: list | None = None,
case_sensitive: bool = True,
min_values: int = 2,
max_values: int = 10,
) -> bool:
"""Validate multi-value enum (2-10 value choice) (generic validator).
Generic validator for enums with 2-10 predefined values.
For exactly 2 values, use _validate_binary_enum instead.
Args:
value: The enum value
input_name: The input name for error messages
valid_values: List of valid values (2-10 items required)
case_sensitive: Whether validation is case-sensitive (default: True)
min_values: Minimum number of valid values (default: 2)
max_values: Maximum number of valid values (default: 10)
Returns:
True if valid, False otherwise
Examples:
>>> # Framework selection (3 values)
>>> validator._validate_multi_value_enum(
... "laravel", "framework",
... valid_values=["auto", "laravel", "generic"]
... )
True
>>> # Language selection (4 values)
>>> validator._validate_multi_value_enum(
... "python", "language",
... valid_values=["php", "python", "go", "dotnet"]
... )
True
"""
if valid_values is None:
raise ValueError("valid_values is required for multi_value_enum validator")
# Validate valid_values count
if len(valid_values) < min_values:
raise ValueError(
f"Multi-value enum requires at least {min_values} valid values, got {len(valid_values)}"
)
if len(valid_values) > max_values:
raise ValueError(
f"Multi-value enum supports at most {max_values} valid values, got {len(valid_values)}"
)
if not value or value.strip() == "":
return True # Optional
# Case-insensitive comparison if needed
if not case_sensitive:
value_lower = value.lower()
valid_values_lower = [v.lower() for v in valid_values]
if value_lower not in valid_values_lower:
self.add_error(
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_values)}"
)
return False
else:
if value not in valid_values:
self.add_error(
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_values)}"
)
return False
return True
def _validate_coverage_driver(self, value: str, input_name: str) -> bool:
"""Validate coverage driver enum.
Wrapper for multi_value_enum validator with PHP coverage driver options.
Args:
value: The coverage driver value
input_name: The input name for error messages
Returns:
True if valid, False otherwise
Examples:
Valid: "xdebug", "pcov", "xdebug3", "none", ""
Invalid: "xdebug2", "XDEBUG", "coverage"
"""
valid_drivers = ["none", "xdebug", "pcov", "xdebug3"]
if value and value not in valid_drivers:
self.add_error(
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_drivers)}"
)
return False
return True
return self._validate_multi_value_enum(
value,
input_name,
valid_values=["none", "xdebug", "pcov", "xdebug3"],
case_sensitive=True,
)
def _validate_mode_enum(self, value: str, input_name: str) -> bool:
"""Validate mode enum for linting actions.
Wrapper for binary_enum validator with check/fix modes.
Args:
value: The mode value
input_name: The input name for error messages
Returns:
True if valid, False otherwise
"""
valid_modes = ["check", "fix"]
if value and value not in valid_modes:
Examples:
Valid: "check", "fix", ""
Invalid: "invalid", "CHECK", "Fix"
"""
return self._validate_binary_enum(
value,
input_name,
valid_values=["check", "fix"],
case_sensitive=True,
)
def _validate_report_format(self, value: str, input_name: str) -> bool:
"""Validate report format for linting/analysis actions.
Wrapper for format_enum validator with comprehensive format list.
Supports multiple report formats used across different tools.
Args:
value: The report format value
input_name: The input name for error messages
Returns:
True if valid, False otherwise
Examples:
Valid: "json", "sarif", "checkstyle", "github-actions", ""
Invalid: "invalid", "txt", "pdf"
"""
return self._validate_format_enum(value, input_name)
def _validate_linter_list(self, value: str, input_name: str) -> bool:
"""Validate comma-separated list of linter names.
Wrapper for comma-separated list validator with linter-specific rules.
Allows alphanumeric characters, hyphens, and underscores.
Args:
value: The linter list value
input_name: The input name for error messages
Returns:
True if valid, False otherwise
Examples:
Valid: "gosec,govet,staticcheck", "errcheck"
Invalid: "gosec,,govet", "invalid linter", "linter@123"
"""
return self._validate_comma_separated_list(
value,
input_name,
item_pattern=r"^[a-zA-Z0-9_-]+$",
item_name="linter",
)
def _validate_timeout_with_unit(self, value: str, input_name: str) -> bool:
"""Validate timeout duration with unit (Go duration format).
Args:
value: The timeout value
input_name: The input name for error messages
Returns:
True if valid, False otherwise
"""
import re
if not value or value.strip() == "":
return True # Optional
# Go duration format: number + unit (ns, us/µs, ms, s, m, h)
pattern = r"^[0-9]+(ns|us|µs|ms|s|m|h)$"
if not re.match(pattern, value):
self.add_error(
f"Invalid {input_name}: {value}. Must be one of: {', '.join(valid_modes)}"
f"Invalid {input_name}: {value}. Expected format: number with unit "
"(e.g., 5m, 30s, 1h, 500ms)"
)
return False
return True
def _validate_severity_enum(self, value: str, input_name: str) -> bool:
"""Validate severity levels enum (generalized).
Generic validator for security tool severity levels.
Supports common severity formats used by various security tools.
Default levels: UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL (Trivy/CVSSv3 style)
Case-sensitive by default.
Args:
value: The severity value (comma-separated for multiple levels)
input_name: The input name for error messages
Returns:
True if valid, False otherwise
"""
if not value or value.strip() == "":
return True # Optional
# Standard severity levels (Trivy/CVSSv3/OWASP compatible)
# Can be extended for specific tools by creating tool-specific validators
valid_severities = ["UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL"]
# Split by comma and validate each severity
severities = [s.strip() for s in value.split(",")]
for severity in severities:
if not severity: # Empty after strip
self.add_error(f"Invalid {input_name}: {value}. Contains empty severity level")
return False
# Case-sensitive validation
if severity not in valid_severities:
self.add_error(
f"Invalid {input_name}: {value}. Severity '{severity}' is not valid. "
f"Must be one of: {', '.join(valid_severities)}"
)
return False
return True
def _validate_scanner_list(self, value: str, input_name: str) -> bool:
"""Validate comma-separated list of scanner types (for Trivy).
Wrapper for comma-separated list validator with Trivy scanner enum validation.
Supports: vuln, config, secret, license
Args:
value: The scanner list value (comma-separated)
input_name: The input name for error messages
Returns:
True if valid, False otherwise
Examples:
Valid: "vuln,config,secret", "vuln", "config,license"
Invalid: "invalid", "vuln,invalid,config", "vuln,,config"
"""
return self._validate_comma_separated_list(
value,
input_name,
valid_items=["vuln", "config", "secret", "license"],
item_name="scanner",
)
def _validate_exit_code_list(self, value: str, input_name: str) -> bool:
"""Validate comma-separated list of exit codes.
Validates Unix/Linux exit codes (0-255) in comma-separated format.
Used for retry logic, success codes, and error handling.
Args:
value: The exit code list value (comma-separated integers)
input_name: The input name for error messages
Returns:
True if valid, False otherwise
Examples:
Valid: "0", "0,1,2", "5,10,15", "0,130", ""
Invalid: "256", "0,256", "-1", "0,abc", "0,,1"
"""
import re
if not value or value.strip() == "":
return True # Optional
# Split by comma and validate each exit code
codes = [code.strip() for code in value.split(",")]
for code in codes:
if not code: # Empty after strip
self.add_error(f"Invalid {input_name}: {value}. Contains empty exit code")
return False
# Check if code is numeric
if not re.match(r"^[0-9]+$", code):
self.add_error(
f"Invalid exit code '{code}' in {input_name}. "
f"Exit codes must be integers (0-255)"
)
return False
# Validate range (0-255 for Unix/Linux exit codes)
code_int = int(code)
if code_int < 0 or code_int > 255:
self.add_error(
f"Invalid exit code '{code}' in {input_name}. Exit codes must be in range 0-255"
)
return False
return True
def _validate_key_value_list(
self,
value: str,
input_name: str,
key_pattern: str | None = None,
check_injection: bool = True,
) -> bool:
"""Validate comma-separated list of key-value pairs (generic validator).
Validates KEY=VALUE,KEY2=VALUE2 format commonly used for Docker build-args,
environment variables, and other configuration parameters.
Args:
value: The key-value list value (comma-separated KEY=VALUE pairs)
input_name: The input name for error messages
key_pattern: Regex pattern for key validation (default: alphanumeric+underscores+hyphens)
check_injection: Whether to check for shell injection patterns in values (default: True)
Returns:
True if valid, False otherwise
Examples:
Valid: "KEY=value", "KEY1=value1,KEY2=value2", "BUILD_ARG=hello", ""
Invalid: "KEY", "=value", "KEY=", "KEY=value,", "KEY=val;whoami"
"""
import re
if not value or value.strip() == "":
return True # Optional
if key_pattern is None:
# Default: alphanumeric, underscores, hyphens (common for env vars and build args)
key_pattern = r"^[a-zA-Z0-9_-]+$"
# Security check for injection patterns in the entire value
if check_injection and re.search(r"[;&|`$()]", value):
self.add_error(
f"Potential injection detected in {input_name}: {value}. "
f"Avoid using shell metacharacters (;, &, |, `, $, parentheses)"
)
return False
# Split by comma and validate each key-value pair
pairs = [pair.strip() for pair in value.split(",")]
for pair in pairs:
if not pair: # Empty after strip
self.add_error(f"Invalid {input_name}: {value}. Contains empty key-value pair")
return False
# Check for KEY=VALUE format
if "=" not in pair:
self.add_error(
f"Invalid key-value pair '{pair}' in {input_name}. Expected format: KEY=VALUE"
)
return False
# Split by first = only (value may contain =)
parts = pair.split("=", 1)
key = parts[0].strip()
# Validate key is not empty
if not key:
self.add_error(
f"Invalid key-value pair '{pair}' in {input_name}. Key cannot be empty"
)
return False
# Validate key pattern
if not re.match(key_pattern, key):
self.add_error(
f"Invalid key '{key}' in {input_name}. "
f"Keys must be alphanumeric with underscores/hyphens"
)
return False
# Note: Value can be empty (KEY=) - this is valid for some use cases
# Value validation is optional and handled by the check_injection flag above
return True
def _validate_path_list(
self,
value: str,
input_name: str,
allow_glob: bool = True,
check_injection: bool = True,
) -> bool:
"""Validate comma-separated list of file paths or glob patterns (generic validator).
Validates file paths and glob patterns commonly used for ignore-paths,
restore-keys, file-pattern, and other path-based inputs.
Args:
value: The path list to validate
input_name: Name of the input being validated
allow_glob: Whether to allow glob patterns (*, **, ?, [])
check_injection: Whether to check for shell injection patterns
Examples:
Valid: "*.js", "src/**/*.ts", "dist/,build/", ".github/workflows/*", ""
Invalid: "../etc/passwd", "file;rm -rf /", "path|whoami"
Returns:
bool: True if valid, False otherwise
"""
import re
if not value or value.strip() == "":
return True # Optional
# Security check for injection patterns
if check_injection and re.search(r"[;&|`$()]", value):
self.add_error(
f"Potential injection detected in {input_name}: {value}. "
f"Avoid using shell metacharacters (;, &, |, `, $, parentheses)"
)
return False
# Split by comma and validate each path
paths = [path.strip() for path in value.split(",")]
for path in paths:
if not path: # Empty after strip
self.add_error(f"Invalid {input_name}: {value}. Contains empty path")
return False
# Check for path traversal attempts
if "../" in path or "/.." in path or path.startswith(".."):
self.add_error(
f"Path traversal detected in {input_name}: {path}. Avoid using '..' in paths"
)
return False
# Validate glob patterns if allowed
if allow_glob:
# Glob patterns are valid: *, **, ?, [], {}
# Check for valid glob characters
glob_pattern = r"^[a-zA-Z0-9_\-./\*\?\[\]\{\},@~+]+$"
if not re.match(glob_pattern, path):
self.add_error(
f"Invalid path '{path}' in {input_name}. "
f"Paths may contain alphanumeric characters, hyphens, underscores, "
f"slashes, and glob patterns (*, **, ?, [], {{}})"
)
return False
else:
# No glob patterns allowed - only alphanumeric, hyphens, underscores, slashes
path_pattern = r"^[a-zA-Z0-9_\-./,@~+]+$"
if not re.match(path_pattern, path):
self.add_error(
f"Invalid path '{path}' in {input_name}. "
f"Paths may only contain alphanumeric characters, hyphens, "
f"underscores, and slashes"
)
return False
return True
def _validate_network_mode(self, value: str, input_name: str) -> bool:
"""Validate Docker network mode enum.
Wrapper for multi_value_enum validator with Docker network mode options.
Examples:
Valid: "host", "none", "default", ""
Invalid: "bridge", "NONE", "custom"
Returns:
bool: True if valid, False otherwise
"""
return self._validate_multi_value_enum(
value,
input_name,
valid_values=["host", "none", "default"],
case_sensitive=True,
)
def _validate_language_enum(self, value: str, input_name: str) -> bool:
"""Validate language enum for version detection.
Wrapper for multi_value_enum validator with supported language options.
Examples:
Valid: "php", "python", "go", "dotnet", ""
Invalid: "node", "ruby", "PHP"
Returns:
bool: True if valid, False otherwise
"""
return self._validate_multi_value_enum(
value,
input_name,
valid_values=["php", "python", "go", "dotnet"],
case_sensitive=True,
)
def _validate_framework_mode(self, value: str, input_name: str) -> bool:
"""Validate PHP framework detection mode.
Wrapper for multi_value_enum validator with framework mode options.
Examples:
Valid: "auto", "laravel", "generic", ""
Invalid: "symfony", "Auto", "LARAVEL"
Returns:
bool: True if valid, False otherwise
"""
return self._validate_multi_value_enum(
value,
input_name,
valid_values=["auto", "laravel", "generic"],
case_sensitive=True,
)
def _validate_json_format(self, value: str, input_name: str) -> bool:
"""Validate JSON format string.
Validates that input is valid JSON. Used for structured configuration
data like platform-specific build arguments.
Examples:
Valid: '{"key":"value"}', '[]', '{"platforms":["linux/amd64"]}', ""
Invalid: '{invalid}', 'not json', '{key:value}'
Returns:
bool: True if valid, False otherwise
"""
import json
if not value or value.strip() == "":
return True # Optional
try:
json.loads(value)
return True
except json.JSONDecodeError as e:
self.add_error(f"Invalid JSON format in {input_name}: {value}. Error: {str(e)}")
return False
except Exception as e:
self.add_error(f"Failed to validate JSON in {input_name}: {str(e)}")
return False
def _validate_cache_config(self, value: str, input_name: str) -> bool:
"""Validate Docker BuildKit cache configuration.
Validates Docker cache export/import configuration format.
Common formats: type=registry,ref=..., type=local,dest=..., type=gha
Examples:
Valid: "type=registry,ref=user/repo:cache", "type=local,dest=/tmp/cache",
"type=gha", "type=inline", ""
Invalid: "invalid", "type=", "registry", "type=unknown"
Returns:
bool: True if valid, False otherwise
"""
import re
if not value or value.strip() == "":
return True # Optional
# Check basic format: type=value[,key=value,...]
if not re.match(r"^type=[a-z0-9-]+", value):
self.add_error(
f"Invalid cache config in {input_name}: {value}. "
f"Must start with 'type=<cache-type>'"
)
return False
# Valid cache types
valid_types = ["registry", "local", "gha", "inline", "s3", "azblob", "oci"]
# Extract type
type_match = re.match(r"^type=([a-z0-9-]+)", value)
if type_match:
cache_type = type_match.group(1)
if cache_type not in valid_types:
self.add_error(
f"Invalid cache type '{cache_type}' in {input_name}. "
f"Valid types: {', '.join(valid_types)}"
)
return False
# Validate key=value pairs format
parts = value.split(",")
for part in parts:
if "=" not in part:
self.add_error(
f"Invalid cache config format in {input_name}: {value}. "
f"Each part must be in 'key=value' format"
)
return False
return True