diff --git a/.checkov.baseline b/.checkov.baseline
new file mode 100644
index 0000000..a535f4d
--- /dev/null
+++ b/.checkov.baseline
@@ -0,0 +1,59 @@
+{
+ "failed_checks": [
+ {
+ "file": "/.github/workflows/checkov-scan.yaml",
+ "findings": [
+ {
+ "resource": "on(Security Scan)",
+ "check_ids": [
+ "CKV2_GHA_1"
+ ]
+ }
+ ]
+ },
+ {
+ "file": "/.github/workflows/deployment-status.yaml",
+ "findings": [
+ {
+ "resource": "on(deployment-status)",
+ "check_ids": [
+ "CKV2_GHA_1"
+ ]
+ }
+ ]
+ },
+ {
+ "file": "/.github/workflows/global-variables.yaml",
+ "findings": [
+ {
+ "resource": "on(global-variables)",
+ "check_ids": [
+ "CKV2_GHA_1"
+ ]
+ }
+ ]
+ },
+ {
+ "file": "/.github/workflows/test-security-scan.yaml",
+ "findings": [
+ {
+ "resource": "on(Test Security Scan Workflows)",
+ "check_ids": [
+ "CKV2_GHA_1"
+ ]
+ }
+ ]
+ },
+ {
+ "file": "/.github/workflows/trivy-scan.yaml",
+ "findings": [
+ {
+ "resource": "on(Security Scan)",
+ "check_ids": [
+ "CKV2_GHA_1"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.github/workflows/checkov-scan.yaml b/.github/workflows/checkov-scan.yaml
new file mode 100644
index 0000000..d3b44b5
--- /dev/null
+++ b/.github/workflows/checkov-scan.yaml
@@ -0,0 +1,125 @@
+# .github/workflows/security-scan.yaml
+name: Security Scan
+
+on:
+ workflow_call:
+ inputs:
+ baseline:
+ description: 'Path to the Checkov baseline file (default: none)'
+ default: ''
+ required: false
+ type: string
+ path:
+ description: 'Directory path where the scan should be performed (default: .)'
+ required: false
+ default: '.'
+ type: string
+ soft-fail-on:
+ description: 'Lowest severity level to cause a failed scan (default: LOW)'
+ required: false
+ default: 'LOW'
+ type: string
+ use-test-reporter:
+ description: 'Attach the test results as a report (default: true)'
+ required: false
+ default: true
+ type: boolean
+ issue-on-findings:
+ description: 'One GitHub user to mention when creating an issue for failed scans (e.g., username). If left empty, no issue will be created.'
+ required: false
+ default: ''
+ type: string
+
+jobs:
+
+ checkov_scan:
+ outputs:
+ NOTIFICATION: ${{ steps.scan.outcome == 'failure' && 'true' || 'false' }}
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: checkout repository
+ uses: actions/checkout@v4
+
+ - name: checkout security-scanning scripts
+ uses: actions/checkout@v4
+ with:
+ repository: zweitag/github-actions
+ ref: chore--create-configurable-Security-Scan
+ path: _security-tools
+ sparse-checkout: security-scanning
+
+ - name: create output folder
+ run: mkdir -p ./scan-results
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.13"
+
+ - name: setup Checkov
+ run: pip install checkov
+
+ - name: run Checkov
+ id: scan
+ env:
+ BASELINE: ${{ inputs.baseline != '' && format('--baseline {0}', inputs.baseline) || '' }}
+ SOFTFAIL: ${{ inputs.soft-fail-on != '' && format('--soft-fail-on {0}', inputs.soft-fail-on) || '' }}
+ run: |
+ checkov \
+ --directory ${{ inputs.path }} \
+ --output json \
+ $BASELINE \
+ $SOFTFAIL > ./scan-results/checkov.json
+
+ - name: convert Checkov report to CTRF format
+ if: always() && inputs.use-test-reporter
+ run: |
+ python3 _security-tools/security-scanning/checkov2ctrf.py \
+ ./scan-results/checkov.json \
+ ./scan-results/checkov.ctrf.json
+
+ - name: Publish Test Report
+ if: always() && inputs.use-test-reporter
+ uses: ctrf-io/github-test-reporter@v1
+ with:
+ report-path: './scan-results/checkov.ctrf.json'
+ template-path: '_security-tools/security-scanning/config_scan_template.hbs'
+ custom-report: true
+
+ create_issue:
+ needs: [checkov_scan]
+ runs-on: ubuntu-latest
+ if: ${{ inputs.issue-on-findings != 'false' }}
+
+ steps:
+ - name: Create issue/Comment on issue
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const repo = context.repo.repo;
+ const owner = context.repo.owner;
+ const issue_title = 'Security scan failed';
+ const issue_body = '@${{ inputs.issue-on-findings }} One or more security scans failed. Please check the workflow run for more information: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\nPlease check if the vulnerabilities are fixable. If there is a fix: Create a ticket for the fix or resolve it.\n'
+ const existing_issue = await github.rest.issues.listForRepo({
+ owner,
+ repo,
+ state: 'open',
+ labels: 'security-scan-failure'
+ });
+ if (existing_issue.data.length === 0) {
+ await github.rest.issues.create({
+ owner,
+ repo,
+ title: issue_title,
+ body: issue_body,
+ labels: ['security-scan-failure']
+ });
+ } else {
+ const issue_number = existing_issue.data[0].number;
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number,
+ body: issue_body
+ });
+ }
diff --git a/.github/workflows/test-security-scan.yaml b/.github/workflows/test-security-scan.yaml
new file mode 100644
index 0000000..d55c1c4
--- /dev/null
+++ b/.github/workflows/test-security-scan.yaml
@@ -0,0 +1,67 @@
+# .github/workflows/test-security-scan.yaml
+name: Test Security Scan Workflows
+
+on:
+ pull_request:
+ paths:
+ - '.github/workflows/trivy-scan.yaml'
+ - '.github/workflows/checkov-scan.yaml'
+ - '.github/workflows/test-security-scan.yaml'
+ - 'security-scanning/**'
+ push:
+ branches:
+ - main
+ paths:
+ - '.github/workflows/trivy-scan.yaml'
+ - '.github/workflows/checkov-scan.yaml'
+ - '.github/workflows/test-security-scan.yaml'
+ - 'security-scanning/**'
+
+jobs:
+ # Trivy Scan Tests
+
+ test-trivy-filesystem:
+ name: Test Trivy Filesystem Scan
+ uses: ./.github/workflows/trivy-scan.yaml
+ with:
+ scan-type: filesystem
+ severity-level: CRITICAL
+ use-test-reporter: true
+ check-secrets: true
+ issue-on-findings: ''
+ secrets:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ test-trivy-config:
+ name: Test Trivy Config Scan
+ uses: ./.github/workflows/trivy-scan.yaml
+ with:
+ scan-type: config
+ severity-level: CRITICAL
+ use-test-reporter: true
+ issue-on-findings: ''
+
+ test-trivy-image:
+ name: Test Trivy Image Scan
+ uses: ./.github/workflows/trivy-scan.yaml
+ with:
+ scan-type: image
+ path: './security-scanning/tests'
+ severity-level: CRITICAL
+ use-test-reporter: true
+ issue-on-findings: ''
+ secrets:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ # ==========================================
+ # Checkov Scan Tests
+ # ==========================================
+
+ test-checkov:
+ name: Test Checkov Scan
+ uses: ./.github/workflows/checkov-scan.yaml
+ with:
+ soft-fail-on: CRITICAL
+ use-test-reporter: true
+ baseline: '.checkov.baseline'
+ issue-on-findings: ''
diff --git a/.github/workflows/trivy-scan.yaml b/.github/workflows/trivy-scan.yaml
new file mode 100644
index 0000000..2895cf7
--- /dev/null
+++ b/.github/workflows/trivy-scan.yaml
@@ -0,0 +1,251 @@
+# .github/workflows/trivy-scan.yaml
+name: Security Scan
+
+on:
+ workflow_call:
+ inputs:
+ scan-type:
+ description: 'Type of scan to perform: "image", "filesystem" (default), or "config"'
+ required: false
+ default: 'filesystem'
+ type: string
+ check-secrets:
+ description: 'Whether to check for secrets in filesystem scans (default: false)'
+ required: false
+ default: false
+ type: boolean
+ severity-level:
+ description: 'Severity levels to report (default: HIGH,CRITICAL,UNKNOWN)'
+ required: false
+ default: 'HIGH,CRITICAL,UNKNOWN'
+ type: string
+ ignorefile:
+ description: 'Path to the Trivy ignore file (default: none)'
+ default: ''
+ required: false
+ type: string
+ path:
+ description: 'Directory path where the scan should be performed (default: .) (for image scans, the directory must contain the Dockerfile)'
+ required: false
+ default: '.'
+ type: string
+ use-test-reporter:
+ description: 'Whether to attach the test results as a report (default: true)'
+ required: false
+ default: true
+ type: boolean
+ issue-on-findings:
+ description: 'One GitHub user to mention when creating an issue for failed scans (e.g., username). If left empty, no issue will be created.'
+ required: false
+ default: ''
+ type: string
+ secrets:
+ GH_TOKEN:
+ description: 'GitHub Token for downloading the Trivy DB'
+ required: false
+ DOCKER_IMAGE_SECRETS:
+ description: 'Docker Image Build Secrets'
+ required: false
+
+env:
+ TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db,aquasec/trivy-db,ghcr.io/aquasecurity/trivy-db
+
+jobs:
+ security_scan:
+ outputs:
+ NOTIFICATION: ${{ steps.scan.outcome == 'failure' && 'true' || 'false' }}
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: checkout repository
+ uses: actions/checkout@v4
+
+ - name: checkout security-scanning scripts
+ uses: actions/checkout@v4
+ with:
+ repository: zweitag/github-actions
+ ref: chore--create-configurable-Security-Scan
+ path: _security-tools
+ sparse-checkout: security-scanning
+
+ - name: create output folder
+ run: mkdir -p ./scan-results
+
+ - name: setup trivy
+ uses: aquasecurity/setup-trivy@e07451d2e059ed86c2870430ea286b3a9e0bf241
+
+ - name: download vulnerabilities database
+ if: inputs.scan-type != 'config'
+ run: |
+ MAX_RETRIES=3
+ RETRY_DELAY=60
+ ATTEMPT=1
+
+ until [ $ATTEMPT -gt $MAX_RETRIES ]
+ do
+ echo "Attempt $ATTEMPT of $MAX_RETRIES..."
+ GITHUB_TOKEN=${{ secrets.GH_TOKEN }} trivy fs --download-db-only --db-repository "${{ env.TRIVY_DB_REPOSITORY }}"
+
+ if [ $? -eq 0 ]; then
+ echo "Trivy DB download succeeded."
+ exit 0
+ else
+ echo "Trivy DB download failed. Retrying in $RETRY_DELAY seconds..."
+ ATTEMPT=$((ATTEMPT + 1))
+ sleep $RETRY_DELAY
+ fi
+ done
+
+ echo "Trying fallback without custom repository..."
+ GITHUB_TOKEN=${{ secrets.GH_TOKEN }} trivy fs --download-db-only
+
+ - name: scan for full report (config)
+ if: inputs.scan-type == 'config'
+ run: trivy config ${{ inputs.path }} --exit-code 0
+
+ - name: scan for full report (filesystem)
+ if: inputs.scan-type == 'filesystem'
+ run: trivy fs ${{ inputs.path }} --exit-code 0 --skip-db-update
+
+ - name: build docker image
+ if: inputs.scan-type == 'image'
+ uses: docker/build-push-action@v4
+ with:
+ context: ${{ inputs.path }}
+ push: false
+ tags: security-scan-image
+ secrets: ${{ secrets.DOCKER_IMAGE_SECRETS }}
+
+ - name: scan for full report (image)
+ if: inputs.scan-type == 'image'
+ run: trivy image security-scan-image --exit-code 0 --skip-db-update --scanners vuln --timeout 10m --list-all-pkgs
+
+ - name: scan with configured severities (config)
+ if: inputs.scan-type == 'config'
+ id: scan-config
+ continue-on-error: true
+ env:
+ REPORT: ${{ inputs.use-test-reporter && '-f json -o ./scan-results/trivy.json' || '' }}
+ IGNOREFILE: ${{ inputs.ignorefile != '' && format('--ignorefile {0}', inputs.ignorefile) || '' }}
+ SEVERITY: ${{ inputs.severity-level }}
+ run: |
+ trivy config ${{ inputs.path }} \
+ --exit-code 1 \
+ --severity $SEVERITY \
+ --timeout 10m \
+ $IGNOREFILE \
+ $REPORT
+
+ - name: scan with configured severities (filesystem)
+ if: inputs.scan-type == 'filesystem'
+ id: scan-filesystem
+ continue-on-error: true
+ env:
+ REPORT: ${{ inputs.use-test-reporter && '-f json -o ./scan-results/trivy.json' || '' }}
+ IGNOREFILE: ${{ inputs.ignorefile != '' && format('--ignorefile {0}', inputs.ignorefile) || '' }}
+ SCANNERS: ${{ !inputs.check-secrets && '--scanners vuln' || '' }}
+ SEVERITY: ${{ inputs.severity-level }}
+ run: |
+ trivy fs ${{ inputs.path }} \
+ --exit-code 1 \
+ --skip-db-update \
+ --severity $SEVERITY \
+ $SCANNERS \
+ $IGNOREFILE \
+ $REPORT
+
+ - name: scan with configured severities (image)
+ if: inputs.scan-type == 'image'
+ id: scan-image
+ continue-on-error: true
+ env:
+ REPORT: ${{ inputs.use-test-reporter && '-f json -o ./scan-results/trivy.json' || '' }}
+ IGNOREFILE: ${{ inputs.ignorefile != '' && format('--ignorefile {0}', inputs.ignorefile) || '' }}
+ SEVERITY: ${{ inputs.severity-level }}
+ run: |
+ trivy image security-scan-image \
+ --exit-code 1 \
+ --skip-db-update \
+ --severity $SEVERITY \
+ --scanners vuln \
+ --timeout 10m \
+ $IGNOREFILE \
+ $REPORT
+
+ - name: set scan outcome
+ id: scan
+ if: always()
+ run: |
+ if [[ "${{ steps.scan-config.outcome }}" == "failure" ]] || \
+ [[ "${{ steps.scan-filesystem.outcome }}" == "failure" ]] || \
+ [[ "${{ steps.scan-image.outcome }}" == "failure" ]]; then
+ echo "outcome=failure" >> $GITHUB_OUTPUT
+ exit 1
+ fi
+
+ - name: convert Trivy report to CTRF format (config)
+ if: always() && inputs.use-test-reporter && inputs.scan-type == 'config'
+ run: |
+ python3 _security-tools/security-scanning/trivyconfig2ctrf.py \
+ ./scan-results/trivy.json \
+ ./scan-results/trivy.ctrf.json
+
+ - name: convert Trivy report to CTRF format (image)
+ if: ${{ always() && inputs.use-test-reporter && inputs.scan-type == 'image' }}
+ run: |
+ python3 _security-tools/security-scanning/trivyimage2ctrf.py \
+ ./scan-results/trivy.json \
+ ./scan-results/trivy.ctrf.json
+
+ - name: convert Trivy report to CTRF format (filessystem)
+ if: ${{ always() && inputs.use-test-reporter && inputs.scan-type == 'filesystem' }}
+ run: |
+ python3 _security-tools/security-scanning/trivyfs2ctrf.py \
+ ./scan-results/trivy.json \
+ ./scan-results/trivy.ctrf.json
+
+ - name: Publish Test Report
+ if: always() && inputs.use-test-reporter
+ uses: ctrf-io/github-test-reporter@v1
+ with:
+ report-path: './scan-results/trivy.ctrf.json'
+ template-path: ${{ format('_security-tools/security-scanning/{0}_scan_template.hbs', inputs.scan-type) }}
+ custom-report: true
+
+ create_issue:
+ needs: [security_scan]
+ runs-on: ubuntu-latest
+ if: ${{ always() && inputs.issue-on-findings != '' && needs.security_scan.outputs.NOTIFICATION == 'true' }}
+
+ steps:
+ - name: Create issue/Comment on issue
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const repo = context.repo.repo;
+ const owner = context.repo.owner;
+ const issue_title = 'Security scan failed';
+ const issue_body = '@${{ inputs.issue-on-findings }} One or more security scans failed. Please check the workflow run for more information: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\nPlease check if the vulnerabilities are fixable. If there is a fix: Create a ticket for the fix or resolve it.\n'
+ const existing_issue = await github.rest.issues.listForRepo({
+ owner,
+ repo,
+ state: 'open',
+ labels: 'security-scan-failure'
+ });
+ if (existing_issue.data.length === 0) {
+ await github.rest.issues.create({
+ owner,
+ repo,
+ title: issue_title,
+ body: issue_body,
+ labels: ['security-scan-failure']
+ });
+ } else {
+ const issue_number = existing_issue.data[0].number;
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number,
+ body: issue_body
+ });
+ }
diff --git a/Readme.md b/Readme.md
index 2f45dfe..7d7b69c 100644
--- a/Readme.md
+++ b/Readme.md
@@ -41,6 +41,158 @@ ANOTHER_KEY=VALUE2
file: 'config/.env.development'
```
+## trivy-scan
+
+
+
+
+
+This Action is part of the Security-Scanning Actions. This Action is for Security-Scanning with [Trivy](https://github.com/aquasecurity/trivy) and provides a cost-effective, reusable security scanning pipeline that works in any repository without relying on paid GitHub Code Scanning features.
+
+It runs Trivy scans (filesystem, image, or configuration), converts the results into a standardized CTRF report, renders a human-friendly summary via Handlebars templates in the job output, and optionally creates or comments on a GitHub Issue when findings cause the scan to fail.
+
+### Inputs
+- scan-type (string, default: "filesystem"): Type of scan to run. One of "image", "filesystem", or "config".
+- check-secrets (boolean, default: false): Whether to include secret scanning (relevant for filesystem scans only).
+- severity-level (string, default: "HIGH,CRITICAL,UNKNOWN"): Comma-separated severities to report. Supported: "LOW,MEDIUM,HIGH,CRITICAL,UNKNOWN".
+- ignorefile (string, default: ""): Optional path to a Trivy ignore file.
+- path (string, default: "."): Directory to scan. For image scans, this directory must contain a Dockerfile.
+- use-test-reporter (boolean, default: true): Whether to render CTRF + template. If false, results are available in the scan step logs.
+- issue-on-findings (string, default: ""): GitHub username to mention. If set and the scan fails, an Issue will be created/commented and the user mentioned.
+
+### Example usage
+
+filesystem scan:
+```yaml
+jobs:
+ scan_filesystem:
+ uses: zweitag/github-actions/.github/workflows/trivy-scan.yaml@main
+ with:
+ scan-type: "filesystem"
+ path: "."
+ check-secrets: true
+ secrets:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Optional: improves reliability of Trivy DB downloads
+```
+
+image scan:
+```yaml
+jobs:
+ scan_docker_image:
+ uses: zweitag/github-actions/.github/workflows/trivy-scan.yaml@main
+ with:
+ scan-type: "image"
+ path: "."
+ severity-level: 'HIGH,CRITICAL'
+ ignorefile: "./.trivyignore.yaml"
+ use-test-reporter: true
+ issue-on-findings: "JohnDoe"
+ secrets:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Optional: improves reliability of Trivy DB downloads
+ DOCKER_IMAGE_SECRETS: ${{ secrets.DOCKER_IMAGE_SECRETS }} # Optional: if your Docker build needs secrets
+```
+
+config-scan:
+```yaml
+jobs:
+ scan_configuration:
+ uses: zweitag/github-actions/.github/workflows/trivy-scan.yaml@main
+ with:
+ scan-type: "config"
+ path: "."
+ severity-level: "LOW,MEDIUM,HIGH,CRITICAL,UNKNOWN"
+ secrets:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Optional: improves reliability of Trivy DB downloads
+```
+
+### Test-reporter
+- Embeds a human‑readable report in the workflow run (Job Summary), rendered from the CTRF JSON via Handlebars.
+- If findings are present, it shows:
+ - A compact summary table (Tests/Passed/Failed, plus severities and Secrets)
+ - A collapsible findings list with context (file/image, package/version, lines) and references/guidelines
+- If no findings match the configured severities, it displays a “no vulnerabilities found” message for quick confirmation.
+
+### Failure policy and severities
+- `severity-level` controls which findings cause the scan step to exit with code 1.
+- All findings/vulnerabilities of the set severity are marked as failed.
+- Consider adjusting `severity-level` to match your risk policy (e.g., `"HIGH,CRITICAL"`).
+
+### Why not SARIF + GitHub Code Scanning?
+- Many security tools (including Trivy) can export SARIF, which is a natural fit for GitHub Code Scanning.
+- Limitation: Comprehensive Code Scanning features for private repositories require GitHub Advanced Security (paid). Public repos are free, but most teams operate in private repos and cannot use SARIF-based Code Scanning without extra licensing.
+- So this provides a solution that works in any repo without paid GitHub features, still offering reports, visuals, and CI integration.
+
+## checkov-scan
+
+
+
+
+
+This Action is part of the Security-Scanning Actions. This Action is for Security-Scanning with [Checkov](https://github.com/bridgecrewio/checkov) and provides a cost-effective, reusable security scanning pipeline that works in any repository without relying on paid GitHub Code Scanning features.
+
+It runs Checkov scans , converts the results into a standardized CTRF report, renders a human-friendly summary via Handlebars templates in the job output, and optionally creates or comments on a GitHub Issue when findings cause the scan to fail.
+
+### Checkov
+Checkov is a static code analysis tool for infrastructure as code and a software composition analysis tool for images and open source packages.
+
+It scans cloud infrastructure provisioned using Terraform, Terraform plan, Cloudformation, AWS SAM, Kubernetes, Helm charts, Kustomize, Dockerfile, Serverless, Bicep, OpenAPI, ARM Templates, or OpenTofu and detects security and compliance misconfigurations using graph-based scanning.
+
+### Inputs
+- baseline (string, default: ""): Optional path to a Checkov baseline file to suppress previously acknowledged findings.
+- path (string, default: "."): Directory to scan.
+- soft-fail-on (string, default: "LOW"): Configures Checkov’s soft-fail behavior. Supported values:
+ - "": no soft-fail (the job fails on any finding)
+ - "LOW" | "MEDIUM" | "HIGH" | "CRITICAL": soft-fail threshold (this severity and lower do not fail the job; higher severities fail)
+ - "any": soft-fail for all findings (the job never fails)
+- use-test-reporter (boolean, default: true): Whether to render CTRF + template. If false, results are available in the scan step logs.
+- issue-on-findings (string, default: ""): GitHub username to mention. If set and the scan fails, an Issue will be created/commented and the user mentioned.
+
+### Example usage
+
+simple scan:
+```yaml
+jobs:
+ checkov_scan:
+ uses: zweitag/github-actions/.github/workflows/security-scan.yaml@main
+ with:
+ path: "."
+ use-test-reporter: true
+ secrets:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Optional: improves reliability of dependency downloads
+```
+
+scan with baseline and issue mention:
+```yaml
+jobs:
+ checkov_scan:
+ uses: zweitag/github-actions/.github/workflows/security-scan.yaml@main
+ with:
+ path: "./infra"
+ baseline: "./.checkov.baseline"
+ use-test-reporter: true
+ issue-on-findings: "JohnDoe"
+ secrets:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Optional: improves reliability of dependency downloads
+```
+
+### Test-reporter
+- Embeds a human‑readable report in the workflow run (Job Summary), rendered from the CTRF JSON via Handlebars (config_scan_template.hbs).
+- If findings are present, it shows:
+ - A compact summary table (Tests/Passed/Failed)
+ - A collapsible findings list with context (file path, optional line range) and guidelines
+- If no findings match the configured policy, it displays a “no misconfigurations found” message for quick confirmation.
+
+### Failure policy
+- The workflow uses soft-fail-on as failure controller:
+ - "": no soft-fail (the job fails on any finding)
+ - "LOW" (default) | "MEDIUM" | "HIGH" | "CRITICAL": soft-fail threshold
+- Findings of higher severity cause the step to fail (and may trigger issue creation).
+- You can use a baseline file to suppress known findings and reduce noise.
+
+### Why not SARIF + GitHub Code Scanning?
+While Checkov can integrate with Code Scanning, comprehensive features for private repositories require GitHub Advanced Security (paid).
+This workflow provides standardized reports (CTRF), visual summaries, and optional issue creation without relying on paid features, making it suitable for any repository.
+
# License
Copyright 2019 Zweitag GmbH
diff --git a/security-scanning/checkov2ctrf.py b/security-scanning/checkov2ctrf.py
new file mode 100644
index 0000000..74c46e5
--- /dev/null
+++ b/security-scanning/checkov2ctrf.py
@@ -0,0 +1,72 @@
+import json
+import sys
+
+def extract_checks(target, status):
+ # extracts checks of a check_type (e.g. terraform) with a status ('fail' oder 'pass').
+ checks = []
+ key = "failed_checks" if status == "fail" else "passed_checks"
+ for check in target.get("results", {}).get(key, []):
+ checks.append({
+ "name": check.get("check_name"),
+ "status": "passed" if status == "pass" else "failed",
+ "duration": 1,
+ "file": check.get("file_path"),
+ "lines": check.get("file_line_range"),
+ "guideline": check.get("guideline") if check.get("guideline") != 'null' else "",
+ })
+ return checks
+
+def checkov_to_ctrf(checkov_json):
+ tests = []
+
+ if isinstance(checkov_json, list):
+ for target in checkov_json:
+ tests.extend(extract_checks(target, "fail"))
+ tests.extend(extract_checks(target, "pass"))
+ elif isinstance(checkov_json, dict):
+ tests.extend(extract_checks(checkov_json, "fail"))
+ tests.extend(extract_checks(checkov_json, "pass"))
+ else:
+ raise ValueError("Unerwartetes JSON-Format!")
+
+ total = len(tests)
+ passed = sum(1 for t in tests if t["status"] == "passed")
+ failed = sum(1 for t in tests if t["status"] == "failed")
+ pending = 0
+ skipped = 0
+ other = 0
+ start = 0
+ stop = 1
+ return {
+ "results": {
+ "tool": {
+ "name": "Checkov "
+ },
+ "summary": {
+ "tests": total,
+ "passed": passed,
+ "failed": failed,
+ "pending": pending,
+ "skipped": skipped,
+ "other": other,
+ "start": start,
+ "stop": stop
+ },
+ "tests": tests,
+ "environment": {
+ "appName": "kamium-deployment",
+ "buildName": "kamium-deployment",
+ "buildNumber": "1"
+ }
+ }
+ }
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("Usage: python checkov2ctrf.py ")
+ sys.exit(1)
+ with open(sys.argv[1]) as old_f:
+ checkov_json = json.load(old_f)
+ ctrf_json = checkov_to_ctrf(checkov_json)
+ with open(sys.argv[2], "w") as new_f:
+ json.dump(ctrf_json, new_f, indent=2)
diff --git a/security-scanning/config_scan_template.hbs b/security-scanning/config_scan_template.hbs
new file mode 100644
index 0000000..10c2352
--- /dev/null
+++ b/security-scanning/config_scan_template.hbs
@@ -0,0 +1,113 @@
+## Summary of {{ctrf.tool.name}} Security Scan Results
+
+
+
+
+ | Tests 📝 |
+ Passed ✅ |
+ Failed ❌ |
+ {{#if ctrf.extensions.severityCounts.LOW}}
+ LOW 🟢 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.MEDIUM}}
+ MEDIUM 🟡 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.HIGH}}
+ HIGH 🔴 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.CRITICAL}}
+ CRITICAL 🚨 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.UNKNOWN}}
+ UNKNOWN ❓ |
+ {{/if}}
+
+
+
+
+ | {{ctrf.summary.tests}} |
+ {{ctrf.summary.passed}} |
+ {{ctrf.summary.failed}} |
+ {{#if ctrf.extensions.severityCounts.LOW}}
+ {{ctrf.extensions.severityCounts.LOW}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.MEDIUM}}
+ {{ctrf.extensions.severityCounts.MEDIUM}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.HIGH}}
+ {{ctrf.extensions.severityCounts.HIGH}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.CRITICAL}}
+ {{ctrf.extensions.severityCounts.CRITICAL}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.UNKNOWN}}
+ {{ctrf.extensions.severityCounts.UNKNOWN}} |
+ {{/if}}
+ {{#if ctrf.extensions.secretsCount}}
+ {{ctrf.extensions.secretsCount}} |
+ {{/if}}
+
+
+
+
+{{#if (eq ctrf.summary.passed ctrf.summary.tests)}}
+### All Tests Passed! 🎉
+{{else}}
+## Findings:
+{{#each ctrf.tests}}
+{{#if (eq status "failed")}}
+
+ 🔴 {{file}} - {{name}} ({{severity}})
+
+
+ | file |
+ {{file}} |
+
+ {{#if lines}}
+
+ | lines |
+ {{lines}} |
+
+ {{/if}}
+ {{#if severity}}
+
+ | severity |
+ {{severity}} |
+
+ {{/if}}
+ {{#if guideline}}
+
+ | guideline |
+
+ {{guideline}}
+ |
+
+ {{/if}}
+ {{#if description}}
+
+ | description |
+ {{description}} |
+
+ {{/if}}
+ {{#if resolution}}
+
+ | proposed solution |
+ {{resolution}} |
+
+ {{/if}}
+ {{#if references}}
+
+ | references |
+
+ {{#each references}}
+ {{this}}{{#unless @last}}, {{/unless}}
+ {{/each}}
+ |
+
+ {{/if}}
+
+
+
+{{/if}}
+{{/each}}
+{{/if}}
diff --git a/security-scanning/filesystem_scan_template.hbs b/security-scanning/filesystem_scan_template.hbs
new file mode 100644
index 0000000..f0158a0
--- /dev/null
+++ b/security-scanning/filesystem_scan_template.hbs
@@ -0,0 +1,120 @@
+## Summary of {{ctrf.tool.name}} Security Scan Results
+
+
+
+
+ {{#if ctrf.extensions.severityCounts.LOW}}
+ | LOW 🟢 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.MEDIUM}}
+ MEDIUM 🟡 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.HIGH}}
+ HIGH 🔴 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.CRITICAL}}
+ CRITICAL 🚨 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.UNKNOWN}}
+ UNKNOWN ❓ |
+ {{/if}}
+ {{#if ctrf.extensions.secretsCount}}
+ SECRETS 🔑 |
+ {{/if}}
+
+
+
+
+ {{#if ctrf.extensions.severityCounts.LOW}}
+ | {{ctrf.extensions.severityCounts.LOW}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.MEDIUM}}
+ {{ctrf.extensions.severityCounts.MEDIUM}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.HIGH}}
+ {{ctrf.extensions.severityCounts.HIGH}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.CRITICAL}}
+ {{ctrf.extensions.severityCounts.CRITICAL}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.UNKNOWN}}
+ {{ctrf.extensions.severityCounts.UNKNOWN}} |
+ {{/if}}
+ {{#if ctrf.extensions.secretsCount}}
+ {{ctrf.extensions.secretsCount}} |
+ {{/if}}
+
+
+
+
+{{#if (eq ctrf.summary.failed 0)}}
+### No Vulnerabilities or Secrets Found! 🎉
+{{else}}
+## Findings:
+{{#each ctrf.tests}}
+
+ {{#if (eq type "secret")}}🔑{{else}}🔴{{/if}} {{name}} ({{severity}})
+
+
+ | file |
+ {{file}} |
+
+ {{#if lines}}
+
+ | lines |
+ {{lines}} |
+
+ {{/if}}
+ {{#if package}}
+
+ | package |
+ {{package}} |
+
+ {{/if}}
+ {{#if installedVersion}}
+
+ | installed version |
+ {{installedVersion}} |
+
+ {{/if}}
+ {{#if fixedVersion}}
+
+ | fixed in |
+ {{fixedVersion}} |
+
+ {{/if}}
+ {{#if guideline}}
+
+ | details |
+
+ {{guideline}}
+ |
+
+ {{/if}}
+ {{#if description}}
+
+ | description |
+ {{description}} |
+
+ {{/if}}
+ {{#if resolution}}
+
+ | proposed solution |
+ {{resolution}} |
+
+ {{/if}}
+ {{#if references}}
+
+ | references |
+
+ {{#each references}}
+ {{this}}{{#unless @last}}, {{/unless}}
+ {{/each}}
+ |
+
+ {{/if}}
+
+
+
+{{/each}}
+{{/if}}
diff --git a/security-scanning/image_scan_template.hbs b/security-scanning/image_scan_template.hbs
new file mode 100644
index 0000000..4aceb0c
--- /dev/null
+++ b/security-scanning/image_scan_template.hbs
@@ -0,0 +1,103 @@
+## Summary of {{ctrf.tool.name}} Security Scan Results
+
+
+
+
+ {{#if ctrf.extensions.severityCounts.LOW}}
+ | LOW 🟢 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.MEDIUM}}
+ MEDIUM 🟡 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.HIGH}}
+ HIGH 🔴 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.CRITICAL}}
+ CRITICAL 🚨 |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.UNKNOWN}}
+ UNKNOWN ❓ |
+ {{/if}}
+
+
+
+
+ {{#if ctrf.extensions.severityCounts.LOW}}
+ | {{ctrf.extensions.severityCounts.LOW}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.MEDIUM}}
+ {{ctrf.extensions.severityCounts.MEDIUM}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.HIGH}}
+ {{ctrf.extensions.severityCounts.HIGH}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.CRITICAL}}
+ {{ctrf.extensions.severityCounts.CRITICAL}} |
+ {{/if}}
+ {{#if ctrf.extensions.severityCounts.UNKNOWN}}
+ {{ctrf.extensions.severityCounts.UNKNOWN}} |
+ {{/if}}
+
+
+
+
+{{#if (eq ctrf.summary.passed ctrf.summary.tests)}}
+### No vulnerabilities with set severity found! 🎉
+{{else}}
+## Findings:
+{{#each ctrf.tests}}
+
+ 🔴 {{pkgName}}@{{installedVersion}} - {{id}} ({{severity}})
+
+
+ | image |
+ {{image}} |
+
+ {{#if package}}
+
+ | package |
+ {{package}} |
+
+ {{/if}}
+ {{#if installedVersion}}
+
+ | installed version |
+ {{installedVersion}} |
+
+ {{/if}}
+ {{#if fixedVersion}}
+
+ | fixed in |
+ {{fixedVersion}} |
+
+ {{/if}}
+ {{#if status}}
+
+ | status |
+ {{status}} |
+
+ {{/if}}
+ {{#if severity}}
+
+ | severity |
+ {{severity}} |
+
+ {{/if}}
+ {{#if description}}
+
+ | description |
+ {{description}} |
+
+ {{/if}}
+ {{#if source}}
+
+ | source |
+
+ {{source.URL}}
+ |
+
+ {{/if}}
+
+
+{{/each}}
+{{/if}}
diff --git a/security-scanning/tests/Dockerfile b/security-scanning/tests/Dockerfile
new file mode 100644
index 0000000..7f69534
--- /dev/null
+++ b/security-scanning/tests/Dockerfile
@@ -0,0 +1,10 @@
+# security-scanning/tests/Dockerfile
+# Minimal test image for security scan workflow testing
+
+FROM alpine:3.23
+
+USER testuser
+
+CMD ["echo", "This is a test image for security scan workflow testing."]
+
+HEALTHCHECK NONE
diff --git a/security-scanning/trivyconfig2ctrf.py b/security-scanning/trivyconfig2ctrf.py
new file mode 100644
index 0000000..1603c4b
--- /dev/null
+++ b/security-scanning/trivyconfig2ctrf.py
@@ -0,0 +1,116 @@
+import json
+import sys
+
+
+def extract_checks_from_trivy_result(result):
+ checks = []
+ misconfigs = result.get("Misconfigurations", []) or []
+ for misconf in misconfigs:
+ lines = None
+ cause = misconf.get("CauseMetadata", {}) or {}
+ if "StartLine" in cause and "EndLine" in cause:
+ lines = [cause["StartLine"], cause["EndLine"]]
+ elif "Code" in cause and "Lines" in (cause.get("Code", {}) or {}) and cause["Code"]["Lines"]:
+ code_lines = cause["Code"]["Lines"]
+ if isinstance(code_lines, list) and code_lines:
+ lines = [code_lines[0].get("Number"), code_lines[-1].get("Number")]
+
+ checks.append({
+ "name": misconf.get("Title", misconf.get("ID", "")),
+ "status": "failed" if misconf.get("Status") == "FAIL" else "passed",
+ "duration": 1,
+ "file": result.get("Target", ""),
+ "lines": lines,
+ "guideline": misconf.get("PrimaryURL", ""),
+ "severity": misconf.get("Severity", ""),
+ "description": misconf.get("Description", ""),
+ "message": misconf.get("Message", ""),
+ "resolution": misconf.get("Resolution", ""),
+ "references": misconf.get("References", []),
+ "type": misconf.get("Type", ""),
+ "id": misconf.get("ID", ""),
+ })
+ return checks
+
+
+def trivy_to_ctrf(trivy_json):
+ tests = []
+ successes_sum = 0
+
+ severity_counts = {
+ "UNKNOWN": 0,
+ "LOW": 0,
+ "MEDIUM": 0,
+ "HIGH": 0,
+ "CRITICAL": 0
+ }
+
+ results = trivy_json.get("Results", []) or []
+ for result in results:
+ tests.extend(extract_checks_from_trivy_result(result))
+
+ misconf_summary = result.get("MisconfSummary", {}) or {}
+ successes_sum += misconf_summary.get("Successes", 0) or 0
+
+ for misconf in result.get("Misconfigurations", []) or []:
+ if misconf.get("Status") == "FAIL":
+ sev = str(misconf.get("Severity", "")).upper().strip()
+ if sev in severity_counts:
+ severity_counts[sev] += 1
+ else:
+ severity_counts["UNKNOWN"] += 1
+
+ total = len(tests) + successes_sum
+ passed = successes_sum
+ failed = sum(1 for t in tests if t.get("status") == "failed")
+ pending = 0
+ skipped = 0
+ other = 0
+ start = 0
+ stop = 1
+
+ severity_counts_nonzero = {k: v for k, v in severity_counts.items() if v}
+ extensions = {}
+
+ if severity_counts_nonzero:
+ extensions["severityCounts"] = severity_counts_nonzero
+
+ result_obj = {
+ "results": {
+ "tool": {
+ "name": "Trivy Configuration"
+ },
+ "summary": {
+ "tests": total,
+ "passed": passed,
+ "failed": failed,
+ "pending": pending,
+ "skipped": skipped,
+ "other": other,
+ "start": start,
+ "stop": stop
+ },
+ "tests": tests,
+ "environment": {
+ "appName": "kamium-elastic",
+ "buildName": "kamium-elastic",
+ "buildNumber": "1"
+ }
+ }
+ }
+
+ if extensions:
+ result_obj["results"]["extensions"] = extensions
+
+ return result_obj
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("Usage: python trivy2ctrf.py ")
+ sys.exit(1)
+ with open(sys.argv[1]) as old_f:
+ trivy_json = json.load(old_f)
+ ctrf_json = trivy_to_ctrf(trivy_json)
+ with open(sys.argv[2], "w") as new_f:
+ json.dump(ctrf_json, new_f, indent=2)
diff --git a/security-scanning/trivyfs2ctrf.py b/security-scanning/trivyfs2ctrf.py
new file mode 100644
index 0000000..5c1e85b
--- /dev/null
+++ b/security-scanning/trivyfs2ctrf.py
@@ -0,0 +1,135 @@
+import json
+import sys
+
+
+def extract_vulnerabilities_from_result(result):
+ """Extract vulnerabilities from a Trivy result."""
+ tests = []
+ vulnerabilities = result.get("Vulnerabilities", []) or []
+
+ for vuln in vulnerabilities:
+ tests.append({
+ "name": f"{vuln.get('PkgName', '')}@{vuln.get('InstalledVersion', '')} - {vuln.get('VulnerabilityID', '')}",
+ "status": "failed",
+ "duration": 1,
+ "file": result.get("Target", ""),
+ "lines": None,
+ "guideline": vuln.get("PrimaryURL", ""),
+ "severity": vuln.get("Severity", ""),
+ "description": vuln.get("Description", ""),
+ "message": vuln.get("Title", ""),
+ "resolution": f"Update to version {vuln.get('FixedVersion', 'N/A')}" if vuln.get("FixedVersion") else "No fix available",
+ "references": vuln.get("References", []),
+ "type": result.get("Type", ""),
+ "id": vuln.get("VulnerabilityID", ""),
+ "package": vuln.get("PkgName", ""),
+ "installedVersion": vuln.get("InstalledVersion", ""),
+ "fixedVersion": vuln.get("FixedVersion", ""),
+ })
+
+ return tests
+
+
+def extract_secrets_from_result(result):
+ """Extract secrets from a Trivy result."""
+ tests = []
+ secrets = result.get("Secrets", []) or []
+
+ for secret in secrets:
+ lines = None
+ if secret.get("StartLine") and secret.get("EndLine"):
+ lines = [secret["StartLine"], secret["EndLine"]]
+
+ tests.append({
+ "name": f"{secret.get('RuleID', '')} - {secret.get('Title', '')}",
+ "status": "failed",
+ "duration": 1,
+ "file": result.get("Target", ""),
+ "lines": lines,
+ "guideline": "",
+ "severity": secret.get("Severity", ""),
+ "description": f"Category: {secret.get('Category', '')}",
+ "message": secret.get("Match", ""),
+ "resolution": "Remove or rotate the exposed secret",
+ "references": [],
+ "type": "secret",
+ "id": secret.get("RuleID", ""),
+ })
+
+ return tests
+
+
+def trivy_fs_to_ctrf(trivy_json):
+ """Convert Trivy filesystem scan JSON to CTRF format."""
+ tests = []
+ results = trivy_json.get("Results", []) or []
+
+ severity_counts = {
+ "UNKNOWN": 0,
+ "LOW": 0,
+ "MEDIUM": 0,
+ "HIGH": 0,
+ "CRITICAL": 0
+ }
+
+ secrets_count = 0
+
+ for result in results:
+ tests.extend(extract_vulnerabilities_from_result(result))
+ tests.extend(extract_secrets_from_result(result))
+
+ for vuln in result.get("Vulnerabilities", []) or []:
+ sev = str(vuln.get("Severity", "")).upper().strip()
+ if sev in severity_counts:
+ severity_counts[sev] += 1
+ else:
+ severity_counts["UNKNOWN"] += 1
+
+ secrets_count += len(result.get("Secrets", []) or [])
+
+ failed = len(tests)
+
+ severity_counts_nonzero = {k: v for k, v in severity_counts.items() if v}
+ extensions = {}
+
+ if severity_counts_nonzero:
+ extensions["severityCounts"] = severity_counts_nonzero
+
+ if secrets_count:
+ extensions["secretsCount"] = secrets_count
+
+ result_obj = {
+ "results": {
+ "tool": {
+ "name": "Trivy Filesystem"
+ },
+ "summary": {
+ "failed": failed
+ },
+ "tests": tests,
+ "environment": {
+ "appName": trivy_json.get("ArtifactName", ""),
+ "buildName": trivy_json.get("Metadata", {}).get("Branch", ""),
+ "buildNumber": (trivy_json.get("Metadata", {}).get("Commit", "") or "")[:8]
+ }
+ }
+ }
+
+ if extensions:
+ result_obj["results"]["extensions"] = extensions
+
+ return result_obj
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("Usage: python trivyfs2ctrf.py ")
+ sys.exit(1)
+
+ with open(sys.argv[1]) as f:
+ trivy_json = json.load(f)
+
+ ctrf_json = trivy_fs_to_ctrf(trivy_json)
+
+ with open(sys.argv[2], "w") as f:
+ json.dump(ctrf_json, f, indent=2)
diff --git a/security-scanning/trivyimage2ctrf.py b/security-scanning/trivyimage2ctrf.py
new file mode 100644
index 0000000..669928a
--- /dev/null
+++ b/security-scanning/trivyimage2ctrf.py
@@ -0,0 +1,105 @@
+import json
+import sys
+
+
+def extract_checks_from_trivy_result(target):
+ checks = []
+ vulnerabilities = target.get("Vulnerabilities", []) or []
+
+ for vuln in vulnerabilities:
+ checks.append({
+ "name": vuln.get("PkgID", ""),
+ "status": vuln.get("Status", "unknown"),
+ "duration": 1,
+ "severity": vuln.get("Severity", ""),
+ "id": vuln.get("VulnerabilityID", ""),
+ "pkgName": vuln.get("PkgName", ""),
+ "installedVersion": vuln.get("InstalledVersion", ""),
+ "fixedVersion": vuln.get("FixedVersion", "no fix available"),
+ "image": target.get("Target", ""),
+ "source": vuln.get("DataSource", []),
+ "description": vuln.get("Description", ""),
+ "references": vuln.get("References", [])
+ })
+ return checks
+
+
+def trivy_to_ctrf(trivy_json):
+ tests = []
+ successes_sum = 0
+ severity_counts = {
+ "UNKNOWN": 0,
+ "LOW": 0,
+ "MEDIUM": 0,
+ "HIGH": 0,
+ "CRITICAL": 0
+ }
+
+ results = trivy_json.get("Results", []) or []
+ for result in results:
+ result_checks = extract_checks_from_trivy_result(result)
+ tests.extend(result_checks)
+
+ misconf_summary = result.get("MisconfSummary", {}) or {}
+ successes_sum += misconf_summary.get("Successes", 0) or 0
+
+ for t in tests:
+ sev = str(t.get("severity", "")).upper().strip()
+ if sev in severity_counts:
+ severity_counts[sev] += 1
+ else:
+ severity_counts["UNKNOWN"] += 1
+
+ total = len(tests)
+ passed = 0
+ failed = len(tests)
+ pending = 0
+ skipped = 0
+ other = 0
+ start = 0
+ stop = 1
+
+ severity_counts_nonzero = {k: v for k, v in severity_counts.items() if v}
+ extensions = {}
+ if severity_counts_nonzero:
+ extensions["severityCounts"] = severity_counts_nonzero
+
+ result_obj = {
+ "results": {
+ "tool": {
+ "name": "Trivy Image"
+ },
+ "summary": {
+ "tests": total,
+ "passed": passed,
+ "failed": failed,
+ "pending": pending,
+ "skipped": skipped,
+ "other": other,
+ "start": start,
+ "stop": stop
+ },
+ "tests": tests,
+ "environment": {
+ "appName": "kamium-elastic",
+ "buildName": "kamium-elastic",
+ "buildNumber": "1"
+ }
+ }
+ }
+
+ if extensions:
+ result_obj["results"]["extensions"] = extensions
+
+ return result_obj
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("Usage: python trivy2ctrf.py ")
+ sys.exit(1)
+ with open(sys.argv[1]) as old_f:
+ trivy_json = json.load(old_f)
+ ctrf_json = trivy_to_ctrf(trivy_json)
+ with open(sys.argv[2], "w") as new_f:
+ json.dump(ctrf_json, new_f, indent=2)