ScopeGuardian is a CLI tool that runs security scanners on your codebase and synchronises the results with DefectDojo. It can optionally enforce a security gate that blocks a CI/CD pipeline when finding counts exceed configurable thresholds.
- Prerequisites
- Quick Start
- CLI Usage
- Configuration File (
config.toml) - Environment Variables
- How Engagements Are Handled
- How the Sync Feature Works
- How the Security Gate Works
- Running with Docker
- Local DefectDojo Setup
- Go 1.25+ (only needed when building from source)
- KICS binary available at
/opt/kics/bin/kics(pre-installed in the Docker image) - OpenGrep binary available at
/opt/opengrep/bin/opengrep(pre-installed in the Docker image; required when[opengrep]is configured) - Syft binary available at
/opt/syft/bin/syft(pre-installed in the Docker image; required when[grype]is configured) - Grype binary available at
/opt/grype/bin/grype(pre-installed in the Docker image; required when[grype]is configured) - A running DefectDojo instance and an API access token (required only when
--syncis used)
# Build the binary
go build -o ScopeGuardian .
# Run a basic scan (no sync, no gate)
SCAN_DIR=/path/to/repos ./ScopeGuardian \
--projectName my-service \
--branch main \
./config.toml
# Run a scan with quiet mode (no logs)
SCAN_DIR=/path/to/repos ./ScopeGuardian \
--projectName my-service \
--branch main \
-q \
./config.toml
# Run a scan and write findings to a file (JSON by default)
SCAN_DIR=/path/to/repos ./ScopeGuardian \
--projectName my-service \
--branch main \
-o /tmp/findings.json \
./config.toml
# Run a scan and write findings as CSV
SCAN_DIR=/path/to/repos ./ScopeGuardian \
--projectName my-service \
--branch main \
-o /tmp/findings.csv \
--format csv \
./config.toml
# Run a scan, sync results to DefectDojo, and enforce a security gate
SCAN_DIR=/path/to/repos \
DD_URL=http://localhost:8080 \
DD_ACCESS_TOKEN=<your-token> \
./ScopeGuardian \
--projectName my-service \
--branch main \
--sync \
--threshold critical=1,high=5 \
./config.tomlScopeGuardian [flags] <config-file>
| Flag | Type | Required | Description |
|---|---|---|---|
--projectName |
string | yes | Name of the project being scanned. Must match the product name in DefectDojo when --sync is used. |
--branch |
string | yes | Branch being scanned (e.g. main, feature/my-branch). |
--sync |
bool | no | Upload scan results to DefectDojo. Requires DD_URL and DD_ACCESS_TOKEN. Default: false. |
--threshold |
string | no | Comma-separated severity thresholds that define the security gate (see Security Gate). |
-q |
bool | no | Quiet mode: suppress all log output. Default: false. |
-o |
string | no | Write findings to the specified file. Banner and logs are not included; only the scan findings are written. |
--format |
string | no | Output format used when -o is set. Accepted values: json (default), csv, raw (plain table). |
<config-file> |
path | yes | Path to the TOML configuration file. |
Parse flags → Load config.toml → Initialize scanners
→ Phase 1: Run prerequisite scanners concurrently (Syft SBOM generation)
→ Phase 2: Run dependent/independent scanners concurrently (Grype, KICS, OpenGrep)
Any scanner whose prerequisite failed is skipped automatically
→ Load findings → [Sync to DefectDojo] → Display findings (stdout)
→ [-o: Dump findings to file in --format (json/csv/raw)]
→ [Evaluate security gate → exit(-1) on failure]
When both --sync and --threshold are provided the gate is evaluated against the findings already stored in DefectDojo (post-deduplication) rather than the raw local scan output.
The configuration file is a TOML document that controls which scanners run and how engagements are managed.
title = "Scope-guardian configuration file" # Optional human-readable label
# Branches whose DefectDojo engagements are given a one-year end date.
# All other branches receive a one-week end date.
protected_branches = ["main", "master"]
# KICS – infrastructure-as-code scanner
[kics]
# Directory to scan, relative to the SCAN_DIR environment variable.
path = "./my-service"
# Infrastructure platform type. Passed as --type to KICS.
# Examples: "Dockerfile", "Terraform", "CloudFormation", "Kubernetes", "Ansible"
platform = "Dockerfile"
# Optional list of KICS query IDs to exclude from scanning.
# exclude_queries = ["a227ec01-f97a-4084-91a4-47b350c1db54"]
# Grype – software-composition analysis (SCA) vulnerability scanner.
# Enabling this section also enables Syft SBOM generation as a prerequisite.
[grype]
# Comma-separated vulnerability states to ignore.
# Common values: "not-fixed", "unknown", "wont-fix"
ignore_states = "not-fixed,unknown,wont-fix"
# When true, Syft resolves transitive Java dependencies from Maven Central.
# This increases scan accuracy for Java projects but significantly increases scan time.
transitive_libraries = false
# Optional list of path patterns to exclude from Grype scanning.
# exclude = ["**/vendor/**", "**/testdata/**"]
# OpenGrep – static application security testing (SAST) scanner.
[opengrep]
# Directory to scan, relative to the SCAN_DIR environment variable.
path = "./my-service"
# Optional list of path patterns to exclude from scanning.
# exclude = ["**/vendor/**", "**/testdata/**"]
# Optional list of rule IDs to skip.
# exclude_rule = ["python.lang.security.audit.formatted-sql-query.formatted-sql-query"]| Field | Type | Required | Description |
|---|---|---|---|
title |
string | no | Human-readable label; not used programmatically. |
protected_branches |
string array | no | Branches whose engagements get a 1-year end date. Defaults to empty (all branches get 1 week). |
[kics].path |
string | yes* | Path to the directory to scan. Resolved as $SCAN_DIR/<path>. |
[kics].platform |
string | no | KICS platform filter (e.g. Dockerfile). When omitted KICS scans all supported types. |
[kics].exclude_queries |
string array | no | KICS query IDs to skip (e.g. ["a227ec01-f97a-4084-91a4-47b350c1db54"]). |
[grype].ignore_states |
string | no | Comma-separated Grype vulnerability states to suppress (e.g. not-fixed,unknown,wont-fix). |
[grype].transitive_libraries |
bool | no | When true, Syft resolves transitive Java dependencies via Maven Central. Default: false. |
[grype].exclude |
string array | no | Path glob patterns to exclude from Grype scanning (e.g. ["**/vendor/**"]). |
[opengrep].path |
string | yes* | Path to the directory to scan. Resolved as $SCAN_DIR/<path>. |
[opengrep].exclude |
string array | no | Path glob patterns to exclude from OpenGrep scanning (e.g. ["**/vendor/**"]). |
[opengrep].exclude_rule |
string array | no | OpenGrep rule IDs to skip (e.g. ["python.lang.security.audit.formatted-sql-query.formatted-sql-query"]). |
* Required only if you want KICS scanning to run. Omitting the entire [kics] section disables the scanner.
Omitting the entire [grype] section disables both Grype and the Syft SBOM generation step.
Omitting the entire [opengrep] section disables the SAST scanner.
| Variable | Required | Description |
|---|---|---|
SCAN_DIR |
yes | Base directory for scan operations. Scan paths and result files are resolved relative to this value. |
DD_URL |
when --sync |
Base URL of the DefectDojo instance (e.g. http://localhost:8080). |
DD_ACCESS_TOKEN |
when --sync |
DefectDojo API token. Generate one in DefectDojo under User → API v2 Key. |
Copy .env.example to .env and fill in the values for local development:
cp .env.example .envScopeGuardian uses a single DefectDojo engagement per project/branch combination to store all findings for that branch. Engagements are managed automatically — you never have to create or update them manually.
Every engagement is named <projectName>-<branch>, for example:
my-service-mainmy-service-feature-my-branch
The project name must correspond to an existing Product in DefectDojo with an exact name match.
When --sync is used the following logic runs on every invocation:
- Look up the product in DefectDojo by exact name (
projectName). - List all engagements for that product (all pages).
- Search for an engagement whose name matches
<projectName>-<branch>.- Found, end date still valid → reuse it as-is.
- Found, end date in the past → automatically extend the end date and reuse the engagement.
- Not found → create a new engagement.
- Upload scan results into the engagement.
The end date is determined by whether the branch appears in protected_branches:
| Branch type | End date |
|---|---|
Protected (e.g. main, master) |
1 year from today |
| Unprotected (feature branches, etc.) | 1 week from today |
New engagements are created with the following attributes:
| Attribute | Value |
|---|---|
| Type | CI/CD |
| Status | In Progress |
| Tags | SCOPE-GUARDIAN, <branch> |
| Deduplication on engagement | disabled |
Passing --sync on the command line uploads the scan results from every registered scanner to DefectDojo after the scan completes.
- A DefectDojo service client is created using
DD_URLandDD_ACCESS_TOKEN. - The engagement ID is resolved (see Engagements).
- For each registered scanner, the scanner's
Syncmethod is called with the engagement ID and branch.
The KICS scanner uploads its JSON output file to DefectDojo via the /api/v2/import-scan/ endpoint as a multipart/form-data request. The following options are set on every import:
| Option | Value | Effect |
|---|---|---|
| Scan type | KICS Scan |
Tells DefectDojo which parser to use |
| Severity threshold | Info |
Import findings of all severities |
| Group by | finding_title |
Merge findings with the same title |
| Create finding groups | true |
Group related findings together |
| Apply tags to findings | true |
Tag each finding with IACST |
| Close old findings | true |
Findings absent from the new scan are closed automatically |
| Branch tag | <branch> |
Associates the results with the scanned branch |
The Grype scanner uploads its JSON output file to DefectDojo via the /api/v2/import-scan/ endpoint as a multipart/form-data request. The following options are set on every import:
| Option | Value | Effect |
|---|---|---|
| Scan type | Anchore Grype |
Tells DefectDojo which parser to use |
| Severity threshold | Info |
Import findings of all severities |
| Group by | finding_title |
Merge findings with the same title |
| Create finding groups | true |
Group related findings together |
| Apply tags to findings | true |
Tag each finding with SCA |
| Close old findings | true |
Findings absent from the new scan are closed automatically |
| Branch tag | <branch> |
Associates the results with the scanned branch |
The OpenGrep scanner uploads its JSON output file to DefectDojo via the /api/v2/import-scan/ endpoint as a multipart/form-data request. Before uploading, the file is enriched so that each result contains an extra.severity field required by DefectDojo's Semgrep JSON Report parser (the value is copied from extra.metadata.impact). The following options are set on every import:
| Option | Value | Effect |
|---|---|---|
| Scan type | Semgrep JSON Report |
Tells DefectDojo which parser to use |
| Severity threshold | Info |
Import findings of all severities |
| Group by | finding_title |
Merge findings with the same title |
| Create finding groups | true |
Group related findings together |
| Apply tags to findings | true |
Tag each finding with SAST |
| Close old findings | true |
Findings absent from the new scan are closed automatically |
| Branch tag | <branch> |
Associates the results with the scanned branch |
When both --sync and --threshold are set, the gate is evaluated against DefectDojo's deduplicated findings rather than the raw local scan output. This means:
- Duplicate or previously-closed findings do not inflate the count.
- Only active findings surviving DefectDojo's deduplication logic are counted.
The security gate fails the pipeline (exit code -1) when the number of findings at or above a configured severity level meets or exceeds the configured limit.
--threshold <severity>=<count>[,<severity>=<count>...]
Supported severity values (case-insensitive): critical, high, medium, low, info.
# Fail on any critical finding
--threshold critical=1
# Fail on 1+ critical OR 5+ high findings
--threshold critical=1,high=5
# Fail on 10+ medium-or-above findings
--threshold medium=10For each threshold rule:
-
Count findings whose severity is equal to or higher than the threshold severity.
Severity ranking (highest to lowest):
CRITICAL>HIGH>MEDIUM>LOW>INFO -
If the count is ≥ the configured value, the gate fails and the process exits with code
-1. -
All threshold rules must pass for the gate to pass.
| Flags used | Findings evaluated |
|---|---|
--threshold only |
Local scan output from all scanners |
--threshold + --sync |
Active findings fetched from DefectDojo |
The provided Dockerfile builds a multi-stage image that bundles ScopeGuardian together with KICS, OpenGrep, Grype, and Syft.
# Build the image
docker build -t ScopeGuardian .
# Run a scan
docker run --rm \
-v /path/to/your/project:/tmp/data/project \
-v /path/to/config.toml:/config.toml \
-e SCAN_DIR=/tmp/data \
-e DD_URL=http://host.docker.internal:8080 \
-e DD_ACCESS_TOKEN=<your-token> \
ScopeGuardian \
--projectName my-service \
--branch main \
--sync \
/config.tomlInside the container SCAN_DIR defaults to /tmp/data.
A docker-compose.yml is provided to spin up a local DefectDojo instance backed by PostgreSQL and Redis.
# 1. Configure credentials
cp .env.example .env
# Edit .env – change passwords and set a strong DD_SECRET_KEY
# 2. Start DefectDojo (this takes a minute on first run)
docker compose up -d
# 3. Open DefectDojo in your browser
open http://localhost:8080
# 4. Log in with the admin credentials you set in .env
# Default: admin / changeme
# 5. Generate an API token
# Profile → API v2 Key → copy the token
# 6. Set the token in your environment
export DD_URL=http://localhost:8080
export DD_ACCESS_TOKEN=<your-token>Before running ScopeGuardian with --sync, create a Product in DefectDojo whose name matches the --projectName value you will pass on the command line.
- In DefectDojo go to Products → Add Product.
- Set Name to exactly the value you will pass as
--projectName(e.g.my-service). - Save. ScopeGuardian will manage engagements under this product automatically.