oficina / home Public

Keep roster responses uncompressed through the platform edge

Paul Campbell pushed by paul@keymail.dev 9e9613d0a54c57e1e284904e26179ba9816eff4f parent fd1194d
4 files changed, +10 −7
  • implementation/roster-http-signing.md +6 −3
  • internal/home/roster_http.go +1 −1
  • internal/home/roster_http_test.go +1 −1
  • internal/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)
}
}