-
Notifications
You must be signed in to change notification settings - Fork 501
Introduce a shared Finding/SeverityLevel type across scanner integrations #54690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
pelikhan
merged 12 commits into
main
from
copilot/deep-report-introduce-shared-finding-type
Aug 22, 2026
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
3dfc0fa
Initial plan
Copilot 1dee27a
Add shared scanfindings package for scanner findings
Copilot 00f0cea
Map all scanner integrations onto shared Finding/SeverityLevel type
Copilot 30b9143
Clarify CountAtLeast test expectation for unknown severities
Copilot d33d406
docs(adr): add draft ADR-54690 for shared scanfindings type
github-actions[bot] b3d49f2
Merge branch 'main' into copilot/deep-report-introduce-shared-finding…
github-actions[bot] da8f01c
Merge branch 'main' into copilot/deep-report-introduce-shared-finding…
github-actions[bot] 286aab4
Fix shared finding adapter regressions
Copilot f55a5cd
Merge branch 'main' into copilot/deep-report-introduce-shared-finding…
github-actions[bot] 9ebdde9
Investigate CI failure
Copilot bf66d5b
Revert "Investigate CI failure"
Copilot e8ce4b4
Avoid closed coverage pipe in CGO tests
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
docs/adr/54690-shared-scanfindings-type-for-scanner-integrations.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # ADR-54690: Shared Finding/SeverityLevel Type Across Scanner Integrations | ||
|
|
||
| **Date**: 2026-08-22 | ||
| **Status**: Accepted | ||
| **Deciders**: copilot-swe-agent (PR author), gh-aw maintainers | ||
|
|
||
| --- | ||
|
|
||
| ### Context | ||
|
|
||
| The codebase integrates nine security scanners (zizmor, poutine, grype, grant, runner-guard, yamllint, audit findings, validation issues, markdown security scanner). Each integration defined its own finding struct with a different severity vocabulary (`High`, `error`, `Negligible`, `note`, …) and its own location shape. As a result, severity classification, message formatting, context-line extraction, and console rendering were reimplemented per tool. This made adding new scanners expensive and meant severity inconsistencies (e.g., `low`-severity zizmor findings rendered as `warning` while grype rendered equivalent findings as `info`) accumulated silently. | ||
|
|
||
| ### Decision | ||
|
|
||
| We will introduce a new `pkg/scanfindings` package that provides a shared `SeverityLevel` enum (`unknown` < `info` < `low` < `medium` < `high` < `critical`) and a shared `Finding` struct (`RuleID`, `Severity`, `Message`, `File`, `Line`, `Column`, `Context`). Each scanner integration retains its own native structs for JSON decoding, then maps onto `scanfindings.Finding` via a small `…FindingsToShared` adapter function. Severity classification, message formatting (`FormatMessage`), context-line extraction (`ContextLines`), rendering (`Render`), sorting (`Sort`), and counting (`CountAtLeast`) are implemented once in `pkg/scanfindings`. | ||
|
|
||
| ### Alternatives Considered | ||
|
|
||
| #### Alternative 1: Extract a shared severity mapping function only | ||
|
|
||
| Extract a single `ParseSeverity(raw string) string` helper returning a normalized string, keeping each tool's finding struct and rendering loop separate. This eliminates only the severity inconsistency while leaving context-line extraction, message building, and rendering duplicated across nine files. It is a smaller change but does not solve the maintenance problem that motivated this PR. | ||
|
|
||
| #### Alternative 2: Define a `Finding` interface instead of a concrete struct | ||
|
|
||
| Define a `Scanner` or `Finding` interface and let each integration implement it via its own native type. This avoids the explicit adapter functions and the coupling to a single shared struct, but adds indirection without eliminating boilerplate: every integration would still implement the same set of methods. The concrete-struct adapter approach chosen here produces less code overall and makes the mapping explicit and testable in isolation. | ||
|
|
||
| ### Consequences | ||
|
|
||
| #### Positive | ||
| - Adding a scanner now requires only a field-mapping adapter; severity classification, rendering, sorting and counting are handled once. | ||
| - The severity vocabulary is normalized through `ParseSeverity`, eliminating per-tool inconsistencies in how identical native labels were classified. | ||
| - `audit_report.go`'s `Finding.Severity` is now a typed `SeverityLevel` rather than an unvalidated `string`, enabling compile-time checks in severity comparisons. | ||
| - Two duplicated rendering loops in `poutine.go` collapse into a single `poutineFindingsToShared` adapter. | ||
|
|
||
| #### Negative | ||
| - All scanner integrations are now coupled to `pkg/scanfindings`; changes to the shared type's API propagate to every integration. | ||
| - The `Severity` field type on `audit_report.go`'s `Finding` struct changes from `string` to `SeverityLevel` — callers that compared `finding.Severity == "critical"` must now use `finding.Severity == scanfindings.SeverityCritical` or `.String()`. | ||
|
|
||
| #### Neutral | ||
| - Low-severity zizmor and runner-guard findings now render as `info` (matching grype's behavior) rather than `warning` — this is an intentional alignment, not a regression. | ||
| - The `yamllintIssue` internal struct is removed; `parseYamllintLine` returns `scanfindings.Finding` directly. | ||
| - JSON serialization of `SeverityLevel` is unchanged: the underlying type is `string`, so existing JSON consumers reading `"severity"` fields see the same lowercase values. | ||
|
|
||
| --- | ||
|
|
||
| *Accepted on 2026-08-22 after validating the shared type and all scanner adapters.* |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ import ( | |
| "github.com/github/gh-aw/pkg/fileutil" | ||
| "github.com/github/gh-aw/pkg/gitutil" | ||
| "github.com/github/gh-aw/pkg/logger" | ||
| "github.com/github/gh-aw/pkg/scanfindings" | ||
| "github.com/github/gh-aw/pkg/workflow" | ||
| "gopkg.in/yaml.v3" | ||
| ) | ||
|
|
@@ -330,7 +331,17 @@ func grantDisplayFindings(imageTag string, output *grantOutput) (int, error) { | |
| return 0, nil | ||
| } | ||
|
|
||
| total := 0 | ||
| findings := grantFindingsToShared(imageTag, output) | ||
| scanfindings.Render(os.Stderr, findings) | ||
|
|
||
| return len(findings), nil | ||
| } | ||
|
|
||
| // grantFindingsToShared maps grant's denied license packages onto the shared | ||
| // finding representation used by every scanner integration. Container images have | ||
| // no source location, so the image tag is reported as the finding location. | ||
| func grantFindingsToShared(imageTag string, output *grantOutput) []scanfindings.Finding { | ||
| var findings []scanfindings.Finding | ||
| for _, target := range output.Run.Targets { | ||
| for _, pkg := range target.Evaluation.Findings.Packages { | ||
| if pkg.Decision != "deny" { | ||
|
|
@@ -354,22 +365,17 @@ func grantDisplayFindings(imageTag string, output *grantOutput) (int, error) { | |
| } | ||
| } | ||
|
|
||
| message := fmt.Sprintf("license policy violation: %s (%s)", grantPackageRef(pkg), licenses) | ||
| compilerErr := console.CompilerError{ | ||
| Position: console.ErrorPosition{ | ||
| File: imageTag, | ||
| Line: 1, | ||
| Column: 1, | ||
| }, | ||
| Type: "error", | ||
| Message: message, | ||
| } | ||
| fmt.Fprint(os.Stderr, console.FormatError(compilerErr)) | ||
| total++ | ||
| findings = append(findings, scanfindings.Finding{ | ||
| RuleID: "license-policy", | ||
| Severity: scanfindings.SeverityHigh, | ||
| Message: fmt.Sprintf("license policy violation: %s (%s)", grantPackageRef(pkg), licenses), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/codebase-design] 💡 SuggestionAdd a brief comment explaining the deliberate choice: // All denied licenses are treated as high-severity policy violations.
// Grant doesn't provide per-package severity, so a fixed level is used here.
Severity: scanfindings.SeverityHigh,@copilot please address this. |
||
| File: imageTag, | ||
| Line: 1, | ||
| Column: 1, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return total, nil | ||
| return findings | ||
| } | ||
|
|
||
| func grantPackageRef(pkg grantPackageFinding) string { | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/diagnosing-bugs]
ParseSeverity(assessment.Severity)silently returnsSeverityUnknownfor any value not in the lookup table. If the upstream AI emits e.g."MEDIUM"(upper-case), the severity disappears without a trace — the finding still surfaces but reads as"unknown".💡 Suggestion
Log unrecognised values so they don't silently degrade:
A test covering the full range of AI-emitted severity strings would also guard against future changes.
@copilot please address this.