diff --git a/nix/host-modules/hive-matrix.nix b/nix/host-modules/hive-matrix.nix index 9ede108a..d1964cb8 100644 --- a/nix/host-modules/hive-matrix.nix +++ b/nix/host-modules/hive-matrix.nix @@ -455,6 +455,10 @@ in Maximum size in bytes of a single matrix client request body. Default 20 MB matches the matrix-spec recommendation for media uploads + the upstream tuwunel default. + + This is the only limit to set: the gateway's own body cap is + derived from it, with headroom, so the homeserver stays the one + that rejects an oversized upload. ''; }; @@ -612,7 +616,13 @@ in proxyWebsockets = true; extraConfig = '' proxy_buffering off; - client_max_body_size 50M; + # Tracks `maxRequestSize` with headroom so the homeserver stays + # the tighter limit: its rejection is a matrix error a client can + # act on, where a 413 here names neither matrix nor the option the + # operator just raised. A second literal beside it was free to + # disagree, and did — raising the option past the old 50M changed + # nothing. + client_max_body_size ${toString (deployCfg.matrix.maxRequestSize + 1048576)}; proxy_read_timeout 1h; proxy_send_timeout 1h; ${gatewayCfg.lib.securityHeaders} diff --git a/nix/module-eval.nix b/nix/module-eval.nix index 712587c7..d95f42a5 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -136,6 +136,13 @@ let swarm.nats.calloutIssuerSeedFile = "/run/secrets/nats-issuer.seed"; }; + # A deliberately odd `maxRequestSize`, so the number the assertion looks for + # cannot have come from a default or from another module's literal. + matrixBodyCap = hive { + deploy.singleHostSwarm = true; + deploy.matrix.maxRequestSize = 99000000; + }; + controllerOldPath = hive { deploy.swarm-controller.enable = true; swarm.controller.socketPath = "/run/test-ctrl/ctrl.sock"; @@ -466,6 +473,21 @@ let && lib.hasInfix "auth_request" m.extraConfig && lib.hasInfix "auth_request" l.extraConfig; } + { + # Two limits governed a matrix upload and nothing kept them in agreement, + # so raising the documented one past the gateway's hardcoded cap changed + # nothing. The second clause is the control: the old directive is gone, so + # a pass means the value is derived rather than that `hasInfix` matched + # something incidental. It targets the whole directive rather than the + # bare size, because the rendered config carries the comment above it and + # a prose mention of the old value would defeat a looser arm. + name = "the matrix gateway's body cap is derived from maxRequestSize, not a literal"; + ok = + let + c = matrixBodyCap.services.nginx.virtualHosts."chat.t.local".locations."/_matrix/".extraConfig; + in + lib.hasInfix "client_max_body_size 100048576;" c && !(lib.hasInfix "client_max_body_size 50M" c); + } { # The arm that actually protects something. A pusher handed # `error_page 401 =302` FOLLOWS it and POSTs its batch at a login page,