mirror of
https://github.com/ivuorinen/actions.git
synced 2026-01-26 03:23:59 +00:00
Compare commits
21 Commits
v2025.12.1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f98ae7cd7d | ||
| cc842575b9 | |||
|
|
cbfddb2433 | ||
|
|
5664cdbfbf | ||
|
|
e740f9d893 | ||
| a247b78178 | |||
|
|
56ff9a511c | ||
|
|
81310f9bd7 | ||
|
|
95b8856c3f | ||
|
|
e69ddbc1e2 | ||
|
|
28e81adc2b | ||
|
|
fb25736f7e | ||
| 54886c3fd5 | |||
|
|
fd030b418f | ||
| 96c305c557 | |||
|
|
5b4e9c8e11 | ||
|
|
2d0bff84ad | ||
|
|
98f260793c | ||
|
|
09ae7517d6 | ||
|
|
61ebe619a8 | ||
|
|
a1d55ac125 |
@@ -17,7 +17,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
|
||||
2
.github/workflows/build-testing-image.yml
vendored
2
.github/workflows/build-testing-image.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/issue-stats.yml
vendored
2
.github/workflows/issue-stats.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run issue-metrics tool
|
||||
uses: github/issue-metrics@55bb0b704982057a101ab7515fb72b2293927c8a # v3.25.4
|
||||
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"'
|
||||
|
||||
16
.github/workflows/new-release.yml
vendored
16
.github/workflows/new-release.yml
vendored
@@ -21,6 +21,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history and tags for comparison
|
||||
|
||||
- name: Create daily release
|
||||
id: daily-version
|
||||
@@ -37,6 +39,20 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get the most recent tag
|
||||
PREVIOUS_TAG=$(git tag --sort=-version:refname | head -1)
|
||||
|
||||
# Check if there are any changes since the previous tag
|
||||
if [ -n "$PREVIOUS_TAG" ]; then
|
||||
CHANGES=$(git rev-list "$PREVIOUS_TAG"..HEAD --count)
|
||||
if [ "$CHANGES" -eq 0 ]; then
|
||||
printf '%s\n' "created=false" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "No changes since $PREVIOUS_TAG - skipping release"
|
||||
exit 0
|
||||
fi
|
||||
printf '%s\n' "Found $CHANGES commit(s) since $PREVIOUS_TAG"
|
||||
fi
|
||||
|
||||
# Create release with auto-generated changelog (also creates tag)
|
||||
gh release create "$VERSION" \
|
||||
--title "Release $VERSION" \
|
||||
|
||||
2
.github/workflows/pr-lint.yml
vendored
2
.github/workflows/pr-lint.yml
vendored
@@ -74,7 +74,7 @@ jobs:
|
||||
|
||||
- name: Upload SARIF Report
|
||||
if: always() && hashFiles('megalinter-reports/sarif/*.sarif')
|
||||
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
sarif_file: megalinter-reports/sarif
|
||||
category: megalinter
|
||||
|
||||
2
.github/workflows/test-actions.yml
vendored
2
.github/workflows/test-actions.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
||||
if: always()
|
||||
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
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
|
||||
|
||||
25
.github/workflows/version-maintenance.yml
vendored
25
.github/workflows/version-maintenance.yml
vendored
@@ -40,6 +40,29 @@ jobs:
|
||||
printf '%s\n' "major=v$current_year" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Ensure Major Version Tag Exists
|
||||
id: ensure-tag
|
||||
shell: sh
|
||||
env:
|
||||
MAJOR_VERSION: ${{ steps.version.outputs.major }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
git fetch --tags --force
|
||||
|
||||
if git rev-list -n 1 "$MAJOR_VERSION" >/dev/null 2>&1; then
|
||||
echo "Tag $MAJOR_VERSION already exists"
|
||||
printf '%s\n' "created=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Tag $MAJOR_VERSION not found, creating..."
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -a "$MAJOR_VERSION" -m "Major version $MAJOR_VERSION"
|
||||
git push origin "$MAJOR_VERSION"
|
||||
echo "Created and pushed tag $MAJOR_VERSION"
|
||||
printf '%s\n' "created=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Run Action Versioning
|
||||
id: action-versioning
|
||||
uses: ./action-versioning
|
||||
@@ -49,7 +72,7 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.action-versioning.outputs.updated == 'true'
|
||||
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
|
||||
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 }}'
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"siblings_only": true
|
||||
},
|
||||
"MD033": false,
|
||||
"MD041": false
|
||||
"MD041": false,
|
||||
"MD060": false
|
||||
}
|
||||
|
||||
@@ -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.17
|
||||
rev: 0.9.24
|
||||
hooks:
|
||||
- id: uv-lock
|
||||
- id: uv-sync
|
||||
@@ -55,7 +55,7 @@ repos:
|
||||
- id: yamllint
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.9
|
||||
rev: v0.14.11
|
||||
hooks:
|
||||
# Run the linter with auto-fix
|
||||
- id: ruff-check
|
||||
@@ -78,13 +78,13 @@ 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/bridgecrewio/checkov.git
|
||||
rev: '3.2.495'
|
||||
rev: '3.2.497'
|
||||
hooks:
|
||||
- id: checkov
|
||||
args:
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
sarif_file: ansible-lint.sarif
|
||||
|
||||
@@ -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@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
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 }}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -186,7 +186,7 @@ runs:
|
||||
echo "Using build mode: $build_mode"
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
category: ${{ steps.set-category.outputs.category }}
|
||||
upload: ${{ inputs.upload-results }}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ runs:
|
||||
|
||||
- name: Create New Pull Request If Needed
|
||||
if: steps.calibre.outputs.markdown != ''
|
||||
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
|
||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
title: 'chore: compress images'
|
||||
|
||||
@@ -206,6 +206,6 @@ runs:
|
||||
fi
|
||||
|
||||
- name: Upload SARIF Report
|
||||
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
sarif_file: dotnet-format.sarif
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ runs:
|
||||
- name: Cache golangci-lint
|
||||
id: cache
|
||||
if: inputs.cache == 'true'
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
sarif_file: ${{ inputs.working-directory }}/reports/golangci-lint.sarif
|
||||
category: golangci-lint
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
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') }}
|
||||
|
||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -1093,7 +1093,6 @@
|
||||
"integrity": "sha512-esPk+8Qvx/f0bzI7YelUeZp+jCtFOk3KjZ7s9iBQZ6HlymSXoTtWGiIRZP05/9Oy2ehIoIjenVwndxGtxOIJYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"globby": "15.0.0",
|
||||
"js-yaml": "4.1.1",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -356,7 +356,7 @@ runs:
|
||||
|
||||
- name: Cache Composer packages
|
||||
id: composer-cache
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
with:
|
||||
path: |
|
||||
vendor
|
||||
|
||||
@@ -156,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@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
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') }}
|
||||
@@ -632,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@55a59b24a441e0e1943080d4a512d827710d4a9d # v9.2.0
|
||||
uses: oxsecurity/megalinter/flavors/cupcake@42bb470545e359597e7f12156947c436e4e3fb9a # v9.3.0
|
||||
id: ml
|
||||
|
||||
# All available variables are described in documentation
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
sarif_file: ${{ inputs.working-directory }}/reports/flake8.sarif
|
||||
category: 'python-lint'
|
||||
|
||||
@@ -161,14 +161,14 @@ runs:
|
||||
|
||||
- name: Upload Trivy results
|
||||
if: steps.verify-sarif.outputs.has_trivy == 'true'
|
||||
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
sarif_file: 'gitleaks-report.sarif'
|
||||
category: 'gitleaks'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
sarif_file: ${{ env.VALIDATED_WORKING_DIR }}/reports/tflint.sarif
|
||||
category: terraform-lint
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user