Keep roster responses uncompressed through the platform edge
4 files changed,
+10
−7
implementation/roster-http-signing.md+6 −3internal/home/roster_http.go+1 −1internal/home/roster_http_test.go+1 −1internal/home/roster_integration_test.go+2 −2
CI passed — run details
diff --git a/implementation/roster-http-signing.md b/implementation/roster-http-signing.md| index 9c572bb..f9ac698 100644 |
| --- a/implementation/roster-http-signing.md |
| +++ b/implementation/roster-http-signing.md |
| @@ -86,7 +86,7 @@ authorize the request nor cause Set-Cookie, sign-in redirects or session writes. |
| All other routes retain the existing directory router and guards. The roster |
| adapter does not reuse browser CSRF or assertion-consumer middleware. |
| -At adapter entry set Cache-Control: no-store, Content-Type: application/json and |
| +At adapter entry set Cache-Control: no-store, no-transform, Content-Type: application/json and |
| X-Content-Type-Options: nosniff. No CORS permission, compression, ETag, Last-Modified, |
| Location or browser cookie is emitted. Refusal precedence is: |
| @@ -121,7 +121,10 @@ Reject comma lists, repeated lines, tabs, extra spaces, alternate schemes and |
| padded/noncanonical values. Use roster.ValidChallenge before calling the source. |
| No browser session, Origin, Referer or query token substitutes for this header. |
| Accept and Accept-Encoding need no special value from the caller: the adapter |
| -always returns the one JSON representation without compression. |
| +always returns the one JSON representation without compression. The no-transform |
| +directive is required by the CARLOS edge compressor as well as the app adapter: |
| +without it, application/json responses can be gzipped for Accept-Encoding: gzip, |
| +including small error responses when the upstream length is unknown. |
| The application adapter never redirects. The existing outer Go ServeMux can |
| canonicalize unrelated malformed paths before the application sees them; the |
| @@ -204,7 +207,7 @@ must use actual runtime/schema/router paths as well as the injected handler: |
| standing. A missing generation never signs a successful page. No epoch/304 |
| response path exists. Two consumers and two teams stay isolated. |
| - Buffer/sign failures return unsigned503 with no partial Page. Every adapter |
| - result has no-store/exact JSON and no Location, encoding, validator or cookie. |
| + result has no-store, no-transform/exact JSON and no Location, encoding, validator or cookie. |
| The outer mux/real server retains these for canonical /roster, and no redirect |
| is followed by the shared client. |
| - Seed names, tokens and cursor plaintext in fixtures and scan DB/WAL plus |
diff --git a/internal/home/roster_http.go b/internal/home/roster_http.go| index 4c9459c..299bee3 100644 |
| --- a/internal/home/roster_http.go |
| +++ b/internal/home/roster_http.go |
| @@ -107,7 +107,7 @@ func rosterBearer(headers http.Header) (string, bool) { |
| return token, roster.ValidChallenge(token) |
| } |
| func (h *rosterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| - w.Header().Set("Cache-Control", "no-store") |
| + w.Header().Set("Cache-Control", "no-store, no-transform") |
| w.Header().Set("Content-Type", "application/json") |
| w.Header().Set("X-Content-Type-Options", "nosniff") |
| if r.Host != strings.TrimPrefix(h.directory, "https://") { |
diff --git a/internal/home/roster_http_test.go b/internal/home/roster_http_test.go| index 223db70..54d03f8 100644 |
| --- a/internal/home/roster_http_test.go |
| +++ b/internal/home/roster_http_test.go |
| @@ -54,7 +54,7 @@ func rosterRequest() *http.Request { |
| } |
| func rosterHeaders(t *testing.T, r *httptest.ResponseRecorder) { |
| t.Helper() |
| - if r.Header().Get("Cache-Control") != "no-store" || r.Header().Get("Content-Type") != "application/json" || r.Header().Get("X-Content-Type-Options") != "nosniff" { |
| + if r.Header().Get("Cache-Control") != "no-store, no-transform" || r.Header().Get("Content-Type") != "application/json" || r.Header().Get("X-Content-Type-Options") != "nosniff" { |
| t.Fatal(r.Header()) |
| } |
| for _, key := range []string{"Location", "Content-Encoding", "Set-Cookie", "ETag", "Last-Modified", "Access-Control-Allow-Origin"} { |
diff --git a/internal/home/roster_integration_test.go b/internal/home/roster_integration_test.go| index 2c70031..e4711d2 100644 |
| --- a/internal/home/roster_integration_test.go |
| +++ b/internal/home/roster_integration_test.go |
| @@ -191,7 +191,7 @@ func (f *rosterIntegration) get(t *testing.T, query, token string) (int, http.He |
| if e != nil { |
| t.Fatal(e) |
| } |
| - if res.Header.Get("Cache-Control") != "no-store" || res.Header.Get("Content-Type") != "application/json" { |
| + if res.Header.Get("Cache-Control") != "no-store, no-transform" || res.Header.Get("Content-Type") != "application/json" { |
| t.Fatal(res.Header) |
| } |
| for _, k := range []string{"Content-Encoding", "Location", "Set-Cookie", "ETag", "Last-Modified"} { |
| @@ -486,7 +486,7 @@ func TestRosterFrameworkMethodAndPathRefusalsNeverYieldPage(t *testing.T) { |
| t.Fatal(e) |
| } |
| res.Body.Close() |
| - if res.StatusCode != 405 || res.Header.Get("Cache-Control") != "no-store" || res.Header.Get("Content-Type") != "application/json" || res.Header.Get("Allow") != "GET" || res.Header.Get("Location") != "" { |
| + if res.StatusCode != 405 || res.Header.Get("Cache-Control") != "no-store, no-transform" || res.Header.Get("Content-Type") != "application/json" || res.Header.Get("Allow") != "GET" || res.Header.Get("Location") != "" { |
| t.Fatal(method, res.StatusCode, res.Header) |
| } |
| } |