Split out of #406 (PR #472) review finding M2. The limiter half of that finding landed; this is the body-cap half, which cannot be expressed against Kynos 0.1.0.
The problem
capsule-server/src/limits.rs mounts one BodySize on the whole router at 32 MiB, sized for a 16 MiB upload chunk plus headroom (S-C33). Every operation inherits it, including ones whose largest legitimate body is under a kilobyte:
POST /v1/federation/reports — six short strings and a base64 Ed25519 signature. It is also the server's only unauthenticated write, so an anonymous caller can hand it a body two thousand times larger than any real report and have it parsed before anything looks at who is speaking.
POST /v1/albums/{album_id}/capabilities, DELETE …/capabilities/{jti}, POST /v1/federation/capabilities/refresh — likewise tiny.
- The same is true well beyond federation:
POST /v1/albums, the auth surface, the drop and share writes.
Why the obvious fix does not compile
Mounting a second BodySize on the group that holds those operations is refused at compile time, by const evaluation:
error[E0080]: evaluation panicked: two interceptors covering this route answer with the same
status; a consumer could not tell which one replied
--> library/core/src/panic.rs:62:8
= note: evaluation of `<Cons<BodySize, Cons<Negotiation, Cons<CodedProblems, ()>>>
as CompatibleWith<BodySize, App>>::CHECK` failed here
Both answer 413, and an operation may not declare two producers of one status. The check is correct — that is exactly the S-C28 discipline, an interceptor's declaration being its type — and it is why the constraint is worth writing down rather than worked around.
The alternative, and why it was not taken here
Move BodySize off the router and onto every group with a per-group cap. That takes 413 off the ten operations mounted outside every group (GET /v1/version, the four /.well-known/capsule/*, the three /s/{opaque_id}*, the two /d/{opaque_id}*), and capsule-server/tests/conformance.rs pins 413 as declared on every operation. Changing that is S-C33's contract, not a federation lane's.
What bounds those routes today
Not nothing, and not this. In capsule-server/src/routes/federation.rs: every field of a report is length-capped (report_bounds) before any store is read, and CounterKey::FederatedIntake is charged before the peer lookup and before the Ed25519 verification. What stays unbounded is bytes parsed per request.
capsule-server/src/limits.rs::MAX_FEDERATION_BODY_BYTES is declared with all of the above in its doc comment and is deliberately not enforced, so the next person finds the constraint rather than rediscovering it.
What would resolve this
Any one of:
- A Kynos per-operation body limit that is not an interceptor — e.g. an attribute on the operation, or a limit carried on the extractor, so one
413 producer remains per route.
- A Kynos
BodySize that composes by taking the minimum of the caps covering a route and declares 413 once.
- Revisiting
S-C33 so BodySize moves to the groups and the exempt ten get theirs explicitly — a contract change, with tests/conformance.rs updated in the same commit.
(1) or (2) is upstream; (3) is ours and is the larger change.
Split out of #406 (PR #472) review finding M2. The limiter half of that finding landed; this is the body-cap half, which cannot be expressed against Kynos 0.1.0.
The problem
capsule-server/src/limits.rsmounts oneBodySizeon the whole router at 32 MiB, sized for a 16 MiB upload chunk plus headroom (S-C33). Every operation inherits it, including ones whose largest legitimate body is under a kilobyte:POST /v1/federation/reports— six short strings and a base64 Ed25519 signature. It is also the server's only unauthenticated write, so an anonymous caller can hand it a body two thousand times larger than any real report and have it parsed before anything looks at who is speaking.POST /v1/albums/{album_id}/capabilities,DELETE …/capabilities/{jti},POST /v1/federation/capabilities/refresh— likewise tiny.POST /v1/albums, the auth surface, the drop and share writes.Why the obvious fix does not compile
Mounting a second
BodySizeon the group that holds those operations is refused at compile time, by const evaluation:Both answer
413, and an operation may not declare two producers of one status. The check is correct — that is exactly theS-C28discipline, an interceptor's declaration being its type — and it is why the constraint is worth writing down rather than worked around.The alternative, and why it was not taken here
Move
BodySizeoff the router and onto every group with a per-group cap. That takes413off the ten operations mounted outside every group (GET /v1/version, the four/.well-known/capsule/*, the three/s/{opaque_id}*, the two/d/{opaque_id}*), andcapsule-server/tests/conformance.rspins413as declared on every operation. Changing that isS-C33's contract, not a federation lane's.What bounds those routes today
Not nothing, and not this. In
capsule-server/src/routes/federation.rs: every field of a report is length-capped (report_bounds) before any store is read, andCounterKey::FederatedIntakeis charged before the peer lookup and before the Ed25519 verification. What stays unbounded is bytes parsed per request.capsule-server/src/limits.rs::MAX_FEDERATION_BODY_BYTESis declared with all of the above in its doc comment and is deliberately not enforced, so the next person finds the constraint rather than rediscovering it.What would resolve this
Any one of:
413producer remains per route.BodySizethat composes by taking the minimum of the caps covering a route and declares413once.S-C33soBodySizemoves to the groups and the exempt ten get theirs explicitly — a contract change, withtests/conformance.rsupdated in the same commit.(1) or (2) is upstream; (3) is ours and is the larger change.