mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 14:14:56 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
---
|
||||
name: securing-github-actions-workflows
|
||||
description: >
|
||||
This skill covers hardening GitHub Actions workflows against supply chain attacks,
|
||||
credential theft, and privilege escalation. It addresses pinning actions to SHA digests,
|
||||
minimizing GITHUB_TOKEN permissions, protecting secrets from exfiltration, preventing
|
||||
script injection in workflow expressions, and implementing required reviewers for
|
||||
workflow changes.
|
||||
domain: cybersecurity
|
||||
subdomain: devsecops
|
||||
tags: [devsecops, cicd, github-actions, supply-chain, workflow-security, secure-sdlc]
|
||||
version: 1.0.0
|
||||
author: mahipal
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Securing GitHub Actions Workflows
|
||||
|
||||
## When to Use
|
||||
|
||||
- When GitHub Actions is the CI/CD platform and workflows need hardening against supply chain attacks
|
||||
- When workflows handle secrets, deploy to production, or have elevated permissions
|
||||
- When preventing script injection via untrusted PR titles, branch names, or commit messages
|
||||
- When requiring audit trails and approval gates for workflow modifications
|
||||
- When third-party actions pose supply chain risk through mutable version tags
|
||||
|
||||
**Do not use** for securing other CI/CD platforms (see platform-specific hardening guides), for application vulnerability scanning (use SAST/DAST), or for secret detection in code (use Gitleaks).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- GitHub repository with GitHub Actions enabled
|
||||
- GitHub organization admin access for organization-level settings
|
||||
- Understanding of GitHub Actions workflow syntax and events
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Pin Actions to SHA Digests
|
||||
|
||||
```yaml
|
||||
# INSECURE: Mutable tag can be overwritten by attacker
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# SECURE: Pinned to immutable SHA digest
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
# Use Dependabot to auto-update pinned SHAs
|
||||
# .github/dependabot.yml
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "ci"
|
||||
```
|
||||
|
||||
### Step 2: Minimize GITHUB_TOKEN Permissions
|
||||
|
||||
```yaml
|
||||
# Set restrictive default permissions at workflow level
|
||||
name: CI Pipeline
|
||||
permissions: {} # Start with no permissions
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read # Only what's needed
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
id-token: write # For OIDC-based cloud auth
|
||||
steps:
|
||||
- name: Deploy
|
||||
run: echo "deploying"
|
||||
```
|
||||
|
||||
### Step 3: Prevent Script Injection
|
||||
|
||||
```yaml
|
||||
# VULNERABLE: User-controlled input in run step
|
||||
- run: echo "PR title is ${{ github.event.pull_request.title }}"
|
||||
|
||||
# SECURE: Use environment variable (properly escaped by shell)
|
||||
- name: Process PR
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
run: |
|
||||
echo "PR title is ${PR_TITLE}"
|
||||
echo "PR body is ${PR_BODY}"
|
||||
|
||||
# SECURE: Use actions/github-script for complex operations
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
|
||||
with:
|
||||
script: |
|
||||
const title = context.payload.pull_request.title;
|
||||
console.log(`PR title: ${title}`);
|
||||
```
|
||||
|
||||
### Step 4: Secure Fork Pull Request Handling
|
||||
|
||||
```yaml
|
||||
# DANGEROUS: pull_request_target runs with base repo permissions
|
||||
# on: pull_request_target # AVOID unless absolutely necessary
|
||||
|
||||
# SAFE: pull_request runs in fork context with limited permissions
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
# If pull_request_target is required, never checkout PR code:
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
safe-job:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'safe-to-test')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
# NEVER do: actions/checkout with ref: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
||||
# This checks out the BASE branch, not the PR
|
||||
```
|
||||
|
||||
### Step 5: Protect Secrets and Environment Variables
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production # Requires approval
|
||||
steps:
|
||||
- name: Deploy with secret
|
||||
env:
|
||||
# Secrets are masked in logs automatically
|
||||
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
||||
run: |
|
||||
# Never echo secrets
|
||||
# echo "$DEPLOY_KEY" # BAD
|
||||
deploy-tool --key-file <(echo "$DEPLOY_KEY")
|
||||
|
||||
- name: Audit secret access
|
||||
run: |
|
||||
# Log that secret was used without exposing it
|
||||
echo "::notice::Deploy key accessed for production deployment"
|
||||
```
|
||||
|
||||
### Step 6: Implement Workflow Change Controls
|
||||
|
||||
```yaml
|
||||
# Require CODEOWNERS approval for workflow changes
|
||||
# .github/CODEOWNERS
|
||||
.github/workflows/ @security-team @platform-team
|
||||
.github/actions/ @security-team @platform-team
|
||||
|
||||
# Organization settings:
|
||||
# 1. Settings > Actions > General > Fork PR policies
|
||||
# - Require approval for first-time contributors
|
||||
# - Require approval for all outside collaborators
|
||||
# 2. Settings > Actions > General > Workflow permissions
|
||||
# - Read repository contents and packages permissions
|
||||
# - Do NOT allow GitHub Actions to create and approve PRs
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| SHA Pinning | Referencing GitHub Actions by their immutable commit SHA instead of mutable version tags |
|
||||
| Script Injection | Attack where untrusted input (PR title, branch name) is interpolated into shell commands |
|
||||
| GITHUB_TOKEN | Automatically generated token with configurable permissions scoped to the current repository |
|
||||
| pull_request_target | Dangerous event trigger that runs in the base repo context with full permissions on fork PRs |
|
||||
| Environment Protection | GitHub feature requiring manual approval before jobs accessing an environment can run |
|
||||
| CODEOWNERS | File defining required reviewers for specific paths including workflow files |
|
||||
| OIDC Federation | Using GitHub's OIDC token to authenticate to cloud providers without storing long-lived credentials |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **Dependabot**: Automated dependency updater that keeps pinned action SHAs current
|
||||
- **StepSecurity Harden Runner**: GitHub Action that monitors and restricts outbound network calls from workflows
|
||||
- **actionlint**: Linter for GitHub Actions workflow files that detects security issues
|
||||
- **allstar**: GitHub App by OpenSSF that enforces security policies on repositories
|
||||
- **scorecard**: OpenSSF tool that evaluates supply chain security practices including CI/CD
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Preventing Supply Chain Attack via Compromised Third-Party Action
|
||||
|
||||
**Context**: A widely-used GitHub Action is compromised and its v3 tag is updated to include credential-stealing code. Repositories using `@v3` automatically pull the malicious version.
|
||||
|
||||
**Approach**:
|
||||
1. Pin all actions to SHA digests immediately across all repositories
|
||||
2. Configure Dependabot for github-actions ecosystem to manage SHA updates
|
||||
3. Restrict GITHUB_TOKEN permissions so even compromised actions have minimal access
|
||||
4. Add StepSecurity harden-runner to detect anomalous outbound network calls
|
||||
5. Review all third-party actions and replace unnecessary ones with inline scripts
|
||||
6. Require CODEOWNERS approval for any changes to .github/workflows/
|
||||
|
||||
**Pitfalls**: SHA pinning without Dependabot means missing legitimate security updates to actions. Overly restrictive permissions can break legitimate workflows. Using `pull_request_target` for label-based gating still exposes secrets if the workflow checks out PR code.
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
GitHub Actions Security Audit
|
||||
================================
|
||||
Repository: org/web-application
|
||||
Date: 2026-02-23
|
||||
|
||||
WORKFLOW ANALYSIS:
|
||||
Total workflows: 8
|
||||
Total action references: 34
|
||||
|
||||
SHA PINNING:
|
||||
[FAIL] 12/34 actions use mutable tags instead of SHA digests
|
||||
- .github/workflows/ci.yml: actions/setup-node@v4
|
||||
- .github/workflows/deploy.yml: aws-actions/configure-aws-credentials@v4
|
||||
|
||||
PERMISSIONS:
|
||||
[FAIL] 3/8 workflows have no explicit permissions (inherit default)
|
||||
[WARN] 1/8 workflows request write-all permissions
|
||||
|
||||
SCRIPT INJECTION:
|
||||
[FAIL] 2 workflow steps interpolate user input directly
|
||||
- .github/workflows/pr-check.yml:23: ${{ github.event.pull_request.title }}
|
||||
|
||||
SECRETS:
|
||||
[PASS] No secrets exposed in workflow logs
|
||||
[PASS] All production deployments use environment protection
|
||||
|
||||
SCORE: 6/10 (Remediate 5 HIGH findings)
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
# GitHub Actions Security Templates
|
||||
|
||||
## Hardened Workflow Template
|
||||
|
||||
```yaml
|
||||
name: Secure CI Pipeline
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: Build
|
||||
run: make build
|
||||
- name: Test
|
||||
run: make test
|
||||
```
|
||||
|
||||
## Dependabot for Actions
|
||||
|
||||
```yaml
|
||||
# .github/dependabot.yml
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "ci"
|
||||
```
|
||||
|
||||
## CODEOWNERS for Workflow Protection
|
||||
|
||||
```
|
||||
# .github/CODEOWNERS
|
||||
.github/workflows/ @org/security-team @org/platform-team
|
||||
.github/actions/ @org/security-team
|
||||
.github/dependabot.yml @org/platform-team
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
# Standards Reference: Securing GitHub Actions
|
||||
|
||||
## NIST SSDF (SP 800-218)
|
||||
|
||||
### PS.1: Protect All Forms of Code
|
||||
- Workflows are code and must be reviewed and protected
|
||||
- Pin action dependencies to SHA digests
|
||||
- Minimize GITHUB_TOKEN permissions
|
||||
|
||||
## CIS Software Supply Chain Security
|
||||
|
||||
- BD-1: Define security requirements for build processes
|
||||
- BD-2: Automate security validation of build configurations
|
||||
- BD-3: Pin all external dependencies to immutable references
|
||||
|
||||
## OWASP CI/CD Top 10 Risks
|
||||
|
||||
| Risk | GitHub Actions Mitigation |
|
||||
|------|--------------------------|
|
||||
| CICD-SEC-1: Insufficient Flow Control | Environment protection rules, CODEOWNERS |
|
||||
| CICD-SEC-3: Dependency Chain Abuse | SHA pinning of actions |
|
||||
| CICD-SEC-4: Poisoned Pipeline Execution | Restrict pull_request_target, input sanitization |
|
||||
| CICD-SEC-6: Credential Hygiene | OIDC federation, minimal GITHUB_TOKEN scope |
|
||||
| CICD-SEC-9: Artifact Integrity | Sign artifacts in workflows |
|
||||
|
||||
## SLSA Framework
|
||||
|
||||
- Level 2: Hosted build service (GitHub Actions qualifies)
|
||||
- Level 3: Hardened build platform with isolation guarantees
|
||||
- Workflow hardening prevents provenance falsification
|
||||
@@ -0,0 +1,40 @@
|
||||
# Workflow Reference: Securing GitHub Actions
|
||||
|
||||
## Hardening Checklist
|
||||
|
||||
1. Pin all actions to SHA digests
|
||||
2. Set restrictive default permissions
|
||||
3. Sanitize all user-controlled inputs
|
||||
4. Never use pull_request_target with PR checkout
|
||||
5. Enable environment protection for production
|
||||
6. Configure CODEOWNERS for workflow files
|
||||
7. Enable Dependabot for github-actions
|
||||
8. Audit third-party actions quarterly
|
||||
9. Use OIDC instead of long-lived cloud credentials
|
||||
10. Add harden-runner for network monitoring
|
||||
|
||||
## Permission Scoping Reference
|
||||
|
||||
| Permission | Use Case |
|
||||
|-----------|----------|
|
||||
| contents: read | Checkout code |
|
||||
| contents: write | Create releases, push tags |
|
||||
| security-events: write | Upload SARIF results |
|
||||
| packages: write | Push container images |
|
||||
| deployments: write | Create deployment status |
|
||||
| id-token: write | OIDC cloud authentication |
|
||||
| pull-requests: write | Comment on PRs |
|
||||
|
||||
## Script Injection Prevention
|
||||
|
||||
```yaml
|
||||
# DANGEROUS patterns to avoid:
|
||||
run: echo "${{ github.event.issue.title }}"
|
||||
run: echo "${{ github.event.comment.body }}"
|
||||
run: echo "${{ github.head_ref }}"
|
||||
|
||||
# SAFE alternatives:
|
||||
env:
|
||||
TITLE: ${{ github.event.issue.title }}
|
||||
run: echo "${TITLE}"
|
||||
```
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
GitHub Actions Workflow Security Audit Script
|
||||
|
||||
Analyzes workflow files for security issues including unpinned actions,
|
||||
excessive permissions, script injection risks, and insecure patterns.
|
||||
|
||||
Usage:
|
||||
python process.py --workflows-dir .github/workflows/ --output audit-report.json
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
@dataclass
|
||||
class SecurityFinding:
|
||||
file: str
|
||||
line: int
|
||||
check: str
|
||||
severity: str
|
||||
message: str
|
||||
remediation: str
|
||||
|
||||
|
||||
SHA_PATTERN = re.compile(r"@[0-9a-f]{40}")
|
||||
TAG_PATTERN = re.compile(r"@v?\d+(\.\d+)*$")
|
||||
INJECTION_PATTERN = re.compile(r"\$\{\{\s*github\.event\.(issue|pull_request|comment|review)\.\w+")
|
||||
DANGEROUS_CONTEXTS = [
|
||||
"github.event.issue.title",
|
||||
"github.event.issue.body",
|
||||
"github.event.pull_request.title",
|
||||
"github.event.pull_request.body",
|
||||
"github.event.comment.body",
|
||||
"github.event.review.body",
|
||||
"github.head_ref",
|
||||
]
|
||||
|
||||
|
||||
def load_workflow(filepath: str) -> dict:
|
||||
"""Load a GitHub Actions workflow YAML file."""
|
||||
try:
|
||||
with open(filepath, "r") as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
except (yaml.YAMLError, FileNotFoundError):
|
||||
return {}
|
||||
|
||||
|
||||
def check_action_pinning(workflow: dict, filepath: str) -> list:
|
||||
"""Check if actions are pinned to SHA digests."""
|
||||
findings = []
|
||||
filename = os.path.basename(filepath)
|
||||
|
||||
for job_name, job in workflow.get("jobs", {}).items():
|
||||
for i, step in enumerate(job.get("steps", [])):
|
||||
uses = step.get("uses", "")
|
||||
if not uses or uses.startswith("./"):
|
||||
continue
|
||||
if not SHA_PATTERN.search(uses):
|
||||
findings.append(SecurityFinding(
|
||||
file=filename, line=0,
|
||||
check="ACTION_PINNING",
|
||||
severity="HIGH",
|
||||
message=f"Job '{job_name}' step {i}: '{uses}' not pinned to SHA digest",
|
||||
remediation=f"Pin to SHA: {uses.split('@')[0]}@<commit-sha>"
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
def check_permissions(workflow: dict, filepath: str) -> list:
|
||||
"""Check for overly permissive GITHUB_TOKEN permissions."""
|
||||
findings = []
|
||||
filename = os.path.basename(filepath)
|
||||
|
||||
top_perms = workflow.get("permissions")
|
||||
if top_perms is None:
|
||||
findings.append(SecurityFinding(
|
||||
file=filename, line=0,
|
||||
check="PERMISSIONS",
|
||||
severity="MEDIUM",
|
||||
message="No top-level permissions defined. Inherits default (may be write-all).",
|
||||
remediation="Add 'permissions: {}' at workflow level and grant per-job."
|
||||
))
|
||||
elif top_perms == "write-all" or (isinstance(top_perms, dict) and
|
||||
all(v == "write" for v in top_perms.values())):
|
||||
findings.append(SecurityFinding(
|
||||
file=filename, line=0,
|
||||
check="PERMISSIONS",
|
||||
severity="HIGH",
|
||||
message="Workflow has write-all permissions.",
|
||||
remediation="Restrict to minimum required permissions per job."
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
def check_script_injection(workflow: dict, filepath: str) -> list:
|
||||
"""Check for script injection via user-controlled inputs."""
|
||||
findings = []
|
||||
filename = os.path.basename(filepath)
|
||||
|
||||
for job_name, job in workflow.get("jobs", {}).items():
|
||||
for i, step in enumerate(job.get("steps", [])):
|
||||
run_cmd = step.get("run", "")
|
||||
if not run_cmd:
|
||||
continue
|
||||
for ctx in DANGEROUS_CONTEXTS:
|
||||
if f"${{{{ {ctx}" in run_cmd or f"${{{{{ctx}" in run_cmd:
|
||||
findings.append(SecurityFinding(
|
||||
file=filename, line=0,
|
||||
check="SCRIPT_INJECTION",
|
||||
severity="CRITICAL",
|
||||
message=f"Job '{job_name}' step {i}: '{ctx}' interpolated in run step",
|
||||
remediation="Use env variable: env: VAR: ${{ " + ctx + " }} then ${VAR}"
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
def check_pr_target(workflow: dict, filepath: str) -> list:
|
||||
"""Check for dangerous pull_request_target usage."""
|
||||
findings = []
|
||||
filename = os.path.basename(filepath)
|
||||
|
||||
triggers = workflow.get("on", {})
|
||||
if isinstance(triggers, dict) and "pull_request_target" in triggers:
|
||||
for job_name, job in workflow.get("jobs", {}).items():
|
||||
for step in job.get("steps", []):
|
||||
uses = step.get("uses", "")
|
||||
if "checkout" in uses:
|
||||
with_ref = step.get("with", {}).get("ref", "")
|
||||
if "pull_request" in with_ref or "head" in with_ref:
|
||||
findings.append(SecurityFinding(
|
||||
file=filename, line=0,
|
||||
check="PR_TARGET_CHECKOUT",
|
||||
severity="CRITICAL",
|
||||
message=f"Job '{job_name}': pull_request_target with PR code checkout",
|
||||
remediation="Never checkout PR code in pull_request_target workflows."
|
||||
))
|
||||
return findings
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="GitHub Actions Security Audit")
|
||||
parser.add_argument("--workflows-dir", required=True)
|
||||
parser.add_argument("--output", default="actions-security-report.json")
|
||||
parser.add_argument("--fail-on-findings", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
workflows_dir = os.path.abspath(args.workflows_dir)
|
||||
all_findings = []
|
||||
|
||||
workflow_files = list(Path(workflows_dir).glob("*.yml")) + list(Path(workflows_dir).glob("*.yaml"))
|
||||
print(f"[*] Auditing {len(workflow_files)} workflow files in {workflows_dir}")
|
||||
|
||||
for wf_path in workflow_files:
|
||||
workflow = load_workflow(str(wf_path))
|
||||
if not workflow:
|
||||
continue
|
||||
|
||||
all_findings.extend(check_action_pinning(workflow, str(wf_path)))
|
||||
all_findings.extend(check_permissions(workflow, str(wf_path)))
|
||||
all_findings.extend(check_script_injection(workflow, str(wf_path)))
|
||||
all_findings.extend(check_pr_target(workflow, str(wf_path)))
|
||||
|
||||
severity_counts = {}
|
||||
for f in all_findings:
|
||||
severity_counts[f.severity] = severity_counts.get(f.severity, 0) + 1
|
||||
|
||||
report = {
|
||||
"metadata": {
|
||||
"directory": workflows_dir,
|
||||
"date": datetime.now(timezone.utc).isoformat(),
|
||||
"workflows_scanned": len(workflow_files)
|
||||
},
|
||||
"summary": {
|
||||
"total_findings": len(all_findings),
|
||||
"severity_counts": severity_counts
|
||||
},
|
||||
"findings": [
|
||||
{"file": f.file, "check": f.check, "severity": f.severity,
|
||||
"message": f.message, "remediation": f.remediation}
|
||||
for f in sorted(all_findings,
|
||||
key=lambda x: {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}.get(x.severity, 4))
|
||||
]
|
||||
}
|
||||
|
||||
output_path = os.path.abspath(args.output)
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"[*] Report: {output_path}")
|
||||
|
||||
for f in all_findings:
|
||||
print(f" [{f.severity}] {f.file}: {f.message}")
|
||||
|
||||
passed = len(all_findings) == 0
|
||||
print(f"\n[{'PASS' if passed else 'FAIL'}] {len(all_findings)} security findings")
|
||||
|
||||
if args.fail_on_findings and not passed:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user