go-conventions — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited go-conventions (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
If the repo doesn't define its own tooling, use:
1.25.x floor). Pin with toolchain go1.25.X in go.mod, set GOTOOLCHAIN=local in CI so the declared toolchain is used verbatim.go build -mod=vendor.golangci-lint run -c .golangci.yml.goimports is enabled as a formatter in .golangci.yml.r/default r/go r/dgryski r/trailofbits rulesets.golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize checks for outdated idioms.CGO_ENABLED=0 for pure-Go packages. Enable only when a specific package needs it; tag those files with //go:build cgo so pure-Go builds still work.//go:build tdx, //go:build sevsnp) when the same binary has multiple platform-specific implementations. Exactly one implementation per build. go build -trimpath -buildvcs=false -ldflags "-s -w -buildid=" ./...-trimpath strips local paths; -buildid= zeros the linker build ID. For any binary whose hash is measured (TEE attestation), a build-reproducible Make target must build twice and diff sha256sum.
Default is the strictest reasonable preset. Config (.golangci.yml):
linters.default: none — enable explicitly, no surprises from upstream additionsasasalint asciicheck bidichk bodyclose copyloopvar cyclop dupl durationcheck errcheck errname errorlint exhaustive forbidigo funlen gocheckcompilerdirectives gochecknoglobals gochecknoinits gocognit goconst gocritic gocyclo godot gomoddirectives gomodguard goprintffuncname gosec govet ineffassign lll loggercheck makezero mirror mnd musttag nakedret nestif nilerr nilnil noctx nolintlint nonamedreturns nosprintfhostport predeclared promlinter reassign revive rowserrcheck sqlclosecheck staticcheck testableexamples testpackage tparallel unconvert unparam unused usestdlibvars usetesting wastedassign whitespace sloglintformatters.enable: [goimports]settings.cyclop.max-complexity: 30, settings.funlen.lines: 100, settings.gocognit.min-complexity: 20, settings.lll.line-length: 140settings.govet.enable-all: true, govet.settings.shadow.strict: true, disable fieldalignmentsettings.errcheck.check-type-assertions: truesettings.exhaustive.check: [switch, map]settings.nolintlint.require-explanation: true, require-specific: truesettings.gomodguard.blocked.modules — at minimum block github.com/golang/protobuf (use google.golang.org/protobuf), github.com/satori/go.uuid + github.com/gofrs/uuid (use github.com/google/uuid)bodyclose dupl funlen goconst gosec noctx wrapcheck disabled in _test.goWarnings block CI. No //nolint without a rule-specific target and an explanation comment.
_test.go. Use require for preconditions that must abort the test (errors, setup) and assert for value comparisons that should keep collecting failures. No hand-written if err != nil { t.Fatalf(...) } / if got != want { t.Errorf(...) } plumbing.| Hand-written | Replace with |
|---|---|
if err != nil { t.Fatalf("...: %v", err) } | require.NoError(t, err) |
if err == nil { t.Fatal("want error") } | require.Error(t, err) / require.ErrorIs(...) |
if got != want { t.Fatalf(...) } | require.Equal(t, want, got) |
if !bytes.Equal(got, want) { t.Fatalf(...) } | assert.Equal(t, want, got) |
if got != want { t.Errorf(...) } (continues on fail) | assert.Equal(t, want, got) |
if !reflect.DeepEqual(got, want) { ... } | assert.Equal(t, want, got) |
Argument order is (t, want, got) — expected first, actual second. Reversing it makes failure messages lie. Inside f.Fuzz(func(t *testing.T, ...)) use require/assert on the inner t the same way.
*_test.go next to the code. External test packages (package foo_test) for black-box tests; testpackage linter enforces this where applicable.//go:build integration), run in separate CI step.go test -fuzz=Fuzz... for parsers and security-relevant decoders. Commit seed corpus under testdata/fuzz/.go test -bench=. -benchmem when performance matters; capture baseline with benchstat.go tool cover -func=coverage.out).ALWAYS use a `test.go` file with `//go:build scratch` tag in project root for ad-hoc exploration:
//go:build scratch
package main
func main() {
// ...
}Run with go run -tags=scratch ./test.go. Comment out previous code to keep history. Gitignored — never commit; move to a real package if the code should persist.
NEVER use inline Go via `go run -` or heredocs.
Follow the standard layout:
cmd/<binary>/main.go — entrypoints, thin main onlyinternal/ — private packages, not importable by other modulespkg/ — exported packages (only if actually consumed externally; otherwise keep in internal/)testdata/ — test fixtures (never compiled)vendor/ — committed, regenerated by go mod vendormain functions delegate to a testable Run(ctx, args, stdout, stderr) int in a sibling package.
When the project uses protobuf:
.proto sources live in a top-level proto/<pkg>/ directory — never co-located with Go code..pb.go lives next to its consumer at internal/<pkg>/<protopkg>/..proto files descriptively (certificate.proto), not version-only (v1.proto) — the proto package and go_package option already encode the version.make generate. Canonical protoc invocation: protoc --go_opt=paths=source_relative \
--go_out=internal/<pkg>/<protopkg> \
--proto_path=proto/<protopkg> \
proto/<protopkg>/<file>.protopaths=source_relative makes the output path predictable — the generated filename matches the input .proto, placed directly under --go_out.
Domain types do not import codec-generated symbols. Enums and structs on the domain side are Go-native and live in the same package as the business logic; conversion to and from the wire format happens at the codec boundary.
Why. A domain object that embeds <pbpkg>.Result (or any wire-format enum) cannot be encoded by anything else — adding CBOR / JSON / a schema rev forces a consumer rewrite. The same applies to peer.ID vs raw multihash bytes: domain holds the typed form, codec converts at I/O.
Layout.
internal/<pkg>/result.go — domain enum (Go-native)
internal/<pkg>/codec.go — public domain API + unexported toProtoX / fromProtoX
internal/<pkg>/<fmt>v1/ — pure protoc-gen-go output, no hand-written codeConversion helpers are unexported and the only file importing the codec package is codec.go:
// codec.go — the only file that imports certv1.
func toProtoResult(r Result) (certv1.Result, error) { /* switch */ }
func fromProtoResult(r certv1.Result) (Result, error) { /* switch */ }Public APIs (Sign(epoch, target, result Result)) take the domain type; the codec converts on the way to the wire. Adding a second wire format later means "add cbor_codec.go that reuses the same domain types," not a consumer rewrite.
Acceptance check. git grep -l '<pbpkg>\.' internal/ must return only codec.go + generated .pb.go files. Any other file naming <pbpkg>.X is a leak.
log/slog not logrus; net/http not gin unless routing complexity justifies it.github.com/golang/protobuf (enforced by gomodguard).satori/gofrs (enforced).gomod, github-actions, docker; daily schedule.Canonical targets every Go project should expose:
vendor go mod vendor
tidy go mod tidy && git diff --exit-code go.mod go.sum
fmt goimports -w $(SOURCE_DIRS)
fmt-check gofmt -l -s $(SOURCE_DIRS) | tee /dev/stderr | ifne false
lint go vet ./... && golangci-lint run -c .golangci.yml
modernize go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest ./...
test go test -race -covermode=atomic -coverprofile=coverage.out ./...
build go build -trimpath -ldflags "-s -w -buildid=" -o build/<name> ./cmd/<name>
build-reproducible build twice, diff sha256sum, fail on mismatch
vuln govulncheck ./...
all vendor tidy fmt-check lint test build~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.