package check import ( "bufio" "encoding/xml" "io" "io/fs" "os" "path/filepath" "regexp" "sort" "strings" "time" "git.coffeylabs.org/coffey-labs/hotdog-cms/internal/media" "git.coffeylabs.org/coffey-labs/hotdog-cms/internal/site" ) // Hosts whose scripts track visitors. Loading one on page view means the // tracking starts before anyone could agree to it. A site that asks first // loads it from its consent code instead, which this check doesn't see. var trackers = []string{ "googletagmanager.com", "google-analytics.com", "doubleclick.net", "googleadservices.com", "connect.facebook.net", "facebook.net", "analytics.tiktok.com", "snap.licdn.com", "static.hotjar.com", "script.hotjar.com", "clarity.ms", "cdn.segment.com", "static.cloudflareinsights.com", "js.hs-scripts.com", "matomo.cloud", "cdn.amplitude.com", "mc.yandex.ru", "bat.bing.com", } func isTracker(h string) bool { for _, t := range trackers { if h == t || strings.HasSuffix(h, "."+t) { return true } } return false } var paginated = regexp.MustCompile(`/page/\d+/$`) func (c *checker) checkPage(pg *page) { strict := c.cfg.Check.CSP == "strict" inlineLevel := Warning if strict { inlineLevel = Error } // Tracking and third parties. seen := map[string]bool{} for _, ref := range append(append(append([]string{}, pg.scripts...), pg.frames...), pg.styles...) { h := c.host(ref) if h == "" || seen[h] { continue } seen[h] = true switch { case isTracker(h): c.add(Error, "tracker-before-consent", pg.file, "%s loads on page view, before any consent; load it from your consent code instead", h) case !c.allow[h]: c.add(Warning, "third-party", pg.file, "loads from %s; list it in check.allow_third_party if that's intended, and in the privacy notice", h) } } if pg.ungated { c.add(Warning, "analytics-ungated", pg.file, "visit statistics start on page view, without asking (analytics.gated: false in site.yaml)") } for _, js := range pg.inlineJS { if strings.Contains(js, "gtag(") || strings.Contains(js, "dataLayer") || strings.Contains(js, "fbq(") || strings.Contains(js, "_paq") { c.add(Error, "tracker-before-consent", pg.file, "inline tracking code runs on page view: %s", js) } } // What a strict Content-Security-Policy would refuse. if len(pg.inlineJS) > 0 { c.add(inlineLevel, "inline-script", pg.file, "%d inline script(s), first: %s", len(pg.inlineJS), pg.inlineJS[0]) } if pg.inlineStyles > 0 { c.add(inlineLevel, "inline-style", pg.file, "%d inline style(s) (style attributes or