Security policy
Last updated: 1 September 2026
LocalForm stores people's answers - names, addresses, and on a good number of sites, things considerably more sensitive than that. We would rather hear about a problem from you than from the people whose data it was.
1. Reporting a vulnerability
Email tim@xeweb.be with SECURITY in the subject line, and tell us:
- what the problem is, and what an attacker gets out of it;
- the steps to reproduce it, ideally against a clean install;
- the LocalForm version, WordPress version and PHP version you saw it on.
Please do not open a public GitHub issue, post it in the WordPress.org support forum, or demonstrate it against a site you do not own.
If you would like to encrypt your report, send a first message with no details in it and we will reply with a key.
This policy is also published at /.well-known/security.txt, per RFC 9116.
2. What happens next
LocalForm is built and maintained by one person, and these targets are set so they can be met on an ordinary week rather than a perfect one. We would rather name a date we can keep than a flattering one we cannot - a reporter who was promised three days and hears nothing for two weeks reasonably concludes they are being ignored.
| Stage | At the latest |
|---|---|
| We acknowledge your report | 5 working days |
| We confirm or dispute it, with reasoning | 30 days |
| We ship a fix for a confirmed high-severity issue | 90 days |
These are outer limits, not the plan. Most reports are answered within a day or two, and a serious bug in a plugin that holds other people's data does not wait ninety days for a release - the ninety is the industry's standard coordinated-disclosure window, and it is there so that a holiday, an illness or a house move cannot turn a missed email into a broken promise.
If something is being actively exploited, put EXPLOITED in the subject line. That is not a queue, and none of the times above apply to it.
If you have not heard back within two weeks, please chase us. It means the email went astray or landed in a spam folder - it does not mean the report was dismissed. A second email is welcome, not a nuisance.
If a fix is going to take longer than the table says, we will tell you why and give you a date. If we dispute a report we will explain the reasoning rather than closing it silently, and we are happy to be argued with - "that is only exploitable by an administrator" is sometimes the end of a conversation and sometimes the beginning of one.
Where a fix affects sites already running LocalForm, it ships as a normal release with the problem described plainly in the changelog. We do not hide security fixes in a list of unrelated improvements.
3. Scope
In scope:
- the LocalForm and LocalForm Pro plugin code;
- this website and the documentation site.
Out of scope:
- vulnerabilities in WordPress core, PHP, or third-party libraries - report those upstream, though do tell us if we are shipping a version that is affected;
- findings that require an administrator to attack their own site, unless they cross a privilege boundary that is supposed to hold (an editor reaching administrator, say);
- missing security headers, or scanner output with no demonstrated impact;
- social engineering, physical access, and denial of service by volume.
4. Safe harbour
If you make a good-faith effort to follow this policy, we will not pursue legal action against you, and we will treat your research as authorised. Good faith means: test only against your own installation, do not access or modify anyone else's data, do not degrade a live service, and give us a reasonable chance to fix the problem before you publish.
5. Recognition
We credit reporters by name in the release notes for the fix, unless you would rather we did not. We do not currently run a paid bounty.
6. Known design decisions
Some things look like findings and are deliberate. These are the ones that come up:
- Nothing in a payment callback is believed. A callback yields an id, and the status behind that id is read back from the provider using the site's own API credentials - so a forged callback cannot mark anything as paid, and that, rather than anything about the request itself, is what makes the endpoint safe. The address also carries a segment unique to your site, so it is not one that can be found by guessing, and Stripe's callbacks are signature-checked when you have given us a signing secret. The address without that segment still answers, so that payments already in flight settle and a Stripe dashboard registered against the old URL keeps working; it will be retired once no site can still be pointed at it.
- A submission is accepted without a per-person token when the visitor is signed out. A WordPress nonce is derived from the current user and their session, which makes it a genuine secret for somebody signed in and the same string for everybody who is not. The gap that leaves is closed by refusing any submission whose
Originheader names another site, which a browser sets and a page cannot forge. A request carrying noOriginat all is still accepted, because that means an old browser or a stripping proxy rather than an attack - alongside the honeypot, the signed timing token, the rate limiter and the spam-signal block. - Uploaded files sit under a folder the web server may still serve. The plugin writes access rules that Apache and IIS honour and nginx ignores. Underneath them, the folder is named from random bytes chosen per site, and every file inside it sits under sixteen more - so on a server that ignores the rules, a file is unreachable rather than merely undocumented. A daily probe checks whether the folder is being served and says so in the admin if it is. Setting
LOCALFORM_UPLOAD_DIRinwp-config.phpmoves storage outside the web root entirely, which is the only complete answer.
If you think one of these is wrong, that is a report worth sending too.