Skip to content

fix(security): declare 204 response schema on PATCH /session - #295

Open
birme wants to merge 1 commit into
mainfrom
bug-fixer/289-patch-session-204-schema
Open

fix(security): declare 204 response schema on PATCH /session#295
birme wants to merge 1 commit into
mainfrom
bug-fixer/289-patch-session-204-schema

Conversation

@birme

@birme birme commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • The PATCH /session/:sessionId handler sends reply.code(204).send() on success, but the TypeBox response schema only declared 200: Type.String(). Because 204 was undeclared, Fastify applied the wrong serializer and skipped response validation for the success path (a validation bypass).
  • Replaced the incorrect 200: Type.String() entry with 204: Type.Null(), keeping the existing 400/500 entries. Minimal one-line schema change.
  • Added a regression test asserting PATCH /session/:sessionId returns 204 with an empty body on success (backend tests mock ./log).

Test plan

  • Tests pass (npm test) — 244 passed
  • TypeScript compiles (npm run typecheck)
  • Lint clean (npm run lint) — 0 errors
  • PATCH /session returns 204 and is now schema-validated

Closes #289

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@birme birme left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Verdict: LGTM

Summary: A correct, minimal fix that aligns the TypeBox response schema (204: Type.Null()) with what the handler actually sends (reply.code(204).send()), restoring response validation on the success path. A focused server.inject() regression test is included. One pre-existing schema gap on the same handler is worth noting but is out of scope for this PR.


Blocking

None.


Warnings

  • src/api_productions.ts:759 (schema block, ~line 782 in file) — The handler has a not-found branch that sends reply.code(410) (session-not-found), but the response schema still declares only 204/400/500. This is the exact class of undeclared-status-code / validation-bypass bug this PR fixes — the 410 path currently falls back to the default serializer and is unvalidated. It predates this PR and is not strictly in scope, but since the PR is specifically hardening this handler's response schema, adding 410: Type.Object({ message: Type.String() }) here would fully close the gap. At minimum, file a follow-up so it isn't lost.

Suggestions

  • src/api_validation.test.ts:243-259 — Good regression test asserting both statusCode === 204 and empty body. Consider a companion assertion that no content-type: application/json body is serialized (204 responses should carry no body), and optionally a test covering the 410 not-found path to lock in the observation above.
  • src/api_validation.test.ts:96-101 — The added requireLine mock throwing on miss is a reasonable fidelity improvement, though it uses any[] typing consistent with the surrounding mock object; acceptable in a test double.

Domain Note

This change affects WHIP/WHEP session lifecycle (the PATCH /session SDP-answer finalization step). The change is a schema/status-code correction only and does not alter SDP handling or handleAnswerRequest behavior, so no broadcast-domain risk. If the 410 follow-up is pursued, the intercom-expert agent can confirm the correct client-facing status for a stale/expired session.

@birme birme left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Verdict: LGTM

Summary: The core fix is correct, minimal, and precisely matches the remediation described in #289: the undeclared success status (200: Type.String()) is replaced with the actual 204: Type.Null() that the handler sends, restoring Fastify's schema-driven serialization/validation on the success path. A well-targeted regression test using server.inject() asserts both the 204 status and the empty body. No Blocking issues. One Warning noted below is pre-existing and adjacent, not introduced by this PR.


Blocking

None.


Warnings

  • src/api_productions.ts:781-785 — The PATCH /session/:sessionId handler also sends reply.code(410).send({ message: ... }) for the not-found case, but 410 is still not declared in the response schema (only 204/400/500 are). This is the exact same class of undeclared-status validation bypass that #289 targets: the 410 object response goes out unvalidated and with a default serializer. Since the aim of this PR is to close a response-schema-mismatch gap on this specific route, consider adding 410: Type.Object({ message: Type.String() }) in the same change (or track it as a fast follow). Note this response is also an object while 400/500 are Type.String() — worth keeping error shapes consistent.

Suggestions

  • src/api_validation.test.ts:239-260 — Good coverage of the success path. Consider a companion test asserting the 410 branch (session not found) returns 410 with a message body; combined with the schema fix above it would lock down the other undeclared-status path on this route.
  • src/api_validation.test.ts:96-101 — The new requireLine mock correctly mirrors the handler's throw-on-missing contract; nicely done. Minor: the surrounding mocks use any[]/any which is consistent with the existing file style, so no action needed.

Domain Note

This change affects the WHIP/WHEP-adjacent session lifecycle (PATCH /session/:sessionId finalizes the connection via handleAnswerRequest). The change is schema-only and does not alter SDP handling or the 204 semantics on the wire, so no broadcast-domain regression is expected. If the 410 follow-up is pursued, consider consulting the intercom-expert agent to confirm 410 Gone remains the intended semantic for an expired/absent session.

@birme

birme commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

daily-backlog-pr: code-reviewer verdict is LGTM and all CI checks pass, but this run cannot merge. The only available GitHub identity in this run (birme) is the PR author, and branch protection requires an approving review from a different user (GitHub forbids self-approval). Leaving this in In review — it needs an independent approver to merge. Not moved to Done.

@birme birme left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Verdict: LGTM

Summary: A tight, correctly-scoped security fix. The PATCH /session/:sessionId response schema now declares 204: Type.Null() matching the handler's reply.code(204).send(), closing the validation-bypass gap where an undeclared 204 status caused Fastify to apply the wrong serializer and skip response validation. A focused regression test asserts the 204 + empty-body contract on the success path. No Blocking issues.


Blocking

None.


Warnings

None. (See Suggestions for a related, pre-existing schema gap that is out of scope for this PR.)


Suggestions

  • src/api_productions.ts:781-784 — The same handler has an undeclared 410 response path (reply.code(410).send({ message: ... })) that is not present in the response schema (204/400/500 only). This is the identical class of issue #289 addresses: an undeclared status code means Fastify applies no schema-specific serializer/validation for that branch, and the object shape { message } diverges from the Type.String() shape used by the other error codes. Out of scope for this PR, but worth a follow-up ticket to declare 410 (e.g. Type.Object({ message: Type.String() })) and align the error-body shapes. Recommend passing to bug-fixer.
  • src/api_validation.test.ts:243-259 — Good regression test. Consider also asserting Content-Length/absence of a body serializer artifact, or adding a negative test that a mistakenly-returned body on the 204 path is stripped, to lock the serializer behavior. Optional.
  • src/api_validation.test.ts:96-100 — The newly-added requireLine mock throws on a missing line; there is no test exercising that throw for this route (it would surface as a 500 via the catch block). Not required for this fix, but a small test would document the not-found behavior.

Domain Note

This change affects the WHEP/WebRTC session lifecycle (PATCH /session finalizes the SDP answer via handleAnswerRequest). The change is purely at the HTTP response-schema layer and does not alter SDP handling, so broadcast-domain risk is negligible. No intercom-expert consultation needed for this diff.


Notes for the record:

  • Backend test hygiene satisfied: jest.mock('./log', ...) is present at src/api_validation.test.ts:1; the test uses server.inject() for the route (integration-style), and mocks getSession, requireProduction, and handleAnswerRequest.
  • No any casts, non-null assertions, DB-without-withRetry, SDP-string-manipulation, index-based media access, hardcoded secrets, or yarn/npm-migration regressions introduced by this diff.
  • CI: all four checks (Linting, Prettier, Type Check, Unit Tests) are green.

Next steps: none blocking. Optionally file a follow-up for the undeclared 410 schema path and pass to bug-fixer.

@birme

birme commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

daily-backlog-pr (Phase 3): code review verdict is LGTM and CI is green, but this PR cannot be auto-merged — the review bot account (birme) is also the PR author, so GitHub forbids self-approval and the base-branch protection policy requires an approving review from a different account. A human maintainer needs to approve and squash-merge. Leaving issue #289 in In review on board #48. Not using admin override.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: PATCH /session response schema declares 200 but handler sends 204 — validation bypass

2 participants