Four admin forms had no CSRF token, so they 403'd in silence #15

Merged
austin merged 1 commits from admin-csrf-forms into main 2026-07-27 19:50:48 -05:00
Owner

This is why "Add something new" could not create a product. It was not the upload limit raised in #14 — the post never got as far as multipart parsing.

The cause

Thymeleaf inserts the hidden _csrf input while it processes a th:action. Five forms were written with a plain action=, so Thymeleaf passed the tag through untouched and they posted with no token. Spring Security answers 403, Spring renders the site's own error page, and nothing is logged at ERROR — which is why the app log was completely clean while the form was broken.

That silence is what made this hard to see. Uploads looked like they were failing; in fact the request was rejected before any application code ran.

form page effect
Add an item /admin could not create a product
Add a category /admin could not add a category
Add a catering table /admin/catering could not add a table
Page notes /admin/catering could not edit the terms
Sign out admin layout, every page had been failing the same way

Test

Forms are counted against tokens rather than named, so a form added later is covered without anyone remembering to extend the test. Before the fix, /admin rendered 89 forms and carried 86 tokens — the test failed with exactly that, which is what confirmed the diagnosis.

69 tests passing.

Two corrections to #14

Both worth recording rather than leaving to be rediscovered:

  1. The 403 probe I used to "verify" the raised upload limit proved nothing. CSRF is checked before the body is parsed, which is precisely why a 5 KB body and a 3 MB body both returned 403. I read matching status codes as evidence the limit was live; they were matching for an unrelated reason.
  2. The @ExceptionHandler added in #14 is narrower than its comment claims. It only catches an over-sized file if Tomcat has not already thrown during parameter parsing — and Tomcat does throw there when a filter reads a parameter first, which is what the original stack traces showed. Left alone here rather than widened on a guess; that path needs a real over-sized upload to characterise properly.

The raised limits themselves (15 MB / 60 MB) are still correct and still needed — a 1 MB cap would have blocked these uploads immediately after the CSRF fix.

🤖 Generated with Claude Code

**This is why "Add something new" could not create a product.** It was not the upload limit raised in #14 — the post never got as far as multipart parsing. ## The cause Thymeleaf inserts the hidden `_csrf` input while it processes a `th:action`. Five forms were written with a plain `action=`, so Thymeleaf passed the tag through untouched and they posted **with no token**. Spring Security answers 403, Spring renders the site's own error page, and **nothing is logged at ERROR** — which is why the app log was completely clean while the form was broken. That silence is what made this hard to see. Uploads looked like they were failing; in fact the request was rejected before any application code ran. | form | page | effect | |---|---|---| | Add an item | `/admin` | **could not create a product** | | Add a category | `/admin` | could not add a category | | Add a catering table | `/admin/catering` | could not add a table | | Page notes | `/admin/catering` | could not edit the terms | | **Sign out** | admin layout, every page | had been failing the same way | ## Test Forms are **counted against tokens** rather than named, so a form added later is covered without anyone remembering to extend the test. Before the fix, `/admin` rendered **89 forms and carried 86 tokens** — the test failed with exactly that, which is what confirmed the diagnosis. 69 tests passing. ## Two corrections to #14 Both worth recording rather than leaving to be rediscovered: 1. **The 403 probe I used to "verify" the raised upload limit proved nothing.** CSRF is checked before the body is parsed, which is precisely why a 5 KB body and a 3 MB body both returned 403. I read matching status codes as evidence the limit was live; they were matching for an unrelated reason. 2. **The `@ExceptionHandler` added in #14 is narrower than its comment claims.** It only catches an over-sized file if Tomcat has not already thrown during parameter parsing — and Tomcat does throw there when a filter reads a parameter first, which is what the original stack traces showed. Left alone here rather than widened on a guess; that path needs a real over-sized upload to characterise properly. The raised limits themselves (15 MB / 60 MB) are still correct and still needed — a 1 MB cap would have blocked these uploads immediately after the CSRF fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
austin added 1 commit 2026-07-27 19:41:46 -05:00
Four admin forms had no CSRF token, so they 403'd in silence
build-and-publish / build (pull_request) Successful in 2m7s
8a6492427c
"Add something new" could not create a product. It was not the upload limit raised in #14: the post
never got as far as multipart parsing.

Thymeleaf inserts the hidden _csrf input while it processes a th:action. Five forms were written with
a plain action= instead, so Thymeleaf passed the tag through untouched and they posted with no token.
Spring Security answers 403, Spring renders the site's own error page, and NOTHING is logged at ERROR
-- which is why the app log was completely clean while the form was broken. From the outside it
looked like the upload silently failed; the request was rejected before any of our code ran.

The four that mattered: add an item, add a category, add a catering table, edit the page notes. The
fifth was Sign out in the admin layout, on every admin page, which had been failing the same way.

Counted rather than named in the test -- /admin renders 89 forms and carried 86 tokens -- so a form
added later is covered without anyone remembering to extend it.

Two notes on the previous change while this is fresh. The 403 probe I used to "verify" the raised
upload limit proved nothing: CSRF is checked before the body is parsed, which is exactly why a 5 KB
and a 3 MB body both came back 403. And the @ExceptionHandler added in #14 only catches an
over-sized file if Tomcat has not already thrown during parameter parsing, which it does when a
filter reads a parameter first -- so it is narrower than its comment claims. Left alone here rather
than widened on a guess; the size path needs a real over-sized upload to characterise.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
austin merged commit 6cab5bb3f1 into main 2026-07-27 19:50:48 -05:00
austin deleted branch admin-csrf-forms 2026-07-27 19:50:48 -05:00
This repo is archived. You cannot comment on pull requests.
No Reviewers
No labels
1 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: TheVine/itsthevine#15