Repository navigation
Releases: pietrobb/BareBonesForms
Release list
v2.2.0
Added
- Embedded mode for host applications (see README "Embedded Mode"):
bbf_form.phpvalidates and normalizes input against a form definition with no configuration, session, output or file writes (bbf_load_form(),bbf_validate()with language fallback to English, message overrides,options_resolver,$_FILESchecks returning keep/remove/replace).bbf_render.phpdraws the same DOM asbbf.json the server (bbf_render_html()with values, errors, hidden inputs, the stored file with a remove checkbox,id_prefix,form_attrs,before_submit,templates.control/templates.field,bbf_attrs()); a field type it cannot draw throws instead of disappearing. bbf.js:BBF.render()optionsdefinition,values,errors,submitUrl(JSON or multipart with files to the host; no BBF CSRF or drafts),hiddenFields,optionsResolver;BBF.enhance()adds client validation and conditions to a server-rendered form;BBF.registerType()andbbf_register_type()add customx-…field types (raw values,normalizeafter a passedvalidate, a second registration throws).'standalone' => falseinconfig.phpmakes every public BBF PHP page answer 404 before any session or file write.- Field property
stepfor number fields.
Changed
- Breaking (only for forms with
options_from): the server now checks a submitted value against the options the source returns (configoptions_resolver, or the source URL fetched withdiagnostic_base_url). A value outside the list is refused, and when the source cannot be loaded no value is accepted (before, any value was stored). Forms withoutoptions_fromare unaffected. - Lithuanian success message is now “Ačiū! Jūsų užklausa gauta.”.
Compatibility for existing sites
- The standalone mode stays the default:
submit.php?form=…&action=definition,action=csrf(64 hex characters) and the JSON submit response keep their shape. CI replays a real standalone form contract end to end (definition, CSRF, submit → HTTP 200,successnot false, the site's own action file receives the data). - A site's own files that are not part of the package (for example its own files in
actions/) are never touched bymaintenance.php upgrade. If you deploy BBF fresh from git instead, copy such files over again. - No change is needed for forms without
options_from. - Before upgrading a site whose forms use a relative
options_from(for example/api/countries.php), setdiagnostic_base_url(oroptions_resolver) inconfig.php. With the default config the server cannot load a relative source and refuses every value of that field. Runcheck.phpafter the upgrade: it reports everyoptions_fromfield the server cannot check as an error.
v2.1.14
Fixed
- Email templates with an unclosed, crossed or misspelled section tag no longer truncate the message silently: unmatched tags stay as visible text, and
check.phpand smoketest warn about them. selfcheckreports a root-ownedlogs/directory oraccess-audit.phpwithout group/other write permission as an error instead of passing, because the web worker cannot append and management returns 503.- Returning with “Back” to a form using
hideOnSuccessshows the recovery actions immediately after a bfcache restore. - Starting a new fill cancels only that form's pending redirect instead of calling
window.stop(), so other forms' requests on the page continue. - Diagnostics keep an inherited ignored SIGINT ignored; stale fixture cleanup works for installation paths containing
[; leftover root-owned fixtures appear as a selfcheck warning; the SPA fallback message no longer blames a control fixture that was served correctly. - The printed
undocommand quotes the backup path. The Windows upgrade launcher stays within the 8191-charactercmd.exelimit. - The sandbox subject preview resolves
{{field_label}}variables such as{{plan_label}}.
v2.1.13
Fixed
- Redirect recovery actions appear only after four seconds on the same page. Starting a new fill stops pending navigation before unlocking, preserves hidden-field layout and restores focus.
- Unidentified renderer modules fall back to the document directory with an explicit warning instead of borrowing another script's origin.
data-bbf-baseaccepts a missing trailing slash. - Audit diagnostics check regular-file shape, write permission bits and the PHP guard before CLI/web ownership comparison, flag root-owned logs and accept symlinked deployment ancestors without accepting linked audit targets.
- CLI diagnostics clean fixtures on SIGTERM/SIGINT where
pcntlis available, scan matching fixture names independently of form-directory count, report unreclaimable stale fixtures, and explain redirect/SPA fallback probe failures. - Nested email sections render in one token pass without evaluating respondent text. Confirmation and notification subjects support authoritative system variables. Interior phone BOM is rejected consistently by client and server.
- Upgrade children preserve literal
${...}INI values. Suggested apply/rollback commands quote POSIX arguments and use a dependency-free encoded PowerShell launcher on Windows to preserve percent/dollar paths and native argument quoting. - Already-published tags are checked before build attestation and release mutation.
v2.1.12
Includes all review fixes below. Tag v2.1.11 was not published: its draft was removed after ownership discovery failed. Draft creation now pins the immutable ID directly from the API response, avoiding a stale list-API result; assets are uploaded and verified by ID.
Fixed
- Redirects that download a file, return HTTP 204 or are cancelled no longer leave the UI on “Sending…”. The success message offers a continuation link and an explicit new-submission action; pending navigation still blocks accidental duplicate POSTs. Empty same-page fragments remain usable. Recovery labels are translated in all 34 client packs.
- Renamed module scripts and dynamic imports no longer crash when
document.currentScriptor a matching script is absent.data-bbf-baseselects the renderer/CSS directory explicitly. Initial CSRF timeouts retain the hidden token field so the next submit can refresh and retry. - HTTP selfcheck tests fresh harmless ordinary fixtures in
submissions/andlogs/as well as dot-file protection. Leaks fail the check; unreachable, fallback or incomplete results remain unverified. Fixtures have finally/shutdown cleanup and bounded exact-format stale cleanup on subsequent runs after forced termination; unfamiliar, incomplete and differently owned files are preserved. - Audit diagnostics distinguish the current CLI identity from the web worker; a different cron UID is unverified, not a false daily unwritable-file incident. Missing-config responses in submissions and sandbox are generic HTTP 503. Authenticated proxy diagnostics warn about forwarding hints without trusting them.
- Phone Unicode boundary whitespace is normalized before storage, exports and delivery, including repeatable rows. Email body and subject placeholders support numeric and hyphenated names in one substitution pass; respondent text is never reinterpreted as template syntax.
- Verified upgrades support scoped-only and deliberately credential-free installations while rejecting malformed credentials and uncheckable access. Checked access-blocked dry runs include an exact quoted apply command. Documentation directs users of old launchers with disabled
proc_opento the verified new external launcher. - Release runs serialize per tag, skip already-published tags, locate their own marked draft across paginated API results, and verify/download/publish/clean by immutable release and asset IDs, not tag lookups. Other drafts and published releases remain untouched.
v2.1.10
Fixed
- Cross-page redirects keep the submit button and handler locked until
pageshow; same-page anchors remain usable. No timer unlock permits a duplicate POST during slow navigation. - Shared CSRF refreshes abort and release their queue after 15 seconds, including stalled response bodies. Module scripts find their own
bbf.jsURL instead of an unrelated last script. - CLI selfcheck includes every access diagnostic (including trailing whitespace), fails for empty/all-invalid or inactive credentials, validates the existing audit file without changing it, and probes active dot-file HTTP protection with disposable fixtures. Missing/unreachable HTTP targets are explicitly unverified.
- Missing-config viewer/editor responses are generic HTTP 503; unavailable audit responses point operators to selfcheck without leaking configuration details.
- Automated upgrade fails closed when access preflight cannot run, preserves active error reporting, temporary-directory restrictions and UNC INI values, and distinguishes CLI failure categories (documented in README).
- Copied Unicode whitespace at phone boundaries matches browser validation. Missing/hidden numeric email placeholders such as
{{5}}are removed. - Failed or cancelled release runs clean only their own marked draft by immutable release ID, never a manually created draft sharing the tag.
v2.1.9
Fixes from the review of 2.1.8. Production installations are not upgraded automatically.
Security
- Anonymous sign-in responses no longer reveal access-token names, configuration errors or administrative access state. Detailed diagnostics remain available through
php maintenance.php selfcheck. - Token validation remains a hexadecimal format check, not an entropy estimate. Continue generating credentials with
php maintenance.php new-token.
Fixed
- Respondent session activity is renewed only after successful CSRF validation, without replacing cookies on rejected or concurrent requests.
- CSRF creation and recovery are serialized across forms in the same installation while retaining separate form-specific tokens. Disabled drafts do not trigger a CSRF refresh.
- Upgrade preflight reports administrative lockout before replacing files. Expected structured refusals are distinguished from child-process crashes. PHP security restrictions, resource limits and diagnostics are forwarded to child processes. Windows replacement never truncates a held
maintenance.php; use the unpacked release'stools/upgrade.phpif the entry point is held open. - Security-rule comparisons recognize safe Apache directive casing and module wrappers without discarding conditional protection.
- Redirect success state, answer reset and
hideOnSuccessare applied when submission is confirmed, before navigation; no five-second timer guesses whether a redirect has finished. - Renderer and stylesheet URLs use
document.currentScriptrather than an unrelated script following the renderer. - Phone validation accepts Unicode spaces such as NBSP consistently with the browser; sandbox email previews preserve empty and escaped numeric field names.
- Failed release verification removes only the draft created by that workflow run, never an already published release. Smoke diagnostics describe the current hexadecimal token policy. Operator diagnostics identify trailing token whitespace; authenticated proxy warnings never treat forwarding headers as trusted evidence.
v2.1.8
Fixes from the review of 2.1.7.
Security
- Breaking: access tokens now require at least 32 hexadecimal characters. This applies to
api_token, scopedaccess_tokensandsmoke_token. Passwords, UUIDs, base64 strings and shorter tokens are ignored. Generate each credential withphp maintenance.php new-token: it prints 64 hex characters from 32 cryptographically random bytes. The password dictionary and entropy estimator are removed. Format validation cannot establish random origin: never invent a token by hand, use repeated characters or copy a sample value. Correct tokens still authenticate even when wrong guesses from the same address are blocked. - Upgrade lockout is not success: if the new policy leaves no usable administrative token, the apply result explicitly reports that code was updated but administrative access is blocked, returns
ok: falseand exits nonzero. Replace the token inconfig.php, then runphp check.phporphp maintenance.php selfcheck. The sign-in page explains the token requirements; diagnostics no longer mark a rejectedapi_tokenas configured correctly. - Release integrity: releases remain drafts until downloaded assets match the build's
SHA256SUMS; only then are they published.
Fixed
- Concurrent CSRF recovery: uploads and drafts share a pending token refresh. A rejected upload does not create a session or replace its cookie, and does not consume the upload quota. Removing a file during recovery prevents its retry.
- Upload errors: disabled uploads and unknown forms do not flood the error log or masquerade as broken storage. Genuine storage problems remain diagnosable.
- Numeric field names in email templates: confirmation and notification bodies preserve keys such as
"1"and"5", while still escaping respondent values. - Upgrade/security rules: stock legacy
.htaccess.distfiles are recognized across published releases; rule checks preserve block context instead of treating all lines as one unordered set. Child-process exit codes survive long PHP-notice output. - Documentation: upgrade ZIPs include
docs.htmlso old installations receive the current token and upgrade instructions. - Proxy/configuration: invalid
cookie_pathis reported; client-supplied forwarding headers do not create trusted-proxy warnings, and Stripe return URLs use trusted TLS-proxy detection. - Respondent messages: payment gateway failures use the selected language; disabled drafts are distinguished from missing drafts.
Upgrading
- Breaking:
show_if.valuecannot be an object. This validation was introduced in 2.1.7 but omitted from its Breaking notes. Use a scalar or a list of scalars. Upgrade preflight validates existing forms with the new code and names incompatible definitions before replacing files. - Replace incompatible credentials before upgrading. Use
--checksumso the package's upgrader and diagnostics evaluate the new policy. FTP uploads do not run preflight: generate tokens, validate forms, and run diagnostics yourself. No production installation is upgraded automatically.
v2.1.7
Fixes from the review of 2.1.6.
Security
- Breaking: a token that is easy to guess is refused. Sample values (
your-secret-token,change-me), words, names, years, keyboard runs (qwerty) and repeats are ignored like a too-short token. Set a random one:php -r "echo bin2hex(random_bytes(24));". After the upgrade runphp check.phporphp maintenance.php selfcheck: they name every ignored token. Details: the right token always signs in, even from an address blocked for wrong tokens, so the token itself must withstand fast guessing (about 1500 tries per second were measured). Each token now gets a pessimistic estimate of the guessing work, where words (also Capitalised or in leetspeak), sample-value parts, runs, years and repeats cost only a few bits; it needs at least 44 bits (2^44 guesses at 1500/s take about 370 years) and must not consist of such parts only.password12345678,adminadminadmin1,qwertyuiopasdfgh,Summer2026!Summerandmy-super-secret-api-tokenare refused; of 1 000 000 random tokens of each kind (16 and 32 hex digits, base64, UUID, password-manager output) none was. The documentation no longer shows a sample token. - An expired or revoked token sent from a blocked address is answered
429like any other wrong token, instead of403, which told that it had once been valid. - A CSV/export link opened from another site (webmail, chat) explains why its
?token=is ignored instead of a bare "Access denied.". - Release workflow: after publishing, the release files are downloaded again from GitHub and checked against the
SHA256SUMSmade when they were built, not only between the build and publish jobs.
Fixed
- Drafts and file uploads after an expired session: saving, loading or deleting a draft, uploading and removing a file refresh the CSRF token once after
403and repeat the request, as sending the form does. Before, every draft save failed in a tab opened before the upgrade to 2.1.6 (cookieBBFSID) or more than 24 minutes ago, and the advice to reload lost the answers the draft was meant to keep. - A field with a numeric name (
"7","2024") is filled intoredirectagain ({{7}}); 2.1.6 left it empty. - Security-rules notice after an upgrade: an
.htaccess.distleft by any earlier release is replaced by this release's, and.htaccessis compared by its rules only, so a release that changes only comments raises no daily "Security rules missing". docs.htmlno longer shows a version in its title (a code-only upgrade does not replace it, so it showed an old one).- Behind a reverse proxy: new
cookie_pathfor a proxy that serves the folder under another path (/forms/→/bbf/), where every submission used to fail with403.X-Forwarded-Proto: httpsfrom a proxy listed intrusted_proxiescounts as HTTPS, so cookies areSecureand forms in iframes on other sites work behind a proxy that ends TLS. "redirect": "#done"on a page with<base href>(single-page apps) stays on the page instead of navigating to the base address.show_ifwith an object asvalueis reported by the definition check (the browser and the server compared it differently).form.schema.jsonaccepts"value": 5,trueand"op": null, which the engine has accepted since 2.1.6.- Restoring a backup made with a now-refused token names that token and says how to replace it, instead of "Restore access policy does not match".
- Upgrade: the
--applyresult repeats the access warnings, judged by the code just installed. A token check or package upgrader that crashes reports its exit code instead of looking like success. A long Breaking note ends at a full sentence instead of being cut in the middle. - Upload storage not ready (folder in the web root, not writable): the respondent gets "The server cannot store uploads right now." in their language; the setup advice goes to the PHP error log (and still to the editor's sandbox).
- E-mail confirmation box is labelled from the language pack (
emailConfirm, all 34 languages) instead of an English "Confirm …" guessed from another message.
Upgrading from 2.1.6 or older
Use --checksum: then the 2.1.7 upgrader inside the package plans and applies the upgrade and names every token 2.1.7 will ignore, in the dry run and again in the --apply result. Without it the installed upgrader does all the work and judges tokens by its own, older rules. The process you start is always the installed version, so under 2.1.5 or older it can still hang on very many PHP notices (turn display_errors off for the command: php -d display_errors=0 maintenance.php upgrade …). In any case run php check.php or php maintenance.php selfcheck after the upgrade.
v2.1.6
Fixes from the review of 2.1.5.
Security
- The right token always signs in again (as in 2.1.2). In 2.1.3–2.1.5 a blocked address got one token check per 2 seconds, and whoever came first took it: someone sharing the admin's address (NAT, IPv6 /64, a proxy or Cloudflare without
trusted_proxies) could keep the admin and scripts usingsubmissions.phpout by sending wrong tokens every fraction of a second. The claim in 2.1.5 that this was no longer possible was wrong. Now only wrong tokens are limited (10 per 15 minutes per address, then429at once withRetry-After); a correct token is always checked and accepted. The protection against guessing is the token itself, see the next item. A blocked wrong token says "Invalid token." instead of "Not checked.". - Breaking: a token with an obvious pattern is refused.
api_token,access_tokensandsmoke_tokenstill need at least 16 characters, and now also must not be a pattern: fewer than 5 different characters (aaaa…,abab…) or mostly consecutive/repeated characters (1234…,abcd…,9876…). Such a token is ignored like a too-short one; check.php, selfcheck and the upgrade dry run name it. A random token (e.g.php -r "echo bin2hex(random_bytes(16));") is never affected. - Respondent cookie
BBFSID. The CSRF session cookie is now namedBBFSIDand limited to the installation folder. Before, it was the sharedPHPSESSIDon/, and strict session IDs replaced the session of another PHP application on the same domain, signing its users out whenever a page with a form was loaded. On HTTPS it isSameSite=None; Secure, so a form embedded in an iframe on another site can be submitted. 2.1.4/2.1.5 claimed this worked with the browser default, but it did not (browsers that block third-party cookies still block it; embed withBBF.renderon the page instead). - Nginx rules:
forms/,README.md,CHANGELOG.mdand.bbf-manifest.jsonare denied by^~/=locations, which win over any regex location. A static-file cache rule such aslocation ~* \.(css|js|json)$placed earlier used to serve form definitions. Paste the remaininglocation ~lines above your other regex locations (see the comments in.htaccess).
Fixed
show_ifwith"op": nullor"field": null(written by generators and editors for "not set") is treated as a missing key again. 2.1.5 rejected such a form and every submission answered 500.show_ifwith a number ("value": 5) now matches the field value"5"in the browser too, as on the server. The field stayed hidden in the browser while the server required it, so the form could not be sent.- Upgrade could hang when the smoke test or the package upgrader printed more than about 64 KB of PHP notices (e.g. deprecations from
config.php): the parent read one pipe to the end while the child waited on the other. stderr now goes to a temporary file. - Upgrade dry run without
--checksumlists access warnings again (tokens that would be ignored), judged by the installed version (access_warnings_by). .htaccess.distleft by any earlier release is recognised by comparing it with this release's checksum from the manifest (also after an FTP transfer changed line endings). A release that only changes comments in.htaccessno longer reports "changed security rules".- Redirect that does not leave the page (
"redirect": "#done", or a target answering 204 No Content) no longer leaves the submit button disabled: the success message is shown. {{_id}},{{_form}}and{{_time}}inredirectare filled in;{{_id}}used to come out empty.maintenance.php deliveries-retry: an old record that cannot be read (locked, damaged) is counted as not checked and read again next time instead of being cached as delivered.- Server messages in the respondent's language: drafts, uploads and a cross-field rule without its own
message(new textcrossFieldInvalidin all 34 languages) no longer answer in English. - Smoke test: a
minlengthabove 5000 characters (up to 1 MB) is met with plain text instead of always failing. - Alert mail: a report that only says "Update available" no longer claims the problem is repeated at most once an hour; it says "one notice per new version".
v2.1.5
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
429at once with the exactRetry-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.phpandsandbox.phpignore 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 sendX-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 inredirectare 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.phploaded) 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-packagefor a package you built yourself). Without it the dry run checks checksums and PHP syntax only, reportscheck: skippedand says how to verify;--applythen uses the installed upgrader. The printed next command keeps--checksum. Takes effect from the next upgrade after 2.1.5 is installed (the runningmaintenance.phpdecides). - 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) whiletrusted_proxiesis empty (check.php already showed it).
Fixed
- Upgrade with PHP notices: with
display_errors=Onin the CLI (XAMPP, some Windows hosts) one notice, e.g. fromconfig.php, broke the package upgrader's JSON result: the dry run failed, and a notice during--applyreported a finished upgrade as failed. Notices now go to stderr, the result is read after a marker line, and notices are listed underphp_messages. The package side of this fix already applies to the upgrade from 2.1.4 to 2.1.5. smoketest --livenever sends to a made-up address. When an email field has apatternthatsmoke_emaildoes 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; setsmoke_emailto your own address that matches.- Smoke test data: an extreme
pattern((a{1000}){1000}) orminlengthno 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_tokenshorter 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_ifno longer breaks every submission. A condition such as"all": {…}(object instead of a list), a string, or a non-stringfield/opmadesubmit.phpanswer 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 unknownopstill 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. \Sinside 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_oldnow 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
locationblocks use^~prefixes, soconfig/,tests/and thebbf_*.phplibraries stay denied even when the server'slocation ~ \.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/*.phpinstead 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
nameoridanswers 400 instead of a PHP TypeError 500. - Release workflow: the third-party
setup-phpaction 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-checksSHA256SUMSbefore 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.distleft 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
--checksumwithSHA256SUMS, say that the code-only ZIP containsCHANGELOG.md, and recommendtools/upgrade.phpfrom the new release for installations on 2.1.0–2.1.2 (their own upgrader is older).