Repository navigation
Security hardening: unblock confirmation, escaping, settings validation - #15
Merged
Merged
Conversation
- The unblock link from the email now only asks for confirmation on GET; the unblock itself is a POST and only works from the IP address the token was issued for. Mail security scanners and link previews that open every link can no longer unblock an attacker automatically. The decision logic is in TokenunblockModel::process(). - Escape the log view (date, priority, message - the message can contain visitor-supplied usernames), the IP information view (request parameter and GeoIP data, incl. the toolbar title) and the raw log line in the "invalid entry" message. - New setting "Usernames Not Matching an Account" (unknownUsernameMode) for the plugin's hashing of usernames that aren't an existing account. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3snnF5dGbTM4SazFxGY7q
- Subnet validation: the prefix length has to be plain digits (is_numeric() accepted "1e1" and similar). - Sending the test email needs the permission to change the settings. - The .htaccess path and GeoIP database path settings are validated (no stream wrappers, a .mmdb file name). - New setting for the plugin's IPv6 tracking granularity; unblock tokens work for clients blocked by their IPv6 network. - ParamHelper no longer fails if the plugin's settings can't be read. - SECURITY.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3snnF5dGbTM4SazFxGY7q
"Enabled" now means: only if the user has logged in from the blocked IP address before; the former behaviour is a new value, "enabled, for any IP address" (see the plugin's CHANGELOG). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3snnF5dGbTM4SazFxGY7q
400 without a token, 403 from another IP address, 404 for an unknown, expired
or used token, 500 if unblocking failed; opening the link (GET) is a 200 for
every token, as is success. Only if the plugin's "Use HTTP Error" setting is
on, as a web server or CDN replacing error pages with its own would hide the
message. The status is set with Joomla's setHeader('status', ...).
The link contains a secret, so the page is never cached (allowCache(false)),
sends Referrer-Policy: no-referrer and X-Robots-Tag: noindex, nofollow.
TokenunblockModel tells "no such token" (ResultNotFound) from a failure to
unblock (ResultFailed).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3snnF5dGbTM4SazFxGY7q
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
The component side of a security review; the plugin side, with the full list of findings, is codeling/bfstop#231.
TokenunblockModel::process().setHeader('status', …), unless the plugin's "Use HTTP Error" setting is off (a web server or CDN replacing error pages with its own would hide the message). Opening the link (GET) stays a 200 for every token, so scanners learn nothing and don't flag the link as dead. Since the link contains a secret, the page is also never cached (allowCache(false)), sendsReferrer-Policy: no-referrerandX-Robots-Tag: noindex, nofollow.is_numeric()also accepted values like1e1, which got as far as the.htaccessfile..htaccesspath and the GeoIP database path are validated (no stream wrappers such asphar://, a.mmdbfile name; the directory has to exist if.htaccessblocking is used). Sending the test email now needs the permission to change the settings. New settings: "Usernames Not Matching an Account", "IPv6 Tracking Granularity", and two new values for "User Block Message" (the plugin's PR explains them).ParamHelperno longer fails if the plugin's settings can't be read.SECURITY.mdadded.Things to know when reviewing
DatabaseHelper::$UNBLOCK_TOKEN_VALID_DAYSandTokenunblockModel::TokenValidDaysmust stay in sync (both 3); there is a comment in each.allowCache(); for the confirmation page that is harmless (same URL, same token, same page).COM_BFSTOP_ROOT.Testing
The plugin's test suite (283 tests) passes with this branch as
COM_BFSTOP_ROOT, including tests for the confirmation/IP-bound unblock, the escaped log view, the settings rules and the subnet validation.UnblockPageHttpTest(in the plugin repository) runs the installed Joomla site on PHP's built-in web server and checks the real status and headers of every outcome of the unblock page; it was checked to fail against the previous view.Merge together
Needs the plugin PR (codeling/bfstop#231): it uses the plugin's
IpHelper::isInSubnet()and settings. Merge both, and release them together.🤖 Generated with Claude Code
https://claude.ai/code/session_01R3snnF5dGbTM4SazFxGY7q