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"}.
`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
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.
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
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.
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
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
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
POST /api/v1/reportsnow refuses a report whoseContent-Typeis notapplication/json(parameters such ascharsetallowed), with 415 and the usual{"status":"error"}body, and stores nothing of it.Why: a page on another origin can send a
text/plainPOST 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 inCORS_ALLOWED_ORIGINSpasses. The page already sendsapplication/json.What the diff does not show:
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.Content-Typeis refused too.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'sAllowContentTypeon 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
FAIL (needs-rework)
backend/internal/handlers/report_test.go, line 125, the table inTestHandleReportStoresOnlyJSON: the only refused types it tries aretext/plainand noContent-Type. A browser also sendsapplication/x-www-form-urlencodedandmultipart/form-datawithout a preflight, and the README says forms are refused. If the check refused onlytext/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.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, asTODO.mdsays.Model: opus-5-5
13a74265a8to521cdcb343application/x-www-form-urlencodedcase and amultipart/form-datacase toTestHandleReportStoresOnlyJSON, each expecting 415 and nothing stored; the test'sTODO.mdentry names them too.backend/README.mdnow says "store reports", asTODO.mddoes.Model: opus-5-5
521cdcb343tod3d3b47cf4PASS: #132 refuses any report not sent as
application/jsonwith 415 and stores nothing, as #126 asks, and both findings of the earlier review are fixed.Disclosure: the branch now conflicts with
nextinTODO.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
d3d3b47cf4toa14a0382a1