From c8eeeca684740a724f6d17df308df8e56fa5f093 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Date: Thu, 23 Oct 2025 18:03:48 -0400 Subject: [PATCH 1/2] feat: add tool annotations to GitLab security reports Extract and expose tool metadata from GitLab Security Reports using scanner field with fallback to analyzer. This enables consistent tool tracking across all material types (SARIF, SBOM, GitLab reports). Implementation uses scanner (actual security tool) as primary source and falls back to analyzer (wrapper) if scanner info is unavailable. Populates standard annotations: chainloop.material.tool.name and chainloop.material.tool.version. Resolves #2478 Signed-off-by: Miguel Martinez --- pkg/attestation/crafter/materials/gitlab.go | 45 ++++++++++++++----- .../crafter/materials/gitlab_test.go | 33 ++++++++++++-- .../testdata/gl-sonarqube-report.json | 27 +++++++++++ 3 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 pkg/attestation/crafter/materials/testdata/gl-sonarqube-report.json diff --git a/pkg/attestation/crafter/materials/gitlab.go b/pkg/attestation/crafter/materials/gitlab.go index cd8f41b82..e97b2f519 100644 --- a/pkg/attestation/crafter/materials/gitlab.go +++ b/pkg/attestation/crafter/materials/gitlab.go @@ -45,27 +45,48 @@ func NewGitlabCrafter(schema *schemaapi.CraftingSchema_Material, backend *cascli } func (i *GitlabCrafter) Craft(ctx context.Context, filePath string) (*api.Attestation_Material, error) { - if err := i.validate(filePath); err != nil { - return nil, err - } - - return uploadAndCraft(ctx, i.input, i.backend, filePath, i.logger) -} - -func (i *GitlabCrafter) validate(filePath string) error { data, err := os.ReadFile(filePath) if err != nil { - return fmt.Errorf("can't open the file: %w", err) + return nil, fmt.Errorf("can't open the file: %w", err) } var glReport report.Report if err = json.Unmarshal(data, &glReport); err != nil { - return fmt.Errorf("error unmarshalling report file: %w", err) + return nil, fmt.Errorf("error unmarshalling report file: %w", err) } if !slices.Contains(supportedTypes, string(glReport.Scan.Type)) { - return fmt.Errorf("error loading Gitlab report. Missing scan type") + return nil, fmt.Errorf("error loading Gitlab report. Missing scan type") + } + + m, err := uploadAndCraft(ctx, i.input, i.backend, filePath, i.logger) + if err != nil { + return nil, err + } + + i.injectAnnotations(m, &glReport) + + return m, nil +} + +func (i *GitlabCrafter) injectAnnotations(m *api.Attestation_Material, glReport *report.Report) { + // Prefer scanner (the actual security tool) over analyzer (the wrapper/integration layer) + toolName := glReport.Scan.Scanner.Name + toolVersion := glReport.Scan.Scanner.Version + + // Fallback to analyzer if scanner information is not available + if toolName == "" { + toolName = glReport.Scan.Analyzer.Name + toolVersion = glReport.Scan.Analyzer.Version } - return nil + if toolName != "" { + if m.Annotations == nil { + m.Annotations = make(map[string]string) + } + m.Annotations[AnnotationToolNameKey] = toolName + if toolVersion != "" { + m.Annotations[AnnotationToolVersionKey] = toolVersion + } + } } diff --git a/pkg/attestation/crafter/materials/gitlab_test.go b/pkg/attestation/crafter/materials/gitlab_test.go index d81eefca0..6a9879614 100644 --- a/pkg/attestation/crafter/materials/gitlab_test.go +++ b/pkg/attestation/crafter/materials/gitlab_test.go @@ -64,9 +64,10 @@ func TestNewGitlabCrafter(t *testing.T) { func TestGitlabCrafter_Craft(t *testing.T) { testCases := []struct { - name string - filePath string - wantErr string + name string + filePath string + wantErr string + annotations map[string]string }{ { name: "invalid path", @@ -86,14 +87,34 @@ func TestGitlabCrafter_Craft(t *testing.T) { { name: "sast report", filePath: "./testdata/gl-sast-report.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "Semgrep", + "chainloop.material.tool.version": ":SKIP:", + }, }, { name: "container scanning report", filePath: "./testdata/gl-container-scanning-report.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "Trivy", + "chainloop.material.tool.version": "0.19.2", + }, }, { name: "secret detection report", filePath: "./testdata/gl-secret-detection-report.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "Gitleaks", + "chainloop.material.tool.version": ":SKIP:", + }, + }, + { + name: "sonarqube report", + filePath: "./testdata/gl-sonarqube-report.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "Sonar", + "chainloop.material.tool.version": "11.2.0.2797", + }, }, } @@ -125,6 +146,12 @@ func TestGitlabCrafter_Craft(t *testing.T) { require.NoError(t, err) assert.Equal(t, contractAPI.CraftingSchema_Material_GITLAB_SECURITY_REPORT.String(), got.MaterialType.String()) assert.True(t, got.UploadedToCas) + + if tc.annotations != nil { + for k, v := range tc.annotations { + assert.Equal(t, v, got.Annotations[k]) + } + } }) } } diff --git a/pkg/attestation/crafter/materials/testdata/gl-sonarqube-report.json b/pkg/attestation/crafter/materials/testdata/gl-sonarqube-report.json new file mode 100644 index 000000000..6b1651b3a --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/gl-sonarqube-report.json @@ -0,0 +1,27 @@ +{ + "version": "15.0.0", + "scan": { + "analyzer": { + "id": "SONARQUBE 2025.1.1.104738", + "name": "SONARQUBE - Enterprise", + "vendor": { + "name": "Sonar" + }, + "version": "2025.1.1.104738" + }, + "scanner": { + "id": "sonar_scan", + "name": "Sonar", + "vendor": { + "name": "Sonar" + }, + "version": "11.2.0.2797" + }, + "start_time": "2025-10-20T20:18:45", + "end_time": "2025-10-20T20:18:45", + "status": "success", + "messages": [], + "type": "sast" + }, + "vulnerabilities": [] +} From bebd544ac40012dacec8b130151f9f900098bf9f Mon Sep 17 00:00:00 2001 From: Miguel Martinez Date: Thu, 23 Oct 2025 18:03:57 -0400 Subject: [PATCH 2/2] feat(policy): has-sast-policy Signed-off-by: Miguel Martinez --- CLAUDE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0d1afff10..c99e14b15 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -264,4 +264,5 @@ Code reviews are required for all submissions via GitHub pull requests. - For each file you modify, update the license header. If it says 2024, change it to 2024-2025. If there's no license header, create one with the current year. - if you add any new dependency to a constructor, remember to run wire ./... - when creating PR message, keep it high-level, what functionality was added, don't add info about testing, no icons, no info about how the message was generated. -- app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts is a special case that we don't want to upgrade, so if it upgrades, put it back to main \ No newline at end of file +- app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts is a special case that we don't want to upgrade, so if it upgrades, put it back to main +- when creating a commit or PR message, NEVER add co-authored by or generated by Claude code \ No newline at end of file