Files
ignacionelson e187513cdd Stop a mistyped CAPTCHA flag from switching the CAPTCHA off
`env()` recognises the words "true" and "false" and returns everything
else as the string it was — and every non-empty string is truthy in PHP.
So `(bool) env('PROJECTSEND_CAPTCHA_DISABLED')` read all of these as "yes,
disabled":

    PROJECTSEND_CAPTCHA_DISABLED=no
    PROJECTSEND_CAPTCHA_DISABLED=off
    PROJECTSEND_CAPTCHA_DISABLED=fasle

An operator who meant to say no took the bot protection off their login
and registration forms and had nothing to tell them so — the setting
screen still shows the CAPTCHA configured, because this is the escape
hatch that runs ahead of it.

For most settings the cast is a shrug: somebody notices the feature is on
and fixes the line. It stops being a shrug when the wrong answer is the
unsafe one, and this is one of those. EnvFlag lists what counts as yes —
`true` and `1`, either case, either type — and reads everything else,
recognised or not, as no. A value typed as `disabled` turns nothing off:
a configuration mistake to be found rather than guessed at.

Found while fixing the same bug in a new cloud-modules flag, where the
unsafe direction was publishing a customer's files rather than dropping a
CAPTCHA. Two of the four remaining `(bool) env()` casts are left alone on
purpose: a wrong S3 path-style value breaks storage loudly, and the
migration tool's direct mode defaults to true anyway, so neither fails
into an unsafe state.
2026-09-08 17:05:30 -03:00

49 lines
1.6 KiB
PHP

<?php
declare(strict_types=1);
use App\Support\EnvFlag;
/**
* An on/off environment variable, read strictly.
*
* `env()` recognises the words `true` and `false` and returns everything
* else as the string it was — and every non-empty string is truthy in
* PHP. So a plain `(bool)` cast reads `no`, `off` and a typo as *on*.
* That is a shrug on most settings and not a shrug on the ones that
* switch a protection off, which is what this is for.
*/
test('only an explicit yes is true', function (mixed $value, bool $expected) {
expect(EnvFlag::isTrue($value))->toBe($expected);
})->with([
'true' => [true, true],
'"true"' => ['true', true],
'"TRUE"' => ['TRUE', true],
'padded' => [' true ', true],
'"1"' => ['1', true],
'1' => [1, true],
'false' => [false, false],
'"false"' => ['false', false],
'"0"' => ['0', false],
'0' => [0, false],
'null' => [null, false],
'empty' => ['', false],
// The values a cast gets backwards, which is the whole reason for this.
'"no"' => ['no', false],
'"off"' => ['off', false],
'"disabled"' => ['disabled', false],
'a typo' => ['ture', false],
// Nothing exotic counts either.
'an array' => [['true'], false],
'a float' => [1.0, false],
]);
test('a mistyped captcha flag leaves the captcha alone', function () {
// The flag this exists for. `PROJECTSEND_CAPTCHA_DISABLED=no` used to
// read as "yes, disabled" and take the bot protection off the login
// and registration forms without anybody asking for that.
expect(EnvFlag::isTrue('no'))->toBeFalse()
->and(config('projectsend.captcha.disabled'))->toBeFalse();
});