Compare commits

..

40 Commits

Author SHA1 Message Date
renovate[bot]
56ff9a511c chore(deps): update oven-sh/setup-bun action (v2.0.2 → v2.1.0) (#416)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-07 10:02:49 +02:00
renovate[bot]
81310f9bd7 chore(deps): update oxsecurity/megalinter action (v9.2.0 → v9.3.0) (#417)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-07 10:02:20 +02:00
renovate[bot]
95b8856c3f chore(deps): update pre-commit hook astral-sh/uv-pre-commit (0.9.18 → 0.9.22) (#418)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-07 10:01:16 +02:00
renovate[bot]
e69ddbc1e2 chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.496 → 3.2.497) (#419)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-07 10:00:55 +02:00
renovate[bot]
28e81adc2b chore(deps): update pre-commit hook rhysd/actionlint (v1.7.9 → v1.7.10) (#420)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-07 10:00:39 +02:00
renovate[bot]
fb25736f7e chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.495 → 3.2.496) (#414)
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-30 08:39:35 +02:00
54886c3fd5 fix: validate-inputs actions pip installation step (#413) 2025-12-24 13:29:18 +02:00
renovate[bot]
fd030b418f chore(deps): update stefanzweifel/git-auto-commit-action action (v7.0.0 → v7.1.0) (#411)
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-23 13:56:47 +02:00
96c305c557 refactor: centralize validation logic with validate_with helper (#412)
* chore: sonarcloud fixes

* chore: coderabbit cr fixes
2025-12-23 13:29:37 +02:00
renovate[bot]
5b4e9c8e11 chore(deps): update docker/setup-buildx-action action (v3.11.1 → v3.12.0) (#410)
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-22 07:42:23 +02:00
renovate[bot]
2d0bff84ad chore(deps): update github/issue-metrics action (v3.25.4 → v3.25.5) (#407)
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-22 07:40:20 +02:00
renovate[bot]
98f260793c chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.14.9 → v0.14.10) (#408)
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-22 07:39:39 +02:00
renovate[bot]
09ae7517d6 chore(deps): update pre-commit hook astral-sh/uv-pre-commit (0.9.17 → 0.9.18) (#409)
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-22 07:39:17 +02:00
renovate[bot]
61ebe619a8 chore(deps): update github/codeql-action action (v4.31.8 → v4.31.9) (#406)
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-22 07:38:46 +02:00
renovate[bot]
a1d55ac125 chore(deps)!: update peter-evans/create-pull-request (v7.0.11 → v8.0.0) (#405)
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-22 03:16:24 +02:00
renovate[bot]
db86bb2f0d chore(deps)!: update actions/download-artifact (v6.0.0 → v7.0.0) (#396) 2025-12-17 23:36:39 +02:00
renovate[bot]
5e7b2fbc11 chore(deps)!: update actions/upload-artifact (v5.0.0 → v6.0.0) (#397) 2025-12-17 23:35:53 +02:00
renovate[bot]
43126631c2 chore(deps)!: update actions/cache (v4.3.0 → v5.0.1) (#395) 2025-12-17 23:35:26 +02:00
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
52 changed files with 617 additions and 1059 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

@@ -48,7 +48,7 @@ jobs:
- name: Notify on Critical Issues
if: failure() && steps.security-scan.outputs.critical_issues != '0'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |-
const { repo, owner } = context.repo;

View File

@@ -38,7 +38,7 @@ jobs:
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log in to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
@@ -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

@@ -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@67526e7bd8100b870f10b1c120780a8375777b43 # v3.25.5
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:ivuorinen/actions is:issue created:${{ env.last_month }} -reason:"not planned"'

View File

@@ -74,14 +74,14 @@ jobs:
- 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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: megalinter-reports/sarif
category: megalinter
- name: Check Results
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const status = '${{ steps.pr-lint.outputs.validation_status }}';

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

@@ -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,14 +73,14 @@ jobs:
if: always()
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
if: always() && hashFiles('_tests/reports/test-results.sarif') != ''
with:
sarif_file: _tests/reports/test-results.sarif
category: github-actions-tests
- name: Upload unit test results
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: always()
with:
name: unit-test-results
@@ -133,7 +133,7 @@ jobs:
fi
- name: Upload integration test results
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: always() && steps.check-integration-reports.outputs.reports-found == 'true'
with:
name: integration-test-results
@@ -167,7 +167,7 @@ jobs:
run: make test-coverage
- name: Upload coverage report
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: coverage-report
path: _tests/coverage/
@@ -263,7 +263,7 @@ jobs:
steps:
- name: Download test results
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: '*-test-results'
merge-multiple: true

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@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}'
@@ -76,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();

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.22
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.10
hooks:
# Run the linter with auto-fix
- id: ruff-check
@@ -78,24 +78,19 @@ repos:
exclude: '^_tests/.*\.sh$'
- repo: https://github.com/rhysd/actionlint
rev: v1.7.9
rev: v1.7.10
hooks:
- 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'
rev: '3.2.497'
hooks:
- id: checkov
args:
- '--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

@@ -21,6 +21,9 @@ import sys
import yaml # pylint: disable=import-error
# Default value for unknown action names (matches shared.validation_core.DEFAULT_UNKNOWN)
_DEFAULT_UNKNOWN = "Unknown"
class ActionValidator:
"""Handles validation of GitHub Action inputs using Python regex engine."""
@@ -86,7 +89,7 @@ class ActionValidator:
return True, ""
# Check for environment variable reference (e.g., $GITHUB_TOKEN)
if re.match(r"^\$[A-Za-z_][A-Za-z0-9_]*$", token):
if re.match(r"^\$[A-Za-z_]\w*$", token, re.ASCII):
return True, ""
# Check against all known token patterns
@@ -330,16 +333,16 @@ def get_action_name(action_file: str) -> str:
action_file: Path to the action.yml file
Returns:
Action name or "Unknown" if not found
Action name or _DEFAULT_UNKNOWN if not found
"""
try:
with Path(action_file).open(encoding="utf-8") as f:
data = yaml.safe_load(f)
return data.get("name", "Unknown")
return data.get("name", _DEFAULT_UNKNOWN)
except Exception:
return "Unknown"
return _DEFAULT_UNKNOWN
def _show_usage():

View File

@@ -25,6 +25,9 @@ from typing import Any
import yaml # pylint: disable=import-error
# Default value for unknown items (used by ActionFileParser)
DEFAULT_UNKNOWN = "Unknown"
class ValidationCore:
"""Core validation functionality with standardized patterns and functions."""
@@ -497,9 +500,9 @@ class ActionFileParser:
"""Get the action name from an action.yml file."""
try:
data = ActionFileParser.load_action_file(action_file)
return data.get("name", "Unknown")
return data.get("name", DEFAULT_UNKNOWN)
except (OSError, ValueError, yaml.YAMLError, AttributeError):
return "Unknown"
return DEFAULT_UNKNOWN
@staticmethod
def get_action_inputs(action_file: str) -> list[str]:

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
@@ -122,7 +122,7 @@ runs:
- name: Commit Fixes
if: steps.check-files.outputs.files_found == 'true'
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style: apply ansible lint fixes'
commit_user_name: ${{ inputs.username }}
@@ -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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
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
@@ -212,13 +212,13 @@ runs:
- name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
with:
bun-version: latest
- name: Cache Node Dependencies
id: cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: node_modules
key: ${{ runner.os }}-biome-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
@@ -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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: biome-report.sarif
@@ -365,7 +365,7 @@ runs:
- name: Commit and Push Fixes
if: inputs.mode == 'fix' && success()
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style: autofix Biome violations'
commit_user_name: ${{ inputs.username }}

View File

@@ -81,21 +81,13 @@ class CustomValidator(BaseValidator):
# Validate threads
if inputs.get("threads"):
result = self.codeql_validator.validate_threads(inputs["threads"])
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
valid &= result
valid &= self.validate_with(
self.codeql_validator, "validate_threads", inputs["threads"]
)
# Validate RAM
if inputs.get("ram"):
result = self.codeql_validator.validate_ram(inputs["ram"])
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
valid &= result
valid &= self.validate_with(self.codeql_validator, "validate_ram", inputs["ram"])
# Validate debug mode
if inputs.get("debug"):
@@ -226,19 +218,10 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Check for empty queries first
if not queries or not queries.strip():
self.add_error("CodeQL queries cannot be empty")
return False
# Use the CodeQL validator
result = self.codeql_validator.validate_codeql_queries(queries)
# Copy any errors from codeql validator
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
return result
return self.validate_with(self.codeql_validator, "validate_codeql_queries", queries)
def validate_categories(self, categories: str) -> bool:
"""Validate CodeQL categories.
@@ -249,14 +232,7 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Use the CodeQL validator
result = self.codeql_validator.validate_category_format(categories)
# Copy any errors from codeql validator
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
return result
return self.validate_with(self.codeql_validator, "validate_category_format", categories)
def validate_category(self, category: str) -> bool:
"""Validate CodeQL category (singular).
@@ -267,14 +243,7 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Use the CodeQL validator
result = self.codeql_validator.validate_category_format(category)
# Copy any errors from codeql validator
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
return result
return self.validate_with(self.codeql_validator, "validate_category_format", category)
def validate_config_file(self, config_file: str) -> bool:
"""Validate CodeQL configuration file path.
@@ -287,21 +256,11 @@ class CustomValidator(BaseValidator):
"""
if not config_file or not config_file.strip():
return True
# Allow GitHub Actions expressions
if self.is_github_expression(config_file):
return True
# Use FileValidator for yaml file validation
result = self.file_validator.validate_yaml_file(config_file, "config-file")
# Copy any errors from file validator
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
return result
return self.validate_with(
self.file_validator, "validate_yaml_file", config_file, "config-file"
)
def validate_database(self, database: str) -> bool:
"""Validate CodeQL database path.
@@ -312,25 +271,13 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(database):
return True
# Use FileValidator for path validation
result = self.file_validator.validate_file_path(database, "database")
# Copy any errors from file validator
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
result = self.validate_with(self.file_validator, "validate_file_path", database, "database")
# Database paths often contain the language
# e.g., "codeql-database/javascript" or "/tmp/codeql_databases/python"
# Just validate it's a reasonable path after basic validation
if result and database.startswith("/tmp/"): # noqa: S108
return True
return result
def validate_debug(self, debug: str) -> bool:
@@ -342,20 +289,9 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(debug):
return True
# Use BooleanValidator
result = self.boolean_validator.validate_boolean(debug, "debug")
# Copy any errors from boolean validator
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
return result
return self.validate_with(self.boolean_validator, "validate_boolean", debug, "debug")
def validate_upload_database(self, upload: str) -> bool:
"""Validate upload-database setting.
@@ -366,20 +302,11 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(upload):
return True
# Use BooleanValidator
result = self.boolean_validator.validate_boolean(upload, "upload-database")
# Copy any errors from boolean validator
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
return result
return self.validate_with(
self.boolean_validator, "validate_boolean", upload, "upload-database"
)
def validate_upload_sarif(self, upload: str) -> bool:
"""Validate upload-sarif setting.
@@ -390,20 +317,11 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(upload):
return True
# Use BooleanValidator
result = self.boolean_validator.validate_boolean(upload, "upload-sarif")
# Copy any errors from boolean validator
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
return result
return self.validate_with(
self.boolean_validator, "validate_boolean", upload, "upload-sarif"
)
def validate_packs(self, packs: str) -> bool:
"""Validate CodeQL packs.
@@ -487,16 +405,9 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Use the TokenValidator for proper validation
result = self.token_validator.validate_github_token(token, required=False)
# Copy any errors from token validator
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
return result
return self.validate_with(
self.token_validator, "validate_github_token", token, required=False
)
def validate_token(self, token: str) -> bool:
"""Validate GitHub token.
@@ -507,21 +418,12 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Check for empty token
if not token or not token.strip():
self.add_error("Input 'token' is missing or empty")
return False
# Use the TokenValidator for proper validation
result = self.token_validator.validate_github_token(token, required=True)
# Copy any errors from token validator
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
return result
return self.validate_with(
self.token_validator, "validate_github_token", token, required=True
)
def validate_working_directory(self, directory: str) -> bool:
"""Validate working directory path.
@@ -532,20 +434,11 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(directory):
return True
# Use FileValidator for path validation
result = self.file_validator.validate_file_path(directory, "working-directory")
# Copy any errors from file validator
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
return result
return self.validate_with(
self.file_validator, "validate_file_path", directory, "working-directory"
)
def validate_upload_results(self, value: str) -> bool:
"""Validate upload-results boolean value.
@@ -556,27 +449,14 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Check for empty
if not value or not value.strip():
self.add_error("upload-results cannot be empty")
return False
# Allow GitHub Actions expressions
if self.is_github_expression(value):
return True
# Check for uppercase TRUE/FALSE first
if value in ["TRUE", "FALSE"]:
self.add_error("Must be lowercase 'true' or 'false'")
return False
# Use BooleanValidator for normal validation
result = self.boolean_validator.validate_boolean(value, "upload-results")
# Copy any errors from boolean validator
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
return result
return self.validate_with(
self.boolean_validator, "validate_boolean", value, "upload-results"
)

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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
category: ${{ steps.set-category.outputs.category }}
upload: ${{ inputs.upload-results }}

View File

@@ -36,47 +36,35 @@ class CustomValidator(BaseValidator):
# Validate optional inputs
if inputs.get("image-quality"):
result = self.numeric_validator.validate_numeric_range(
inputs["image-quality"], min_val=0, max_val=100
valid &= self.validate_with(
self.numeric_validator,
"validate_numeric_range",
inputs["image-quality"],
min_val=0,
max_val=100,
)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
if not result:
valid = False
if inputs.get("png-quality"):
result = self.numeric_validator.validate_numeric_range(
inputs["png-quality"], min_val=0, max_val=100
valid &= self.validate_with(
self.numeric_validator,
"validate_numeric_range",
inputs["png-quality"],
min_val=0,
max_val=100,
)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
if not result:
valid = False
if inputs.get("directory"):
result = self.file_validator.validate_file_path(inputs["directory"], "directory")
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.file_validator, "validate_file_path", inputs["directory"], "directory"
)
if inputs.get("ignore-paths"):
# Validate for injection
result = self.security_validator.validate_no_injection(
inputs["ignore-paths"], "ignore-paths"
valid &= self.validate_with(
self.security_validator,
"validate_no_injection",
inputs["ignore-paths"],
"ignore-paths",
)
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
return valid

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@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
token: ${{ inputs.token }}
title: 'chore: compress images'

View File

@@ -203,7 +203,7 @@ runs:
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: csharp-test-results
path: |

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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: dotnet-format.sarif

View File

@@ -65,35 +65,24 @@ class CustomValidator(BaseValidator):
# Validate image name
if inputs.get("image-name"):
result = self.docker_validator.validate_image_name(inputs["image-name"], "image-name")
# Propagate errors from docker validator
for error in self.docker_validator.errors:
if error not in self.errors:
self.add_error(error)
self.docker_validator.clear_errors()
valid &= result
valid &= self.validate_with(
self.docker_validator, "validate_image_name", inputs["image-name"], "image-name"
)
# Validate tag (singular - as per action.yml)
if inputs.get("tag"):
result = self.docker_validator.validate_docker_tag(inputs["tag"], "tag")
# Propagate errors
for error in self.docker_validator.errors:
if error not in self.errors:
self.add_error(error)
self.docker_validator.clear_errors()
valid &= result
valid &= self.validate_with(
self.docker_validator, "validate_docker_tag", inputs["tag"], "tag"
)
# Validate architectures/platforms
if inputs.get("architectures"):
result = self.docker_validator.validate_architectures(
inputs["architectures"], "architectures"
valid &= self.validate_with(
self.docker_validator,
"validate_architectures",
inputs["architectures"],
"architectures",
)
# Propagate errors
for error in self.docker_validator.errors:
if error not in self.errors:
self.add_error(error)
self.docker_validator.clear_errors()
valid &= result
# Validate build arguments
if inputs.get("build-args"):
@@ -101,12 +90,9 @@ class CustomValidator(BaseValidator):
# Validate push flag
if inputs.get("push"):
result = self.boolean_validator.validate_optional_boolean(inputs["push"], "push")
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
valid &= result
valid &= self.validate_with(
self.boolean_validator, "validate_optional_boolean", inputs["push"], "push"
)
# Validate cache settings
if inputs.get("cache-from"):
@@ -117,22 +103,35 @@ class CustomValidator(BaseValidator):
# Validate cache-mode
if inputs.get("cache-mode"):
valid &= self.validate_cache_mode(inputs["cache-mode"])
valid &= self.validate_enum(
inputs["cache-mode"],
"cache-mode",
["min", "max", "inline"],
case_sensitive=True,
)
# Validate buildx-version
if inputs.get("buildx-version"):
valid &= self.validate_buildx_version(inputs["buildx-version"])
version = inputs["buildx-version"]
# Allow 'latest' as special value
if version != "latest" and not self.is_github_expression(version):
valid &= self.validate_with(
self.version_validator,
"validate_semantic_version",
version,
"buildx-version",
)
# Validate parallel-builds
if inputs.get("parallel-builds"):
result = self.numeric_validator.validate_numeric_range(
inputs["parallel-builds"], min_val=0, max_val=16, name="parallel-builds"
valid &= self.validate_with(
self.numeric_validator,
"validate_numeric_range",
inputs["parallel-builds"],
min_val=0,
max_val=16,
name="parallel-builds",
)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
valid &= result
# Validate boolean flags
for bool_input in [
@@ -144,29 +143,32 @@ class CustomValidator(BaseValidator):
"auto-detect-platforms",
]:
if inputs.get(bool_input):
result = self.boolean_validator.validate_optional_boolean(
inputs[bool_input], bool_input
valid &= self.validate_with(
self.boolean_validator,
"validate_optional_boolean",
inputs[bool_input],
bool_input,
)
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
valid &= result
# Validate sbom-format
if inputs.get("sbom-format"):
valid &= self.validate_sbom_format(inputs["sbom-format"])
valid &= self.validate_enum(
inputs["sbom-format"],
"sbom-format",
["spdx-json", "cyclonedx-json", "syft-json"],
case_sensitive=True,
)
# Validate max-retries
if inputs.get("max-retries"):
result = self.numeric_validator.validate_numeric_range(
inputs["max-retries"], min_val=0, max_val=10, name="max-retries"
valid &= self.validate_with(
self.numeric_validator,
"validate_numeric_range",
inputs["max-retries"],
min_val=0,
max_val=10,
name="max-retries",
)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
valid &= result
return valid
@@ -209,19 +211,11 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(dockerfile):
return True
# Use file validator for path validation
result = self.file_validator.validate_file_path(dockerfile, "dockerfile")
# Propagate errors
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
return result
return self.validate_with(
self.file_validator, "validate_file_path", dockerfile, "dockerfile"
)
def validate_context(self, context: str) -> bool:
"""Validate build context path.
@@ -245,10 +239,9 @@ class CustomValidator(BaseValidator):
# We allow path traversal for context as Docker needs to access parent directories
# Only check for command injection patterns like ; | ` $()
dangerous_chars = [";", "|", "`", "$(", "&&", "||"]
for char in dangerous_chars:
if char in context:
self.add_error(f"Command injection detected in context: {context}")
return False
if any(char in context for char in dangerous_chars):
self.add_error(f"Command injection detected in context: {context}")
return False
return True
@@ -261,15 +254,9 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Use docker validator for architectures
result = self.docker_validator.validate_architectures(platforms, "platforms")
# Propagate errors
for error in self.docker_validator.errors:
if error not in self.errors:
self.add_error(error)
self.docker_validator.clear_errors()
return result
return self.validate_with(
self.docker_validator, "validate_architectures", platforms, "platforms"
)
def validate_build_args(self, build_args: str) -> bool:
"""Validate build arguments.
@@ -353,78 +340,3 @@ class CustomValidator(BaseValidator):
# Check for security issues
return self.validate_security_patterns(cache_to, "cache-to")
def validate_cache_mode(self, cache_mode: str) -> bool:
"""Validate cache mode.
Args:
cache_mode: Cache mode value
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(cache_mode):
return True
# Valid cache modes
valid_modes = ["min", "max", "inline"]
if cache_mode.lower() not in valid_modes:
self.add_error(f"Invalid cache-mode: {cache_mode}. Must be one of: min, max, inline")
return False
return True
def validate_buildx_version(self, version: str) -> bool:
"""Validate buildx version.
Args:
version: Buildx version
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(version):
return True
# Allow 'latest'
if version == "latest":
return True
# Check for security issues (semicolon injection etc)
if not self.validate_security_patterns(version, "buildx-version"):
return False
# Basic version format validation (e.g., 0.12.0, v0.12.0)
import re
if not re.match(r"^v?\d+\.\d+(\.\d+)?$", version):
self.add_error(f"Invalid buildx-version format: {version}")
return False
return True
def validate_sbom_format(self, sbom_format: str) -> bool:
"""Validate SBOM format.
Args:
sbom_format: SBOM format value
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(sbom_format):
return True
# Valid SBOM formats
valid_formats = ["spdx-json", "cyclonedx-json", "syft-json"]
if sbom_format.lower() not in valid_formats:
self.add_error(
f"Invalid sbom-format: {sbom_format}. "
"Must be one of: spdx-json, cyclonedx-json, syft-json"
)
return False
return True

View File

@@ -175,7 +175,7 @@ runs:
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
with:
version: ${{ inputs.buildx-version }}
platforms: ${{ inputs.architectures }}

View File

@@ -11,6 +11,7 @@ This validator handles Docker publish-specific validation including:
from __future__ import annotations
from pathlib import Path
import re
import sys
# Add validate-inputs directory to path to import validators
@@ -58,12 +59,9 @@ class CustomValidator(BaseValidator):
# Validate platforms
if inputs.get("platforms"):
result = self.docker_validator.validate_architectures(inputs["platforms"], "platforms")
for error in self.docker_validator.errors:
if error not in self.errors:
self.add_error(error)
self.docker_validator.clear_errors()
valid &= result
valid &= self.validate_with(
self.docker_validator, "validate_architectures", inputs["platforms"], "platforms"
)
# Validate boolean flags
for bool_input in [
@@ -74,18 +72,18 @@ class CustomValidator(BaseValidator):
"verbose",
]:
if inputs.get(bool_input):
result = self.boolean_validator.validate_optional_boolean(
inputs[bool_input], bool_input
valid &= self.validate_with(
self.boolean_validator,
"validate_optional_boolean",
inputs[bool_input],
bool_input,
)
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
valid &= result
# Validate cache-mode
if inputs.get("cache-mode"):
valid &= self.validate_cache_mode(inputs["cache-mode"])
valid &= self.validate_enum(
inputs["cache-mode"], "cache-mode", ["min", "max", "inline"]
)
# Validate buildx-version
if inputs.get("buildx-version"):
@@ -96,24 +94,18 @@ class CustomValidator(BaseValidator):
valid &= self.validate_username(inputs["dockerhub-username"])
if inputs.get("dockerhub-password"):
# Use token validator for password/token
result = self.token_validator.validate_docker_token(
inputs["dockerhub-password"], "dockerhub-password"
valid &= self.validate_with(
self.token_validator,
"validate_docker_token",
inputs["dockerhub-password"],
"dockerhub-password",
)
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
valid &= result
# Validate github-token
if inputs.get("github-token"):
result = self.token_validator.validate_github_token(inputs["github-token"])
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
valid &= result
valid &= self.validate_with(
self.token_validator, "validate_github_token", inputs["github-token"]
)
return valid
@@ -156,40 +148,7 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(registry):
return True
# Valid registry values according to action description
valid_registries = ["dockerhub", "github", "both"]
if registry.lower() not in valid_registries:
self.add_error(
f"Invalid registry: {registry}. Must be one of: dockerhub, github, or both"
)
return False
return True
def validate_cache_mode(self, cache_mode: str) -> bool:
"""Validate cache mode.
Args:
cache_mode: Cache mode value
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(cache_mode):
return True
# Valid cache modes
valid_modes = ["min", "max", "inline"]
if cache_mode.lower() not in valid_modes:
self.add_error(f"Invalid cache-mode: {cache_mode}. Must be one of: min, max, inline")
return False
return True
return self.validate_enum(registry, "registry", ["dockerhub", "github", "both"])
def validate_buildx_version(self, version: str) -> bool:
"""Validate buildx version.
@@ -213,8 +172,6 @@ class CustomValidator(BaseValidator):
return False
# Basic version format validation
import re
if not re.match(r"^v?\d+\.\d+(\.\d+)?$", version):
self.add_error(f"Invalid buildx-version format: {version}")
return False
@@ -244,8 +201,6 @@ class CustomValidator(BaseValidator):
return False
# Docker Hub username rules: lowercase letters, digits, periods, hyphens, underscores
import re
if not re.match(r"^[a-z0-9._-]+$", username.lower()):
self.add_error(f"Invalid Docker Hub username format: {username}")
return False

View File

@@ -202,7 +202,7 @@ runs:
printf '%s\n' "Input validation completed successfully"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Determine Image Names and Tags
id: meta

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
@@ -319,13 +319,13 @@ runs:
- name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
with:
bun-version: latest
- name: Cache Node Dependencies
id: cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: node_modules
key: ${{ runner.os }}-eslint-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
@@ -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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: ${{ inputs.working-directory }}/eslint-results.sarif
@@ -508,7 +508,7 @@ runs:
- name: Commit and Push Fixes
if: inputs.mode == 'fix' && success()
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style: autofix ESLint violations'
commit_user_name: ${{ inputs.username }}

View File

@@ -253,7 +253,7 @@ runs:
- name: Upload Build Artifacts
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: go-build-artifacts
path: |

View File

@@ -37,105 +37,78 @@ class CustomValidator(BaseValidator):
# Validate working-directory if provided
if inputs.get("working-directory"):
result = self.file_validator.validate_file_path(
inputs["working-directory"], "working-directory"
valid &= self.validate_with(
self.file_validator,
"validate_file_path",
inputs["working-directory"],
"working-directory",
)
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
valid = False
# Validate golangci-lint-version if provided
if inputs.get("golangci-lint-version"):
value = inputs["golangci-lint-version"]
# Accept 'latest' or version format
if value != "latest" and not self.is_github_expression(value):
result = self.version_validator.validate_semantic_version(
value, "golangci-lint-version"
valid &= self.validate_with(
self.version_validator,
"validate_semantic_version",
value,
"golangci-lint-version",
)
for error in self.version_validator.errors:
if error not in self.errors:
self.add_error(error)
self.version_validator.clear_errors()
if not result:
valid = False
# Validate go-version if provided
if inputs.get("go-version"):
value = inputs["go-version"]
# Accept 'stable', 'oldstable' or version format
if value not in ["stable", "oldstable"] and not self.is_github_expression(value):
result = self.version_validator.validate_go_version(value, "go-version")
for error in self.version_validator.errors:
if error not in self.errors:
self.add_error(error)
self.version_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.version_validator, "validate_go_version", value, "go-version"
)
# Validate config-file if provided
if inputs.get("config-file"):
result = self.file_validator.validate_file_path(inputs["config-file"], "config-file")
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.file_validator, "validate_file_path", inputs["config-file"], "config-file"
)
# Validate timeout if provided
if inputs.get("timeout"):
value = inputs["timeout"]
# Validate timeout format (e.g., 5m, 1h, 30s)
if not self.is_github_expression(value):
timeout_pattern = r"^\d+[smh]$"
if not re.match(timeout_pattern, value):
self.add_error(
f"Invalid timeout format: {value}. Expected format like '5m', '1h', '30s'"
)
valid = False
if not self.is_github_expression(value) and not re.match(r"^\d+[smh]$", value):
self.add_error(
f"Invalid timeout format: {value}. Expected format like '5m', '1h', '30s'"
)
valid = False
# Validate boolean inputs
for field in ["cache", "fail-on-error", "only-new-issues", "disable-all"]:
if inputs.get(field):
result = self.boolean_validator.validate_boolean(inputs[field], field)
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.boolean_validator, "validate_boolean", inputs[field], field
)
# Validate report-format
if inputs.get("report-format"):
value = inputs["report-format"]
valid_formats = ["json", "sarif", "github-actions", "colored-line-number", "tab"]
if value not in valid_formats and not self.is_github_expression(value):
self.add_error(
f"Invalid report format: {value}. Must be one of: {', '.join(valid_formats)}"
)
valid = False
valid &= self.validate_enum(
inputs["report-format"],
"report-format",
["json", "sarif", "github-actions", "colored-line-number", "tab"],
case_sensitive=True,
)
# Validate max-retries
if inputs.get("max-retries"):
result = self.numeric_validator.validate_numeric_range(
inputs["max-retries"], min_val=1, max_val=10, name="max-retries"
valid &= self.validate_with(
self.numeric_validator,
"validate_numeric_range",
inputs["max-retries"],
min_val=1,
max_val=10,
name="max-retries",
)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
if not result:
valid = False
# Validate enable-linters and disable-linters
for field in ["enable-linters", "disable-linters"]:
if inputs.get(field):
value = inputs[field]
# First check format - must be comma-separated without spaces
if not self.is_github_expression(value):
if " " in value:
self.add_error(f"Invalid {field} format: spaces not allowed in linter list")
@@ -145,15 +118,9 @@ class CustomValidator(BaseValidator):
f"Invalid {field} format: must be comma-separated list of linters"
)
valid = False
# Then check for injection
result = self.security_validator.validate_no_injection(value, field)
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.security_validator, "validate_no_injection", value, field
)
return valid

View File

@@ -218,7 +218,7 @@ runs:
- name: Cache golangci-lint
id: cache
if: inputs.cache == 'true'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: |
~/.cache/golangci-lint
@@ -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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: ${{ inputs.working-directory }}/reports/golangci-lint.sarif
category: golangci-lint

View File

@@ -42,109 +42,40 @@ class CustomValidator(BaseValidator):
self.add_error("Input 'npm_token' is required")
valid = False
elif inputs["npm_token"]:
token = inputs["npm_token"]
# Check for NPM classic token format first
if token.startswith("npm_"):
# NPM classic token format: npm_ followed by 36+ alphanumeric characters
if not re.match(r"^npm_[a-zA-Z0-9]{36,}$", token):
self.add_error("Invalid NPM token format")
valid = False
# Also check for injection
result = self.security_validator.validate_no_injection(token, "npm_token")
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
else:
# Otherwise validate as GitHub token
result = self.token_validator.validate_github_token(token, required=True)
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
if not result:
valid = False
valid &= self._validate_npm_token(inputs["npm_token"])
# Validate registry-url
if inputs.get("registry-url"):
url = inputs["registry-url"]
if not self.is_github_expression(url):
# Must be http or https URL
if not url.startswith(("http://", "https://")):
self.add_error("Registry URL must use http or https protocol")
valid = False
else:
# Validate URL format
result = self.network_validator.validate_url(url, "registry-url")
for error in self.network_validator.errors:
if error not in self.errors:
self.add_error(error)
self.network_validator.clear_errors()
if not result:
valid = False
valid &= self._validate_registry_url(inputs["registry-url"])
# Validate scope
if inputs.get("scope"):
scope = inputs["scope"]
if not self.is_github_expression(scope):
# Scope must start with @ and contain only valid characters
if not scope.startswith("@"):
self.add_error("Scope must start with @ symbol")
valid = False
elif not re.match(r"^@[a-z0-9][a-z0-9\-_.]*$", scope):
self.add_error(
"Invalid scope format: must be @org-name with lowercase "
"letters, numbers, hyphens, dots, and underscores"
)
valid = False
# Check for injection
result = self.security_validator.validate_no_injection(scope, "scope")
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
valid &= self._validate_scope(inputs["scope"])
# Validate access
if inputs.get("access"):
access = inputs["access"]
if not self.is_github_expression(access):
valid_access = ["public", "restricted", "private"]
if access and access not in valid_access:
self.add_error(
f"Invalid access level: {access}. Must be one of: {', '.join(valid_access)}"
)
valid = False
valid &= self.validate_enum(
inputs["access"], "access", ["public", "restricted", "private"]
)
# Validate boolean inputs (only always-auth and include-merged-tags are strict)
for field in ["always-auth", "include-merged-tags"]:
if inputs.get(field):
result = self.boolean_validator.validate_boolean(inputs[field], field)
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.boolean_validator, "validate_boolean", inputs[field], field
)
# provenance and dry-run accept any value (npm handles them)
# No validation needed for these
# Validate package-version
if inputs.get("package-version"):
result = self.version_validator.validate_semantic_version(
inputs["package-version"], "package-version"
valid &= self.validate_with(
self.version_validator,
"validate_semantic_version",
inputs["package-version"],
"package-version",
)
for error in self.version_validator.errors:
if error not in self.errors:
self.add_error(error)
self.version_validator.clear_errors()
if not result:
valid = False
# Validate tag
if inputs.get("tag"):
@@ -161,16 +92,57 @@ class CustomValidator(BaseValidator):
# Validate working-directory and ignore-scripts as file paths
for field in ["working-directory", "ignore-scripts"]:
if inputs.get(field):
result = self.file_validator.validate_path(inputs[field], field)
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.file_validator, "validate_path", inputs[field], field
)
return valid
def _validate_npm_token(self, token: str) -> bool:
"""Validate NPM token format."""
# Check for NPM classic token format first
if token.startswith("npm_"):
# NPM classic token format: npm_ followed by 36+ alphanumeric characters
if not re.match(r"^npm_[a-zA-Z0-9]{36,}$", token):
self.add_error("Invalid NPM token format")
return False
# Also check for injection
return self.validate_with(
self.security_validator, "validate_no_injection", token, "npm_token"
)
# Otherwise validate as GitHub token
return self.validate_with(
self.token_validator, "validate_github_token", token, required=True
)
def _validate_registry_url(self, url: str) -> bool:
"""Validate registry URL format."""
if self.is_github_expression(url):
return True
# Must be http or https URL
if not url.startswith(("http://", "https://")):
self.add_error("Registry URL must use http or https protocol")
return False
# Validate URL format
return self.validate_with(self.network_validator, "validate_url", url, "registry-url")
def _validate_scope(self, scope: str) -> bool:
"""Validate NPM scope format."""
if self.is_github_expression(scope):
return True
# Scope must start with @ and contain only valid characters
if not scope.startswith("@"):
self.add_error("Scope must start with @ symbol")
return False
if not re.match(r"^@[a-z0-9][a-z0-9\-_.]*$", scope):
self.add_error(
"Invalid scope format: must be @org-name with lowercase "
"letters, numbers, hyphens, dots, and underscores"
)
return False
# Check for injection
return self.validate_with(self.security_validator, "validate_no_injection", scope, "scope")
def get_required_inputs(self) -> list[str]:
"""Get list of required inputs."""
return ["npm_token"]

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
@@ -152,13 +152,13 @@ runs:
- name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
with:
bun-version: latest
- name: Cache Node Dependencies
id: cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: node_modules
key: ${{ runner.os }}-npm-publish-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}

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

@@ -33,59 +33,31 @@ class CustomValidator(BaseValidator):
# Validate token (optional)
if inputs.get("token"):
token = inputs["token"]
result = self.token_validator.validate_github_token(token)
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(self.token_validator, "validate_github_token", token)
# Also check for variable expansion
if not self.is_github_expression(token):
result = self.security_validator.validate_no_injection(token, "token")
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.security_validator, "validate_no_injection", token, "token"
)
# Validate email (optional, empty means use default)
if "email" in inputs and inputs["email"] and inputs["email"] != "":
if inputs.get("email"):
email = inputs["email"]
result = self.network_validator.validate_email(email, "email")
for error in self.network_validator.errors:
if error not in self.errors:
self.add_error(error)
self.network_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(self.network_validator, "validate_email", email, "email")
# Also check for shell metacharacters (but allow @ and .)
if not self.is_github_expression(email):
# Only check for dangerous shell metacharacters, not @ or .
dangerous_chars = [";", "&", "|", "`", "$", "(", ")", "<", ">", "\n", "\r"]
for char in dangerous_chars:
if char in email:
self.add_error(f"email: Contains dangerous character '{char}'")
valid = False
break
if any(char in email for char in dangerous_chars):
self.add_error("email: Contains dangerous shell metacharacter")
valid = False
# Validate username (optional)
if inputs.get("username"):
username = inputs["username"]
if not self.is_github_expression(username):
# Check for injection
result = self.security_validator.validate_no_injection(username, "username")
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
# Check username length (GitHub usernames are max 39 characters)
valid &= self.validate_with(
self.security_validator, "validate_no_injection", username, "username"
)
if len(username) > 39:
self.add_error("Username is too long (max 39 characters)")
valid = False

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 }}
@@ -356,7 +356,7 @@ runs:
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: |
vendor

View File

@@ -57,10 +57,6 @@ runs:
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)
@@ -132,14 +156,14 @@ runs:
- name: Setup Bun
if: steps.detect-node.outputs.found == 'true' && steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
with:
bun-version: latest
- name: Cache Node Dependencies
if: steps.detect-node.outputs.found == 'true'
id: node-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: node_modules
key: ${{ runner.os }}-pr-lint-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
@@ -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@42bb470545e359597e7f12156947c436e4e3fb9a # v9.3.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,129 +674,13 @@ 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()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: MegaLinter reports
include-hidden-files: 'true'
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
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
printf '%s\n' "Repository is in detached HEAD state"
# Get the branch name from git refs (safer than trusting event data)
# This finds the branch that points to the current commit
branch_ref=$(git for-each-ref --format='%(refname:short)' --points-at=HEAD 'refs/remotes/origin/*' | head -1 | sed 's|^origin/||')
if [ -z "$branch_ref" ]; then
echo "::error::Could not determine branch name from git refs"
exit 1
fi
# 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
echo "Checking out branch: $branch_ref"
git checkout "$branch_ref"
# Export for next step
echo "VALIDATED_BRANCH=$branch_ref" >> "$GITHUB_ENV"
else
echo "Repository is on branch: $current_branch"
echo "VALIDATED_BRANCH=$current_branch" >> "$GITHUB_ENV"
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: ${{ env.VALIDATED_BRANCH }}
commit_message: 'style: apply linter fixes'
commit_user_name: ${{ inputs.username }}
commit_user_email: ${{ inputs.email }}

View File

@@ -34,74 +34,45 @@ class CustomValidator(BaseValidator):
# Validate pre-commit-config if provided
if "pre-commit-config" in inputs:
result = self.file_validator.validate_file_path(
inputs["pre-commit-config"], "pre-commit-config"
valid &= self.validate_with(
self.file_validator,
"validate_file_path",
inputs["pre-commit-config"],
"pre-commit-config",
)
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
valid = False
# Validate base-branch if provided (just check for injection)
if inputs.get("base-branch"):
# Check for dangerous characters that could cause shell injection
result = self.security_validator.validate_no_injection(
inputs["base-branch"], "base-branch"
valid &= self.validate_with(
self.security_validator,
"validate_no_injection",
inputs["base-branch"],
"base-branch",
)
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
# Validate token if provided
if inputs.get("token"):
result = self.token_validator.validate_github_token(inputs["token"])
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.token_validator, "validate_github_token", inputs["token"]
)
# Validate commit_user if provided (allow spaces for Git usernames)
# Check both underscore and hyphen versions since inputs can have either
commit_user_key = (
"commit_user"
if "commit_user" in inputs
else "commit-user"
if "commit-user" in inputs
else None
)
commit_user_key = self.get_key_variant(inputs, "commit_user", "commit-user")
if commit_user_key and inputs[commit_user_key]:
# Check for dangerous injection patterns
value = inputs[commit_user_key]
if any(char in value for char in [";", "&", "|", "`", "$", "(", ")", "\n", "\r"]):
if any(c in value for c in [";", "&", "|", "`", "$", "(", ")", "\n", "\r"]):
self.add_error(f"{commit_user_key}: Contains potentially dangerous characters")
valid = False
# Validate commit_email if provided
# Check both underscore and hyphen versions
commit_email_key = (
"commit_email"
if "commit_email" in inputs
else "commit-email"
if "commit-email" in inputs
else None
)
commit_email_key = self.get_key_variant(inputs, "commit_email", "commit-email")
if commit_email_key and inputs[commit_email_key]:
result = self.network_validator.validate_email(
inputs[commit_email_key], commit_email_key
valid &= self.validate_with(
self.network_validator,
"validate_email",
inputs[commit_email_key],
commit_email_key,
)
for error in self.network_validator.errors:
if error not in self.errors:
self.add_error(error)
self.network_validator.clear_errors()
if not result:
valid = False
return valid

View File

@@ -83,7 +83,7 @@ runs:
- name: Push pre-commit fixes
id: push-fixes
if: always() # Push changes even when pre-commit fails
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style(pre-commit): autofix'
commit_user_name: ${{ inputs.commit_user }}

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
@@ -305,13 +305,13 @@ runs:
- name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
with:
bun-version: latest
- name: Cache Node Dependencies
id: cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: node_modules
key: ${{ runner.os }}-prettier-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
@@ -468,7 +468,7 @@ runs:
- name: Commit and Push Fixes
if: inputs.mode == 'fix' && success()
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style: autofix Prettier formatting'
commit_user_name: ${{ inputs.username }}

View File

@@ -31,68 +31,42 @@ class CustomValidator(BaseValidator):
valid = True
# Validate python-version if provided
if "python-version" in inputs or "python_version" in inputs:
key = "python-version" if "python-version" in inputs else "python_version"
value = inputs[key]
# Empty string should fail validation
if value == "":
version_key = self.get_key_variant(inputs, "python-version", "python_version")
if version_key:
value = inputs[version_key]
if not value:
self.add_error("Python version cannot be empty")
valid = False
elif value:
result = self.version_validator.validate_python_version(value, key)
# Propagate errors from the version validator
for error in self.version_validator.errors:
if error not in self.errors:
self.add_error(error)
self.version_validator.clear_errors()
if not result:
valid = False
else:
valid &= self.validate_with(
self.version_validator, "validate_python_version", value, version_key
)
# Validate username
if "username" in inputs:
if inputs.get("username"):
username = inputs["username"]
if username:
# Check username length (GitHub usernames are max 39 characters)
if len(username) > 39:
self.add_error("Username is too long (max 39 characters)")
valid = False
# Check for command injection patterns
if ";" in username or "`" in username or "$" in username:
self.add_error("Username contains potentially dangerous characters")
valid = False
if len(username) > 39:
self.add_error("Username is too long (max 39 characters)")
valid = False
if ";" in username or "`" in username or "$" in username:
self.add_error("Username contains potentially dangerous characters")
valid = False
# Validate email
if "email" in inputs:
email = inputs["email"]
if email:
result = self.network_validator.validate_email(email, "email")
for error in self.network_validator.errors:
if error not in self.errors:
self.add_error(error)
self.network_validator.clear_errors()
if not result:
valid = False
if inputs.get("email"):
valid &= self.validate_with(
self.network_validator, "validate_email", inputs["email"], "email"
)
# Validate token
if "token" in inputs:
if inputs.get("token"):
token = inputs["token"]
if token:
# Check for variable expansion (but allow GitHub Actions expressions)
if "${" in token and not token.startswith("${{ ") and not token.endswith(" }}"):
self.add_error("Token contains potentially dangerous variable expansion")
valid = False
else:
result = self.token_validator.validate_github_token(token)
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
if not result:
valid = False
# Check for variable expansion (but allow GitHub Actions expressions)
if "${" in token and not token.startswith("${{ ") and not token.endswith(" }}"):
self.add_error("Token contains potentially dangerous variable expansion")
valid = False
else:
valid &= self.validate_with(self.token_validator, "validate_github_token", token)
return valid

View File

@@ -361,7 +361,7 @@ runs:
- name: Commit Fixes
if: ${{ fromJSON(steps.fix.outputs.fixed_count) > 0 }}
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style: apply python lint fixes'
commit_user_name: ${{ inputs.username }}
@@ -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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: ${{ inputs.working-directory }}/reports/flake8.sarif
category: 'python-lint'

View File

@@ -99,7 +99,7 @@ runs:
- name: Run actionlint
if: steps.check-configs.outputs.run_actionlint == 'true'
uses: raven-actions/actionlint@3a24062651993d40fed1019b58ac6fbdfbf276cc # v2.0.1
uses: raven-actions/actionlint@963d4779ef039e217e5d0e6fd73ce9ab7764e493 # v2.1.0
with:
cache: true
fail-on-error: true
@@ -161,21 +161,21 @@ runs:
- name: Upload Trivy results
if: steps.verify-sarif.outputs.has_trivy == 'true'
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
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
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: 'gitleaks-report.sarif'
category: 'gitleaks'
- name: Archive security reports
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: security-reports-${{ github.run_id }}
path: |

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

@@ -78,16 +78,9 @@ class CustomValidator(BaseValidator):
# Validate token if provided
if "token" in inputs:
token_valid = self.token_validator.validate_github_token(
inputs["token"],
required=False, # Token is optional, defaults to ${{ github.token }}
valid &= self.validate_with(
self.token_validator, "validate_github_token", inputs["token"], required=False
)
# Copy any errors from token validator
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
valid &= token_valid
return valid
@@ -100,27 +93,15 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(path):
return True
# First check basic file path security
result = self.file_validator.validate_file_path(path, "labels")
# Copy any errors from file validator
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
result = self.validate_with(self.file_validator, "validate_file_path", path, "labels")
if not result:
return False
# Check file extension
if not (path.endswith(".yml") or path.endswith(".yaml")):
self.add_error(f'Invalid labels file: "{path}". Must be a .yml or .yaml file')
return False
# Additional custom validation could go here
# For example, checking if the file exists, validating YAML structure, etc.
return True

View File

@@ -30,54 +30,32 @@ class CustomValidator(BaseValidator):
"""Validate terraform-lint-fix action inputs."""
valid = True
# Validate terraform-version if provided
if "terraform-version" in inputs:
value = inputs["terraform-version"]
# Validate terraform-version if provided (empty is OK - uses default)
if inputs.get("terraform-version"):
valid &= self.validate_with(
self.version_validator,
"validate_terraform_version",
inputs["terraform-version"],
"terraform-version",
)
# Empty string is OK - uses default
if value == "":
pass # Allow empty, will use default
elif value:
result = self.version_validator.validate_terraform_version(
value, "terraform-version"
)
# Propagate errors from the version validator
for error in self.version_validator.errors:
if error not in self.errors:
self.add_error(error)
self.version_validator.clear_errors()
if not result:
valid = False
# Validate token if provided
if "token" in inputs:
value = inputs["token"]
if value == "":
# Empty token is OK - uses default
pass
elif value:
result = self.token_validator.validate_github_token(value, required=False)
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
if not result:
valid = False
# Validate token if provided (empty is OK - uses default)
if inputs.get("token"):
valid &= self.validate_with(
self.token_validator,
"validate_github_token",
inputs["token"],
required=False,
)
# Validate working-directory if provided
if "working-directory" in inputs:
value = inputs["working-directory"]
if value:
result = self.file_validator.validate_file_path(value, "working-directory")
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
valid = False
if inputs.get("working-directory"):
valid &= self.validate_with(
self.file_validator,
"validate_file_path",
inputs["working-directory"],
"working-directory",
)
return valid

View File

@@ -247,7 +247,7 @@ runs:
- name: Commit Fixes
if: steps.check-files.outputs.found == 'true' && inputs.auto-fix == 'true' && fromJSON(steps.fix.outputs.fixed_count) > 0
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style: apply terraform formatting fixes'
commit_user_name: ${{ inputs.username }}
@@ -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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: ${{ env.VALIDATED_WORKING_DIR }}/reports/tflint.sarif
category: terraform-lint

View File

@@ -27,57 +27,45 @@ class CustomValidator(BaseValidator):
self.boolean_validator = BooleanValidator()
self.file_validator = FileValidator()
def validate_inputs(self, inputs: dict[str, str]) -> bool: # pylint: disable=too-many-branches
def validate_inputs(self, inputs: dict[str, str]) -> bool:
"""Validate validate-inputs action inputs."""
valid = True
# Validate action/action-type input
if "action" in inputs or "action-type" in inputs:
action_input = inputs.get("action") or inputs.get("action-type", "")
# Check for empty action
action_key = self.get_key_variant(inputs, "action", "action-type")
if action_key:
action_input = inputs[action_key]
if action_input == "":
self.add_error("Action name cannot be empty")
valid = False
# Allow GitHub expressions
elif action_input.startswith("${{") and action_input.endswith("}}"):
pass # GitHub expressions are valid
# Check for dangerous characters
elif any(
char in action_input
for char in [";", "`", "$", "&", "|", ">", "<", "\n", "\r", "/"]
):
self.add_error(f"Invalid characters in action name: {action_input}")
valid = False
# Validate action name format (should be lowercase with hyphens or underscores)
elif action_input and not re.match(r"^[a-z][a-z0-9_-]*[a-z0-9]$", action_input):
self.add_error(f"Invalid action name format: {action_input}")
valid = False
elif not self.is_github_expression(action_input):
# Only validate non-GitHub expressions
if any(
char in action_input
for char in [";", "`", "$", "&", "|", ">", "<", "\n", "\r", "/"]
):
self.add_error(f"Invalid characters in action name: {action_input}")
valid = False
elif action_input and not re.match(r"^[a-z][a-z0-9_-]*[a-z0-9]$", action_input):
self.add_error(f"Invalid action name format: {action_input}")
valid = False
# Validate rules-file if provided
if inputs.get("rules-file"):
result = self.file_validator.validate_file_path(inputs["rules-file"], "rules-file")
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.file_validator, "validate_file_path", inputs["rules-file"], "rules-file"
)
# Validate fail-on-error boolean
if "fail-on-error" in inputs:
value = inputs["fail-on-error"]
# Reject empty string
if value == "":
self.add_error("fail-on-error cannot be empty")
valid = False
elif value:
result = self.boolean_validator.validate_boolean(value, "fail-on-error")
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
if not result:
valid = False
valid &= self.validate_with(
self.boolean_validator, "validate_boolean", value, "fail-on-error"
)
return valid

View File

@@ -213,6 +213,10 @@ outputs:
runs:
using: composite
steps:
- name: Install Python dependencies
shell: bash
run: pip install pyyaml==6.0.3
- name: Validate Action Inputs with Python
id: validate
shell: bash

View File

@@ -895,7 +895,7 @@ optional_inputs:
self.validator._validate_multi_value_enum("test", "input", valid_values=["only_one"])
raise AssertionError("Should raise ValueError for single value")
except ValueError as e:
assert "at least 2 valid values" in str(e)
assert ">= 2 values" in str(e)
# Should raise ValueError if more than max_values
try:
@@ -906,7 +906,7 @@ optional_inputs:
)
raise AssertionError("Should raise ValueError for 11 values")
except ValueError as e:
assert "at most 10 valid values" in str(e)
assert "<= 10 values" in str(e)
def test_validate_exit_code_list_valid(self):
"""Test exit code list validation with valid values."""

View File

@@ -227,3 +227,82 @@ class BaseValidator(ABC):
or ("${{" in value and "}}" in value)
or (value.strip().startswith("${{") and value.strip().endswith("}}"))
)
def propagate_errors(self, validator: BaseValidator, result: bool) -> bool:
"""Copy errors from another validator and return result.
Args:
validator: The validator to copy errors from
result: The validation result to return
Returns:
The result parameter unchanged
"""
for error in validator.errors:
if error not in self.errors:
self.add_error(error)
validator.clear_errors()
return result
def validate_with(
self, validator: BaseValidator, method: str, *args: Any, **kwargs: Any
) -> bool:
"""Call validator method and propagate errors.
Args:
validator: The validator instance to use
method: The method name to call on the validator
*args: Positional arguments to pass to the method
**kwargs: Keyword arguments to pass to the method
Returns:
The validation result
"""
result = getattr(validator, method)(*args, **kwargs)
return self.propagate_errors(validator, result)
def validate_enum(
self,
value: str,
name: str,
valid_values: list[str],
*,
case_sensitive: bool = False,
) -> bool:
"""Validate value is one of allowed options.
Args:
value: The value to validate
name: The name of the input for error messages
valid_values: List of allowed values
case_sensitive: Whether comparison should be case sensitive
Returns:
True if value is valid or empty/GitHub expression, False otherwise
"""
if not value or self.is_github_expression(value):
return True
check = value if case_sensitive else value.lower()
allowed = valid_values if case_sensitive else [v.lower() for v in valid_values]
if check not in allowed:
self.add_error(f"Invalid {name}: {value}. Must be one of: {', '.join(valid_values)}")
return False
return True
@staticmethod
def get_key_variant(inputs: dict[str, str], *variants: str) -> str | None:
"""Get first matching key variant from inputs.
Useful for inputs that may use underscore or hyphen variants.
Args:
inputs: Dictionary of inputs to check
*variants: Key variants to search for in order
Returns:
The first matching key, or None if no match
"""
for key in variants:
if key in inputs:
return key
return None

View File

@@ -5,6 +5,7 @@ This validator automatically applies validation based on input naming convention
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
@@ -424,7 +425,10 @@ class ConventionBasedValidator(BaseValidator):
if error not in self.errors:
self.add_error(error)
# Clear the module's errors after copying
validator_module.errors = []
if hasattr(validator_module, "clear_errors"):
validator_module.clear_errors()
else:
validator_module.errors = []
return result
# Method not found, skip validation
@@ -629,7 +633,8 @@ class ConventionBasedValidator(BaseValidator):
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)
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")
@@ -654,8 +659,6 @@ class ConventionBasedValidator(BaseValidator):
... )
True
"""
import re
if not value or value.strip() == "":
return True # Optional
@@ -895,14 +898,12 @@ class ConventionBasedValidator(BaseValidator):
# 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)}"
)
msg = f"Multi-value enum needs >= {min_values} values, got {len(valid_values)}"
raise ValueError(msg)
if len(valid_values) > max_values:
raise ValueError(
f"Multi-value enum supports at most {max_values} valid values, got {len(valid_values)}"
)
msg = f"Multi-value enum allows <= {max_values} values, got {len(valid_values)}"
raise ValueError(msg)
if not value or value.strip() == "":
return True # Optional
@@ -1024,8 +1025,6 @@ class ConventionBasedValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
import re
if not value or value.strip() == "":
return True # Optional
@@ -1123,8 +1122,6 @@ class ConventionBasedValidator(BaseValidator):
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
@@ -1169,8 +1166,10 @@ class ConventionBasedValidator(BaseValidator):
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)
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
@@ -1179,7 +1178,6 @@ class ConventionBasedValidator(BaseValidator):
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
@@ -1260,8 +1258,6 @@ class ConventionBasedValidator(BaseValidator):
Returns:
bool: True if valid, False otherwise
"""
import re
if not value or value.strip() == "":
return True # Optional
@@ -1412,8 +1408,6 @@ class ConventionBasedValidator(BaseValidator):
Returns:
bool: True if valid, False otherwise
"""
import re
if not value or value.strip() == "":
return True # Optional

View File

@@ -12,7 +12,8 @@ class TokenValidator(BaseValidator):
"""Validator for various authentication tokens."""
# Token patterns for different token types (based on official GitHub documentation)
# https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats
# See: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/
# about-authentication-to-github#githubs-token-formats
# Note: The lengths include the prefix
TOKEN_PATTERNS: ClassVar[dict[str, str]] = {
# Personal access token (classic):