Merges two or more security scanner reports into one gate. Use when you need a single BLOCK or PASS decision from multiple scanners instead of reading N separate reports. Normalizes each report into one common finding format (a canonical `Finding`), deduplicates on a per-domain key while recording which scanners agree (`caught_by` consensus), validates a waiver (finding-suppression) file, rejecting any missing `expires:` / `approved_by:` / `reason:` or expired, enriches CVE findings with EPSS (exploit-probability) and CISA KEV (known-exploited catalog), then applies a `fail_on` severity threshold to emit BLOCK or PASS plus a bucketed pull-request comment. Works across static (SAST), dynamic (DAST), secret, dependency (SCA), container, and IaC scanners. To run a single scanner instead use semgrep-rules, codeql-queries, or one of the language-native-sast linters; this runs after them to merge output - the cross-scanner gate, not a single-scanner wrapper.
77
97%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Waivers live in one committed YAML file per domain, named for the gate it feeds
(.sast-waivers.yaml, .dast-waivers.yaml, .secrets-waivers.yaml,
.sca-waivers.yaml, .vuln-waivers.yaml, .iac-waivers.yaml). Step 5 of
multi-tool-finding-triage references this file.
One schema, with exact-match keys or *_pattern glob keys:
waivers:
# exact-match waiver, code / policy domain
- scanner: scanner-a
rule_id: js/hardcoded-credentials
file: src/dev-only-server.js
line: 42
reason: "Dev-only server; runs on localhost without TLS by design"
expires: 2026-12-31
approved_by: alice@example.com
# pattern waiver: any scanner, any matching path
- scanner_pattern: "*"
rule_id_pattern: "K8S_*"
file_pattern: "helm/dev-overrides/**"
reason: "Dev overrides; not deployed to production"
expires: 2026-09-30
approved_by: platform-team
# CVE-domain waiver
- cve: CVE-2024-1234
package: lodash@4.17.20
reason: "Vulnerable function not in the call path; verified via dependency tree"
expires: 2026-12-31
approved_by: alice@example.comMatching keys per domain: scanner / rule_id / file / line for code and
policy findings, url_pattern / finding_class for dynamic findings, cve /
package for CVE findings. Any *_pattern variant takes a glob.
A waiver that fails any of these is rejected, and the underlying finding stays active:
expires: missing.expires: in the past relative to today.approved_by: missing or empty.reason: missing or empty.A rejected waiver is never a silent no-op. Report it explicitly, with the reason for rejection, so the author fixes the waiver instead of assuming it applied.
REQUIRED = ('expires', 'approved_by', 'reason')
def validate_waiver(w, today):
for field in REQUIRED:
if not w.get(field):
return f"missing `{field}:`" # rejection reason, or None if valid
return f"expired {w['expires']}" if w['expires'] < today else Nonenot_affected status with an empty justification
(https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md).