oficina / home Public

Allow the configured directory in credential form redirects

Paul Campbell pushed by paul@keymail.dev 9b77ec85ad23c87fa234bd7130b616179d96cd37 parent eb6e739
4 files changed, +53 −7
  • cmd/home/main.go +1 −0
  • internal/home/noscript_test.go +6 −7
  • internal/home/security.go +8 −0
  • internal/home/security_test.go +38 −0

CI passed — run details

diff --git a/cmd/home/main.go b/cmd/home/main.go
index 355cb9e..de742d6 100644
--- a/cmd/home/main.go
+++ b/cmd/home/main.go
@@ -112,6 +112,7 @@ func main() {
mux := http.NewServeMux()
mux.Handle("/", app.Router())
opts.Mux = mux
+ opts.CSP = app.SecurityPolicy()
// Blanked because this file opened the handle itself: Serve would
// otherwise open a second one against the same file.
diff --git a/internal/home/noscript_test.go b/internal/home/noscript_test.go
index 1b3055b..e60e27c 100644
--- a/internal/home/noscript_test.go
+++ b/internal/home/noscript_test.go
@@ -232,7 +232,7 @@ func TestServedCSPAllowsNoInlineScript(t *testing.T) {
mux := http.NewServeMux()
mux.Handle("/", app.Router())
- h, cleanup, err := rastrillo.Handler(rastrillo.Options{Mux: mux, Logger: quietLogger()})
+ h, cleanup, err := rastrillo.Handler(rastrillo.Options{Mux: mux, Logger: quietLogger(), CSP: app.SecurityPolicy()})
if err != nil {
t.Fatalf("rastrillo.Handler: %v", err)
}
@@ -338,16 +338,15 @@ func TestServedCSPAllowsNoInlineScript(t *testing.T) {
}
}
-// The app must not replace the CSP at all. Setting Options.CSP is the
-// one move that turns the carve-out into a blanket exemption, and it
-// would not show up in any rendered page.
-func TestAppNeverSetsItsOwnCSP(t *testing.T) {
+// The binary uses the policy whose only framework change is tested separately.
+func TestAppUsesReviewedCSP(t *testing.T) {
b, err := os.ReadFile("../../cmd/home/main.go")
if err != nil {
t.Fatal(err)
}
- if bytes.Contains(b, []byte("CSP")) {
- t.Error("cmd/home/main.go mentions CSP; the default is what ruling 21 assumes")
+ assignment := []byte("opts.CSP = app.SecurityPolicy()")
+ if bytes.Count(b, assignment) != 1 || bytes.Contains(bytes.ReplaceAll(b, assignment, nil), []byte("CSP")) {
+ t.Error("binary must wire exactly the reviewed policy")
}
}
diff --git a/internal/home/security.go b/internal/home/security.go
new file mode 100644
index 0000000..21fb5fc
--- /dev/null
+++ b/internal/home/security.go
@@ -0,0 +1,8 @@
+package home
+
+// SecurityPolicy preserves the framework policy while allowing the configured
+// directory origin at the end of a credential form's redirect chain.
+func (a *App) SecurityPolicy() string {
+ return "default-src 'self'; style-src 'self' 'unsafe-inline'; " +
+ "img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self' " + a.origins.Directory
+}
diff --git a/internal/home/security_test.go b/internal/home/security_test.go
new file mode 100644
index 0000000..e166f32
--- /dev/null
+++ b/internal/home/security_test.go
@@ -0,0 +1,38 @@
+package home
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/carlosframework/rastrillo"
+)
+
+func TestSecurityPolicyPreservesFrameworkDefaults(t *testing.T) {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })
+ handler, cleanup, err := rastrillo.Handler(rastrillo.Options{Mux: mux, Logger: quietLogger()})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if cleanup != nil {
+ defer cleanup()
+ }
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, httptest.NewRequest("GET", "https://id.example.test/", nil))
+ baseline := rec.Header().Get("Content-Security-Policy")
+ for _, origins := range []Origins{{Origin, directoryOrigin}, {"https://id.example.test", "https://directory.example.test"}} {
+ if err := origins.validate(); err != nil {
+ t.Fatal(err)
+ }
+ app := &App{origins: origins}
+ want := strings.Replace(baseline, "form-action 'self'", "form-action 'self' "+origins.Directory, 1)
+ if baseline == want {
+ t.Fatal("framework form-action changed; review policy")
+ }
+ if got := app.SecurityPolicy(); got != want {
+ t.Fatalf("policy differs beyond directory permission: %q", got)
+ }
+ }
+}