Skip to content

Commit a2db2c3

Browse files
authored
fix(policies): graceful fallback for structured finding violations (#2960)
Signed-off-by: Miguel Martinez <miguel@chainloop.dev> Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>
1 parent 3ee75f4 commit a2db2c3

2 files changed

Lines changed: 46 additions & 3 deletions

File tree

pkg/policies/policies.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ func engineEvaluationsToAPIViolations(results []*engine.EvaluationResult, findin
758758
res := make([]*v12.PolicyEvaluation_Violation, 0)
759759
var warnings []string
760760
warnedNoFindingType := false
761+
warnedNoStructuredData := false
761762

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

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

785793
case findingType != "" && hasStructuredData:
786794
finding, err := findings.ValidateFinding(findingType, v.RawFinding)

pkg/policies/policies_test.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,12 +1526,47 @@ func (s *testSuite) TestEngineEvaluationsToAPIViolationsBehaviorMatrix() {
15261526
},
15271527
},
15281528
{
1529-
name: "finding_type + string violations - error",
1529+
name: "finding_type + string violations - fallback with warning",
15301530
findingType: "VULNERABILITY",
15311531
violations: []*engine.PolicyViolation{
15321532
{Subject: "p1", Violation: "plain string"},
15331533
},
1534-
wantErr: "declares finding_type",
1534+
wantViolations: 1,
1535+
wantWarnings: 1,
1536+
checkFn: func(violations []*v1.PolicyEvaluation_Violation) {
1537+
// The violation is kept as a plain string, no structured finding
1538+
s.Equal("plain string", violations[0].GetMessage())
1539+
s.Nil(violations[0].GetVulnerability())
1540+
},
1541+
},
1542+
{
1543+
name: "finding_type + mixed string and structured violations - per-violation handling",
1544+
findingType: "VULNERABILITY",
1545+
violations: []*engine.PolicyViolation{
1546+
{Subject: "p1", Violation: "plain string fallback"},
1547+
{Subject: "p2", Violation: "vuln found", RawFinding: map[string]any{
1548+
"message": "vuln found", "external_id": "CVE-2024-1234",
1549+
"package_purl": "pkg:golang/example.com/lib@v1.0.0", "severity": "HIGH",
1550+
}},
1551+
{Subject: "p3", Violation: "another plain string"},
1552+
},
1553+
wantViolations: 3,
1554+
wantWarnings: 1, // deduplicated warning
1555+
checkFn: func(violations []*v1.PolicyEvaluation_Violation) {
1556+
// First violation: plain string, no structured finding
1557+
s.Equal("plain string fallback", violations[0].GetMessage())
1558+
s.Nil(violations[0].GetVulnerability())
1559+
1560+
// Second violation: structured, validated
1561+
f := violations[1].GetVulnerability()
1562+
s.Require().NotNil(f)
1563+
s.Equal("CVE-2024-1234", f.GetExternalId())
1564+
s.Equal("HIGH", f.GetSeverity())
1565+
1566+
// Third violation: plain string, no structured finding
1567+
s.Equal("another plain string", violations[2].GetMessage())
1568+
s.Nil(violations[2].GetVulnerability())
1569+
},
15351570
},
15361571
{
15371572
name: "finding_type + valid structured violations - validates and sets oneof",

0 commit comments

Comments
 (0)