-
Notifications
You must be signed in to change notification settings - Fork 0
How the Punchline Stays Hidden
The obvious way to build this widget is to send the question and its answer to the browser together and hide the answer with CSS. Anyone curious enough to press F12 would find it in seconds.
This plugin does not do that. Here is what it does instead, in plain terms.
When the widget asks for a question, the server generates all four parts — question, hint, category, punchline — and stores them together on the server. The response sent to the browser deliberately includes only three of them. The punchline is left behind.
So "view source" and the browser's network inspector show a question and a hint, and nothing else. There is nothing to uncover, because the answer was never sent.
To get the punchline, the browser has to submit an answer. That is a POST request, and it is the only route to the punchline. There is no way to fetch it with a plain URL — no GET endpoint returns it, and requesting the endpoints with GET returns a 404 rather than data.
Once released, the question is marked as answered. Submitting again returns "This question has already been answered" rather than the punchline a second time.
A generated question lives for ten minutes. After that it is gone from the server and the token is worthless — the visitor is told it expired and can request a new one.
After a reveal, the record is kept for a further two minutes only, so that a double-click gets a clear "already answered" message rather than a confusing "expired" one, and then it is discarded.
When a question is created, the server records a salted fingerprint of the requester. When an answer comes back, the fingerprint is recomputed and compared. A mismatch returns "This question belongs to another visitor" instead of the punchline.
Both the question token and the widget token are 48-character random strings, checked for shape on every request.
It does protect against the casual route: reading the answer from the page, from the network tab, or by guessing a URL. That is the realistic threat for a joke widget, and it is properly closed.
It does not make the punchline a secret. Anyone can request a question and submit any answer to receive it — that is what the widget is for. The reveal is gated on making an attempt, not on being right, and the plugin never checks whether an answer is correct.
So the guarantee is: you cannot skip ahead without playing. It is not: only worthy people get the answer.
AI output is treated as untrusted. Everything the model returns is stripped of markup and length-checked server-side before storage, and rendered as text rather than HTML in the browser. A model that returned a <script> tag would have it removed, and what survived would be printed as visible characters rather than executed.
Credentials never reach the browser. The AI call is made from your server. The only data handed to the widget's JavaScript is the REST URL and the interface labels — no keys, no endpoints, no model names.
The endpoints are public on purpose. They have to be: the widget works for logged-out visitors with no login prompt. Instead of authentication, they are protected by short-lived tokens, client binding, POST-only reveal, and rate limiting. A missing nonce here is a design decision, not an oversight — adding one would break anonymous use.
The repository's docs/security.md covers the same ground for a developer audience, including the specific token and hashing mechanics.
Getting started
- Home
- What AI Fun Questions Does
- Installing the Plugin
- The Settings Screen
- Adding the Widget to Your Site
Provider setup
Running it
- Keeping API Keys Out of the Database
- Rate Limits Explained
- Running Behind Cloudflare or a CDN
- What It Costs to Run
Troubleshooting
- Error Messages Reference
- Please Wait Before Requesting Another Question
- Could Not Generate a Question
Privacy and security
Extending