Skip to content

server: no operation can carry a body cap tighter than the router-wide backstop — Kynos refuses a second BodySize on a route #478

Description

@justin13888

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:

  1. 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.
  2. A Kynos BodySize that composes by taking the minimum of the caps covering a route and declares 413 once.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions