feat(github_actions): enhance workflow exclusion with full path support

Zizmor doesn't support --exclude flag natively, so implemented
exclusion at Prowler level by filtering findings after scan completes.

The exclusion now supports both pattern types:
- Filename patterns: 'test-*.yml' matches any file named test-*.yml
- Full path patterns: '.github/workflows/test-*.yml' matches specific paths
- Subdirectory patterns: '**/experimental/*.yml' matches subdirs

Changes:
- Added fnmatch import for glob pattern matching
- Removed invalid --exclude flags from zizmor command
- Implemented _should_exclude_workflow() with dual matching logic
- Filter findings during processing based on exclusion patterns
- Added comprehensive test covering all pattern types
- Updated documentation with examples of both pattern types
This commit is contained in:
Andoni A.
2025-11-06 17:11:15 +01:00
parent a693d7af7e
commit 2800b34d54
3 changed files with 113 additions and 6 deletions
@@ -98,12 +98,31 @@ Authentication for private repositories can be provided using one of the followi
### Excluding Workflows
Exclude specific workflows or patterns from scanning:
Exclude specific workflows from the scan results using glob patterns. Prowler filters findings after zizmor completes the scan, matching patterns against both workflow filenames and full paths:
```bash
prowler github_actions --exclude-workflows "test-*.yml" "experimental/*"
# Exclude by filename pattern (matches any path)
prowler github_actions --exclude-workflows "test-*.yml" "api-*.yml"
# Exclude by full path pattern
prowler github_actions --exclude-workflows ".github/workflows/experimental-*.yml"
# Exclude entire subdirectories
prowler github_actions --exclude-workflows "**/draft/*.yml"
# Combine multiple pattern types
prowler github_actions \
--workflow-path .github/workflows \
--exclude-workflows "test-*.yml" ".github/workflows/experimental/*"
```
<Note>
Patterns can match against either the full path or just the filename:
- `test-*.yml` matches `test-deploy.yml` in any directory
- `.github/workflows/test-*.yml` matches only workflows in that specific directory
- `**/experimental/*.yml` matches workflows in any `experimental` subdirectory
</Note>
### Output Formats
The GitHub Actions provider supports all standard Prowler output formats:
@@ -3,6 +3,7 @@ import shutil
import subprocess
import sys
import tempfile
from fnmatch import fnmatch
from os import environ
from typing import List
@@ -132,6 +133,42 @@ class GithubActionsProvider(Provider):
"""GitHub Actions provider doesn't need a session since it uses zizmor directly"""
return None
def _should_exclude_workflow(self, workflow_file: str, exclude_patterns: list[str]) -> bool:
"""
Check if a workflow file should be excluded based on exclude patterns.
Patterns can match against either:
- The full workflow path (e.g., ".github/workflows/test-*.yml")
- Just the filename (e.g., "test-*.yml")
Args:
workflow_file: The workflow file path
exclude_patterns: List of glob patterns to exclude
Returns:
True if the workflow should be excluded, False otherwise
"""
if not exclude_patterns:
return False
# Get just the filename from the path
from os.path import basename
filename = basename(workflow_file)
# Check if the pattern matches either the full path or just the filename
for pattern in exclude_patterns:
# Try matching against full path first
if fnmatch(workflow_file, pattern):
logger.debug(f"Excluding workflow {workflow_file} (matches full path pattern: {pattern})")
return True
# Also try matching against just the filename for convenience
if fnmatch(filename, pattern):
logger.debug(f"Excluding workflow {workflow_file} (matches filename pattern: {pattern})")
return True
return False
def _extract_workflow_file_from_location(self, location: dict) -> str:
"""
Extract the workflow file path from a location object.
@@ -368,6 +405,7 @@ class GithubActionsProvider(Provider):
logger.info(f"Running GitHub Actions security scan on {directory} ...")
# Build zizmor command
# Note: zizmor doesn't support --exclude, so we filter findings after scanning
zizmor_command = [
"zizmor",
directory,
@@ -375,10 +413,6 @@ class GithubActionsProvider(Provider):
"json",
]
# Add exclude patterns if provided
for exclude_pattern in exclude_workflows:
zizmor_command.extend(["--exclude", exclude_pattern])
with alive_bar(
ctrl_c=False,
bar="blocks",
@@ -444,6 +478,10 @@ class GithubActionsProvider(Provider):
location
)
if workflow_file:
# Check if this workflow should be excluded
if self._should_exclude_workflow(workflow_file, exclude_workflows):
continue
report = self._process_zizmor_finding(
finding, workflow_file, location
)
@@ -222,3 +222,53 @@ class TestGithubActionsProvider:
args = mock_print.call_args[0]
assert "https://github.com/test/repo" in str(args[0])
assert "test*.yml" in str(args[0])
def test_should_exclude_workflow(self):
"""Test workflow exclusion pattern matching"""
with patch.object(GithubActionsProvider, "setup_session", return_value=None):
provider = GithubActionsProvider()
# Test no exclusions
assert not provider._should_exclude_workflow(
".github/workflows/test.yml", []
)
# Test exact filename match
assert provider._should_exclude_workflow(
".github/workflows/test.yml", ["test.yml"]
)
# Test wildcard pattern on filename
assert provider._should_exclude_workflow(
".github/workflows/test-api.yml", ["test-*.yml"]
)
# Test multiple patterns
assert provider._should_exclude_workflow(
".github/workflows/api-test.yml", ["test-*.yml", "api-*.yml"]
)
# Test no match
assert not provider._should_exclude_workflow(
".github/workflows/deploy.yml", ["test-*.yml", "api-*.yml"]
)
# Test full path matching
assert provider._should_exclude_workflow(
".github/workflows/test.yml", [".github/workflows/test.yml"]
)
# Test full path with wildcard
assert provider._should_exclude_workflow(
".github/workflows/api-tests.yml", [".github/workflows/api-*.yml"]
)
# Test subdirectory patterns
assert provider._should_exclude_workflow(
".github/workflows/experimental/test.yml", ["**/experimental/*.yml"]
)
# Test that filename pattern works regardless of path
assert provider._should_exclude_workflow(
"workflows/subdir/test-deploy.yml", ["test-*.yml"]
)