lint: adopt org-standard .golangci.yml and golangci-lint v2.12.2 (closes #14)
check / check (push) Failing after 1s
check / check (push) Failing after 1s
The old backend/.golangci.yml declared version "2" but used v1 schema keys, so under v2 it never validated and its thresholds were inert: the linter ran at defaults. Replace it verbatim with the org-standard file, repin the Dockerfile.backend lint stage to golangci-lint v2.12.2, and assert the config's sha256 as the first step of the backend lint target so it cannot silently drift again -- a local hash check, no network. The standard config surfaces findings only in the tests: the repeated IP literals in middleware_test.go become named constants (goconst) and its request switches to NewRequestWithContext (noctx). reportbuf.go's gosec suppression gains a plain justification comment. The rest of the backend, including the fx-based server lifecycle, is already clean. TODO.md updated. Model: opus-4-8
This commit was merged in pull request #31.
This commit is contained in:
+14
-12
@@ -1,5 +1,9 @@
|
||||
version: "2"
|
||||
|
||||
# Config schema uses the golangci-lint v2 layout (settings live under
|
||||
# linters.settings, not top-level linters-settings) so that the
|
||||
# thresholds below are actually applied by golangci-lint >= v2.
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
modules-download-mode: readonly
|
||||
@@ -14,19 +18,17 @@ linters:
|
||||
- wsl # Deprecated, replaced by wsl_v5
|
||||
- wrapcheck # Too verbose for internal packages
|
||||
- varnamelen # Short names like db, id are idiomatic Go
|
||||
|
||||
linters-settings:
|
||||
lll:
|
||||
line-length: 88
|
||||
funlen:
|
||||
lines: 80
|
||||
statements: 50
|
||||
cyclop:
|
||||
max-complexity: 15
|
||||
dupl:
|
||||
threshold: 100
|
||||
settings:
|
||||
lll:
|
||||
line-length: 88
|
||||
funlen:
|
||||
lines: 80
|
||||
statements: 50
|
||||
cyclop:
|
||||
max-complexity: 15
|
||||
dupl:
|
||||
threshold: 100
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
@@ -10,6 +10,17 @@ GOLDFLAGS += -s -w
|
||||
GOLDFLAGS += -X main.Version=$(VERSION)
|
||||
GOLDFLAGS += -X main.Buildarch=$(BUILDARCH)
|
||||
|
||||
# macOS ships shasum rather than sha256sum.
|
||||
SHA256SUM := $(shell command -v sha256sum >/dev/null 2>&1 && echo sha256sum || echo shasum -a 256)
|
||||
|
||||
# .golangci.yml is standardized org-wide and must never be edited here
|
||||
# (REPO_POLICIES.md). Its last silent drift replaced the v2 schema with
|
||||
# v1 keys, which left every threshold in the file inert while the build
|
||||
# stayed green. The lint target therefore asserts the file still matches
|
||||
# the canonical copy byte for byte. The check is a local hash comparison:
|
||||
# no network, no remote schema, nothing unpinned in the build path.
|
||||
GOLANGCI_CONFIG_SHA256 := 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb
|
||||
|
||||
.PHONY: all build test lint fmt fmt-check check docker hooks run clean
|
||||
|
||||
all: build
|
||||
@@ -22,6 +33,14 @@ test:
|
||||
timeout 30 go test ./...
|
||||
|
||||
lint:
|
||||
@actual=$$($(SHA256SUM) .golangci.yml | cut -d' ' -f1); \
|
||||
if [ "$$actual" != "$(GOLANGCI_CONFIG_SHA256)" ]; then \
|
||||
echo ".golangci.yml has drifted from the org standard."; \
|
||||
echo " expected $(GOLANGCI_CONFIG_SHA256)"; \
|
||||
echo " actual $$actual"; \
|
||||
echo "Restore it verbatim from sneak/prompts; do not edit it."; \
|
||||
exit 1; \
|
||||
fi
|
||||
golangci-lint run ./...
|
||||
|
||||
fmt:
|
||||
|
||||
@@ -9,6 +9,15 @@ import (
|
||||
"sneak.berlin/go/netwatch/internal/middleware"
|
||||
)
|
||||
|
||||
const (
|
||||
// loopbackPeer is a remote address inside the trusted-proxy allowlist.
|
||||
loopbackPeer = "127.0.0.1:5000"
|
||||
// forwardedIP is the client address presented via X-Forwarded-For.
|
||||
forwardedIP = "203.0.113.7"
|
||||
// realIP is the client address presented via X-Real-IP.
|
||||
realIP = "203.0.113.9"
|
||||
)
|
||||
|
||||
func mustPrefixes(t *testing.T, cidrs ...string) []netip.Prefix {
|
||||
t.Helper()
|
||||
|
||||
@@ -41,32 +50,32 @@ func clientIPCases() []clientIPCase {
|
||||
return []clientIPCase{
|
||||
{
|
||||
name: "trusted proxy uses forwarded-for",
|
||||
remoteAddr: "127.0.0.1:5000",
|
||||
xff: "203.0.113.7",
|
||||
want: "203.0.113.7",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: forwardedIP,
|
||||
want: forwardedIP,
|
||||
},
|
||||
{
|
||||
name: "trusted proxy uses left-most of chain",
|
||||
remoteAddr: "10.1.2.3:5000",
|
||||
xff: "203.0.113.7, 10.1.2.3",
|
||||
want: "203.0.113.7",
|
||||
xff: forwardedIP + ", 10.1.2.3",
|
||||
want: forwardedIP,
|
||||
},
|
||||
{
|
||||
name: "trusted proxy falls back to x-real-ip",
|
||||
remoteAddr: "127.0.0.1:5000",
|
||||
xRealIP: "203.0.113.9",
|
||||
want: "203.0.113.9",
|
||||
remoteAddr: loopbackPeer,
|
||||
xRealIP: realIP,
|
||||
want: realIP,
|
||||
},
|
||||
{
|
||||
name: "untrusted peer ignores forwarded-for",
|
||||
remoteAddr: "198.51.100.4:5000",
|
||||
xff: "203.0.113.7",
|
||||
xff: forwardedIP,
|
||||
want: "198.51.100.4",
|
||||
},
|
||||
{
|
||||
name: "untrusted peer ignores x-real-ip",
|
||||
remoteAddr: "198.51.100.4:5000",
|
||||
xRealIP: "203.0.113.9",
|
||||
xRealIP: realIP,
|
||||
want: "198.51.100.4",
|
||||
},
|
||||
{
|
||||
@@ -76,7 +85,7 @@ func clientIPCases() []clientIPCase {
|
||||
},
|
||||
{
|
||||
name: "trusted proxy with garbage header uses peer",
|
||||
remoteAddr: "127.0.0.1:5000",
|
||||
remoteAddr: loopbackPeer,
|
||||
xff: "not-an-ip",
|
||||
want: "127.0.0.1",
|
||||
},
|
||||
@@ -119,7 +128,7 @@ func TestSecurityHeaders(t *testing.T) {
|
||||
)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
|
||||
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", http.NoBody)
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
want := map[string]string{
|
||||
|
||||
@@ -169,7 +169,9 @@ func (b *Buffer) writeFile(data []byte) {
|
||||
name := fmt.Sprintf("reports-%s.jsonl.zst", ts)
|
||||
path := filepath.Join(b.dataDir, name)
|
||||
|
||||
f, err := os.OpenFile( //nolint:gosec // path built from controlled dataDir + timestamp
|
||||
// path is built from the operator-supplied dataDir plus a
|
||||
// generated timestamp, so it carries no external input.
|
||||
f, err := os.OpenFile( //nolint:gosec // see comment above
|
||||
path,
|
||||
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
|
||||
filePerms,
|
||||
|
||||
Reference in New Issue
Block a user