Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cd479f2
feat: add exclude_test_libraries option to reduce SBOM noise from tes…
Copilot Apr 30, 2026
8eb64bd
refactor: replace ExcludeTestLibraries bool with SyftExclude []string…
Copilot Apr 30, 2026
653df33
docs: update README for syft_exclude array replacing exclude_test_lib…
Copilot Apr 30, 2026
49b2a9b
feat(grype): populate CWE/CVE field with CVE ID; rename column header…
Copilot Apr 30, 2026
72ae6d0
feat(syft): add syft_max_parent_recursive_depth toml parameter
Copilot Apr 30, 2026
3366e4f
Add CLAUDE.md with project context and development guidelines
Copilot May 1, 2026
1274962
Remove env copy hint line from CLAUDE.md
Copilot May 1, 2026
3056df7
Clarify testing coverage expectation in CLAUDE.md
Copilot May 1, 2026
6e08df9
Add .github/copilot-instructions.md skill to enforce README.md updates
Copilot May 1, 2026
8a0770c
Create claude skill
May 1, 2026
2113a17
Rewrite .claude/skills/update-readme/SKILL.md as a proper Claude Code…
Copilot May 1, 2026
be6b0e0
Add .claude/skills/go-fmt/SKILL.md for go fmt skill
Copilot May 2, 2026
c2e268f
feat: rename syft_depth, CVE from epss.cve, quote excludes, group_by …
Copilot May 2, 2026
e4ecafa
fix: use single quotes for syft --exclude patterns
Copilot May 2, 2026
ada570c
fix(syft): remove quote wrapping from --exclude patterns in exec.Comm…
Copilot May 2, 2026
ebbabe7
fix: update GrypeEpss to slice to match new Grype JSON array schema
Copilot May 2, 2026
61901c1
fix: restore tab indentation in grype mock JSON fixture
Copilot May 2, 2026
e6d0b62
Merge branch 'main' into copilot/reduce-noise-syft-sbom
Nitr4x May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ ignore_states = "not-fixed,unknown,wont-fix"
transitive_libraries = false
# Optional list of path patterns to exclude from Grype scanning.
# exclude = ["**/vendor/**", "**/testdata/**"]
# Optional list of glob patterns passed to Syft via --exclude during SBOM generation.
# Use this to skip paths such as test sources from the SBOM (e.g. src/test/).
# Note: test-scoped pom.xml dependencies are not affected (Syft has no Maven scope filter).
# syft_exclude = ["**/src/test/**"]
# Depth to recursively resolve parent POMs (env: SYFT_JAVA_MAX_PARENT_RECURSIVE_DEPTH).
# Default is 1; set to 0 for no limit.
# syft_depth = 1

# OpenGrep – static application security testing (SAST) scanner.
[opengrep]
Expand Down Expand Up @@ -182,6 +189,8 @@ transitive_libraries = false
| `[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/**"]`). |
| `[grype].syft_exclude` | string array | no | Path glob patterns passed to Syft via `--exclude` during SBOM generation (e.g. `["**/src/test/**"]`). Excludes filesystem paths only; test-scoped `pom.xml` dependencies are unaffected. |
| `[grype].syft_depth` | int | no | Maximum number of parent POM levels Syft will recursively resolve during Java/Maven analysis. Default is `1`; `0` means no limit. Forwarded as `SYFT_JAVA_MAX_PARENT_RECURSIVE_DEPTH`. |
| `[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"]`). |
| `[proxy].http_proxy` | string | no | HTTP proxy URL forwarded as `HTTP_PROXY` / `http_proxy` to all scanner sub-processes. |
Expand Down Expand Up @@ -330,6 +339,8 @@ The Grype scanner uploads its JSON output file to DefectDojo as a `multipart/for
| Do not reactivate | `true` | Previously suppressed findings are not reactivated on reimport |
| Branch tag | `<branch>` | Associates the results with the scanned branch |

The **CWE/CVE** column in the CLI output and in file exports is populated with the CVE identifier (e.g. `CVE-2021-44228`) reported by Grype for each vulnerability match. KICS and OpenGrep findings use a CWE number in the same column.

### OpenGrep Sync Behaviour

The OpenGrep scanner uploads its JSON output file to DefectDojo 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 endpoint used is `/api/v2/import-scan/` on the first run and `/api/v2/reimport-scan/` on subsequent runs. The following options are set on every upload:
Expand Down
1 change: 1 addition & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ platform = "Dockerfile"
[grype]
ignore_states = "not-fixed,unknown,wont-fix"
transitive_libraries = false
# syft_exclude = ["**/src/test/**"] # glob patterns passed to Syft --exclude to skip paths during SBOM generation

[opengrep]
exclude = ["tests/**"]
Expand Down
2 changes: 1 addition & 1 deletion connectors/defectdojo/client/client.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package client

import (
"ScopeGuardian/logger"
"bytes"
"fmt"
"io"
"net/http"
"ScopeGuardian/logger"
)

// Client is the interface for making HTTP requests to the DefectDojo API.
Expand Down
18 changes: 9 additions & 9 deletions connectors/defectdojo/const.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package defectdojo

const (
APIPrefix = "/api/v2"
GetProductsPath = "/products?name_exact="
GetEngagementsPath = "/engagements?product=%d&offset=%d&limit=%d"
CreateEngagementPath = "/engagements/"
UpdateEngagementPath = "/engagements/%d/"
ImportScanPath = "/import-scan/"
ReimportScanPath = "/reimport-scan/"
GetTestsPath = "/tests/?engagement=%d&scan_type=%s"
APIPrefix = "/api/v2"
GetProductsPath = "/products?name_exact="
GetEngagementsPath = "/engagements?product=%d&offset=%d&limit=%d"
CreateEngagementPath = "/engagements/"
UpdateEngagementPath = "/engagements/%d/"
ImportScanPath = "/import-scan/"
ReimportScanPath = "/reimport-scan/"
GetTestsPath = "/tests/?engagement=%d&scan_type=%s"
// GetFindingsPath fetches only active (non-duplicate) findings and is used for
// polling until the post-import count stabilises.
GetFindingsPath = "/findings/?test__engagement=%d&active=true&offset=%d&limit=%d"
GetFindingsPath = "/findings/?test__engagement=%d&active=true&offset=%d&limit=%d"
// GetAllEngagementFindingsPath fetches all findings for an engagement regardless
// of their active/duplicate status so that callers can read the "active" and
// "duplicate" fields directly and derive the correct local Status.
Expand Down
2 changes: 1 addition & 1 deletion connectors/defectdojo/factory_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package defectdojo

import (
"net/http"
"ScopeGuardian/connectors/defectdojo/client"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
Expand Down
4 changes: 2 additions & 2 deletions connectors/defectdojo/service.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package defectdojo

import (
"ScopeGuardian/connectors/defectdojo/client"
"ScopeGuardian/logger"
"bytes"
"encoding/json"
"errors"
Expand All @@ -9,8 +11,6 @@ import (
"net/http"
"net/url"
"reflect"
"ScopeGuardian/connectors/defectdojo/client"
"ScopeGuardian/logger"
"strconv"
"time"
)
Expand Down
6 changes: 3 additions & 3 deletions display/display.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package display

import (
"ScopeGuardian/domains/models"
environment_variable "ScopeGuardian/environnement_variable"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"strconv"
"ScopeGuardian/domains/models"
environment_variable "ScopeGuardian/environnement_variable"

"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
Expand All @@ -18,7 +18,7 @@ const (
rowEngine = "Engine"
rowSeverity = "Severity"
rowName = "Name"
rowCwe = "CWE"
rowCwe = "CWE/CVE"
rowDescription = "Description"
rowSinkFile = "Sink File"
rowSinkLine = "Sink Line"
Expand Down
4 changes: 2 additions & 2 deletions display/display_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package display

import (
"ScopeGuardian/domains/models"
"bytes"
"encoding/json"
"strings"
"ScopeGuardian/domains/models"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -138,7 +138,7 @@ func TestDumpFindings_CSV_ContainsHeaders(t *testing.T) {

assert.Nil(t, err)
output := buf.String()
assert.True(t, strings.HasPrefix(output, "Engine,Severity,Name,CWE,Description,SinkFile,SinkLine,Recommendation,Status"))
assert.True(t, strings.HasPrefix(output, "Engine,Severity,Name,CWE/CVE,Description,SinkFile,SinkLine,Recommendation,Status"))
}

func TestDumpFindings_CSV_ContainsStatus(t *testing.T) {
Expand Down
22 changes: 13 additions & 9 deletions domains/models/finding.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import (
const (
// FindingStatusActive indicates a finding that is active in DefectDojo
// (not a duplicate, not suppressed). Without --sync all findings default to ACTIVE.
FindingStatusActive = "ACTIVE"
FindingStatusActive = "ACTIVE"
// FindingStatusInactive indicates a finding that DefectDojo has suppressed,
// marked as a false positive, or accepted as a risk. The local scanner still
// reports it but it is excluded from security-gate evaluation.
FindingStatusInactive = "INACTIVE"
FindingStatusInactive = "INACTIVE"
// FindingStatusDuplicate indicates a finding that DefectDojo's deduplication
// engine has identified as a duplicate of another finding in the product.
// Duplicate findings are excluded from security-gate evaluation.
Expand All @@ -23,10 +23,14 @@ const (

// Finding represents a single security finding produced by a scanner.
type Finding struct {
Engine string
Severity string
Name string
VulnId string
Engine string
Severity string
Name string
VulnId string
// Cwe holds the CWE identifier for most scanners (e.g. "CWE-79"). For Grype
// findings it holds the CVE identifier (extracted from vulnerability.epss.cve
// when present, falling back to vulnerability.id). The display layer renders
// this column as "CWE/CVE".
Cwe string
Description string
SinkFile string
Expand Down Expand Up @@ -77,9 +81,9 @@ func FilterFindingsByStatus(findings []Finding, statuses []string) []Finding {
// Scanner-specific notes:
// - Grype: recommendation is the "Upgrade to X" string derived from fix.versions.
// - OpenGrep: recommendation is always "" because DefectDojo's Semgrep parser stores
// extra.message in description, not mitigation. The hash is additionally
// injected into extra.fingerprint before upload so that DefectDojo stores
// it as unique_id_from_tool, enabling a direct lookup without recomputation.
// extra.message in description, not mitigation. The hash is additionally
// injected into extra.fingerprint before upload so that DefectDojo stores
// it as unique_id_from_tool, enabling a direct lookup without recomputation.
// - KICS: recommendation is the expected_value from each file entry.
func ComputeFindingHash(severity, sinkFile string, sinkLine int, recommendation string) string {
input := strings.ToLower(strings.TrimSpace(severity)) + "|" +
Expand Down
8 changes: 4 additions & 4 deletions engine/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const (
)

const (
kicsScannerName = "Kics (IACST)"
syftScannerName = "Syft (SBOM)"
grypeScannerName = "Grype (SCA)"
opengrepScannerName = "OpenGrep (SAST)"
kicsScannerName = "Kics (IACST)"
syftScannerName = "Syft (SBOM)"
grypeScannerName = "Grype (SCA)"
opengrepScannerName = "OpenGrep (SAST)"
)
10 changes: 5 additions & 5 deletions engine/engine.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package engine

import (
"fmt"
"net/http"
"os"
"path/filepath"
"ScopeGuardian/connectors/defectdojo"
"ScopeGuardian/connectors/defectdojo/client"
"ScopeGuardian/domains/interfaces"
"ScopeGuardian/domains/models"
environment_variable "ScopeGuardian/environnement_variable"
featuresync "ScopeGuardian/features/sync"
"ScopeGuardian/features/scans/grype"
"ScopeGuardian/features/scans/kics"
"ScopeGuardian/features/scans/opengrep"
"ScopeGuardian/features/scans/syft"
featuresync "ScopeGuardian/features/sync"
"ScopeGuardian/loader"
"ScopeGuardian/logger"
"fmt"
"net/http"
"os"
"path/filepath"
"sync"
)

Expand Down
2 changes: 1 addition & 1 deletion engine/engine_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package engine

import (
"errors"
"ScopeGuardian/connectors/defectdojo"
"ScopeGuardian/domains/interfaces"
"ScopeGuardian/domains/models"
"ScopeGuardian/loader"
"errors"
"testing"

"github.com/stretchr/testify/assert"
Expand Down
8 changes: 4 additions & 4 deletions features/scans/grype/const.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package grype

const (
binaryPath = "/opt/grype/bin/grype"
dirPath = "/opt/grype"
configPath = "/opt/grype/config/grype.yaml"
binaryPath = "/opt/grype/bin/grype"
dirPath = "/opt/grype"
configPath = "/opt/grype/config/grype.yaml"
outputFolder = "results"
scannerType = "SCA"
)

const (
severityThreshold = "Info"
groupByProperty = "finding_title"
groupByProperty = "component_name+component_version"
findingGroupProperty = true
findingTagProperty = true
SCAEngineTag = "SCA"
Expand Down
13 changes: 9 additions & 4 deletions features/scans/grype/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ type GrypeFix struct {
State string `json:"state"`
}

type GrypeEpss struct {
Cve string `json:"cve"`
}

type GrypeVulnerability struct {
ID string `json:"id"`
Severity string `json:"severity"`
Description string `json:"description"`
Fix GrypeFix `json:"fix"`
ID string `json:"id"`
Severity string `json:"severity"`
Description string `json:"description"`
Fix GrypeFix `json:"fix"`
Epss []GrypeEpss `json:"epss"`
}

type GrypeArtifactLocation struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
"matches": [
{
"vulnerability": {
"id": "CVE-2021-1234",
"id": "GHSA-xxxx-1234",
"severity": "High",
"description": "A test high severity vulnerability in test-package",
"fix": {
"versions": ["1.2.0"],
"state": "fixed"
}
},
"epss": [
{
"cve": "CVE-2021-1234"
}
]
},
"artifact": {
"name": "test-package",
Expand Down
16 changes: 11 additions & 5 deletions features/scans/grype/service.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package grype

import (
"encoding/json"
"errors"
"fmt"
"os"
"ScopeGuardian/connectors/defectdojo"
"ScopeGuardian/domains/interfaces"
"ScopeGuardian/domains/models"
environment_variable "ScopeGuardian/environnement_variable"
"ScopeGuardian/exec"
"ScopeGuardian/loader"
"ScopeGuardian/logger"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"time"
)
Expand Down Expand Up @@ -101,12 +101,18 @@ func (s *GrypeServiceImpl) LoadFindings() ([]models.Finding, error) {
recommendation = fmt.Sprintf(recommendationUpgradeMultiple, strings.Join(match.Vulnerability.Fix.Versions, ", "))
}

cveId := match.Vulnerability.ID
if len(match.Vulnerability.Epss) > 0 && match.Vulnerability.Epss[0].Cve != "" {
cveId = match.Vulnerability.Epss[0].Cve
}

severity := strings.ToUpper(match.Vulnerability.Severity)
f := models.Finding{
Engine: scannerType,
Severity: severity,
Name: fmt.Sprintf("%s %s", match.Artifact.Name, match.Artifact.Version),
VulnId: match.Vulnerability.ID,
VulnId: cveId,
Cwe: cveId,
Description: match.Vulnerability.Description,
SinkFile: sinkFile,
Recommendation: recommendation,
Expand Down
Loading
Loading