feat: add merge-sarif #37
@@ -82,3 +82,31 @@ jobs:
|
||||
|
||||
echo ""
|
||||
echo "All checks passed."
|
||||
|
||||
test-merge-sarif-files:
|
||||
name: Test Merge SARIF Files
|
||||
runs-on:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Merge SARIF Files
|
||||
uses: ./merge-sarif-files
|
||||
with:
|
||||
files: |
|
||||
- merge-sarif-files/test_sarif1.json
|
||||
- merge-sarif-files/test_sarif2.json
|
||||
output-file: merged.sarif
|
||||
- name: Check merged SARIF file
|
||||
run: |
|
||||
# Structure: version, schema, 2 runs
|
||||
run_count=$(jq '.runs | length' merged.sarif)
|
||||
[ "$run_count" -eq 2 ] || { echo "FAIL: expected 2 runs, got $run_count"; exit 1; }
|
||||
echo "Merged SARIF has $run_count runs."
|
||||
jq -e '.version and .["$schema"] and (.runs | length > 0)' merged.sarif >/dev/null || { echo "FAIL: invalid SARIF structure"; exit 1; }
|
||||
|
||||
# Content: results from test_sarif1.json (ruleId KSV-0014) and test_sarif2.json (ruleId CVE-2026-26019) must be present
|
||||
rule_ids=$(jq -r '[.runs[].results[].ruleId] | unique[]' merged.sarif)
|
||||
echo "$rule_ids" | grep -q 'KSV-0014' || { echo "FAIL: merged file missing result from test_sarif1.json (ruleId KSV-0014)"; exit 1; }
|
||||
echo "$rule_ids" | grep -q 'CVE-2026-26019' || { echo "FAIL: merged file missing result from test_sarif2.json (ruleId CVE-2026-26019)"; exit 1; }
|
||||
echo "Merged results contain ruleIds from both input files."
|
||||
echo "All checks passed."
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Merge SARIF Files Action
|
||||
|
||||
A reusable Gitea Action that merges multiple SARIF files into a single SARIF file.
|
||||
|
||||
**Note:** This action only merges the files. It doesn't perform any validation, filtering, or deduplication.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```yaml
|
||||
- name: Merge SARIF Files
|
||||
uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/merge-sarif-files@0.0.1
|
||||
with:
|
||||
files: |
|
||||
- test_sarif1.json
|
||||
- test_sarif2.json
|
||||
output-file: merged.sarif
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
|
||||
```yaml
|
||||
name: Merge SARIF Files
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
merge-sarif-files:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Merge SARIF Files
|
||||
uses: https://gitea.t000-n.de/t.behrendt/trivy-actions/merge-sarif-files@0.0.1
|
||||
with:
|
||||
files: |
|
||||
- test_sarif1.json
|
||||
- test_sarif2.json
|
||||
output-file: merged.sarif
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Description | Required | Default |
|
||||
| ------------- | ----------------------------- | -------- | -------------- |
|
||||
| `files` | Paths to SARIF files to merge | Yes | |
|
||||
| `output-file` | Output file path | Yes | `merged.sarif` |
|
||||
@@ -0,0 +1,42 @@
|
||||
name: "Merge SARIF Files"
|
||||
description: "Merge multiple SARIF files into a single SARIF file"
|
||||
author: "Timo Behrendt <t.behrendt@t00n.de>"
|
||||
branding:
|
||||
icon: "database"
|
||||
color: "blue"
|
||||
|
||||
inputs:
|
||||
files:
|
||||
description: "Paths to SARIF files to merge"
|
||||
required: true
|
||||
output-file:
|
||||
description: "Output file path"
|
||||
required: true
|
||||
default: "merged.sarif"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- id: merge
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
files="${{ inputs.files }}"
|
||||
output_file="${{ inputs.output-file }}"
|
||||
|
||||
# Parse YAML list: lines like " - path/to/file" or "- file"
|
||||
file_list=$(echo "$files" | sed -n 's/^[[:space:]]*-[[:space:]]*//p' | tr -d '"' | tr -d "'")
|
||||
|
||||
# Collect all runs from all SARIF files (each run as one compact JSON line)
|
||||
runs_json=$(while IFS= read -r file; do
|
||||
[ -z "$file" ] && continue
|
||||
jq -c '.runs[]?' "$file" 2>/dev/null || true
|
||||
done <<< "$file_list" | jq -s '.')
|
||||
|
||||
# Take first file for version/schema, replace .runs with merged array
|
||||
first_file=$(echo "$file_list" | head -1)
|
||||
if [ -z "$first_file" ]; then
|
||||
echo "No input files given."
|
||||
exit 1
|
||||
fi
|
||||
jq -n --argjson runs "$runs_json" --slurpfile first "$first_file" '$first[0] | .runs = $runs' > "$output_file"
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"fullName": "Trivy Vulnerability Scanner",
|
||||
"informationUri": "https://github.com/aquasecurity/trivy",
|
||||
"name": "Trivy",
|
||||
"rules": [
|
||||
{
|
||||
"id": "KSV-0014",
|
||||
"name": "Misconfiguration",
|
||||
"shortDescription": {
|
||||
"text": "Root file system is not read-only"
|
||||
},
|
||||
"fullDescription": {
|
||||
"text": "An immutable root file system prevents applications from writing to their local disk. This can limit intrusions, as attackers will not be able to tamper with the file system or write foreign executables to disk."
|
||||
},
|
||||
"defaultConfiguration": {
|
||||
"level": "error"
|
||||
},
|
||||
"helpUri": "https://avd.aquasec.com/misconfig/ksv-0014",
|
||||
"help": {
|
||||
"text": "Misconfiguration KSV-0014\nType: Kubernetes Security Check\nSeverity: HIGH\nCheck: Root file system is not read-only\nMessage: Container 'renovate' of CronJob 'renovate' should set 'securityContext.readOnlyRootFilesystem' to true\nLink: [KSV-0014](https://avd.aquasec.com/misconfig/ksv-0014)\nAn immutable root file system prevents applications from writing to their local disk. This can limit intrusions, as attackers will not be able to tamper with the file system or write foreign executables to disk.",
|
||||
"markdown": "**Misconfiguration KSV-0014**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Kubernetes Security Check|HIGH|Root file system is not read-only|Container 'renovate' of CronJob 'renovate' should set 'securityContext.readOnlyRootFilesystem' to true|[KSV-0014](https://avd.aquasec.com/misconfig/ksv-0014)|\n\nAn immutable root file system prevents applications from writing to their local disk. This can limit intrusions, as attackers will not be able to tamper with the file system or write foreign executables to disk."
|
||||
},
|
||||
"properties": {
|
||||
"precision": "very-high",
|
||||
"security-severity": "8.0",
|
||||
"tags": ["misconfiguration", "security", "HIGH"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": "0.69.1"
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"ruleId": "KSV-0014",
|
||||
"ruleIndex": 0,
|
||||
"level": "error",
|
||||
"message": {
|
||||
"text": "Artifact: k8s/31_deployment_redis.yaml\nType: kubernetes\nVulnerability KSV-0014\nSeverity: HIGH\nMessage: Container 'redis' of Deployment 'redis' should set 'securityContext.readOnlyRootFilesystem' to true\nLink: [KSV-0014](https://avd.aquasec.com/misconfig/ksv-0014)"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "k8s/31_deployment_redis.yaml",
|
||||
"uriBaseId": "ROOTPATH"
|
||||
},
|
||||
"region": {
|
||||
"startLine": 34,
|
||||
"startColumn": 1,
|
||||
"endLine": 51,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"text": "k8s/31_deployment_redis.yaml"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"fullName": "Trivy Vulnerability Scanner",
|
||||
"informationUri": "https://github.com/aquasecurity/trivy",
|
||||
"name": "Trivy",
|
||||
"rules": [
|
||||
{
|
||||
"id": "CVE-2026-26019",
|
||||
"name": "LanguageSpecificPackageVulnerability",
|
||||
"shortDescription": {
|
||||
"text": "@langchain/community: @langchain/community SSRF Bypass in RecursiveUrlLoader"
|
||||
},
|
||||
"fullDescription": {
|
||||
"text": "LangChain is a framework for building LLM-powered applications. Prior to 1.1.14, the RecursiveUrlLoader class in @langchain/community is a web crawler that recursively follows links from a starting URL. Its preventOutside option (enabled by default) is intended to restrict crawling to the same site as the base URL. The implementation used String.startsWith() to compare URLs, which does not perform semantic URL validation. An attacker who controls content on a crawled page could include links to domains that share a string prefix with the target, causing the crawler to follow links to attacker-controlled or internal infrastructure. Additionally, the crawler performed no validation against private or reserved IP addresses. A crawled page could include links targeting cloud metadata services, localhost, or RFC 1918 addresses, and the crawler would fetch them without restriction. This vulnerability is fixed in 1.1.14."
|
||||
},
|
||||
"defaultConfiguration": {
|
||||
"level": "warning"
|
||||
},
|
||||
"helpUri": "https://avd.aquasec.com/nvd/cve-2026-26019",
|
||||
"help": {
|
||||
"text": "Vulnerability CVE-2026-26019\nSeverity: MEDIUM\nPackage: @langchain/community\nFixed Version: 1.1.14\nLink: [CVE-2026-26019](https://avd.aquasec.com/nvd/cve-2026-26019)\nLangChain is a framework for building LLM-powered applications. Prior to 1.1.14, the RecursiveUrlLoader class in @langchain/community is a web crawler that recursively follows links from a starting URL. Its preventOutside option (enabled by default) is intended to restrict crawling to the same site as the base URL. The implementation used String.startsWith() to compare URLs, which does not perform semantic URL validation. An attacker who controls content on a crawled page could include links to domains that share a string prefix with the target, causing the crawler to follow links to attacker-controlled or internal infrastructure. Additionally, the crawler performed no validation against private or reserved IP addresses. A crawled page could include links targeting cloud metadata services, localhost, or RFC 1918 addresses, and the crawler would fetch them without restriction. This vulnerability is fixed in 1.1.14.",
|
||||
"markdown": "**Vulnerability CVE-2026-26019**\n| Severity | Package | Fixed Version | Link |\n| --- | --- | --- | --- |\n|MEDIUM|@langchain/community|1.1.14|[CVE-2026-26019](https://avd.aquasec.com/nvd/cve-2026-26019)|\n\nLangChain is a framework for building LLM-powered applications. Prior to 1.1.14, the RecursiveUrlLoader class in @langchain/community is a web crawler that recursively follows links from a starting URL. Its preventOutside option (enabled by default) is intended to restrict crawling to the same site as the base URL. The implementation used String.startsWith() to compare URLs, which does not perform semantic URL validation. An attacker who controls content on a crawled page could include links to domains that share a string prefix with the target, causing the crawler to follow links to attacker-controlled or internal infrastructure. Additionally, the crawler performed no validation against private or reserved IP addresses. A crawled page could include links targeting cloud metadata services, localhost, or RFC 1918 addresses, and the crawler would fetch them without restriction. This vulnerability is fixed in 1.1.14."
|
||||
},
|
||||
"properties": {
|
||||
"cvssv3_baseScore": 4.1,
|
||||
"cvssv3_vector": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:N/A:N",
|
||||
"precision": "very-high",
|
||||
"security-severity": "4.1",
|
||||
"tags": ["vulnerability", "security", "MEDIUM"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": "0.69.1"
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"ruleId": "CVE-2026-26019",
|
||||
"ruleIndex": 0,
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Package: @langchain/community\nInstalled Version: 0.3.36\nVulnerability CVE-2026-26019\nSeverity: MEDIUM\nFixed Version: 1.1.14\nLink: [CVE-2026-26019](https://avd.aquasec.com/nvd/cve-2026-26019)"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "bun.lock",
|
||||
"uriBaseId": "ROOTPATH"
|
||||
},
|
||||
"region": {
|
||||
"startLine": 104,
|
||||
"startColumn": 1,
|
||||
"endLine": 104,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"text": "bun.lock: @langchain/community@0.3.36"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user