Skip to content

Fix date input validation in Tools.php, modernize test infrastructure, add DB injection tests#2

Merged
daedeloth merged 5 commits intomasterfrom
copilot/fix-input-validation-bug
Mar 6, 2026
Merged

Fix date input validation in Tools.php, modernize test infrastructure, add DB injection tests#2
daedeloth merged 5 commits intomasterfrom
copilot/fix-input-validation-bug

Conversation

Copy link
Contributor

Copilot AI commented Mar 6, 2026

Tools::checkInput('a-b-c', 'date') returns true because validation only checks for 3 dash-separated parts without verifying they're integers or a valid date. When these values reach mktime() in getInput, PHP emits type errors and returns false instead of null.

Bug fixes

  • checkInput for date: Validate parts with ctype_digit() + checkdate() instead of just counting segments
  • getInput for date: Cast exploded parts to (int) before mktime() (defence-in-depth)
// Before — "a-b-c" passes
$time = explode('-', $value);
return self::isValidUTF8($value) && (count($time) == 3);

// After — requires valid integer date parts
$time = explode('-', $value);
return count($time) === 3
    && ctype_digit($time[0]) && ctype_digit($time[1]) && ctype_digit($time[2])
    && checkdate((int)$time[1], (int)$time[2], (int)$time[0]);

Test infrastructure & coverage

  • Upgraded composer.json to PHPUnit 10/11 and Carbon 2/3 (PHP 8.1+)
  • Migrated test base class from PHPUnit_Framework_TestCasePHPUnit\Framework\TestCase
  • Tagged DbQueryTest with #[Group('database')] for CI exclusion (requires MySQL)
  • Added autoload-dev PSR-4 mapping for Neuron\Tests\ so test-only classes are autoloaded
  • 177 tests / 496 assertions covering:
    • Date validation (valid, invalid parts, invalid calendar dates, malformed formats)
    • SQL injection patterns across all input types (email, username, date, int, number, md5, url)
    • XSS stripping via getInput
    • Collection, ErrorCollection, SimpleCrypt, URLBuilder

Neuron\DB namespace — SQL injection tests (76 tests)

Added Database::setInstance() / $testInstance static override (12 lines) so the full Query builder can be tested without a real MySQL connection. A TestDatabase stub implements escape() with the same rules as mysqli::real_escape_string.

DbQueryInjectionTest covers:

  • PARAM_STR injection: classic OR '1'='1, DROP TABLE, UNION SELECT, time-based SLEEP, stacked queries, comment-based --, blind boolean injection, null byte, backslash, newline, carriage return, SUB character (\x1a), multi-vector attacks
  • PARAM_NUMBER: throws InvalidParameter for every non-numeric injection attempt (no quotes → no escape path)
  • PARAM_DATE: throws on non-timestamp strings; accepts Unix timestamps and DateTime objects
  • PARAM_POINT: throws on non-Point values; constructor rejects non-numeric coordinates
  • PARAM_UNKNOWN: integer stays unquoted; string injection is escaped; auto-detect DateTime/Point via bindValues
  • All query builders: SELECT (columns, WHERE, ORDER BY, LIMIT), INSERT, REPLACE, UPDATE, DELETE
  • All WHERE comparators: LIKE, NOT, >, <, >=, <=, !=, IN, IS NULL
  • Named and positional parameters: injection payloads, placeholder-in-value safety, chaining
  • NULL handling: canBeNull true/false, static-builder null, IS NULL in WHERE

CI & docs

  • Added .github/workflows/tests.yml — matrix across PHP 8.1/8.2/8.3
  • Added .ai/ project summary, conventions, and claude.md
Original prompt

This section details on the original issue you should resolve

<issue_title>Input validation bug + tests</issue_title>
<issue_description>Bug
There are two problems in Tools.php:

  1. checkInput for 'date' is too permissive (line ~107–111)

// Current — only checks there are 3 dash-separated parts; does NOT validate they are integers
elseif ($type == 'date')
{
$time = explode ('-', $value);
return self::isValidUTF8 ($value) && (count ($time) == 3);
}
A value like "a-b-c" passes this check.

  1. getInput for 'date' calls mktime() with the raw (unvalidated) string parts (line ~35–37)

case 'date':
$time = explode ('-', $dat[$key]);
return mktime (0, 0, 1, $time[1], $time[2], $time[0]);
When those parts aren't integers, PHP emits mktime() expects parameter N to be int, string given and returns false. The caller then gets false instead of null, which causes a TypeError if a typed ?DateTime parameter is expected.

Recommended fix
Fix checkInput to validate the date format with a regex and checkdate():

elseif ($type == 'date')
{
if (!self::isValidUTF8($value)) {
return false;
}
$time = explode('-', $value);
return count($time) === 3
&& ctype_digit($time[0])
&& ctype_digit($time[1])
&& ctype_digit($time[2])
&& checkdate((int)$time[1], (int)$time[2], (int)$time[0]);
}
Fix getInput to cast parts to int before passing to mktime():

case 'date':
$time = explode('-', $dat[$key]);
return mktime(0, 0, 1, (int)$time[1], (int)$time[2], (int)$time[0]);
The checkInput fix is the real guard; the getInput cast is a defence-in-depth measure. Together they ensure any value reaching mktime() is always a valid date with integer parts.

Additionally, expand the testing to make sure situations like these are catched.
Also add a bunch of tests that specifically focus on various forms of database query injections.
Aim for 90% coverage of the complete code base.

Summarize your project understanding in .ai folder and create a claude.md referencing these documents.
Add github CI to run the tests.</issue_description>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI and others added 3 commits March 6, 2026 10:06
- Fix checkInput for 'date' type to validate parts are integers using ctype_digit() and checkdate()
- Fix getInput for 'date' type to cast parts to (int) before mktime()
- Update composer.json dependencies for PHP 8.3 compatibility (PHPUnit 10/11, Carbon 2/3)
- Update phpunit.xml to modern PHPUnit format
- Update all existing tests to use PHPUnit\Framework\TestCase
- Add comprehensive tests for date validation, SQL injection patterns, all input types
- Add tests for Collection, ErrorCollection, SimpleCrypt, URLBuilder, TokenGenerator

Co-authored-by: daedeloth <1168599+daedeloth@users.noreply.github.com>
Co-authored-by: daedeloth <1168599+daedeloth@users.noreply.github.com>
Co-authored-by: daedeloth <1168599+daedeloth@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix input validation for date in Tools.php Fix date input validation in Tools.php, modernize test infrastructure Mar 6, 2026
@daedeloth
Copy link
Member

@copilot add tests covering the Neuron\DB namespace. Heavy focus on mysql injection. We need to have 99% coverage there.

- Add Database::setInstance() to allow test injection without MySQL
- Add TestDatabase stub with pure-PHP MySQL-compatible escaping
- Add DbQueryInjectionTest with 76 tests covering:
  - SQL injection via PARAM_STR (OR, DROP TABLE, UNION, SLEEP, stacked, comment, blind)
  - PARAM_NUMBER throws InvalidParameter on any non-numeric injection attempt
  - PARAM_DATE throws on non-numeric strings, accepts timestamps and DateTime
  - PARAM_POINT throws on non-Point values
  - PARAM_UNKNOWN auto-type detection via bindValues
  - NULL handling (canBeNull true/false, IS NULL in WHERE, static builder)
  - All query builders: SELECT, INSERT, REPLACE, UPDATE, DELETE
  - All WHERE comparators: LIKE, NOT, >, <, >=, <=, !=, IN
  - Named and positional params with injection payloads
  - Special chars: \r, \x1a (SUB), multi-vector attacks
- Add autoload-dev PSR-4 mapping for Neuron\Tests namespace

Co-authored-by: daedeloth <1168599+daedeloth@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 6, 2026

@copilot add tests covering the Neuron\DB namespace. Heavy focus on mysql injection. We need to have 99% coverage there.

Done in d7176ce. Added 76 tests covering the Neuron\DB namespace with a heavy MySQL injection focus:

New files:

  • src/Neuron/DB/Database.php — added setInstance()/$testInstance static override (12 lines) so tests can inject a stub without a real MySQL connection
  • tests/TestDatabase.php — pure-PHP stub implementing escape() with the same rules as mysqli::real_escape_string (\, \0, \n, \r, ', ", \x1a)
  • tests/DbQueryInjectionTest.php — 76 tests, 103 assertions

Injection coverage (PARAM_STR): classic OR '1'='1, DROP TABLE, UNION SELECT, SLEEP time-based, stacked queries, comment-based --, blind boolean, plus special chars (\r, \x1a SUB, null byte). All confirmed to produce escaped \' output.

Type-level protection: PARAM_NUMBER throws InvalidParameter for every non-numeric injection string. PARAM_DATE throws on non-timestamp strings. PARAM_POINT throws on non-Point values.

All builders and WHERE comparators covered: SELECT/INSERT/REPLACE/UPDATE/DELETE, LIKE/NOT/>/</>=/<=/!=/IN/IS NULL, named parameters, positional parameters, array IN-clause values, NULL handling, auto-detection of DateTime/Point.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/EpCe3G /usr/bin/composer install --prefer-dist --no-progress (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/s4cuw6 /usr/bin/composer install --prefer-dist --no-progress (http block)
  • https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/EpCe3G /usr/bin/composer install --prefer-dist --no-progress (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/s4cuw6 /usr/bin/composer install --prefer-dist --no-progress (http block)
  • https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/EpCe3G /usr/bin/composer install --prefer-dist --no-progress (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/s4cuw6 /usr/bin/composer install --prefer-dist --no-progress (http block)
  • https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/EpCe3G /usr/bin/composer install --prefer-dist --no-progress (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/s4cuw6 /usr/bin/composer install --prefer-dist --no-progress (http block)
  • https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/EpCe3G /usr/bin/composer install --prefer-dist --no-progress (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/s4cuw6 /usr/bin/composer install --prefer-dist --no-progress (http block)
  • https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/EpCe3G /usr/bin/composer install --prefer-dist --no-progress (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/s4cuw6 /usr/bin/composer install --prefer-dist --no-progress (http block)
  • https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/EpCe3G /usr/bin/composer install --prefer-dist --no-progress (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/s4cuw6 /usr/bin/composer install --prefer-dist --no-progress (http block)
  • https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/EpCe3G /usr/bin/composer install --prefer-dist --no-progress (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/s4cuw6 /usr/bin/composer install --prefer-dist --no-progress (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Fix date input validation in Tools.php, modernize test infrastructure Fix date input validation in Tools.php, modernize test infrastructure, add DB injection tests Mar 6, 2026
@daedeloth daedeloth marked this pull request as ready for review March 6, 2026 11:11
@daedeloth daedeloth merged commit b2eae6f into master Mar 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Input validation bug + tests

2 participants