detached 7cfa412

fix(http): share loopback nets and redact more query secrets

commit
7cfa412aa8d7114e650a7eec02b897ae658b0344
author
brice · · 2026-04-08 18:18 UTC
parent
be42624
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +9 -13
M internal/adapters/in/http/httphelper/trust.go +3 -3
@@ -7,8 +7,8 @@ import (
77 "strings"
88 )
99
10-// localhostNets contains IPv4 and IPv6 loopback ranges.
11-var localhostNets = ParseTrustedProxies([]string{"127.0.0.0/8", "::1"})
10+// LocalhostNets contains IPv4 and IPv6 loopback ranges.
11+var LocalhostNets = ParseTrustedProxies([]string{"127.0.0.0/8", "::1"})
1212
1313 // ParseTrustedProxies converts a list of IP addresses and CIDR ranges to [net.IPNet].
1414 // It accepts both single IPs (e.g., "10.0.0.1") and CIDR notation (e.g., "10.0.0.0/8").
@@ -62,7 +62,7 @@ func IsTrustedProxy(ip string, trustedNets []*net.IPNet) bool {
6262 // IsTrustedOrLocal reports whether an IP belongs to localhost or the given
6363 // trusted proxy networks.
6464 func IsTrustedOrLocal(ip string, proxyNets []*net.IPNet) bool {
65- return IsTrustedProxy(ip, localhostNets) || IsTrustedProxy(ip, proxyNets)
65+ return IsTrustedProxy(ip, LocalhostNets) || IsTrustedProxy(ip, proxyNets)
6666 }
6767
6868 // ExtractRemoteIP returns the IP portion of an [http.Request.RemoteAddr],
@@ -7,8 +7,8 @@ import (
7 "strings"7 "strings"
8 )8 )
9 9
10-// localhostNets contains IPv4 and IPv6 loopback ranges.10+// LocalhostNets contains IPv4 and IPv6 loopback ranges.
11-var localhostNets = ParseTrustedProxies([]string{"127.0.0.0/8", "::1"})11+var LocalhostNets = ParseTrustedProxies([]string{"127.0.0.0/8", "::1"})
12 12
13 // ParseTrustedProxies converts a list of IP addresses and CIDR ranges to [net.IPNet].13 // ParseTrustedProxies converts a list of IP addresses and CIDR ranges to [net.IPNet].
14 // It accepts both single IPs (e.g., "10.0.0.1") and CIDR notation (e.g., "10.0.0.0/8").14 // It accepts both single IPs (e.g., "10.0.0.1") and CIDR notation (e.g., "10.0.0.0/8").
@@ -62,7 +62,7 @@ func IsTrustedProxy(ip string, trustedNets []*net.IPNet) bool {
62 // IsTrustedOrLocal reports whether an IP belongs to localhost or the given62 // IsTrustedOrLocal reports whether an IP belongs to localhost or the given
63 // trusted proxy networks.63 // trusted proxy networks.
64 func IsTrustedOrLocal(ip string, proxyNets []*net.IPNet) bool {64 func IsTrustedOrLocal(ip string, proxyNets []*net.IPNet) bool {
65- return IsTrustedProxy(ip, localhostNets) || IsTrustedProxy(ip, proxyNets)65+ return IsTrustedProxy(ip, LocalhostNets) || IsTrustedProxy(ip, proxyNets)
66 }66 }
67 67
68 // ExtractRemoteIP returns the IP portion of an [http.Request.RemoteAddr],68 // ExtractRemoteIP returns the IP portion of an [http.Request.RemoteAddr],
M internal/adapters/in/http/middleware/accesslog.go +3 -4
@@ -42,11 +42,10 @@ func AccessLogger(writer out.AccessLogWriter, excludeHealthChecks bool, log zero
4242 // Apply health-check exclusion after the response so the request
4343 // always completes — we just skip writing the log entry.
4444 // UA check uses the shared domain constant so the prober and this
45- // filter can never drift. Loopback check reuses localhostNets from
46- // cidr.go (same package) — no separate loopback definition needed.
45+ // filter can never drift. Loopback check uses the shared helper list.
4746 if excludeHealthChecks {
4847 if strings.HasPrefix(r.UserAgent(), out.HealthCheckUserAgentPrefix) ||
49- httphelper.IsTrustedProxy(clientIP, localhostNets) {
48+ httphelper.IsTrustedProxy(clientIP, httphelper.LocalhostNets) {
5049 return
5150 }
5251 }
@@ -121,7 +120,7 @@ func sanitizeLoggedQuery(rawQuery string) string {
121120
122121 func isSensitiveQueryKey(key string) bool {
123122 switch strings.ToLower(key) {
124- case "access_token", "auth", "code", "key", "password", "refresh_token", "reset", "secret", "sig", "signature", "token":
123+ case "access_token", "api_key", "apikey", "auth", "bearer", "code", "credential", "credentials", "key", "password", "private_key", "refresh_token", "reset", "secret", "session", "session_id", "sig", "signature", "token":
125124 return true
126125 default:
127126 return false
@@ -42,11 +42,10 @@ func AccessLogger(writer out.AccessLogWriter, excludeHealthChecks bool, log zero
42 // Apply health-check exclusion after the response so the request42 // Apply health-check exclusion after the response so the request
43 // always completes — we just skip writing the log entry.43 // always completes — we just skip writing the log entry.
44 // UA check uses the shared domain constant so the prober and this44 // UA check uses the shared domain constant so the prober and this
45- // filter can never drift. Loopback check reuses localhostNets from45+ // filter can never drift. Loopback check uses the shared helper list.
46- // cidr.go (same package) — no separate loopback definition needed.
47 if excludeHealthChecks {46 if excludeHealthChecks {
48 if strings.HasPrefix(r.UserAgent(), out.HealthCheckUserAgentPrefix) ||47 if strings.HasPrefix(r.UserAgent(), out.HealthCheckUserAgentPrefix) ||
49- httphelper.IsTrustedProxy(clientIP, localhostNets) {48+ httphelper.IsTrustedProxy(clientIP, httphelper.LocalhostNets) {
50 return49 return
51 }50 }
52 }51 }
@@ -121,7 +120,7 @@ func sanitizeLoggedQuery(rawQuery string) string {
121 120
122 func isSensitiveQueryKey(key string) bool {121 func isSensitiveQueryKey(key string) bool {
123 switch strings.ToLower(key) {122 switch strings.ToLower(key) {
124- case "access_token", "auth", "code", "key", "password", "refresh_token", "reset", "secret", "sig", "signature", "token":123+ case "access_token", "api_key", "apikey", "auth", "bearer", "code", "credential", "credentials", "key", "password", "private_key", "refresh_token", "reset", "secret", "session", "session_id", "sig", "signature", "token":
125 return true124 return true
126 default:125 default:
127 return false126 return false
M internal/adapters/in/http/middleware/accesslog_test.go +2 -2
@@ -69,7 +69,7 @@ func TestAccessLogger_RedactsSensitiveQueryParams(t *testing.T) {
6969 server := httptest.NewServer(handler)
7070 defer server.Close()
7171
72- resp, err := http.Get(server.URL + "/oauth/callback?code=secret-code&state=ok&token=secret-token")
72+ resp, err := http.Get(server.URL + "/oauth/callback?code=secret-code&state=ok&token=secret-token&api_key=top-secret&session_id=session-secret")
7373 require.NoError(t, err)
7474 defer func() {
7575 assert.NoError(t, resp.Body.Close())
@@ -78,7 +78,7 @@ func TestAccessLogger_RedactsSensitiveQueryParams(t *testing.T) {
7878 require.NoError(t, err)
7979
8080 require.Len(t, mock.entries, 1)
81- assert.Equal(t, "code=[REDACTED]&state=ok&token=[REDACTED]", mock.entries[0].Query)
81+ assert.Equal(t, "code=[REDACTED]&state=ok&token=[REDACTED]&api_key=[REDACTED]&session_id=[REDACTED]", mock.entries[0].Query)
8282 }
8383
8484 func TestAccessLogger_RequestIDReused(t *testing.T) {
@@ -69,7 +69,7 @@ func TestAccessLogger_RedactsSensitiveQueryParams(t *testing.T) {
69 server := httptest.NewServer(handler)69 server := httptest.NewServer(handler)
70 defer server.Close()70 defer server.Close()
71 71
72- resp, err := http.Get(server.URL + "/oauth/callback?code=secret-code&state=ok&token=secret-token")72+ resp, err := http.Get(server.URL + "/oauth/callback?code=secret-code&state=ok&token=secret-token&api_key=top-secret&session_id=session-secret")
73 require.NoError(t, err)73 require.NoError(t, err)
74 defer func() {74 defer func() {
75 assert.NoError(t, resp.Body.Close())75 assert.NoError(t, resp.Body.Close())
@@ -78,7 +78,7 @@ func TestAccessLogger_RedactsSensitiveQueryParams(t *testing.T) {
78 require.NoError(t, err)78 require.NoError(t, err)
79 79
80 require.Len(t, mock.entries, 1)80 require.Len(t, mock.entries, 1)
81- assert.Equal(t, "code=[REDACTED]&state=ok&token=[REDACTED]", mock.entries[0].Query)81+ assert.Equal(t, "code=[REDACTED]&state=ok&token=[REDACTED]&api_key=[REDACTED]&session_id=[REDACTED]", mock.entries[0].Query)
82 }82 }
83 83
84 func TestAccessLogger_RequestIDReused(t *testing.T) {84 func TestAccessLogger_RequestIDReused(t *testing.T) {
M internal/adapters/in/http/middleware/cidr.go +1 -4
@@ -13,9 +13,6 @@ import (
1313 "github.com/bnema/gordon/internal/adapters/in/http/httphelper"
1414 )
1515
16-// localhostNets contains IPv4 and IPv6 loopback ranges that are always allowed.
17-var localhostNets = httphelper.ParseTrustedProxies([]string{"127.0.0.0/8", "::1"})
18-
1916 // cidrAllowlist is the shared implementation for CIDR-based access control middleware.
2017 // ipExtractor determines how the client IP is obtained from the request.
2118 // logLabel is used in the deny log message (e.g. "registry", "proxy origin").
@@ -27,7 +24,7 @@ func cidrAllowlist(allowedNets []*net.IPNet, ipExtractor func(*http.Request) str
2724 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2825 clientIP := ipExtractor(r)
2926
30- if httphelper.IsTrustedProxy(clientIP, localhostNets) {
27+ if httphelper.IsTrustedProxy(clientIP, httphelper.LocalhostNets) {
3128 next.ServeHTTP(w, r)
3229 return
3330 }
@@ -13,9 +13,6 @@ import (
13 "github.com/bnema/gordon/internal/adapters/in/http/httphelper"13 "github.com/bnema/gordon/internal/adapters/in/http/httphelper"
14 )14 )
15 15
16-// localhostNets contains IPv4 and IPv6 loopback ranges that are always allowed.
17-var localhostNets = httphelper.ParseTrustedProxies([]string{"127.0.0.0/8", "::1"})
18-
19 // cidrAllowlist is the shared implementation for CIDR-based access control middleware.16 // cidrAllowlist is the shared implementation for CIDR-based access control middleware.
20 // ipExtractor determines how the client IP is obtained from the request.17 // ipExtractor determines how the client IP is obtained from the request.
21 // logLabel is used in the deny log message (e.g. "registry", "proxy origin").18 // logLabel is used in the deny log message (e.g. "registry", "proxy origin").
@@ -27,7 +24,7 @@ func cidrAllowlist(allowedNets []*net.IPNet, ipExtractor func(*http.Request) str
27 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {24 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28 clientIP := ipExtractor(r)25 clientIP := ipExtractor(r)
29 26
30- if httphelper.IsTrustedProxy(clientIP, localhostNets) {27+ if httphelper.IsTrustedProxy(clientIP, httphelper.LocalhostNets) {
31 next.ServeHTTP(w, r)28 next.ServeHTTP(w, r)
32 return29 return
33 }30 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close