feat: Go support — artifactory-oidc ecosystem + go-lockfile-hygiene - #18
Merged
Conversation
Adds the Go lane to the shared actions so public Go SDK repos (twilio-go
first) can resolve through curated Artifactory and gate what they publish.
artifactory-oidc: `ecosystem: go`
- GOPROXY -> https://<host>/artifactory/api/go/virtual-go-thirdparty, with
NO `,direct` fallback so curation is fail-closed. With `direct`, any
module Artifactory refuses would be fetched straight from its VCS host.
- Token goes in ~/.netrc (0600), not in GOPROXY: `go env` prints GOPROXY
verbatim, so a token in the URL leaks into any log dumping the Go env.
- Clears GOPRIVATE / GONOPROXY / GONOSUMDB. On a self-hosted runner
GOPRIVATE=github.com/twilio/* silently disables both the proxy and the
checksum database for exactly the modules that matter.
- GOSUMDB is left alone: the committed go.sum verifies everything already
required, and the sumdb is only consulted for modules not yet in it.
go-lockfile-hygiene (new)
- Scan: internal module paths in go.mod / go.sum, using a denylist rather
than an allowlist — in Go the module path IS the fetch location, and the
long tail of legitimate public vanity domains makes an allowlist noise.
- Rejects `replace` directives: the go command honours them only in the
main module, so a library with one resolves a different graph in CI than
at every consumer.
- Checks the module path sits under github.com/$GITHUB_REPOSITORY, and on
a tag ref that the /vN suffix matches the tag's major version.
- Clean room: hermetic GOMODCACHE/GOPATH, public proxy + sumdb, ambient
Go env cleared, then `go mod download all` + `go mod verify` + `go build`.
Self-tests: four scan fixtures asserting expected pass/fail, a clean-room job
against a real public module, and a table test for the /vN rule (which only
fires on a tag ref, so a PR run can never reach it).
README documents the thing that does not transfer from the other guides: in
Go the tag push IS the publish, so an `environment:` gate on a tag-push
workflow gates nothing. The gate has to sit in front of tag creation — a tag
ruleset plus a workflow_dispatch release job carrying environment: production.
kridai
approved these changes
Aug 19, 2026
There was a problem hiding this comment.
Pull request overview
Adds first-class Go support to this repo’s shared GitHub Actions by (a) enabling Artifactory OIDC configuration for Go module resolution and (b) introducing a new go-lockfile-hygiene supply-chain gate with CI coverage and documentation updates.
Changes:
- Extend
artifactory-oidcwithecosystem: goto configureGOPROXYand authenticate via~/.netrc, while clearing private-module env vars that can bypass proxy/sumdb behavior. - Add
go-lockfile-hygienecomposite action to scan Go module metadata for internal/unsafe patterns and optionally run a hermetic “clean-room” resolve/build. - Add fixture-based workflow tests plus README guidance for composing Go CI + release flows.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
artifactory-oidc/action.yml |
Adds Go ecosystem support (GOPROXY + netrc auth) and updates ecosystem defaults/docs. |
go-lockfile-hygiene/action.yml |
New composite action implementing Go module scanning and clean-room resolution/build. |
.github/workflows/test.yml |
Adds end-to-end fixture tests and a semantic-import-versioning table test for the Go hygiene action. |
README.md |
Documents Go usage patterns and release mechanics where tags are the publish event. |
test/fixtures/go/clean/go.mod |
Fixture for a passing Go module graph. |
test/fixtures/go/internal-host/go.mod |
Fixture for failing internal module host detection. |
test/fixtures/go/replace-directive/go.mod |
Fixture for failing replace directive detection. |
test/fixtures/go/wrong-module-path/go.mod |
Fixture for failing module-path-vs-repo validation. |
Suppressed comments (1)
go-lockfile-hygiene/action.yml:58
- This step name claims go.work is scanned for internal module hosts, but the step only scans go.mod and go.sum (go.work is handled later with a warning only). Consider renaming the step (or actually parsing go.work) so logs aren’t misleading.
- name: Scan go.mod / go.sum / go.work for internal module hosts
if: ${{ inputs.scan == 'true' }}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+2
to
+6
| description: >- | ||
| Supply-chain gate for public Go modules. (1) Scan tracked go.mod / go.sum / | ||
| go.work for internal module hosts, `replace` directives, and a module path | ||
| that doesn't match the public repo; (2) optionally prove the committed | ||
| go.mod + go.sum resolve and build from the PUBLIC module proxy only. |
| # In a published library it is dead weight to consumers — so CI resolves | ||
| # one graph and every consumer resolves a different one. This is the Go | ||
| # analogue of a committed Composer `repositories` block. | ||
| REPL=$(go mod edit -json "$f" | jq -r '(.Replace // [])[] | " \(.Old.Path) => \(.New.Path // .New.Version)"') |
| GOPROXY=https://proxy.golang.org GOSUMDB=sum.golang.org \ | ||
| GOPRIVATE= GONOPROXY= GONOSUMDB= GOFLAGS=-mod=mod \ | ||
| go get "${MOD}@${VERSION}" | ||
| grep -E "^${MOD} ${VERSION} h1:" go.sum |
ryanrishi
approved these changes
Aug 20, 2026
baderbuddy
approved these changes
Aug 24, 2026
DavidVtwilio
approved these changes
Aug 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the Go lane to the shared actions so public Go SDK repos (
twilio/twilio-gofirst) can resolve through curated Artifactory and gate what they publish.The Artifactory side is already in place —
virtual-go-thirdpartyand thetwilio/twilio-goOIDC trust both exist inartifactory-cloud-twilio-config. This is the missing consumer half.artifactory-oidc:ecosystem: goGOPROXY→https://<host>/artifactory/api/go/virtual-go-thirdparty, with no,directfallback so curation is fail-closed. Withdirect, any module Artifactory refuses (curation block, CVE, version-age cooldown) would be silently fetched straight from its VCS host — the control would fail open with no error. Same reasoning as disablingpackagist.orgin the PHP branch.~/.netrc(0600), not inGOPROXY.go envprintsGOPROXYverbatim, so a token embedded in the URL leaks into any log that dumps the Go environment.GOPRIVATE/GONOPROXY/GONOSUMDB. A self-hosted runner withGOPRIVATE=github.com/twilio/*makes the go command skip both the proxy and the checksum database for exactly the modules this lane exists to control.GOSUMDBis deliberately left alone. The committedgo.sumverifies every module already required; the checksum DB is only consulted for modules not yet in it, and the go command falls back tosum.golang.orgdirectly if the proxy doesn't serve/sumdb/.go-lockfile-hygiene(new)go.mod/go.sumgolang.org/x,k8s.io,go.uber.org,rsc.io,gopkg.in) makes an allowlist pure noise. The clean-room resolve is the real fail-closed proof.replacedirectives rejectedreplaceonly in the main module. A library with one resolves a different graph in CI than at every consumer — the Go analogue of a committed Composerrepositoriesblock.github.com/$GITHUB_REPOSITORYgo getfetches by module path. A mismatched path publishes to a name nothing serves./vNsuffix vs. the tag's major (on tag refs)GOMODCACHE/GOPATH, public proxy + sumdb, ambient Go env cleared, thengo mod download all+go mod verify+go build ./....Parsing uses
go mod edit -jsonrather than regex, so single-line and blockrequire/replace/excludeforms, comments, and quoting all work. A parse failure is a hard error — the gate never reports a pass it couldn't actually verify.Tests
clean,internal-host,replace-directive,wrong-module-path) each asserting an expected outcome, so a gate that stops failing is caught as loudly as one that starts failing./vNrule, which only fires on a tag ref and so can never be reached by a PR run.The thing that does not transfer from the other guides
In Go, the tag push is the publish. There is no upload, no registry account, no credential —
proxy.golang.orgfetches the tag on first request andsum.golang.orgpins itsh1:hash permanently.So an
environment:gate on anon: push: tags:workflow gates nothing — by the time the job starts the version is already public. Packagist at least waits for a GitHub Release; Go waits for nothing. The README documents the two controls that do work: a tag ruleset onrefs/tags/v*, and aworkflow_dispatchrelease workflow whose tagging job carriesenvironment: production, so the tag only comes into existence after approval.Companion IPD guide (
setup-go-publishing.md) coming in a separate PR.