Skip to content

feat(artifacts): analyzed commit ref in every artifact + analyzed CI file in the JSON report - #444

Merged
thomasboni merged 1 commit into
mainfrom
feat/artifact-commit-ref-and-ci-file
Sep 3, 2026
Merged

feat(artifacts): analyzed commit ref in every artifact + analyzed CI file in the JSON report#444
thomasboni merged 1 commit into
mainfrom
feat/artifact-commit-ref-and-ci-file

Conversation

@thomasboni

Copy link
Copy Markdown
Contributor

Closes #443.

Amine (AI) needs analysis outputs to be self-describing: a consumer should read which commit was analyzed and which CI input produced the findings straight from the artifact, without reconstructing it from the CI environment the artifact was written in.

What this adds

1. The analyzed commit ref, in every relevant artifact

A single resolver (cmd/artifact_provenance.go) computes the analyzed commit once, at output time, and every writer reads it off the result:

  • Commit SHA: the CI commit-SHA env, then the analysis head, then the local git checkout.
  • Ref (branch/tag): the CI ref-name env, then the analyzed branch, then the default branch.

The HEAD placeholder a --project run leaves behind is a ref, not a commit: it is never emitted as a commit SHA, it is omitted. A commit that cannot be resolved is omitted rather than faked.

Artifact Where the ref lands
JSON report headCommitSha (now resolves reliably, never "HEAD") + analyzeBranch
SARIF run.versionControlProvenance[] (revisionId + branch + repositoryUri), the spec's own provenance slot
PBOM project.commitSHA + project.ref
CycloneDX plumber:git:commit / plumber:git:ref on the project component
OCSF an OCSF resource per finding: repo in uid, commit in version, ref in labels

The platform push already carries ref{branch, sha} and CSV is a flat findings dump, so both are out of scope.

2. The analyzed CI file, in the JSON report only

A new analyzedCiConfig block names the exact input the findings came from:

  • GitLab: the resolved merged pipeline (path + content + merged: true).
  • GitHub: each scanned workflow file under workflows[] (path + content), for both the local and remote scan paths.

The GitHub scanners keep the file bytes on the IR under an existing json:"-" field, so nothing new reaches the OPA input. This block is JSON-report only by design (verified absent from SARIF/PBOM/CycloneDX/OCSF).

Non-breaking

Strictly additive. An e2e diff of every artifact against main on the same fixture, both providers, shows 0 removed and 0 retyped keys, only the new fields:

report-gl: removed=0 retyped=0 added=4
sarif-gl:  removed=0 retyped=0 added=4
pbom-gl:   removed=0 retyped=0 added=2
cdx-gl:    removed=0 retyped=0 added=0   (new props sit inside the existing properties[] array)
ocsf-gl:   removed=0 retyped=0 added=6

Testing

  • TDD throughout: unit tests for the resolver (resolveArtifactRef, incl. HEAD rejection and the ref-name-env-over-default-branch precedence), and per-artifact provenance tests (report, SARIF, OCSF, PBOM, CycloneDX).
  • Full suite green; gofmt clean; pinned golangci-lint 0 issues; deadcode gate clean.
  • e2e sweep on both providers: 18/18 provenance assertions pass (commit ref present in all five artifacts; analyzedCiConfig present in the report and absent everywhere else; GitLab merged YAML vs GitHub per-workflow content).

🤖 Generated with Claude Code

Comment thread cmd/artifact_provenance.go
Comment thread control/task_github.go
Comment thread cmd/artifact_provenance.go
Comment thread cmd/artifact_provenance.go Outdated
Comment thread control/task.go Outdated
Comment thread cmd/ocsf_provenance_test.go
Comment thread cmd/artifact_provenance.go
Comment thread cmd/analyze_gitlab.go
Comment thread github/github_workflows.go
Comment thread pbom/generate_github.go
Comment thread cmd/analyze_shared.go
Comment thread cmd/artifact_provenance.go
Comment thread cmd/artifact_provenance_test.go
Comment thread cmd/artifact_provenance.go
Comment thread github/github_workflows_remote.go
Comment thread provider/gitlab.go
Comment thread cmd/artifact_provenance.go
Comment thread provider/github.go
Comment thread cmd/artifact_provenance.go Outdated
Comment thread github/github_workflows_remote.go
Comment thread cmd/artifact_provenance.go Outdated
Comment thread github/github_workflows.go
@thomasboni
thomasboni force-pushed the feat/artifact-commit-ref-and-ci-file branch from 59120a4 to 0e14b17 Compare September 3, 2026 11:06
Comment thread cmd/artifact_provenance.go Outdated
Comment thread cmd/ocsf.go
@thomasboni
thomasboni force-pushed the feat/artifact-commit-ref-and-ci-file branch from 0e14b17 to 909fe4d Compare September 3, 2026 11:15
Comment thread cmd/artifact_provenance.go
@thomasboni
thomasboni force-pushed the feat/artifact-commit-ref-and-ci-file branch from 909fe4d to 0dd03b1 Compare September 3, 2026 11:21
Comment thread cmd/artifact_provenance.go Outdated
Comment thread cmd/sarif.go
@thomasboni
thomasboni force-pushed the feat/artifact-commit-ref-and-ci-file branch from 0dd03b1 to e193d08 Compare September 3, 2026 11:29
Comment thread control/types.go
Comment thread cmd/artifact_provenance.go
@thomasboni
thomasboni force-pushed the feat/artifact-commit-ref-and-ci-file branch 2 times, most recently from 474aa82 to 93a3ad3 Compare September 3, 2026 11:38
Comment thread cmd/artifact_provenance.go
…he analyzed CI file in the JSON report (#443)

Two pieces of provenance the AI pipeline needs so a result set is
self-describing, without reconstructing "which commit" and "which CI input"
from the CI environment around the artifact.

Commit ref, in every relevant artifact. One resolver
(cmd/artifact_provenance.go) computes the analyzed commit once at output time
and every writer reads it off the result:

- JSON report: headCommitSha (always resolved, never the literal "HEAD") next
  to analyzeBranch.
- SARIF: run.versionControlProvenance (revisionId + branch + repositoryUri).
- PBOM: project.commitSHA + project.ref.
- CycloneDX: plumber:git:commit / plumber:git:ref on the project component.
- OCSF: a resource per finding naming the repo (uid) at the commit (version)
  on the ref (labels).

The commit and ref are sourced by the same self-vs-elsewhere test
ProjectFromCIEnvironment makes: when the CI job is building the very project
analyzed (a self-analysis), its own $CI_COMMIT_SHA / $CI_COMMIT_REF_NAME are
the analyzed commit and ref and win. When the analysis points elsewhere - a
cross-project --project scan, or an explicit --branch on a different ref -
only the analyzed target's own head and branch (which control.task resolves
into the result) may be used, so the artifact names the analyzed project, not
the scanner that ran it. In that case the job's env commit and the local
checkout are not fallbacks (they describe the scanner): when the target head
did not resolve - a remote scan may not resolve it - the commit is omitted
rather than mis-attributed. artifactRepoURI takes the repo path from
result.ProjectPath for the same reason, keeping the repositoryUri coherent with
the resolved commit. The literal "HEAD" placeholder is never emitted as a
commit; when nothing resolves the field is omitted, and analyzeBranch is
deleted symmetrically so a raw "HEAD" never leaks through the report's
whole-result marshal.

The platform push already carries ref{branch,sha} and CSV is a flat findings
dump, so both are out of scope.

Analyzed CI file, JSON report only. A new analyzedCiConfig block names the
exact input the findings came from: for GitLab the resolved merged pipeline
(path + content + merged), for GitHub each scanned workflow file (path +
content), for both the local and remote scan paths. The GitHub scanners retain
the file bytes on the IR under an existing json:"-" field, so nothing reaches
the OPA input, and the files are recorded before parsing so an unparseable
workflow is still reported as analyzed. It is written to the local --output
report but kept out of the score-push payload, so the merged CI configuration
content is never exported to the hosted score service.

All fields are additive: a main-vs-#443 diff across the JSON, SARIF, PBOM,
CycloneDX and OCSF artifacts shows zero removed or retyped keys on both
providers, only the new fields.

Tested end to end on both providers (commit ref present in all five artifacts;
analyzedCiConfig present in the report and absent everywhere else; GitLab merged
YAML vs GitHub per-workflow content), with unit coverage for the resolver
(self-analysis, cross-project, --branch, unresolved-target-head omission, both
provider env mappings, the local git fallback), the report/SARIF/OCSF/PBOM/
CycloneDX writers including OCSF partial-provenance boundaries, the provider
PBOM wiring, and the local and remote workflow retention.
@thomasboni
thomasboni force-pushed the feat/artifact-commit-ref-and-ci-file branch from 93a3ad3 to f246919 Compare September 3, 2026 11:43
@thomasboni
thomasboni merged commit 15e5bd6 into main Sep 3, 2026
16 checks passed
@thomasboni
thomasboni deleted the feat/artifact-commit-ref-and-ci-file branch September 3, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Emit the analyzed commit ref in every artifact, and the analyzed CI file in the JSON artifact

1 participant