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: 11 additions & 5 deletions docs/sca.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ credentials are required.
| `sca list` | List SCA projects known to Dependency-Track |
| `sca get <codebase>` | Project overview: risk score, severity counts, last BOM import |
| `sca components <codebase>` | Dependencies with outdated / direct / severity filters |
| `sca findings <codebase>` | Flat vulnerability listing (CVE-level) with severity filter + truncation |
| `sca findings <codebase>` | Flat vulnerability listing (CVE-level), unpaginated, server-cap 1000 rows |

All commands accept `-o, --output` with `table` (default) or `json`.

Expand Down Expand Up @@ -118,8 +118,10 @@ commons-text 1.9 1.13.1 yes Apache-2.0 5.5 0/1
2 components, page 1 of 1 (page-size 50)
```

Combined filters are **AND** — server-side `--only-outdated` / `--only-direct`
are forwarded to Dep-Track, and `--severity` narrows client-side afterwards:
Combined filters are **AND** — `--only-outdated`, `--only-direct`, and
`--severity` are all applied server-side. The portal auto-pages across the
full project (up to a safety cap) to evaluate the severity filter before
paginating; if the cap is reached, the response carries `truncated=true`:

```bash
# Outdated direct dependencies with at least one HIGH or CRITICAL finding
Expand Down Expand Up @@ -150,10 +152,14 @@ 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 with a footer hint:
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:

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

Script-friendly CVE extraction:
Expand Down
6 changes: 3 additions & 3 deletions internal/portal/sca.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,13 @@ func scaBranchNotFoundErr(err error, body []byte, codebase, branch string) error
bodyLower := strings.ToLower(string(body))
switch {
case branch != "":
return &scaNotFoundError{msg: fmt.Sprintf("codebase %s not found", codebase)}
return &scaNotFoundError{msg: fmt.Sprintf("project %s not found", codebase)}
case strings.Contains(bodyLower, "default_branch_missing"):
return &scaNotFoundError{msg: fmt.Sprintf(
"codebase %s has no spec.defaultBranch configured — pass --branch explicitly", codebase)}
"project %s has no spec.defaultBranch configured — pass --branch explicitly", codebase)}
default:
return &scaNotFoundError{msg: fmt.Sprintf(
"codebase %s not found — use 'krci sca list --search=%s' to find projects known to Dep-Track",
"project %s not found — use 'krci sca list --search=%s' to find projects known to Dep-Track",
codebase, codebase)}
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/portal/sca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func TestSCAService_Get_404_CodebaseNotFound_WithExplicitBranch(t *testing.T) {
if !errors.Is(err, ErrNotFound) {
t.Errorf("want wrap of ErrNotFound, got %v", err)
}
if !strings.Contains(err.Error(), "codebase nope not found") {
if !strings.Contains(err.Error(), "project nope not found") {
t.Errorf("unexpected message: %v", err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/project/deployments/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ project is registered (CDPipeline.spec.applications) but no Application
exists yet are emitted with "-" placeholders (table) or null values (JSON).

Rows are sorted by deployment ascending, then by Stage.spec.order ascending.`,
Args: cmdutil.ExactArgs(1, "a project (codebase) name",
Args: cmdutil.ExactArgs(1, "a project name",
"to see available projects: krci project list"),
Example: ` # Default
krci project deployments my-app
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
func NewCmdProject(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "project",
Short: "Manage projects (Codebases)",
Short: "Manage projects",
Aliases: []string{"proj"},
}

Expand Down
7 changes: 4 additions & 3 deletions pkg/cmd/sca/components/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func NewCmdComponents(f *cmdutil.Factory, runF func(*ComponentsOptions) error) *
}

cmd := &cobra.Command{
Use: "components <codebase>",
Use: "components <project>",
Short: "List dependencies (components) for an SCA project",
Args: cmdutil.ExactArgs(1, "a KubeRocketCI codebase name",
Args: cmdutil.ExactArgs(1, "a KubeRocketCI project name",
"to see available projects: krci sca list"),
Example: ` # Default branch, first 50 dependencies
krci sca components payments-api
Expand Down Expand Up @@ -83,7 +83,8 @@ func NewCmdComponents(f *cmdutil.Factory, runF func(*ComponentsOptions) error) *
cmd.Flags().StringSliceVar(&opts.Severity, "severity", nil,
scainternal.SeverityFlagUsage+
" Applied server-side across all dependencies of the project.")
cmd.Flags().BoolVar(&opts.OnlyOutdated, "only-outdated", false, "Only components marked outdated by Dependency-Track")
cmd.Flags().BoolVar(&opts.OnlyOutdated, "only-outdated", false,
"Only components Dep-Track marks outdated (newer version exists; independent of vulnerability status)")
cmd.Flags().BoolVar(&opts.OnlyDirect, "only-direct", false, "Only direct (non-transitive) dependencies")
cmd.Flags().IntVar(&opts.Page, "page", 1, "Page index (1-based)")
cmd.Flags().IntVar(&opts.PageSize, "page-size", defaultComponentsPageSize, "Page size (max 500)")
Expand Down
29 changes: 18 additions & 11 deletions pkg/cmd/sca/findings/findings.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,16 @@ func NewCmdFindings(f *cmdutil.Factory, runF func(*FindingsOptions) error) *cobr
}

cmd := &cobra.Command{
Use: "findings <codebase>",
Short: "List Dep-Track vulnerability findings for a codebase",
Args: cmdutil.ExactArgs(1, "a KubeRocketCI codebase name",
Use: "findings <project>",
Short: "List Dep-Track vulnerability findings for a project",
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),
Args: cmdutil.ExactArgs(1, "a KubeRocketCI project name",
"to see available projects: krci sca list"),
Example: ` # All unsuppressed findings, default branch
krci sca findings payments-api
Expand Down Expand Up @@ -111,7 +118,10 @@ func findingsRun(ctx context.Context, opts *FindingsOptions) error {
return scainternal.HandleError(opts.IO, opts.OutputFormat, err)
}

// Client-side inclusive severity filter (server does not narrow by severity).
// 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 {
Expand All @@ -120,11 +130,6 @@ func findingsRun(ctx context.Context, opts *FindingsOptions) error {
}
}
result.Items = filtered
// Once the user has narrowed by --severity, the upstream "1000-row cap"
// hint is misleading: they already supplied the only follow-up flag we
// would have suggested. Clear the flag so neither the table footer nor
// the JSON envelope keeps advertising it.
result.Truncated = false
}

return scainternal.Render(opts.IO, opts.OutputFormat, result, func(w io.Writer, isTTY bool) error {
Expand Down Expand Up @@ -174,8 +179,10 @@ func renderTable(w io.Writer, isTTY bool, codebase string, result *portal.SCAFin
if _, err := fmt.Fprintln(w); err != nil {
return err
}
if _, err := fmt.Fprintln(w,
"(findings truncated to 1000 rows — narrow the query via --severity or --source)"); err != nil {
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",
scainternal.FindingsServerCap); err != nil {
return err
}
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/cmd/sca/get/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ func NewCmdGet(f *cmdutil.Factory, runF func(*GetOptions) error) *cobra.Command
}

cmd := &cobra.Command{
Use: "get <codebase>",
Short: "Show a Dep-Track project's overview for a codebase",
Args: cmdutil.ExactArgs(1, "a KubeRocketCI codebase name",
Use: "get <project>",
Short: "Show SCA scan overview for a project",
Args: cmdutil.ExactArgs(1, "a KubeRocketCI project name",
"to see available projects: krci sca list"),
Example: ` # Uses Codebase.spec.defaultBranch when --branch is omitted
krci sca get payments-api
Expand Down Expand Up @@ -118,7 +118,7 @@ func printDetail(w io.Writer, codebase, requestedBranch string, d *portal.SCAPro
}

pairs := []kvPair{
{label: "Codebase", value: codebase},
{label: "Project", value: codebase},
{label: "Branch", value: project.Version},
{label: "Classifier", value: scainternal.OrDash(project.Classifier)},
{label: "Active", value: formatActive(project.Active, styled)},
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/sca/get/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestGet_BranchFlagUsageStringIsVerbatim(t *testing.T) {
if !strings.Contains(flag.Usage, "Dep-Track project 'version'") {
t.Errorf("--branch usage must reference Dep-Track version field: %q", flag.Usage)
}
if !strings.Contains(flag.Usage, "krci sca list --search=<codebase>") {
if !strings.Contains(flag.Usage, "krci sca list --search=<project>") {
t.Errorf("--branch usage must hint discovery path: %q", flag.Usage)
}
}
Expand Down Expand Up @@ -140,7 +140,7 @@ func TestPrintDetail_HappyPath(t *testing.T) {
t.Fatalf("printDetail: %v", err)
}
out := buf.String()
wants := []string{"svc @ main", "Codebase", "Branch", "Classifier", "Risk score",
wants := []string{"svc @ main", "Project", "Branch", "Classifier", "Risk score",
"Vulnerabilities", "Critical", "Components", "Total"}
for _, w := range wants {
if !strings.Contains(out, w) {
Expand Down
16 changes: 11 additions & 5 deletions pkg/cmd/sca/internal/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ import (
// verb. Matches the OpenAPI ceiling.
const MaxPageSize = 500

// FindingsServerCap is the maximum number of rows the portal returns from
// the unpaginated `sca findings` endpoint. Mirrors the server-side ceiling
// documented in the OpenAPI spec; the help text and truncation footer must
// stay in sync with it.
const FindingsServerCap = 1000

// BranchFlagUsage is the verbatim help text for `--branch` across every
// per-codebase sca verb. Spec Requirement "Codebase + Branch Addressing"
// mandates this exact string so users always see the same Dep-Track `version`
// field explanation.
const BranchFlagUsage = "branch name (maps to the Dep-Track project 'version' field). " +
"Defaults to the codebase's spec.defaultBranch. " +
"Run 'krci sca list --search=<codebase>' to discover all recorded versions."
"Defaults to the project's spec.defaultBranch. " +
"Run 'krci sca list --search=<project>' to discover all recorded versions."

// SeverityFlagUsage is the verbatim help text for `--severity` across the
// verbs that expose it. Spec design §D4 mandates this exact string: it
Expand All @@ -38,15 +44,15 @@ const SeverityFlagUsage = "minimum severity to include (inclusive). " +
// names by platform convention follow DNS-1123.
func ValidateCodebaseKey(codebase string) error {
if codebase == "" {
return fmt.Errorf("<codebase> must not be empty")
return fmt.Errorf("<project> must not be empty")
}

if len(codebase) > cmdutil.DNS1123SubdomainMaxLength {
return fmt.Errorf("<codebase> must be at most %d characters", cmdutil.DNS1123SubdomainMaxLength)
return fmt.Errorf("<project> must be at most %d characters", cmdutil.DNS1123SubdomainMaxLength)
}

if !cmdutil.IsValidDNS1123Label(codebase) {
return fmt.Errorf("<codebase> must be a valid DNS-1123 name")
return fmt.Errorf("<project> must be a valid DNS-1123 name")
}

return nil
Expand Down
Loading