Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 8 additions & 8 deletions docs/sca.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ Every per-codebase verb takes a positional `<codebase>` and an optional
Dep-Track project "version". You can also supply an explicit branch name or
release tag.

### Severity filter (inclusive)
### Severity filter (inclusive, server-side)

`--severity` accepts case-insensitive values `critical | high | medium | low | info`.
The filter is **inclusive**: `--severity=high` returns rows at HIGH or above;
`--severity=medium` returns CRITICAL + HIGH + MEDIUM. `INFO` implicitly
includes `UNASSIGNED`.
includes `UNASSIGNED`. Applied server-side on both `findings` and `components`,
so any server-side cap (`truncated=true`) applies only to matching rows.

## `sca list`

Expand Down Expand Up @@ -152,14 +153,13 @@ Filter by upstream vulnerability source (e.g. `NVD`, `GITHUB`, `OSV`):
krci sca findings payments-api --source=NVD
```

Very large projects are capped server-side at 1000 rows. `--source` is the
only flag that narrows the upstream query; `--severity` filters client-side
after the cap and cannot recover findings beyond row 1000. To audit a
truncated project for severities, drop `--severity` and post-filter the JSON,
or scope by `--branch` first:
Very large projects are capped server-side at 1000 rows. Both `--severity`
and `--source` are applied before the cap, so `truncated=true` means more
**matching** findings exist, not just more total findings. Narrow further with
`--branch` when still truncated:

```
(findings truncated to 1000 rows server-side — only --source narrows the upstream query; --severity filters client-side and cannot recover capped rows)
(findings truncated to 1000 rows — narrow with --severity or --source to reduce the upstream result set)
```

Script-friendly CVE extraction:
Expand Down
8 changes: 8 additions & 0 deletions internal/portal/openapi/spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -1836,6 +1836,14 @@
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "severity",
"description": "Comma-separated canonical Dep-Track severity set (CRITICAL,HIGH,MEDIUM,LOW,INFO,UNASSIGNED). Filter applied server-side before the 1000-row cap, so truncated=true accurately reports more matching findings exist.",
"schema": {
"type": "string"
}
}
],
"responses": {
Expand Down
19 changes: 19 additions & 0 deletions internal/portal/restapi/api_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions internal/portal/sca.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,16 @@ type SCAListParams struct {
}

// SCAComponentsParams carries the CLI-validated inputs for `krci sca components`.
// Severity holds the canonical upper-case Dep-Track severity set to filter by;
// empty slice means no filter.
type SCAComponentsParams struct {
Codebase string
Branch string
Page int
PageSize int
OnlyOutdated bool
OnlyDirect bool
Severity []string
// Severity is the canonical upper-case Dep-Track severity set to filter by;
// empty slice means no filter. Applied server-side before the cap.
Severity []string
}

// SCAFindingsParams carries the CLI-validated inputs for `krci sca findings`.
Expand All @@ -159,6 +159,9 @@ type SCAFindingsParams struct {
Branch string
IncludeSuppressed bool
Source string
// Severity is the canonical upper-case Dep-Track severity set to filter by;
// empty slice means no filter. Applied server-side before the cap.
Severity []string
}

// SCAService wraps the generated restapi ScaList/ScaGet/ScaComponents/ScaFindings
Expand Down Expand Up @@ -355,7 +358,7 @@ func (s *SCAService) Components(ctx context.Context, params SCAComponentsParams)

// Findings returns the vulnerability findings for a codebase/branch pair.
// The Portal caps the server-side result at 1000 rows; when exceeded,
// Truncated is true.
// Truncated is true. Severity filter is applied server-side before the cap.
func (s *SCAService) Findings(ctx context.Context, params SCAFindingsParams) (*SCAFindingList, error) {
p := &restapi.ScaFindingsParams{Codebase: params.Codebase}
if params.Branch != "" {
Expand All @@ -365,6 +368,9 @@ func (s *SCAService) Findings(ctx context.Context, params SCAFindingsParams) (*S
if params.Source != "" {
p.Source = ptr.To(params.Source)
}
if len(params.Severity) > 0 {
p.Severity = ptr.To(strings.Join(params.Severity, ","))
}

resp, err := s.client.ScaFindingsWithResponse(ctx, p)
if err != nil {
Expand Down
25 changes: 5 additions & 20 deletions pkg/cmd/sca/findings/findings.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ func NewCmdFindings(f *cmdutil.Factory, runF func(*FindingsOptions) error) *cobr
Long: fmt.Sprintf(
"List Dep-Track vulnerability findings for a project.\n\n"+
"Unpaginated: the portal returns a single response capped at %d rows\n"+
"server-side. Only --source narrows the upstream query; --severity is\n"+
"applied client-side after the cap, so it cannot recover findings beyond\n"+
"row %d.",
scainternal.FindingsServerCap, scainternal.FindingsServerCap),
"server-side. Both --severity and --source are applied server-side before\n"+
"the cap, so truncated=true accurately reports more matching findings exist.",
scainternal.FindingsServerCap),
Args: cmdutil.ExactArgs(1, "a KubeRocketCI project name",
"to see available projects: krci sca list"),
Example: ` # All unsuppressed findings, default branch
Expand Down Expand Up @@ -113,25 +112,12 @@ func findingsRun(ctx context.Context, opts *FindingsOptions) error {
Branch: opts.Branch,
IncludeSuppressed: opts.IncludeSuppressed,
Source: opts.Source,
Severity: scainternal.ExpandSeverityFlag(opts.Severity),
})
if err != nil {
return scainternal.HandleError(opts.IO, opts.OutputFormat, err)
}

// Client-side inclusive severity filter — server does not narrow by
// severity. Truncated stays as-returned by the server: hiding it after a
// client-side filter would silently drop findings that fell off the cap
// before we ever saw them.
if inclusive := scainternal.ExpandSeverityFlag(opts.Severity); len(inclusive) > 0 {
filtered := make([]portal.SCAFinding, 0, len(result.Items))
for _, f := range result.Items {
if scainternal.SeverityMatches(f.Vulnerability.Severity, inclusive) {
filtered = append(filtered, f)
}
}
result.Items = filtered
}

return scainternal.Render(opts.IO, opts.OutputFormat, result, func(w io.Writer, isTTY bool) error {
return renderTable(w, isTTY, opts.Codebase, result)
})
Expand Down Expand Up @@ -180,8 +166,7 @@ func renderTable(w io.Writer, isTTY bool, codebase string, result *portal.SCAFin
return err
}
if _, err := fmt.Fprintf(w,
"(findings truncated to %d rows server-side — only --source narrows the upstream query;"+
" --severity filters client-side and cannot recover capped rows)\n",
"(findings truncated to %d rows — narrow with --severity or --source to reduce the upstream result set)\n",
scainternal.FindingsServerCap); err != nil {
return err
}
Expand Down
8 changes: 0 additions & 8 deletions pkg/cmd/sca/internal/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,6 @@ func InclusiveSeverities(min string) []string {
return out
}

// SeverityMatches returns true if severity is in the allowed set. Empty allowed
// means "no filter" — callers expected to short-circuit before calling.
// Severity is normalised to upper-case so upstream casing variations don't
// silently drop rows (allowed is always canonical upper-case).
func SeverityMatches(severity string, allowed []string) bool {
return slices.Contains(allowed, strings.ToUpper(severity))
}

// severityRank ranks Dep-Track severities ascending (UNASSIGNED=0 is least
// severe). Used by InclusiveFromSet to pick the lowest-rank threshold.
var severityRank = map[string]int{
Expand Down
13 changes: 0 additions & 13 deletions pkg/cmd/sca/internal/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,19 +185,6 @@ func TestInclusiveFromSet(t *testing.T) {
}
}

func TestSeverityMatches(t *testing.T) {
t.Parallel()
if !SeverityMatches("CRITICAL", []string{"CRITICAL", "HIGH"}) {
t.Error("CRITICAL must match")
}
if SeverityMatches("LOW", []string{"CRITICAL", "HIGH"}) {
t.Error("LOW must not match")
}
if SeverityMatches("CRITICAL", nil) {
t.Error("empty allowed must not match")
}
}

func TestConstants(t *testing.T) {
t.Parallel()
if MaxPageSize != 500 {
Expand Down
Loading