July 15, 2026
The Endpoint Everyone Ignored Just Took Down a Server
A $1,024 bug hidden inside the most ignored endpoint on the platform — and the mistake every developer makes without knowing it.

By Abhishek meena
5 min read
30-second version Discourse's
/drafts.jsonendpoint had zero payload size limits. A researcher stuffed 800,000+ characters into a draft save request. The server choked, returned a 502 error — and silently saved the draft anyway. Nine of those requests in a row pushed response times past 32 seconds. The bug wasn't in anything exotic. It was in the gap between where the validation lives and where the attacker actually hit.
The request looked completely normal.
A POST to /drafts.json. The kind of thing your browser fires quietly every few seconds while you're typing a reply, making sure your work is saved if you close the tab.
Nobody thinks to attack a draft endpoint.
That's the whole reason this worked.
Wait, what even is this endpoint?
Discourse is a widely deployed open-source forum platform — Ember.js on the front end, Ruby on Rails on the back.
It has a built-in auto-save system. While you type, the browser periodically sends your in-progress post to POST /drafts.json so you don't lose it.
Good UX. Close the tab, your draft is still there.
Now here's the question nobody asked at build time:
What happens if you send 800,000 characters to that endpoint?
The server tries to process it.
It chokes.
It returns a 502 Bad Gateway.
And then it saves the draft anyway.
That last line is the entire bug. Not the big payload. Not the 502. The fact that the write happened even after the error.
Here's the part that makes this genuinely interesting
Discourse does validate payload size — on the post creation endpoint.
Try to publish an 800K-character post and you get an error before anything touches the database. The validation is there. It works.
The draft endpoint just never inherited it.
This is one of those patterns that shows up everywhere once you know to look:
- Security controls get applied at the final action — publish, submit, send
- The "temporary" endpoints — draft, preview, stage, queue — get built with fewer guards because they feel safe
- Attackers hit the temporary endpoints
The guards are at the front door.
The window is unlocked.
The validation existed — just not on the path that mattered.
How @dpaysm actually pulled this off
Reported to Discourse's HackerOne programme on 26 October 2025. Here's the exact flow:
Log in to try.discourse.org — Discourse's public sandbox. This needs a real account. Not unauthenticated.
Intercept a draft save in Burp Suite. A normal request looks like this:
Replace the content with ~800,000 characters. The researcher hosted the payload on GitHub. It goes straight into the data field — the same field a real browser uses.
Send it. The server returns 502 Bad Gateway. Looks like it failed.
Check the drafts. The draft is there. It was saved.
Now do it nine times — using unique draft_key values like new_topic_1, new_topic_2, and so on. Discourse's "already editing" check only blocks duplicate keys, so each one becomes a separate database write.
Watch the response times climb.
From under a second to over 32 seconds per request.
Server didn't crash. It just became unusable.
CVSS 7.5, High severity. Network attack vector. No special privileges. No user interaction required.
502 doesn't mean "it failed" — this is the part most people miss
A 502 Bad Gateway means the reverse proxy (Nginx, Caddy, whatever's in front) got a bad response from the upstream app — or got no response at all because the process timed out.
But "the proxy gave up" is not the same as "the write was rolled back."
By the time a 502 fires, the Rails app had often already:
- Parsed the full request body
- Run through the application logic
- Committed the database transaction
The 502 fires because response generation timed out — after the write. The database doesn't know the HTTP client never saw a 200. The data is there.
"This is a class of bug that never gets tested — because the error looks like a failure from the outside."
You'd only catch it if you check the database state after an error response.
Most developers don't. Most testers don't. @dpaysm did.
Why 32 seconds? The boring-but-important mechanics
This isn't magic. It's basic queue theory.
Rails runs on a thread pool — typically 5 to 10 threads in production. When a massive payload arrives, parsing and writing it blocks a thread for the full duration of that request.
Nine massive draft requests running back-to-back? Nine threads saturated.
Every legitimate request from real users now queues behind them.
The server doesn't go down. It just slows to a crawl. And that's actually worse from a detection standpoint:
- It looks like a performance issue, not an attack
- Monitoring alerts often don't fire on slow — they fire on down
- Correlating slow responses back to one account sending large drafts takes time
The best DoS attacks look like traffic jams, not crashes.
The real lesson: "temporary" endpoints are a consistent blind spot
Drafts. Previews. Upload staging. Checkout carts. Notification queues.
Platforms build these surfaces all the time. And they almost always get fewer security guards than the final action endpoint — because they feel safe. Nothing is "really" happening yet, right?
Wrong. The write is happening. The resource is being consumed. The database row is being created.
Next time you're testing a platform, add these to your list:
- Is there a pre-publish or pre-commit endpoint for any write action?
- Does it share the same payload size limits as the final endpoint?
- What happens when a request is large enough to trigger a 5xx — does the write still go through?
- Can you create multiple unique records fast enough to saturate something?
- Does a 502 or 504 actually mean a rollback happened — or are you just assuming it did?
That last one almost never gets asked.
Start asking it.
What the fix looks like (and why it took 8 months)
Disclosed publicly on 30 June 2026, eight months after the initial submission.
The fix is straightforward: apply the same content-length validation to the draft endpoint that already exists on the post creation endpoint. Check the payload size before touching the database, not after.
Simple change.
The kind of thing that's obvious in hindsight and invisible at build time.
That gap — between "simple to fix" and "easy to miss" — is exactly where a $1,024 bounty lives.
Three things to take with you
- Error responses are not proof of failure. A 4xx or 5xx from a server tells you what the response was. It says nothing about what the application already did before that response was generated. Always check state after an error — especially on write endpoints.
- Validation at the final step doesn't cover the steps before it. If a platform validates at publish but not at draft, the draft is your target. This applies to every feature with a multi-step flow: stage, preview, queue, buffer, prepare.
- 73 upvotes on HackerOne. That number matters. The community recognised this pattern because it repeats — across platforms, across bug classes, across years of disclosures. The pre-publish surface is a real and repeating blind spot, not a one-off quirk of Discourse.
The endpoint everyone ignored just took down a server.
The next one is sitting in whatever platform you're testing right now, quietly auto-saving someone's draft.