security-gate is a CLI tool that ingests output from multiple vulnerability scanners
(Trivy, Snyk, SARIF, Checkmarx, SonarQube), normalizes findings into a unified model,
applies a deterministic policy engine, and emits auditable ALLOW / WARN / BLOCK
release decisions with full decision traces.
Built around production-oriented Go patterns: interface-based scanner adapters,
structured logging via log/slog, input hashing for reproducibility, context
propagation, and comprehensive test coverage including fuzz tests for parsers.
Optional non-authoritative LLM output can elaborate recommended next steps without ever influencing the decision, score, trust, or exit code.
- Why security-gate
- Project Status
- Key Features
- Supported Scanners
- Requirements
- Installation
- Quick Start
- Run Examples
- CLI Usage
- Baseline Diff Mode
- CI Auto-Context
- Decision Contract
- Input Contracts
- Output Artifacts
- Architecture and Authority Docs
- Repository Layout
- Development and Validation
- Contributing
- Security
- Roadmap
- License
Scanners produce findings; release pipelines need deterministic decisions.
security-gate adds context-aware, policy-driven gating on top of scanner reports while keeping the system:
- Local only (no cloud calls, no external APIs)
- Deterministic (same normalized inputs -> same decision and trace)
- Explainable (
report.jsonincludes decision trace) - Strict about untrusted input validation
Active development. Core contracts are implemented and validated by unit tests, race tests, and simulation scenarios.
- Deterministic decision engine with canonical outcomes:
ALLOW,WARN,BLOCK - Canonical exit-code contract:
0,1,2 - Strict precedence:
- hard-stop domains
- accepted-risk governance
- numeric scoring
- noise budget (presentation only)
- stage matrix
- exit mapping
- Scanner ingestion from local JSON files
- SARIF 2.1.0 adapter ingestion (strict envelope validation)
- Trivy adapter ingestion for vulnerabilities, misconfigurations, and secrets
- Native Snyk vulnerability JSON adapter ingestion
- Native Checkmarx JSON v2 adapter ingestion
- Sonar Generic Issues JSON compatibility adapter ingestion
- Strict adapter-level report envelope checks (required sections + schema version guard when provided)
- Optional baseline diff mode for PR/merge (
--new-findings-onlywith--baseline-scan) - Noise-budget preview in derived HTML with suppression counts/reasons (presentation-only)
- Strict YAML validation for context/policy/accepted-risk (unknown fields, duplicate keys, missing required fields)
- Authoritative machine output (
report.json) + derived human report (report.html) - Artifact integrity outputs (
checksums.sha256) and structured run logs (security-gate.run.log)
security-gate currently supports these scanner inputs via --scan (JSON only):
- Trivy JSON (
Results) - SARIF 2.1.0 JSON (
version,runs) - Snyk vulnerability JSON (
vulnerabilities) - Checkmarx JSON v2 (
scanResults, optionalreportType=json-v2) - Sonar Generic Issues JSON (
issues)
- Go
1.25+language compatibility floor - Repository toolchain pin:
go1.26.2 - Local files for:
- scanner reports (JSON)
- context (YAML)
- policy (YAML)
- optional accepted risk (YAML)
Build from source:
go build -o security-gate ./cmd/security-gateOptional: run directly without a local binary:
go run ./cmd/security-gate --helpRun the included deterministic simulation:
./examples/simulation/simulate.shThis generates WARN and BLOCK examples under examples/simulation/out/.
It also generates:
- a SARIF example (
block-sarif-pr) - a Snyk JSON example (
block-snyk-pr) - a Checkmarx JSON v2 example (
block-checkmarx-pr) - a Sonar Generic Issues example (
block-sonar-pr) - a CI auto-context example (
block-context-auto-github-pr) - a baseline diff example (
allow-new-findings-only-baseline-all)
Run manually with sample inputs:
./security-gate \
--scan examples/simulation/scanner-report.warn.json \
--context examples/simulation/context.yaml \
--policy examples/simulation/policy.yaml \
--accepted-risk examples/simulation/accepted-risk.yaml \
--out-json report.json \
--out-html report.htmlRun with CI auto-detected context (when running inside GitHub/GitLab/Jenkins):
./security-gate \
--scan examples/simulation/scanner-report.warn.json \
--context-auto \
--policy examples/simulation/policy.yaml \
--out-json report.json \
--out-html report.htmlRun with SARIF 2.1.0:
./security-gate \
--scan examples/simulation/scanner-report.warn.sarif.json \
--context examples/simulation/context.pr-feature.yaml \
--policy examples/simulation/policy.yaml \
--out-json report.json \
--out-html report.htmlRun with Snyk JSON:
./security-gate \
--scan examples/simulation/scanner-report.warn.snyk.json \
--context examples/simulation/context.pr-feature.yaml \
--policy examples/simulation/policy.yaml \
--out-json report.json \
--out-html report.htmlRun with Checkmarx JSON v2:
./security-gate \
--scan examples/simulation/scanner-report.warn.checkmarx.json \
--context examples/simulation/context.pr-feature.yaml \
--policy examples/simulation/policy.yaml \
--out-json report.json \
--out-html report.htmlRun with Sonar Generic Issues JSON:
./security-gate \
--scan examples/simulation/scanner-report.warn.sonar.json \
--context examples/simulation/context.pr-feature.yaml \
--policy examples/simulation/policy.yaml \
--out-json report.json \
--out-html report.htmlsecurity-gate \
--scan <scan.json> [--scan <scan2.json> ...] \
[--baseline-scan <baseline.json> ...] \
[--new-findings-only] \
[--context <context.yaml> | --context-auto] \
--policy <policy.yaml> \
[--accepted-risk <accepted-risk.yaml>] \
[--out-json report.json] \
[--out-html report.html] \
[--checksums checksums.sha256] \
[--run-log security-gate.run.log] \
[--evaluation-time 2026-02-19T12:00:00Z] \
[--no-html]
Required:
--scan(repeatable, JSON only)- one of:
--context(YAML)--context-auto(deterministic CI environment auto-detection)
--policy(YAML)
Optional:
--accepted-risk(YAML)--baseline-scan(repeatable baseline scanner JSON)--new-findings-only(only forpr/merge; requires--baseline-scan)- output path overrides
--evaluation-timefor deterministic replay of freshness and accepted-risk expiry checks--no-htmlto disablereport.html
--new-findings-only is an optional PR/merge workflow mode:
- Requires one or more
--baseline-scanfiles. - Supported only for effective stages
prandmerge. - Baseline findings are compared deterministically to current findings.
- Numeric aggregation ignores baseline-known non-hard-stop findings.
- Hard-stop domains are still always enforced.
Baseline source model:
security-gatedoes not persist baseline state.- Baseline is provided per run via
--baseline-scan. - Typical CI usage: pass the latest trusted
main(or release) scanner artifact into PR jobs.
Canonical decisions and exit codes:
ALLOW->0WARN->1BLOCK->2
Hard-stop domains always force BLOCK and cannot be overridden by accepted-risk records.
- Scanner input: JSON only (
--scan) - Optional baseline scanner input: JSON only (
--baseline-scan) - Supported scanner JSON envelopes:
- Trivy JSON
- SARIF 2.1.0 JSON
- Snyk vulnerability JSON
- Checkmarx JSON v2 (
scanResults) - Sonar Generic Issues JSON (
issues)
- Adapter-level envelope validation is strict:
- Trivy:
Resultsarray required. - SARIF:
version=2.1.0,runsrequired, each run requirestool.driver.nameandresults. - Snyk:
vulnerabilitiesarray required. - Checkmarx:
scanResultsarray required; if present,reportTypemust bejson-v2. - Sonar Generic:
issuesarray required; each issue requires non-emptyruleId.
- Trivy:
- Context input:
- YAML (
--context) OR - deterministic CI auto-detection (
--context-auto)
- YAML (
- Policy/Accepted Risk: YAML only (
--policy,--accepted-risk) - Input files are untrusted and validated strictly
- Scanner-version trust is derived from scanner report evidence, not context overrides.
See authoritative docs for schemas and rules:
docs/md/core-decision-engine.mddocs/md/policy-format.mddocs/md/governance-accepted-risk.md
report.json(authoritative)inputs[].roledifferentiatesscan_jsonprovenance asprimaryvsbaselinewhen baseline mode is used
report.html(derived, non-authoritative)checksums.sha256security-gate.run.log(JSON lines)
--context-auto derives context deterministically from local CI environment variables.
Provider detection:
- GitHub Actions:
GITHUB_ACTIONS=true - GitLab CI:
GITLAB_CI=true - Jenkins:
JENKINS_URLorJENKINS_HOMEor (BUILD_ID+JOB_NAME) - Otherwise: generic fallback
Deterministic override variables (optional):
SECURITY_GATE_BRANCH_TYPESECURITY_GATE_PIPELINE_STAGESECURITY_GATE_ENVIRONMENTSECURITY_GATE_REPO_CRITICALITYSECURITY_GATE_EXPOSURESECURITY_GATE_CHANGE_TYPESECURITY_GATE_SCANNER_NAMESECURITY_GATE_SCANNER_VERSIONSECURITY_GATE_ARTIFACT_SIGNEDSECURITY_GATE_PROVENANCE_LEVELSECURITY_GATE_BUILD_CONTEXT_INTEGRITY
All auto-detected values are normalized to canonical enums with safe fallbacks.
Design authority chain:
docs/md/core-decision-engine.mddocs/md/policy-format.mdinternal/securitygate- derived docs/html
Supporting docs:
docs/md/architecture.mddocs/md/modules.md
- CLI:
cmd/security-gate/main.go - Core engine:
internal/securitygate/ - Scanner ingest:
internal/ingest/trivy/,internal/ingest/sarif/,internal/ingest/snyk/,internal/ingest/checkmarx/,internal/ingest/sonar/ - Scoring:
internal/scoring/ - Policy rules:
internal/policy/ - Reporting:
internal/report/ - Simulation:
examples/simulation/andexamples/simulation/simulate.sh
Local development: make check
Parser hardening: make fuzz
Run required checks:
go test ./...
GOTOOLCHAIN=go1.26.2 go test -race ./...
./examples/simulation/simulate.shExpected simulation behavior:
- WARN scenario exits with
1 - BLOCK scenario exits with
2 - SARIF PR scenario exits with
2 - Snyk PR scenario exits with
2 - Checkmarx PR scenario exits with
2 - Sonar PR scenario exits with
2 - CI auto-context PR scenario exits with
2 - new-findings-only with full baseline exits with
0
Contributions are welcome. Start with:
CONTRIBUTING.mdfor workflow and quality gatesAGENTS.mdfor deterministic design constraints and authority chain
Do not report vulnerabilities in public issues.
Use SECURITY.md for responsible disclosure instructions.
- Additional scanner adapters
- Signed report attestations
- Repository policy packs
- Optional local-only historical analytics
Licensed under Apache License 2.0. See LICENSE.