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
10 changes: 9 additions & 1 deletion pkg/policies/policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@ func engineEvaluationsToAPIViolations(results []*engine.EvaluationResult, findin
res := make([]*v12.PolicyEvaluation_Violation, 0)
var warnings []string
warnedNoFindingType := false
warnedNoStructuredData := false

for _, r := range results {
for _, v := range r.Violations {
Expand All @@ -780,7 +781,14 @@ func engineEvaluationsToAPIViolations(results []*engine.EvaluationResult, findin
}

case findingType != "" && !hasStructuredData:
return nil, nil, fmt.Errorf("declares finding_type %q but violation is not a structured object", findingType)
// Policy declares a finding type but this violation is a plain string.
// This can happen when some evaluation branches do not support structured output yet.
// Fall back to treating it as a regular string violation without the typed finding.
if !warnedNoStructuredData {
warnings = append(warnings,
fmt.Sprintf("policy declares finding_type %q but some violations are plain strings — structured finding data will not be available for those", findingType))
warnedNoStructuredData = true
}

case findingType != "" && hasStructuredData:
finding, err := findings.ValidateFinding(findingType, v.RawFinding)
Expand Down
39 changes: 37 additions & 2 deletions pkg/policies/policies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1526,12 +1526,47 @@ func (s *testSuite) TestEngineEvaluationsToAPIViolationsBehaviorMatrix() {
},
},
{
name: "finding_type + string violations - error",
name: "finding_type + string violations - fallback with warning",
findingType: "VULNERABILITY",
violations: []*engine.PolicyViolation{
{Subject: "p1", Violation: "plain string"},
},
wantErr: "declares finding_type",
wantViolations: 1,
wantWarnings: 1,
checkFn: func(violations []*v1.PolicyEvaluation_Violation) {
// The violation is kept as a plain string, no structured finding
s.Equal("plain string", violations[0].GetMessage())
s.Nil(violations[0].GetVulnerability())
},
},
{
name: "finding_type + mixed string and structured violations - per-violation handling",
findingType: "VULNERABILITY",
violations: []*engine.PolicyViolation{
{Subject: "p1", Violation: "plain string fallback"},
{Subject: "p2", Violation: "vuln found", RawFinding: map[string]any{
"message": "vuln found", "external_id": "CVE-2024-1234",
"package_purl": "pkg:golang/example.com/lib@v1.0.0", "severity": "HIGH",
}},
{Subject: "p3", Violation: "another plain string"},
},
wantViolations: 3,
wantWarnings: 1, // deduplicated warning
checkFn: func(violations []*v1.PolicyEvaluation_Violation) {
// First violation: plain string, no structured finding
s.Equal("plain string fallback", violations[0].GetMessage())
s.Nil(violations[0].GetVulnerability())

// Second violation: structured, validated
f := violations[1].GetVulnerability()
s.Require().NotNil(f)
s.Equal("CVE-2024-1234", f.GetExternalId())
s.Equal("HIGH", f.GetSeverity())

// Third violation: plain string, no structured finding
s.Equal("another plain string", violations[2].GetMessage())
s.Nil(violations[2].GetVulnerability())
},
},
{
name: "finding_type + valid structured violations - validates and sets oneof",
Expand Down
Loading