Skip to content

Commit 553a8c1

Browse files
NickMillerUKclaude
andauthored
Fix IP redaction: assign ReplaceAllString return values (#79)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7001fa4 commit 553a8c1

2 files changed

Lines changed: 129 additions & 8 deletions

File tree

results/telemetry.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ var fontMediumBytes []byte
4444
var fontLightBytes []byte
4545

4646
var (
47-
ipv4Regex = regexp.MustCompile(`(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`)
47+
ipv4Regex = regexp.MustCompile(`(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`)
4848
ipv6Regex = regexp.MustCompile(`(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))`)
4949
hostnameRegex = regexp.MustCompile(`"hostname":"([^\\\\"]|\\\\")*"`)
5050

@@ -164,13 +164,13 @@ func Record(w http.ResponseWriter, r *http.Request) {
164164
extra := r.FormValue("extra")
165165

166166
if config.LoadedConfig().RedactIP {
167-
ipAddr = "0.0.0.0"
168-
ipv4Regex.ReplaceAllString(ispInfo, "0.0.0.0")
169-
ipv4Regex.ReplaceAllString(logs, "0.0.0.0")
170-
ipv6Regex.ReplaceAllString(ispInfo, "0.0.0.0")
171-
ipv6Regex.ReplaceAllString(logs, "0.0.0.0")
172-
hostnameRegex.ReplaceAllString(ispInfo, `"hostname":"REDACTED"`)
173-
hostnameRegex.ReplaceAllString(logs, `"hostname":"REDACTED"`)
167+
ipAddr = "0.0.0.0"
168+
ispInfo = ipv4Regex.ReplaceAllString(ispInfo, "0.0.0.0")
169+
logs = ipv4Regex.ReplaceAllString(logs, "0.0.0.0")
170+
ispInfo = ipv6Regex.ReplaceAllString(ispInfo, "::")
171+
logs = ipv6Regex.ReplaceAllString(logs, "::")
172+
ispInfo = hostnameRegex.ReplaceAllString(ispInfo, `"hostname":"REDACTED"`)
173+
logs = hostnameRegex.ReplaceAllString(logs, `"hostname":"REDACTED"`)
174174
}
175175

176176
var record schema.TelemetryData

results/telemetry_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package results
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestIPRedaction(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
input string
11+
wantOut string
12+
regex func(string, string) string
13+
redactTo string
14+
}{
15+
{
16+
name: "IPv4 in ispInfo is redacted",
17+
input: `{"ip":"203.0.113.42","org":"AS12345 Example ISP"}`,
18+
wantOut: `{"ip":"0.0.0.0","org":"AS12345 Example ISP"}`,
19+
regex: ipv4Regex.ReplaceAllString,
20+
redactTo: "0.0.0.0",
21+
},
22+
{
23+
name: "multiple IPv4 addresses in logs are all redacted",
24+
input: `connected from 203.0.113.42, forwarded for 198.51.100.7`,
25+
wantOut: `connected from 0.0.0.0, forwarded for 0.0.0.0`,
26+
regex: ipv4Regex.ReplaceAllString,
27+
redactTo: "0.0.0.0",
28+
},
29+
{
30+
name: "empty string is handled safely by IPv4 regex",
31+
input: "",
32+
wantOut: "",
33+
regex: ipv4Regex.ReplaceAllString,
34+
redactTo: "0.0.0.0",
35+
},
36+
{
37+
name: "IPv6 in ispInfo is redacted",
38+
input: `{"ip":"2001:0db8:85a3:0000:0000:8a2e:0370:7334","org":"AS12345 Example ISP"}`,
39+
wantOut: `{"ip":"::","org":"AS12345 Example ISP"}`,
40+
regex: ipv6Regex.ReplaceAllString,
41+
redactTo: "::",
42+
},
43+
{
44+
name: "empty string is handled safely by IPv6 regex",
45+
input: "",
46+
wantOut: "",
47+
regex: ipv6Regex.ReplaceAllString,
48+
redactTo: "::",
49+
},
50+
{
51+
name: "hostname in ispInfo is redacted",
52+
input: `{"ip":"0.0.0.0","hostname":"client.example.com","org":"AS12345 Example ISP"}`,
53+
wantOut: `{"ip":"0.0.0.0","hostname":"REDACTED","org":"AS12345 Example ISP"}`,
54+
regex: hostnameRegex.ReplaceAllString,
55+
redactTo: `"hostname":"REDACTED"`,
56+
},
57+
{
58+
name: "empty string is handled safely by hostname regex",
59+
input: "",
60+
wantOut: "",
61+
regex: hostnameRegex.ReplaceAllString,
62+
redactTo: `"hostname":"REDACTED"`,
63+
},
64+
}
65+
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
got := tt.regex(tt.input, tt.redactTo)
69+
if got != tt.wantOut {
70+
t.Errorf("\ngot: %s\nwant: %s", got, tt.wantOut)
71+
}
72+
})
73+
}
74+
}
75+
76+
func TestIPRedactionChain(t *testing.T) {
77+
tests := []struct {
78+
name string
79+
ispInfo string
80+
logs string
81+
wantISP string
82+
wantLogs string
83+
}{
84+
{
85+
name: "IPv4 client: addresses and hostname redacted",
86+
ispInfo: `{"ip":"203.0.113.42","hostname":"client.example.com","org":"AS12345 Example ISP"}`,
87+
logs: `connected from 203.0.113.42 and 198.51.100.7`,
88+
wantISP: `{"ip":"0.0.0.0","hostname":"REDACTED","org":"AS12345 Example ISP"}`,
89+
wantLogs: `connected from 0.0.0.0 and 0.0.0.0`,
90+
},
91+
{
92+
name: "IPv6 client: redacted as :: to preserve address family for debugging",
93+
ispInfo: `{"ip":"2001:0db8:85a3:0000:0000:8a2e:0370:7334","hostname":"client.example.com","org":"AS12345 Example ISP"}`,
94+
logs: `connected from 2001:0db8:85a3:0000:0000:8a2e:0370:7334`,
95+
wantISP: `{"ip":"::","hostname":"REDACTED","org":"AS12345 Example ISP"}`,
96+
wantLogs: `connected from ::`,
97+
},
98+
}
99+
100+
for _, tt := range tests {
101+
t.Run(tt.name, func(t *testing.T) {
102+
ispInfo := tt.ispInfo
103+
logs := tt.logs
104+
105+
// Mirror the full redaction block in Record()
106+
ispInfo = ipv4Regex.ReplaceAllString(ispInfo, "0.0.0.0")
107+
logs = ipv4Regex.ReplaceAllString(logs, "0.0.0.0")
108+
ispInfo = ipv6Regex.ReplaceAllString(ispInfo, "::")
109+
logs = ipv6Regex.ReplaceAllString(logs, "::")
110+
ispInfo = hostnameRegex.ReplaceAllString(ispInfo, `"hostname":"REDACTED"`)
111+
logs = hostnameRegex.ReplaceAllString(logs, `"hostname":"REDACTED"`)
112+
113+
if ispInfo != tt.wantISP {
114+
t.Errorf("ispInfo\ngot: %s\nwant: %s", ispInfo, tt.wantISP)
115+
}
116+
if logs != tt.wantLogs {
117+
t.Errorf("logs\ngot: %s\nwant: %s", logs, tt.wantLogs)
118+
}
119+
})
120+
}
121+
}

0 commit comments

Comments
 (0)