Skip to content

Phase 1 research: Go build type#180

Open
TomHennen wants to merge 5 commits into
mainfrom
research/go-phase-1
Open

Phase 1 research: Go build type#180
TomHennen wants to merge 5 commits into
mainfrom
research/go-phase-1

Conversation

@TomHennen
Copy link
Copy Markdown
Owner

@TomHennen TomHennen commented Apr 26, 2026

Summary

Phase 1 ecosystem research for a go build type, captured per docs/HOW_TO_ADD_A_BUILD_TYPE.md. Recommends defaults for an eventual build/actions/go/ implementation. Research only — no action.yml exists yet; inputs and step sequence are sketched at the level needed to justify the picks.

Recommended defaults

  • Build tool: goreleaser for binary releases. Dominant ecosystem norm; handles the cross-compilation matrix, archive packaging, dist/checksums.txt, and GitHub Release upload in one config-driven invocation.
  • SBOM: syft (same tool python uses, same Cosign-keyless install, SPDX-native).
  • Publish target: GitHub Releases. Goreleaser handles the upload given a GITHUB_TOKEN with contents: write.
  • Attestation: Pattern B (generator_generic_slsa3.yml). Goreleaser produces dist/checksums.txt; wrangle hashes filenames into base64 subjects; the SLSA generic generator signs. Same shape as python and container. Pattern A (builder_go_slsa3.yml) is not picked — security strength is comparable, but Pattern A binds build and sign in one upstream-controlled workflow with no caller hook for syft/test/release_gate.
  • Cosign keyless signing of binaries: in scope. cosign sign-blob --yes against each artifact, OIDC-bound to the calling workflow's identity.
  • Lint: gofmt + golangci-lint in wrangle's source-scan stage (alongside OSV/Zizmor/Scorecard). Wrangle-wide convention — python and container should adopt source-stage lint in the same iteration.
  • Tests: go test ./... before the build step.
  • Reproducibility: -trimpath and -buildvcs=false on by default (zero runtime cost). CGO_ENABLED=0 is not a default (real performance cost; opt-in only).
  • Validation-only sub-shape (non-binary repos): library-only modules and go install-pattern repos are NOT out of scope. Wrangle still adds SBOM, go test, vulnscan, lint — just no SLSA build provenance (no artifact to attest). Implementation can use a mode: input or a separate action; decide in the implementation PR.

Out of scope for v0.1

  • Pattern A (builder_go_slsa3.yml). Wrangle does not currently plan a Pattern A variant. Adopters with a specific need can file an issue.
  • ko. Uses its own toolchain (not docker buildx, not Dockerfile-based), so wrangle's existing container build type does not cover it. A ko-aware variant is a possible follow-up.

Open questions (for the implementation PR)

  • Binary vs. validate-only as one action with a mode: input vs. two actions.
  • .goreleaser.yml template ownership (ship a starter vs. require adopters to bring their own).
  • govulncheck for Go-aware vulnscan, complementary to OSV-Scanner — likely both.

Test plan

  • No tests intended — research-only doc.
  • Reviewer: confirm cited claims about goreleaser, builder_go_slsa3.yml, slsa-verifier release.yml, sum.golang.org's tlog role.

@TomHennen TomHennen temporarily deployed to integration-test April 26, 2026 19:34 — with GitHub Actions Inactive
Copy link
Copy Markdown
Owner Author

@TomHennen TomHennen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase 1 research review. Comment-only — leaving the call to the human.

Summary

The doc is solid. The most load-bearing claims (the builder_go_slsa3.yml seam-inversion finding, slsa-verifier's Pattern A use, goreleaser's neither-A-nor-B release, sum.golang.org as integrity backbone) all check out against primary sources. The matrix-fan-out observation for multi-binary / multi-platform Pattern A vs. Pattern B is sharp and well-grounded. Below are mostly polish items; nothing I'd block on.

Verified upstream (so you don't have to re-check)

  • builder_go_slsa3.yml does own the build. Inputs are config-file, evaluated-envs, go-version, go-version-file, upload-assets, upload-tag-name, prerelease, private-repository, draft-release — no hashes/base64-subjects input. Outputs are go-binary-name and go-provenance-name. The job graph is rng → detect-env → builder → build-dry → build → provenance → upload-assets, intentionally linear and sealed; there is no caller hook between build and provenance. The doc's seam-inversion claim is correct.
  • slsa-verifier release.yml uses Pattern A with the 6-cell os × arch matrix and config files at .slsa-goreleaser/${{ matrix.os }}-${{ matrix.arch }}.yml, pinned @v2.0.0, with id-token: write, contents: write, actions: read on the builder job. Matches the doc.
  • goreleaser's own release.yml uses neither Pattern A nor Pattern B — it uses actions/attest@v4 (not slsa-github-generator, not actions/attest-build-provenance), plus sigstore/cosign-installer, anchore/sbom-action/download-syft, and goreleaser/goreleaser-action. The doc's claim is right but understated (see point 2 below).
  • sum.golang.org is a Trillian-backed Merkle log of go.sum-shaped lines (SHA-256 of source + go.mod) and serves as the integrity backbone for go install/go get from a tag. Doc characterization matches the upstream announcement and proposal 25530.
  • generator_generic_slsa3.yml consumes base64-subjects built from dist/checksums.txt in the goreleaser-example repo — same shape wrangle's python uses.

Suggested edits (in rough priority)

  1. Own the Phase 1 deferral, not just the #171 deferral. The runbook's Phase 1 says (literal quote): "When multiple tools are first-class, document the detection rule and ship a fixture for each variant. When one is dominant, document why you chose it." The doc does neither — it explicitly says "This document does not pick." That is defensible because the runbook itself flags Go as the awkward case ("'build tool' may not be the right framing"), but the doc currently rolls the deferral up entirely under #171. One sentence acknowledging that the Phase 1 "pick the canonical tool" question is being deferred — not just the contract question — would make the scope discipline honest. Otherwise a future reader could fairly say Phase 1 didn't finish its job.

  2. Pattern C is hedge-y where it could be precise. Section "Reference workflow patterns" says of goreleaser/goreleaser's release.yml: "relies on goreleaser's built-in Sigstore signing and (presumably) GitHub artifact attestations." I verified the file: it uses actions/attest@v4 (current goreleaser-recommended), invoked twice — once over dist/checksums.txt, once over Docker digests. That's worth saying outright; the doc's whole purpose is to be the verified-source reference. While you're there, the same ambiguity is in the "Canonical build tool(s)" section: "plus presumably GitHub artifact attestations." Same fix.

  3. The release_gate claim conflates two things. The seam analysis says: "there is no place inside wrangle's reusable workflow to insert go test, syft-driven SBOM generation, or actions/release_gate-style gating between 'build' and 'provenance'." True for the inline seam — but release_gate is currently used to decide whether to invoke the provenance reusable workflow at all (if: ${{ needs.gate.outputs.should-release == 'true' }}), and that pattern works fine for builder_go_slsa3.yml too (gate the uses: invocation). Pattern A doesn't support pull_request upstream anyway, so the gating story isn't broken — only the inline seam where SBOM/test would slot in is. Calling out that nuance would tighten the finding.

  4. Reproducibility is the unstated assumption behind "tests don't certify the SLSA-built bytes." Section "#171 Notes" point 2 makes the right finding but assumes implicitly that the trusted builder produces deterministic output from source. If a Go build with timestamps/embedded build info isn't reproducible, the wrangle-tested-bytes-vs-builder-produced-bytes gap is wider than the doc suggests, and the trade-off in option (ii) is correspondingly worse. Worth a sentence — or at least a flag that reproducibility is an implicit prerequisite for option (ii) to be a small loss rather than a large one.

  5. Mode 3 dismissal skates past one case. "Mode 3 is 'the maintainer hasn't adopted wrangle yet' rather than a separate shape" is mostly right, but it elides adopters who might want SLSA-shaped source attestations — "this tag was reviewed/tested/scanned by my CI" — without producing a binary. Mode 2 isn't quite served by sum.golang.org for that use case (sumdb proves source integrity, not anything CI did). Minor; the doc's resolution is still defensible because that adopter wants something orthogonal to a build type.

  6. Stale docker_best_practices.md reference is well-handled. The note that the cyclonedx-gomod listing predates python's syft adoption and "should not be read as a contract" is the right call. If you wanted to be belt-and-suspenders, a one-line update to that file in a follow-up PR would prevent the next agent reading it from re-litigating.

Things to call out positively

  • The seam-inversion finding (Pattern A inverts wrangle's "composite owns build, reusable workflow owns provenance" decomposition) is the right load-bearing finding for #171, and the framing as three honest paths — match python / adopt builder / two variants — is exactly the shape #171 needs.
  • Multi-binary / cross-compilation matrix observations are sharp: Pattern A requires fan-out at the wrangle-reusable-workflow level (slsa-verifier does 6 cells), Pattern B handles it inside one goreleaser invocation. That's a real ergonomic delta and the doc surfaces it cleanly.
  • Predicate version v0.2 vs v1 / actions/attest-build-provenance tension is correctly identified and consistent with container SPEC's existing stance. The explicit "the Go SPEC should not silently endorse actions/attest-build-provenance" is the right stake to drive.
  • SBOM-tool reuse (syft already in wrangle, cyclonedx-gomod is a stale prediction) is correctly grounded.

Naming three patterns without picking — is it appropriate?

Yes for #171's contract decision (the doc isn't the right place for that). Mostly yes for Phase 1's "canonical tool" decision, with the caveat in point 1 — the doc should make the deferral explicit rather than implicit. The runbook's Go caveat ("build tool may not be the right framing") gives this PR cover that npm/generic won't have, so the asymmetry is fine here as long as it doesn't leak into those sister PRs.

Bottom line

Ready for human review. The findings are accurate against upstream, the framing is honest about what Go costs the existing wrangle contract, and the three-modes resolution of the binary-vs-tag question is the right read. The polish items above would tighten it but none are blockers.

Comment thread build/actions/go/SPEC.md Outdated
2. **Pure libraries (no `main` package).** There is no binary, ever. The "artifact" is the tagged source tree as fetched by `go get` / `go install`. Integrity comes from Go's checksum database (`sum.golang.org`), which is itself a tamper-evident Merkle-tree transparency log over `(module@version, hash(source))` pairs ([Go module mirror launch announcement](https://go.dev/blog/module-mirror-launch); [proposal 25530](https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md)). This is integrity-of-source, not build provenance — they answer different questions — but it is not nothing.
3. **`go install <repo>@<tag>` for CLI tools whose maintainers chose not to run a release pipeline.** This is mode 1 minus the release pipeline. The "missing build" the runbook gestures at is almost always a maintainer-side choice not to run CI release for a project that *could* release binaries; it is rarely a property of the project's source.

**Resolution.** Wrangle's Go build type should target mode 1 as primary. Mode 2 is out of scope for an artifact-producing template — wrangle has no surface to attach to when there is no build job, and `sum.golang.org` already provides what mode-2 consumers verify. Mode 3 is "the maintainer hasn't adopted wrangle yet" rather than a separate shape; once they do, they're in mode 1. The runbook's framing — "Go may not fit the artifact-producing template" — overstates the gap. *Projects that adopt wrangle* are by definition projects that want a CI release pipeline; for those, the artifact-producing template applies, with the caveats below.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me push back and then you tell me what you think.

Even if wrangle doesn't build the thing (e.g. because it's a library or a go install pattern), wrangle can still produce SBOMs, run tests, run vulnscans, run linters, and it can do that on the release "artifact" it just so happens that artifact is built elsewhere? So maybe there's not proveneance (or maybe there could be IDK), but those other things seem valuable still?

These all seem worth supporting. I also bet wrangle would be an easy way for people that only do go install now to support binary releases? Maybe the right approach would be to treat them as separate build types?

Comment thread build/actions/go/SPEC.md Outdated

The composite owns the build, emits hashes, and the SLSA generator runs in a separate job consuming those hashes. SBOM generation, test execution, the `release_gate` job, and the verify job all slot in around this seam.

`builder_go_slsa3.yml` does not have a hashes input — it has a `config-file` input and performs the build itself. There is no place inside wrangle's reusable workflow to insert `go test`, `syft`-driven SBOM generation, or `actions/release_gate`-style gating between "build" and "provenance," because by the time wrangle hands off to the builder, the build has not yet happened, and after the builder returns, the build has shipped to a release. The build-then-attest seam wrangle relies on for python isn't there.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just sounds similar to what happens for container images?

Also, can we not go test before we start the build?

Comment thread build/actions/go/SPEC.md Outdated
There are three honest paths, and #171 has to pick one:

- **(i) Match python.** Use Pattern B (`goreleaser` or `go build` inside the composite, `generator_generic_slsa3.yml` for provenance). The contract stays uniform; SBOM/test/gate/verify slot in identically. Loss: the Go-specific isolated-builder property.
- **(ii) Adopt `builder_go_slsa3.yml`.** Wrangle's reusable workflow becomes a coordinator that runs SBOM/test in one job, then delegates to `builder_go_slsa3.yml` for the actual build+provenance, then runs verify. Test/SBOM run on a *separate checkout* of the same source — they don't certify the same byte-for-byte build the SLSA builder produces. The contract for Go diverges from python in a visible way.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought go builds were reproducible? Will the digest be the same in the end?

Comment thread build/actions/go/SPEC.md Outdated

- **(i) Match python.** Use Pattern B (`goreleaser` or `go build` inside the composite, `generator_generic_slsa3.yml` for provenance). The contract stays uniform; SBOM/test/gate/verify slot in identically. Loss: the Go-specific isolated-builder property.
- **(ii) Adopt `builder_go_slsa3.yml`.** Wrangle's reusable workflow becomes a coordinator that runs SBOM/test in one job, then delegates to `builder_go_slsa3.yml` for the actual build+provenance, then runs verify. Test/SBOM run on a *separate checkout* of the same source — they don't certify the same byte-for-byte build the SLSA builder produces. The contract for Go diverges from python in a visible way.
- **(iii) Two variants, like python's pip vs. uv.** Adopters who care about the strongest L3 isolation get the `builder_go_slsa3.yml` flavor; adopters who want the full wrangle pipeline (SBOM-of-the-built-artifact, integrated test gating) get the Pattern B flavor. Doubles surface area, but each variant is internally clean.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pick one. We need to reduce cognitive overload.

I'm not sure I agree that one is stronger than the other though. Don't they all run in a wrangle-defined reusable workflow? Isn't the strength the same as for the other build types?

Comment thread build/actions/go/SPEC.md Outdated
- **(ii) Adopt `builder_go_slsa3.yml`.** Wrangle's reusable workflow becomes a coordinator that runs SBOM/test in one job, then delegates to `builder_go_slsa3.yml` for the actual build+provenance, then runs verify. Test/SBOM run on a *separate checkout* of the same source — they don't certify the same byte-for-byte build the SLSA builder produces. The contract for Go diverges from python in a visible way.
- **(iii) Two variants, like python's pip vs. uv.** Adopters who care about the strongest L3 isolation get the `builder_go_slsa3.yml` flavor; adopters who want the full wrangle pipeline (SBOM-of-the-built-artifact, integrated test gating) get the Pattern B flavor. Doubles surface area, but each variant is internally clean.

This document does not pick. The point for #171 is that Go is the first build type where an ecosystem-native SLSA L3 path *inverts* wrangle's "composite owns build, reusable workflow owns provenance" seam. Python and container both decompose cleanly along that seam (the SLSA generator consumes the composite's outputs); `builder_go_slsa3.yml` does not. Whether the contract bends to accommodate or whether wrangle accepts a weaker isolation guarantee for Go is the trade-off.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, why are you mentioning 171 at all. I thought this doc was to reference best practices within the ecosystem (at least for now).

Comment thread build/actions/go/SPEC.md Outdated
- **(ii) Adopt `builder_go_slsa3.yml`.** Wrangle's reusable workflow becomes a coordinator that runs SBOM/test in one job, then delegates to `builder_go_slsa3.yml` for the actual build+provenance, then runs verify. Test/SBOM run on a *separate checkout* of the same source — they don't certify the same byte-for-byte build the SLSA builder produces. The contract for Go diverges from python in a visible way.
- **(iii) Two variants, like python's pip vs. uv.** Adopters who care about the strongest L3 isolation get the `builder_go_slsa3.yml` flavor; adopters who want the full wrangle pipeline (SBOM-of-the-built-artifact, integrated test gating) get the Pattern B flavor. Doubles surface area, but each variant is internally clean.

This document does not pick. The point for #171 is that Go is the first build type where an ecosystem-native SLSA L3 path *inverts* wrangle's "composite owns build, reusable workflow owns provenance" seam. Python and container both decompose cleanly along that seam (the SLSA generator consumes the composite's outputs); `builder_go_slsa3.yml` does not. Whether the contract bends to accommodate or whether wrangle accepts a weaker isolation guarantee for Go is the trade-off.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I really understand how this differs from the container solution?

Comment thread build/actions/go/SPEC.md Outdated
The Go ecosystem has two viable SBOM generators:

- **`syft`** ([anchore/syft](https://github.com/anchore/syft)). Wrangle already uses syft for the python build type, with a Cosign-keyless-verified install (`tools/syft/install.sh`). It produces SPDX natively and works against both source trees and compiled binaries. Reusing it for Go is the lowest-friction path: no new install script, no new Cosign verification dance, no new checksum to track. Per [the SBOM-tools landscape](https://sbomgenerator.com/guides/go), syft is the de facto choice for Go SBOM generation in 2026.
- **`cyclonedx-gomod`** ([CycloneDX/cyclonedx-gomod](https://github.com/CycloneDX/cyclonedx-gomod)). Tighter Go integration (uses the Go toolchain to introspect modules), produces CycloneDX natively. Listed in wrangle's [`docs/docker_best_practices.md`](../../../docs/docker_best_practices.md) Go row — but that table predates the python build type's syft adoption, and the listing should not be read as a contract. CycloneDX → SPDX conversion exists but is lossy; the cleanest SPDX path for a Go project is "run syft directly," not "run cyclonedx-gomod and convert."
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if wrangle should support both or whatever the ecosystem prefers? I dont' know how much it matters.

Comment thread build/actions/go/SPEC.md Outdated

There is no Go-specific package registry to publish to. `pkg.go.dev` is a documentation index, not a registry; it auto-discovers tagged versions from `proxy.golang.org`'s view of GitHub (and other VCS hosts). No publish action, no token, no auth — just push the tag. (This is also why mode 2 / mode 3 from the binary-vs-tag discussion are even possible: there is no separate publish step to skip.)

Container images via [`ko`](https://ko.build/) are a parallel publish path some Go projects use (small distroless images built from Go binaries without a Dockerfile). Out of scope for the Go build type — `ko`-using projects already have a container build need, and wrangle's container build type is the right home for that.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it the right tool for the job? Would projects using ko be able to use our container build solution?

Comment thread build/actions/go/SPEC.md Outdated

In the Go ecosystem, "the attestation" almost always means SLSA L3 provenance, signed via Sigstore keyless OIDC, recorded in Rekor. Both Pattern A (`builder_go_slsa3.yml`) and Pattern B (`generator_generic_slsa3.yml`) produce this; both emit `slsa.dev/provenance/v0.2` predicates today. There is no Go-ecosystem-native attestation analogous to npm provenance or PEP 740 — Go binaries' attestations *are* SLSA provenance. The transparency log is Rekor for the provenance, plus `sum.golang.org` for *source* integrity (which is independent — `sum.golang.org` doesn't know or care that a binary was produced).

Cosign keyless signing of binaries is a complementary layer (signs the binary digest directly, like wrangle's container build type does for image digests). `goreleaser` integrates with cosign natively; the [Cosign v3 upgrade post](https://goreleaser.com/blog/cosign-v3/) covers the current `--bundle`-style signing. This layer is ecosystem-supported but less universally adopted than SLSA provenance — `slsa-verifier`'s release workflow attests but does not separately Cosign-sign its binaries (the SLSA provenance bundle already includes a Sigstore-signed in-toto envelope), while goreleaser's own release workflow does. Whether wrangle's Go build type ships Cosign signing as a default, an opt-in, or not at all is a contract decision for #171, not a Phase 1 finding.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably support cosign keyless signing too. We probably don't need to care to care about making the implementation match exactly as long as the interface is the same and it's secure (very important!) and doesn't add maintenance burden.

Comment thread build/actions/go/SPEC.md Outdated

#### Pattern A (alternative, not picked)

`slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml` is a Go-specific *builder* that performs the build itself inside an isolated reusable workflow, driven by a `.slsa-goreleaser.yml` config file (the filename predates goreleaser conventions; the format is the SLSA builder's own). `slsa-verifier`'s [release workflow](https://github.com/slsa-framework/slsa-verifier/blob/main/.github/workflows/release.yml) demonstrates the canonical 6-cell `os × arch` matrix. It exists as an alternative for adopters who specifically want `.slsa-goreleaser.yml`-driven isolated builds. The cost is wrangle's seam: `builder_go_slsa3.yml` has no `hashes` input — the build, the SBOM, the test, and the provenance all happen inside one sealed reusable workflow with no caller hook between them, so wrangle's `syft` / `go test` / `release_gate` steps either run on a separate checkout (different bytes than the builder produces) or don't run at all. Adopters who want that trade can opt in via a separate variant in a later iteration; not the v0.x default. Pattern A also doesn't support `pull_request` triggers (per the upstream README).
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its unclear to me what the trade actually is. Is there a real security benefit to builder_go_slsa3.yml compared to what we're thinking for wrangle's implementation with generic?

In both cases a reusable workflow will do the build itself and create the provenance. The adopter's workflow won't be able to falsify the provenance in either case?

Comment thread build/actions/go/SPEC.md Outdated

Out of the box, `go build` embeds build info (working directory, VCS revision, dirty flag, build timestamps) into the binary, which breaks reproducibility — two builds of the same source produce different bytes. Setting `-trimpath` (strips local filesystem paths from the binary) and `-buildvcs=false` (suppresses VCS-info embedding) plus a fixed `CGO_ENABLED=0` (where applicable) makes Go binaries reproducible. Goreleaser exposes these via `builds.flags` and `builds.env`; the wrangle action should set them by default and let the adopter's `.goreleaser.yml` opt out if they have a specific reason.

This matters because it closes the wrangle-tested-bytes vs. SLSA-attested-bytes gap. Without reproducibility, "we tested the source and SLSA attests the build" is two different artifacts; with it, they are the same artifact byte-for-byte.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still the case if we're generating our own provenance? I think this gap has been fixed?

Comment thread build/actions/go/SPEC.md Outdated

## ko / container builds

Go projects that publish container images via [`ko`](https://ko.build/) (small distroless images built from Go binaries without a Dockerfile) should use **wrangle's existing container build type**, not the Go build type. ko produces an OCI image; the container build type already handles SBOM, Cosign signing, and SLSA provenance for OCI images. The Go build type doesn't need a ko-specific code path — ko-using projects already have a container build need, and routing them to the container action keeps wrangle's per-ecosystem boundaries clean.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is wrong. Ko uses specific build tools, not dockerfiles. https://ko.build/get-started/

So if users want to use ko they'd need those tools available and to drive builds. We don't have to solve it for go but we shouldn't say they'll be fine with the existing container support.

Comment thread build/actions/go/SPEC.md Outdated
## Open questions

- **Binary vs. validate-only as one action or two.** See "Validation-only sub-shape." Decide in the implementation PR.
- **Lint placement.** Source-stage (in `actions/scan`) vs. build-stage (in the Go build action) vs. both. Python doesn't currently lint in the build action; Go could either follow that or differ.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking lint in source scans (and we should probably add it to python and container images)

Comment thread build/actions/go/SPEC.md Outdated
- **Binary vs. validate-only as one action or two.** See "Validation-only sub-shape." Decide in the implementation PR.
- **Lint placement.** Source-stage (in `actions/scan`) vs. build-stage (in the Go build action) vs. both. Python doesn't currently lint in the build action; Go could either follow that or differ.
- **`.goreleaser.yml` template ownership.** Should wrangle ship a starter `.goreleaser.yml` for adopters (with `-trimpath` / `-buildvcs=false` baked in), or require adopters to bring their own and validate it has the reproducibility flags? Python doesn't ship a starter `pyproject.toml`; consistency with python argues "require adopters to bring their own."
- **`govulncheck` vs. `osv-scanner` for Go vulnscan.** Source-stage scanning already uses OSV-Scanner; `govulncheck` is Go-aware (callgraph-based, fewer false positives) and could be a complementary check. Decide in the implementation PR; not load-bearing for Phase 1.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good callout. we should probably support govulncheck (and maybe also osv-scanner??)

TomHennen added a commit that referenced this pull request Apr 27, 2026
- L58: clarify Pattern A vs B is operational, not a security delta
  (both run in adopter-unfalsifiable reusable workflows). The actual
  difference is whether wrangle has a caller hook between build and sign.
- L78: drop the wrangle-tested-bytes vs SLSA-attested-bytes gap framing
  (Pattern B closes it by construction). Reframe reproducibility as
  consumer-side verification + audit, which is the durable rationale.
- L101: ko correction. ko doesn't use Dockerfiles, so wrangle's container
  build type can't cover it. Out of scope for Phase 1; mode: ko on the
  Go action is a possible follow-up.
- L142: lint placement decided as source-stage only, wrangle-wide.
  Python and container should follow.
- L144: recommend supporting govulncheck (Go-aware, callgraph-based);
  OSV-Scanner against go.sum stays as a complementary candidate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TomHennen added a commit that referenced this pull request Apr 27, 2026
Per Tom's directive on #180 L142 (lint goes in source scans, wrangle-wide).
Drops the "build action could optionally invoke npm run lint" hedge in
favor of an unambiguous source-stage commitment, matching what the Go
SPEC just adopted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread build/actions/go/SPEC.md Outdated
#### Pattern A (alternative, not picked)

`slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml` is a Go-specific *builder* that performs the build itself inside an isolated reusable workflow, driven by a `.slsa-goreleaser.yml` config file (the filename predates goreleaser conventions; the format is the SLSA builder's own). `slsa-verifier`'s [release workflow](https://github.com/slsa-framework/slsa-verifier/blob/main/.github/workflows/release.yml) demonstrates the canonical 6-cell `os × arch` matrix. It exists as an alternative for adopters who specifically want `.slsa-goreleaser.yml`-driven isolated builds. The cost is wrangle's seam: `builder_go_slsa3.yml` has no `hashes` input — the build, the SBOM, the test, and the provenance all happen inside one sealed reusable workflow with no caller hook between them, so wrangle's `syft` / `go test` / `release_gate` steps either run on a separate checkout (different bytes than the builder produces) or don't run at all. Adopters who want that trade can opt in via a separate variant in a later iteration; not the v0.x default. Pattern A also doesn't support `pull_request` triggers (per the upstream README).
`slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml` is a Go-specific *builder* that performs the build itself inside the same reusable workflow that signs the provenance, driven by a `.slsa-goreleaser.yml` config file (the filename predates goreleaser conventions; the format is the SLSA builder's own). `slsa-verifier`'s [release workflow](https://github.com/slsa-framework/slsa-verifier/blob/main/.github/workflows/release.yml) demonstrates the canonical 6-cell `os × arch` matrix. **Security strength is comparable to Pattern B** — both run the build inside a reusable workflow the adopter can't falsify, both sign provenance via Sigstore against the workflow's OIDC identity, and Pattern B's `generator_generic_slsa3.yml` is itself a reusable workflow with the same isolation property. The actual difference is **operational**: Pattern A binds build and sign in one upstream-controlled reusable workflow with no caller hook, so wrangle's `syft` / `go test` / `release_gate` steps either run on a separate checkout (against different bytes than the builder produces) or don't run at all. Pattern B keeps build inside wrangle's reusable workflow and signs via `generator_generic_slsa3.yml` — two reusable-workflow boundaries instead of one, with a caller-side hook in between for hygiene. Adopters who want Pattern A's no-second-checkout property can opt in via a separate variant in a later iteration; not the v0.x default. Pattern A also doesn't support `pull_request` triggers (per the upstream README).
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't mention that we'll eventually support Pattern A. I'm not sure we want to. People can file an issue if they care.

Comment thread build/actions/go/SPEC.md Outdated
Out of the box, `go build` embeds build info (working directory, VCS revision, dirty flag, build timestamps) into the binary, which breaks reproducibility — two builds of the same source produce different bytes. Setting `-trimpath` (strips local filesystem paths from the binary) and `-buildvcs=false` (suppresses VCS-info embedding) plus a fixed `CGO_ENABLED=0` (where applicable) makes Go binaries reproducible. Goreleaser exposes these via `builds.flags` and `builds.env`; the wrangle action should set them by default and let the adopter's `.goreleaser.yml` opt out if they have a specific reason.

This matters because it closes the wrangle-tested-bytes vs. SLSA-attested-bytes gap. Without reproducibility, "we tested the source and SLSA attests the build" is two different artifacts; with it, they are the same artifact byte-for-byte.
Under Pattern B (the pick) wrangle's test step and wrangle's hash step both run against the same bytes goreleaser produced in the same job, so reproducibility isn't needed to close a wrangle-vs-builder gap (Pattern A's gap doesn't apply here). Reproducibility still matters for **consumer-side verification** — a downstream consumer who rebuilds from source should get the same binary the SLSA provenance attests — and for security audits that want to confirm "this binary corresponds to this source." That justifies the flags by default.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is the right trade. Maybe it should be an option, but if we're publishing a binary, users can run the binary themselves? My biggest concern is that disabling CGO will cause performance problems.

TomHennen and others added 4 commits April 26, 2026 21:12
Research-only deliverable per docs/HOW_TO_ADD_A_BUILD_TYPE.md Phase 1.
Resolves the binary-vs-tag question into three modes; mode 1 (binary
releases) is the only one wrangle's artifact-producing template should
target. No action.yml, no implementation, no #171 contract design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…only mode

Restructured per maintainer feedback. Picks Pattern B (goreleaser +
generator_generic_slsa3.yml) — preserves the seam wrangle uses for
python/container, same L3 isolation property since all patterns run in
a wrangle reusable workflow. Pattern A gets one paragraph as
alternative; Pattern C one sentence (precise about actions/attest@v4
per upstream verification).

Adds validation-only sub-shape for non-binary repos (libraries,
go install-pattern): SBOM/test/vulnscan/lint on source, no provenance.
Routes ko-using projects to the existing container build type. Adds
Cosign keyless signing of binaries via cosign sign-blob. Adds
reproducibility paragraph (-trimpath -buildvcs=false). Adds Linting
section (gofmt + golangci-lint). Operating model stated in Overview.
Contract-stress findings catalogued separately on #171.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…alongside

Replaces the "one attestation per artifact, not stacked" framing per
maintainer feedback on #178. Wrangle generates and stores its own L3
SLSA provenance via the upstream generator regardless of what
ecosystem-native attestations exist; ecosystem-native attestations
(Cosign signatures, GitHub artifact attestations, OCI attestations)
populate alongside, not instead of. This is consistent with the Go
pick (Pattern B + cosign keyless) and resolves the inconsistency that
the previous wording introduced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- L58: clarify Pattern A vs B is operational, not a security delta
  (both run in adopter-unfalsifiable reusable workflows). The actual
  difference is whether wrangle has a caller hook between build and sign.
- L78: drop the wrangle-tested-bytes vs SLSA-attested-bytes gap framing
  (Pattern B closes it by construction). Reframe reproducibility as
  consumer-side verification + audit, which is the durable rationale.
- L101: ko correction. ko doesn't use Dockerfiles, so wrangle's container
  build type can't cover it. Out of scope for Phase 1; mode: ko on the
  Go action is a possible follow-up.
- L142: lint placement decided as source-stage only, wrangle-wide.
  Python and container should follow.
- L144: recommend supporting govulncheck (Go-aware, callgraph-based);
  OSV-Scanner against go.sum stays as a complementary candidate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TomHennen added a commit that referenced this pull request Apr 27, 2026
Per Tom's directive on #180 L142 (lint goes in source scans, wrangle-wide).
Drops the "build action could optionally invoke npm run lint" hedge in
favor of an unambiguous source-stage commitment, matching what the Go
SPEC just adopted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@TomHennen TomHennen force-pushed the research/go-phase-1 branch from 15e57f7 to e6e8932 Compare April 27, 2026 01:12
…bility flags

- L58: drop the "later iteration" Pattern A variant promise. Adopters
  who want Pattern A can file an issue if they care.
- L78: split reproducibility recommendation. -trimpath and
  -buildvcs=false are zero-runtime-cost and stay as defaults.
  CGO_ENABLED=0 has real performance costs (cgo-backed net/crypto/etc.)
  and is moved to opt-in only. Reframe rationale: the binary IS what
  consumers run; reproducibility's primary value is for security audits
  and SLSA verification chains, not routine consumer rebuilds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@TomHennen TomHennen temporarily deployed to integration-test April 27, 2026 01:15 — with GitHub Actions Inactive
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.

1 participant