Skip to content

v2.1.5

Choose a tag to compare

@github-actions github-actions released this 29 Sep 21:35
· 26 commits to main since this release

Fixes from the review of 2.1.4.

Security

  • Sign-in limit can no longer lock the admin out or tie up PHP. In 2.1.3/2.1.4 a blocked address waited in PHP (up to 10 s, holding a PHP worker and the session lock) and, once that queue was full, even the right token got 429 without being checked; one address could hold every worker of a small host and stall the public forms. Now PHP never waits: a blocked address still gets one token check per 2 seconds (right or wrong), every other attempt is answered 429 at once with the exact Retry-After (1–2 s instead of 900), so the right token gets in at the next free check. A signed-in browser is not affected. A blocked HTML sign-in shows the sign-in form with the wait instead of JSON.
  • Breaking: ?token= no longer signs a browser in. viewer.php, editor.php and sandbox.php ignore a token in the URL (login CSRF through a link, and an <img src="viewer.php?token=x"> on another site spending your address's wrong-token budget); they show the sign-in form with a notice. Sign in with the form or send X-BBF-Token. The sandbox has its own sign-in form. submissions.php?token= (scripts, CSV links) keeps working, except for requests a browser marks as coming from another site (Sec-Fetch-Site: cross-site), which cannot use it.
  • Breaking (since 2.1.3): a redirect target that is only a field ("redirect": "{{return_url}}") is not followed. Field values in redirect are percent-encoded since 2.1.3 so a respondent cannot change the target (open redirect); such a template led to a relative 404 in 2.1.3/2.1.4. Now it shows the success message instead. Use a fixed target with the value as a parameter, e.g. "/thanks?ref={{ref}}".
  • Upgrade dry run no longer runs unverified package code. Since 2.1.3 a dry run started the package's upgrader and smoke test (with your config.php loaded) after checking the package only against its own manifest, which proves integrity, not origin. Now package code runs only with --checksum=<SHA-256 from SHA256SUMS> (or --trust-package for a package you built yourself). Without it the dry run checks checksums and PHP syntax only, reports check: skipped and says how to verify; --apply then uses the installed upgrader. The printed next command keeps --checksum. Takes effect from the next upgrade after 2.1.5 is installed (the running maintenance.php decides).
  • Upgrade on Windows: only maintenance.php (held open by the running upgrade) may be written in place when renaming over it fails; any other file that cannot be renamed (after 5 retries, e.g. while a web request reads it) fails the upgrade and it rolls back, so the web never loads a half-written library.
  • submit.php's daily self-check logs a warning when requests arrive through a proxy (X-Forwarded-For / CF-Connecting-IP) while trusted_proxies is empty (check.php already showed it).

Fixed

  • Upgrade with PHP notices: with display_errors=On in the CLI (XAMPP, some Windows hosts) one notice, e.g. from config.php, broke the package upgrader's JSON result: the dry run failed, and a notice during --apply reported a finished upgrade as failed. Notices now go to stderr, the result is read after a marker line, and notices are listed under php_messages. The package side of this fix already applies to the upgrade from 2.1.4 to 2.1.5.
  • smoketest --live never sends to a made-up address. When an email field has a pattern that smoke_email does not match, the smoke test used to generate a matching address (e.g. a@firma.sk, a real domain) and the confirmation email went there. Now that form fails with "smoke_email does not match the pattern of email field …" and nothing is submitted; set smoke_email to your own address that matches.
  • Smoke test data: an extreme pattern ((a{1000}){1000}) or minlength no longer exhausts memory (generated values are capped at 5000 characters); a required field never gets an empty value (^a*$, a "Choose…" option with value "" is skipped).
  • Audit log: a smoke_token shorter than 16 characters is not a credential (live smoke refuses it, check.php/selfcheck warn), so it no longer masks ordinary words such as "contact" in the audit log.
  • "Back" after a redirect no longer stores a duplicate. Returning from the thank-you page (back/forward cache) showed the form still filled in with the button enabled, and one click stored the same answers again under a new key. The restored form is now cleared (with the success message), and the next fill gets its own key.
  • Malformed show_if no longer breaks every submission. A condition such as "all": {…} (object instead of a list), a string, or a non-string field/op made submit.php answer 500 on every submission while the browser ignored it. The server now evaluates such a condition like the browser (ignored), and the definition check (editor, smoketest, selfcheck, submit.php) names it, e.g. fields[3].show_if.all: Expected a list of conditions. An unknown op still means "equals" on both sides, so working forms are not rejected.
  • Redirect with {{field}}: a checkbox value (joined with ,) or a missing value (empty) no longer leaves a literal {{tags}} in the URL.
  • \S inside a character class (e.g. [^\S\r\n], "whitespace but no line break") now matches like the browser on the server too (a no-break space counted as non-space in PHP).
  • maintenance.php deliveries-retry: skipped_old now counts only old records that still have an undelivered job; delivered ledgers are no longer reported as "not checked". The result is cached in .delivery/.old-ledgers.json, so repeated runs do not reread every old record.
  • Nginx rules: the documented location blocks use ^~ prefixes, so config/, tests/ and the bbf_*.php libraries stay denied even when the server's location ~ \.php$ block comes first (a plain regex location lost to it).
  • Server messages in the respondent's language: "too many requests" (429), "request too large" (413), save failures (500/503), "still being processed" and the payment-start/status 503 answers now come from lang/*.php instead of fixed English text.
  • Respondent session cookie (PHPSESSID, CSRF only) is now HttpOnly, Secure on HTTPS and uses strict session IDs. SameSite stays at the browser default so forms embedded in an iframe keep working.
  • Editor: creating a form with a non-text name or id answers 400 instead of a PHP TypeError 500.
  • Release workflow: the third-party setup-php action now runs only in a read-only build job; attestation and publishing run in a separate job that uses only GitHub's own actions and re-checks SHA256SUMS before signing.
  • Tests: the editor-preview security test no longer races the iframe load (it failed about 1 in 5 runs on slow machines and could block a release).
  • selfcheck / check.php: an .htaccess.dist left by an earlier release (e.g. an older FTP upgrade) is ignored and named instead of recommending its older, weaker rules; the essential rules are always checked.
  • Docs: README and docs recommend --checksum with SHA256SUMS, say that the code-only ZIP contains CHANGELOG.md, and recommend tools/upgrade.php from the new release for installations on 2.1.0–2.1.2 (their own upgrader is older).