diff --git a/internal/scan/probe.go b/internal/scan/probe.go
index 827fd425..3c145b54 100644
--- a/internal/scan/probe.go
+++ b/internal/scan/probe.go
@@ -15,6 +15,7 @@ package scan
import (
"context"
"fmt"
+ "html"
"io"
"net/http"
"regexp"
@@ -133,13 +134,14 @@ func Probe(targetURL string, timeout time.Duration, logdir string) (*ProbeResult
}
// extractTitle returns the trimmed text of the first
in body, or "" when
-// there isn't one.
+// there isn't one. html entities are decoded so the title matches the rendered
+// page rather than carrying raw "&"-style markup.
func extractTitle(body []byte) string {
m := titleRe.FindSubmatch(body)
if len(m) < 2 {
return ""
}
- return strings.TrimSpace(string(m[1]))
+ return strings.TrimSpace(html.UnescapeString(string(m[1])))
}
// ResultType identifies probe results for the result registry.
diff --git a/internal/scan/probe_test.go b/internal/scan/probe_test.go
index 246e8db8..231795ed 100644
--- a/internal/scan/probe_test.go
+++ b/internal/scan/probe_test.go
@@ -108,6 +108,7 @@ func TestProbe_ExtractTitle(t *testing.T) {
{"trimmed", " spaced ", "spaced"},
{"attrs", `attr`, "attr"},
{"multiline", "line one\nline two", "line one\nline two"},
+ {"entities", "Tom & Jerry – Home", "Tom & Jerry – Home"},
{"none", "no title", ""},
}
for _, tt := range tests {