Gitfed
bastien-mrq/gitfed / cmd / gitfed-web / security_headers_test.go
package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"html/template"
	"net/http/httptest"
	"strings"
	"testing"
)

// TestCSPScriptHashMatchesRenderedScript renders the shell and checks that the
// CSP hash we advertise equals the hash of the script actually served — if a
// future edit to the shell changes either the script or how it's emitted, this
// fails instead of silently breaking every page under the strict policy.
func TestCSPScriptHashMatchesRenderedScript(t *testing.T) {
	var buf bytes.Buffer
	err := shellTpl.Execute(&buf, struct {
		Title, Domain, Active, Username, Version, Initials, SearchQuery, Lang, Theme, ThemeIcon, ThemeNext, NextPath string
		LoggedIn, IsAdmin                                                                                            bool
		Body, BrandMark, IconSprite                                                                                  template.HTML
	}{Title: "t", Lang: "en"})
	if err != nil {
		t.Fatalf("execute shell: %v", err)
	}

	html := buf.String()
	open := strings.Index(html, "<script>")
	close := strings.Index(html, "</script>")
	if open < 0 || close < 0 || close < open {
		t.Fatalf("no <script>…</script> in rendered shell")
	}
	served := html[open+len("<script>") : close]

	sum := sha256.Sum256([]byte(served))
	want := "sha256-" + base64.StdEncoding.EncodeToString(sum[:])
	if want != scriptHash {
		t.Fatalf("CSP script hash mismatch:\n advertised %s\n served     %s", scriptHash, want)
	}
}

func TestSameOriginPOST(t *testing.T) {
	s := &server{}
	handler := s.sameOriginPOST(nil)
	_ = handler // constructed to ensure it wraps without panicking

	cases := []struct {
		name       string
		method     string
		origin     string
		referer    string
		host       string
		wantOrigin bool
	}{
		{"get always passes", "GET", "", "", "example.com", true},
		{"matching origin", "POST", "https://example.com", "", "example.com", true},
		{"mismatched origin", "POST", "https://evil.com", "", "example.com", false},
		{"referer fallback match", "POST", "", "https://example.com/login", "example.com", true},
		{"no headers refused", "POST", "", "", "example.com", false},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			r := httptest.NewRequest(c.method, "http://example.com/x", nil)
			r.Host = c.host
			if c.origin != "" {
				r.Header.Set("Origin", c.origin)
			}
			if c.referer != "" {
				r.Header.Set("Referer", c.referer)
			}
			got := c.method == "GET" || sameOrigin(r)
			if got != c.wantOrigin {
				t.Fatalf("got allowed=%v, want %v", got, c.wantOrigin)
			}
		})
	}
}