Send only the Referer's scheme and host to Sentry (closes #548)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
A Sentry event kept the Referer header verbatim, and a Referer can be an entrypoint URL: a sender that follows the README's http:// to https:// redirect sends the URL it was redirected from, and a browser sends the 404 page's URL under /h/ when it fetches that page's stylesheet. The Sentry hook now keeps only the Referer's scheme and host. The access log's reduction moves, unchanged, into a new package, internal/referer, as SchemeAndHost; the access log and the Sentry hook both call it there. The comment above sentryKeepsHeader and the README's Sentry passage say so. Model: opus-5-5
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// Package referer reduces a Referer header to its scheme and host, so
|
||||
// that a URL path, which on the receiver route carries the entrypoint
|
||||
// UUID, never reaches a log line or an error report.
|
||||
package referer
|
||||
|
||||
import "net/url"
|
||||
|
||||
// redacted stands in for a Referer that cannot be reduced to its
|
||||
// scheme and host.
|
||||
const redacted = "(redacted)"
|
||||
|
||||
// SchemeAndHost returns a Referer's scheme and host, never its path or
|
||||
// query. The path can be an entrypoint URL, which carries the UUID: a
|
||||
// sender that follows a redirect from http:// to https:// sends the
|
||||
// URL it was redirected from, and a browser sends the 404 page's URL
|
||||
// under /h/ when it fetches that page's stylesheet. A Referer without
|
||||
// both a scheme and a host comes back as "(redacted)".
|
||||
//
|
||||
// It is the access log's referer field, and the Referer header the
|
||||
// Sentry hook sends.
|
||||
func SchemeAndHost(referer string) string {
|
||||
if referer == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(referer)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
return redacted
|
||||
}
|
||||
|
||||
return parsed.Scheme + "://" + parsed.Host
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package referer_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sneak.berlin/go/webhooker/internal/referer"
|
||||
)
|
||||
|
||||
// entrypointUUID is the secret part of an entrypoint URL; no case may
|
||||
// return it.
|
||||
const entrypointUUID = "0b6f7d3e-6a39-4e3c-9f0a-2c4d8e1b5a77"
|
||||
|
||||
// redacted is what SchemeAndHost returns for a Referer it cannot
|
||||
// reduce to a scheme and host.
|
||||
const redacted = "(redacted)"
|
||||
|
||||
func TestSchemeAndHost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
referer string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "no Referer",
|
||||
referer: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "entrypoint URL with a query",
|
||||
referer: "http://webhooker.example/h/" + entrypointUUID +
|
||||
"?src=ci",
|
||||
want: "http://webhooker.example",
|
||||
},
|
||||
{
|
||||
name: "host with a port",
|
||||
referer: "https://webhooker.example:8443/h/" + entrypointUUID,
|
||||
want: "https://webhooker.example:8443",
|
||||
},
|
||||
{
|
||||
name: "path without a scheme or host",
|
||||
referer: "/h/" + entrypointUUID,
|
||||
want: redacted,
|
||||
},
|
||||
{
|
||||
name: "host without a scheme",
|
||||
referer: "webhooker.example/h/" + entrypointUUID,
|
||||
want: redacted,
|
||||
},
|
||||
{
|
||||
name: "not a URL",
|
||||
referer: "http://[::1/h/" + entrypointUUID,
|
||||
want: redacted,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := referer.SchemeAndHost(tc.referer)
|
||||
|
||||
assert.Equal(t, tc.want, got)
|
||||
assert.NotContains(t, got, entrypointUUID)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user