Skip to content

Commit 876c01b

Browse files
committed
adopt golangci-lint, replace the CI gofmt check with a lint job
Enable errcheck, gosec, misspell, unconvert, and ineffassign plus the gofmt/goimports formatters; staticcheck and unused stay off until golangci-lint ships staticcheck 2026.2+, whose current IR builder panics on the Go 1.27 standard library. Noisy gosec rules (G115 integer conversions in tlog index math, operator-supplied path/URL taint rules, public-data file modes) are excluded with justifications in .golangci.yml. golangci-lint release binaries are built with released Go and cannot type-check a `go 1.27` module, so CI and the new make lint/fmt targets install it from source with the 1.27 toolchain instead of using golangci-lint-action.
1 parent b72eedf commit 876c01b

4 files changed

Lines changed: 125 additions & 10 deletions

File tree

.github/workflows/ci.yml

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,6 @@ jobs:
3535
- name: Toolchain version
3636
run: go version
3737

38-
- name: Check gofmt
39-
run: |
40-
fmtout=$(gofmt -l .)
41-
if [ -n "$fmtout" ]; then
42-
echo "These files are not gofmt-clean:"
43-
echo "$fmtout"
44-
exit 1
45-
fi
46-
4738
# The Makefile defaults to gotip for local development; CI has a
4839
# real 1.27 toolchain on PATH as `go`.
4940
- name: Vet
@@ -64,3 +55,26 @@ jobs:
6455
# workflow's job.
6556
- name: Vet stress-tagged tests
6657
run: go vet -tags=stress ./integration/...
58+
59+
lint:
60+
runs-on: ubuntu-latest
61+
steps:
62+
- uses: actions/checkout@v6
63+
64+
- name: Install Go
65+
uses: actions/setup-go@v6
66+
with:
67+
go-version: ${{ env.GO_VERSION }}
68+
69+
# golangci-lint release binaries are built with the latest released
70+
# Go and cannot type-check a `go 1.27` module, so build it from
71+
# source with the toolchain installed above instead of using
72+
# golangci/golangci-lint-action. setup-go's cache keeps this fast
73+
# after the first run. Keep the pinned version in sync with the
74+
# Makefile comment. Formatting (gofmt + goimports) is checked here
75+
# too, via the formatters enabled in .golangci.yml.
76+
- name: Install golangci-lint
77+
run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
78+
79+
- name: Lint
80+
run: make GO=go lint

.golangci.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
version: "2"
2+
3+
run:
4+
# The stress tag only adds integration/stress_test.go; linting with it
5+
# on covers the whole tree in one pass.
6+
build-tags:
7+
- stress
8+
9+
linters:
10+
# Standard set: errcheck, govet, ineffassign, staticcheck, unused.
11+
default: standard
12+
enable:
13+
- gosec
14+
- misspell
15+
- unconvert
16+
# golangci-lint vendors honnef.co/go/tools v0.7.0 (staticcheck
17+
# 2026.1), whose IR builder panics on the Go 1.27 standard library.
18+
# Re-enable staticcheck and unused once golangci-lint ships
19+
# staticcheck 2026.2+.
20+
disable:
21+
- staticcheck
22+
- unused
23+
settings:
24+
errcheck:
25+
exclude-functions:
26+
# Best-effort HTTP response writes; there is nothing useful to
27+
# do with a client that hung up.
28+
- (net/http.ResponseWriter).Write
29+
# Close on a read-only body; errors there are meaningless.
30+
- (io.ReadCloser).Close
31+
# Fprint* is used on HTTP responses and hash.Hash writers, where
32+
# the error is likewise not actionable.
33+
- fmt.Fprint
34+
- fmt.Fprintf
35+
- fmt.Fprintln
36+
gosec:
37+
excludes:
38+
# G104 (unhandled errors) duplicates errcheck, which is
39+
# configured precisely above.
40+
- G104
41+
# G115 (integer overflow on conversion): tlog tile/index math
42+
# converts between uint64, int64, and int pervasively and
43+
# intentionally; the flagged sites are all bounded by tree size.
44+
- G115
45+
# G304 (file path from variable): key seeds, configs, and the
46+
# storage root are operator-supplied paths by design.
47+
- G304
48+
config:
49+
# Log data (tiles, checkpoints) is public and served over HTTP;
50+
# world-readable directories and files are intentional.
51+
G301: "0755"
52+
G302: "0644"
53+
exclusions:
54+
rules:
55+
# Tests build/run local binaries, hit local test servers with
56+
# constructed URLs, and write world-readable fixtures; gosec's
57+
# G107/G204/G306/G602 hits there are all noise.
58+
- path: _test\.go
59+
linters:
60+
- gosec
61+
# cactus-cli's purpose is fetching operator-supplied log URLs and
62+
# reading operator-supplied cert files, so G703 (path traversal)
63+
# and G704 (SSRF) flag the tool working as intended.
64+
- path: cmd/cactus-cli/
65+
text: "G70[34]"
66+
# The tile handler serves stored binary tiles as
67+
# application/octet-stream; G705's XSS taint trace (URL path ->
68+
# storage key -> response body) is not script injection.
69+
- path: tile/server\.go
70+
text: "G705"
71+
72+
issues:
73+
# Report everything; the default per-linter and per-issue caps hide
74+
# findings.
75+
max-issues-per-linter: 0
76+
max-same-issues: 0
77+
78+
formatters:
79+
enable:
80+
- gofmt
81+
- goimports
82+
settings:
83+
goimports:
84+
local-prefixes:
85+
- github.com/letsencrypt/cactus

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ make build # builds bin/cactus, bin/cactus-cli, bin/cactus-keygen,
2323
make test # gotip test ./...
2424
make test-race # gotip test -race ./...
2525
make vet # gotip vet ./...
26+
make lint # golangci-lint run (config: .golangci.yml)
27+
make fmt # golangci-lint fmt (gofmt + goimports)
2628
make integration # gotip test -race -count=1 -tags=integration ./integration/...
2729
make stress # bulk issuance stress test (800 certs, `stress` build tag)
2830

Makefile

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: build test test-race vet integration stress clean \
1+
.PHONY: build test test-race vet lint fmt integration stress clean \
22
docker-binaries docker-build docker-up docker-down docker-logs
33

44
# cactus requires Go 1.27+ (built-in crypto/mldsa). Until 1.27 ships, the
@@ -22,6 +22,20 @@ test-race:
2222
vet:
2323
$(GO) vet ./...
2424

25+
# golangci-lint must itself be built with a Go 1.27+ toolchain to
26+
# type-check this module, and its release binaries are built with the
27+
# latest released Go, so install it from source:
28+
# gotip install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
29+
# The PATH prefix makes golangci-lint's `go` invocations use $(GO)'s
30+
# toolchain.
31+
GOLANGCI_LINT ?= golangci-lint
32+
33+
lint:
34+
PATH="$$($(GO) env GOROOT)/bin:$$PATH" GOTOOLCHAIN=local $(GOLANGCI_LINT) run
35+
36+
fmt:
37+
PATH="$$($(GO) env GOROOT)/bin:$$PATH" GOTOOLCHAIN=local $(GOLANGCI_LINT) fmt
38+
2539
integration:
2640
$(GO) test -race -count=1 -tags=integration ./integration/...
2741

0 commit comments

Comments
 (0)