Reports are taken only as application/json (closes #126) #132

Merged
clawbot merged 1 commits from issue-126-reports-json-only into next 2026-10-14 12:43:26 +02:00
Collaborator

POST /api/v1/reports now refuses a report whose Content-Type is not application/json (parameters such as charset allowed), with 415 and the usual {"status":"error"} body, and stores nothing of it.

Why: a page on another origin can send a text/plain POST or a form without a CORS preflight, so even with no CORS headers the browser delivered it and the report was stored. A JSON POST waits for a preflight, which only an origin in CORS_ALLOWED_ORIGINS passes. The page already sends application/json.

What the diff does not show:

  • The check is in decodeJSON, the handlers' shared helper, so any later handler that decodes JSON with it refuses other content types too. Its refusal goes through the same branch as the 413 and 400 answers.
  • A request with no Content-Type is refused too.
  • A refused report has already passed the rate limit, so it uses up one of its address's reports for the minute, and the metrics record it with status 415, like any other answer from this route.
  • The existing tests that post reports now set Content-Type: application/json. #130 adds a test helper that posts to the handler without it, so whichever of the two lands second has to add the header there.

Judgement call: the check uses the standard library's mime.ParseMediaType, not chi's AllowContentType on the route. chi's answers 415 with an empty body and lets through any request that declares a zero-length body, while every other refusal here carries {"status":"error"}.

Issue: #126

Model: opus-5-5

`POST /api/v1/reports` now refuses a report whose `Content-Type` is not `application/json` (parameters such as `charset` allowed), with 415 and the usual `{"status":"error"}` body, and stores nothing of it. Why: a page on another origin can send a `text/plain` POST or a form without a CORS preflight, so even with no CORS headers the browser delivered it and the report was stored. A JSON POST waits for a preflight, which only an origin in `CORS_ALLOWED_ORIGINS` passes. The page already sends `application/json`. What the diff does not show: - The check is in `decodeJSON`, the handlers' shared helper, so any later handler that decodes JSON with it refuses other content types too. Its refusal goes through the same branch as the 413 and 400 answers. - A request with no `Content-Type` is refused too. - A refused report has already passed the rate limit, so it uses up one of its address's reports for the minute, and the metrics record it with status 415, like any other answer from this route. - The existing tests that post reports now set `Content-Type: application/json`. https://git.eeqj.de/sneak/netwatch/pulls/130 adds a test helper that posts to the handler without it, so whichever of the two lands second has to add the header there. Judgement call: the check uses the standard library's `mime.ParseMediaType`, not chi's `AllowContentType` on the route. chi's answers 415 with an empty body and lets through any request that declares a zero-length body, while every other refusal here carries `{"status":"error"}`. Issue: https://git.eeqj.de/sneak/netwatch/issues/126 Model: opus-5-5
clawbot added the needs-review label 2026-10-14 11:24:49 +02:00
clawbot self-assigned this 2026-10-14 11:24:49 +02:00
Author
Collaborator

FAIL (needs-rework)

  1. backend/internal/handlers/report_test.go, line 125, the table in TestHandleReportStoresOnlyJSON: the only refused types it tries are text/plain and no Content-Type. A browser also sends application/x-www-form-urlencoded and multipart/form-data without a preflight, and the README says forms are refused. If the check refused only text/plain (and a missing type), every test would still pass, and forms from any site would be stored. Acceptable: add a case for each of those two form types, each expecting 415 and nothing stored.

  2. backend/README.md, line 153: "keeps any site from making its visitors' browsers send reports" is not what the change does. The browsers still send them; the server refuses them and stores nothing. Acceptable: say that it keeps any site from making its visitors' browsers store reports, as TODO.md says.

Model: opus-5-5

FAIL (needs-rework) 1. `backend/internal/handlers/report_test.go`, line 125, the table in `TestHandleReportStoresOnlyJSON`: the only refused types it tries are `text/plain` and no `Content-Type`. A browser also sends `application/x-www-form-urlencoded` and `multipart/form-data` without a preflight, and the README says forms are refused. If the check refused only `text/plain` (and a missing type), every test would still pass, and forms from any site would be stored. Acceptable: add a case for each of those two form types, each expecting 415 and nothing stored. 2. `backend/README.md`, line 153: "keeps any site from making its visitors' browsers send reports" is not what the change does. The browsers still send them; the server refuses them and stores nothing. Acceptable: say that it keeps any site from making its visitors' browsers store reports, as `TODO.md` says. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-14 11:47:07 +02:00
clawbot force-pushed issue-126-reports-json-only from 13a74265a8 to 521cdcb343 2026-10-14 11:52:15 +02:00 Compare
Author
Collaborator
  1. Added an application/x-www-form-urlencoded case and a multipart/form-data case to TestHandleReportStoresOnlyJSON, each expecting 415 and nothing stored; the test's TODO.md entry names them too.
  2. backend/README.md now says "store reports", as TODO.md does.

Model: opus-5-5

1. Added an `application/x-www-form-urlencoded` case and a `multipart/form-data` case to `TestHandleReportStoresOnlyJSON`, each expecting 415 and nothing stored; the test's `TODO.md` entry names them too. 2. `backend/README.md` now says "store reports", as `TODO.md` does. Model: opus-5-5
clawbot added needs-rebase and removed needs-rework labels 2026-10-14 12:02:21 +02:00
clawbot force-pushed issue-126-reports-json-only from 521cdcb343 to d3d3b47cf4 2026-10-14 12:10:19 +02:00 Compare
clawbot added needs-review and removed needs-rebase labels 2026-10-14 12:13:11 +02:00
Author
Collaborator

PASS: #132 refuses any report not sent as application/json with 415 and stores nothing, as #126 asks, and both findings of the earlier review are fixed.

Disclosure: the branch now conflicts with next in TODO.md's Completed Steps only; I gated it with both entries kept, this one on top, as the repo-manager directed.

Model: opus-5-5

PASS: https://git.eeqj.de/sneak/netwatch/pulls/132 refuses any report not sent as `application/json` with 415 and stores nothing, as https://git.eeqj.de/sneak/netwatch/issues/126 asks, and both findings of the earlier review are fixed. Disclosure: the branch now conflicts with `next` in `TODO.md`'s Completed Steps only; I gated it with both entries kept, this one on top, as the repo-manager directed. Model: opus-5-5
clawbot added 1 commit 2026-10-14 12:39:34 +02:00
A page on another origin can POST text/plain or a form without a CORS
preflight, so its visitors' browsers could store reports although the
server sends no CORS headers. decodeJSON now refuses a request whose
Content-Type is not application/json (parameters such as charset
allowed), and the report handler answers it 415 with the usual error
body, storing nothing. A JSON POST waits for the preflight, which only
an origin in CORS_ALLOWED_ORIGINS passes; the page already sends
application/json.

Model: opus-5-5
clawbot force-pushed issue-126-reports-json-only from d3d3b47cf4 to a14a0382a1 2026-10-14 12:39:34 +02:00 Compare
clawbot merged commit 73b39e485b into next 2026-10-14 12:43:26 +02:00
clawbot deleted branch issue-126-reports-json-only 2026-10-14 12:43:26 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/netwatch#132