Branch: refs/heads/master Home: https://github.com/phpmyadmin/error-reporting-server Commit: 75418c13a378bda6acdc8361228f79cd4669f683 https://github.com/phpmyadmin/error-reporting-server/commit/75418c13a378bda6... Author: William Desportes <williamdes@wdes.fr> Date: 2026-11-02 (Mon, 11 November 2026) +01:00 Changed paths: M tests/TestCase/ReportTest.php Log Message: ----------- tests: Pin the timestamp of the report routing tests testReportRoutingSystem and testReportFromString compare what getReports() builds with a second call to getTimestampUTC(). The fixtures carry no date, so that method falls back to date() and both sides read the clock: when the two reads straddle a second boundary the arrays differ, which is what the last CI run reported, 2026-11-02T14:53:09 against 2026-11-02T14:53:08. Set the timestamp once, as c9be109151 should have when it added the comparison, so the value no longer depends on when the test runs. Signed-off-by: William Desportes <williamdes@wdes.fr> Co-Authored-By: DeepSeek <noreply@deepseek.com> Commit: 5595bc3ee002f035cdd0ee4c1893e49fd7cf9b22 https://github.com/phpmyadmin/error-reporting-server/commit/5595bc3ee002f035... Author: William Desportes <williamdes@wdes.fr> Date: 2026-11-07 (Sat, 11 November 2026) +01:00 Changed paths: M src/Controller/ReportsController.php M tests/TestCase/Controller/ReportsControllerTest.php Log Message: ----------- fix: Clear the notification when a report is opened The check that guards the delete reads $current_developer['Developer'], a key of an entity that has no such field: it was already wrong when 6aa5001d70 ported the code to CakePHP 3, and 41dfcd97de only added the null test in front of it. Array access on the entity answers null, so the method returned before deleting anything and no notification was ever cleared. Read the id of the entity instead. Signed-off-by: William Desportes <williamdes@wdes.fr> Co-Authored-By: DeepSeek <noreply@deepseek.com> Commit: 80bf4582f15e00197d0a121336b8793d1245c9ab https://github.com/phpmyadmin/error-reporting-server/commit/80bf4582f15e0019... Author: William Desportes <williamdes@wdes.fr> Date: 2026-11-07 (Sat, 11 November 2026) +01:00 Changed paths: A tests/TestCase/Controller/AppControllerPagingTest.php M src/Controller/AppController.php M src/Controller/NotificationsController.php M src/Controller/ReportsController.php Log Message: ----------- security: Bound the page size of the data tables iDisplayLength is read from the query string and passed straight to the query, and both actions build every row of the answer in memory: an unauthenticated caller asks for the whole table with /reports/data_tables?iDisplayLength=100000000, one row per report with its version, name, message and location. Clamp it to the 100 rows the page offers, keep 25 when the value is absent or not a number, and refuse a negative offset. Signed-off-by: William Desportes <williamdes@wdes.fr> Co-Authored-By: DeepSeek <noreply@deepseek.com> Commit: 86f8fff2c9f73d79f206bfe66f80b4fcd1216332 https://github.com/phpmyadmin/error-reporting-server/commit/86f8fff2c9f73d79... Author: William Desportes <williamdes@wdes.fr> Date: 2026-11-07 (Sat, 11 November 2026) +01:00 Changed paths: M src/Controller/IncidentsController.php M src/Forwarding/Sentry.php M src/Model/Table/IncidentsTable.php M src/Report.php M tests/TestCase/Forwarding/SentryTest.php Log Message: ----------- security: Forward a report to Sentry only once it is stored and capped The ingest endpoint forwarded every payload to Sentry before looking at the answer of the storage, and from the raw request rather than from what was stored: a report the version filter refused, or one whose errors or stack was never capped, was still forwarded. 920c75d5c5 wired the call before the caps of the ingest existed, and nothing bounded the number of events one request could produce or the time the ingestion could wait for Sentry. Forward only an accepted report, from the capped payload, stop at 40 events per report, bound the two requests to 3 s of connection and 5 s in total, and log a Sentry failure instead of letting it fail the ingest and the notification email. The Sentry payload also reads type, stackTrace and file defensively: a PHP report that omits one of them raised a warning, and a missing type reached a string parameter. Signed-off-by: William Desportes <williamdes@wdes.fr> Co-Authored-By: DeepSeek <noreply@deepseek.com> Commit: d3fbfa25d558e2aea61ce2da7c56aed8751b7159 https://github.com/phpmyadmin/error-reporting-server/commit/d3fbfa25d558e2ae... Author: William Desportes <williamdes@wdes.fr> Date: 2026-11-07 (Sat, 11 November 2026) +01:00 Changed paths: M src/Controller/DevelopersController.php M templates/layout/default.php M tests/TestCase/Controller/DevelopersControllerTest.php Log Message: ----------- security: Sign the developer out on POST only logout() destroyed the session on any method since 6aa5001d70, and the layout reached it with a plain link: a page on another site signed the developer out of this application, which is the CSRF logout the audit describes. Answer only POST, and build the link with FormHelper so the request carries the CSRF token. Signed-off-by: William Desportes <williamdes@wdes.fr> Co-Authored-By: DeepSeek <noreply@deepseek.com> Commit: 9540c60c8a10ac274b0ffcf71f30a58613c9caee https://github.com/phpmyadmin/error-reporting-server/commit/9540c60c8a10ac27... Author: William Desportes <williamdes@wdes.fr> Date: 2026-11-07 (Sat, 11 November 2026) +01:00 Changed paths: M src/Controller/GithubController.php M tests/TestCase/Controller/GithubControllerTest.php Log Message: ----------- security: Keep the values of a report inside the Markdown of the issue The body of the issue is built by concatenation from the name, the message, the version and the description of a report, all written by a client without an account: a pipe adds a column to the table, a line break ends the row or starts a list, a fence closes the block, an HTML tag is rendered by GitHub, and an @ mentions whoever the value names, which notifies them. Keep a cell on one line with its pipe escaped, write the description in a block it cannot close, and neutralise the mentions. Signed-off-by: William Desportes <williamdes@wdes.fr> Co-Authored-By: DeepSeek <noreply@deepseek.com> Commit: 138abb5398d084e6b1da98d63943c7949ba67021 https://github.com/phpmyadmin/error-reporting-server/commit/138abb5398d084e6... Author: William Desportes <williamdes@wdes.fr> Date: 2026-11-07 (Sat, 11 November 2026) +01:00 Changed paths: M config/app.default.php M config/app_ci.php M docs/server-setup.md M src/Controller/IncidentsController.php M src/Report.php M tests/TestCase/ReportTest.php Log Message: ----------- security: Read the client address instead of the forwarded header getUserId() salted the identity of a report with HTTP_CLIENT_IP, then HTTP_X_FORWARDED_FOR, then REMOTE_ADDR, since the class was written: the two first are written by the caller, so a report chose the address that groups it and that is shown to the developers. The test of 6aa5001d70 even pinned that behaviour. Read the address of the request instead, through the trusted proxies of Security.trustedProxies, and keep REMOTE_ADDR as the fallback so the identity of the reports already stored does not change. The setting is empty by default and documented in the server setup: a proxy is listed by its own address, never by a network the caller can reach. Signed-off-by: William Desportes <williamdes@wdes.fr> Co-Authored-By: DeepSeek <noreply@deepseek.com> Commit: cd5dd9e98573ff2a78c000b8f5ad811ecd81196a https://github.com/phpmyadmin/error-reporting-server/commit/cd5dd9e98573ff2a... Author: William Desportes <williamdes@wdes.fr> Date: 2026-11-07 (Sat, 11 November 2026) +01:00 Changed paths: A src/Github/PermissionChecker.php A tests/TestCase/Github/PermissionCheckerTest.php M src/Controller/DevelopersController.php M src/Middleware/AuthenticationMiddleware.php M tests/TestCase/Middleware/AuthenticationMiddlewareTest.php Log Message: ----------- security: Read the access of a developer again while the session lives The permission of a developer was decided at login from one answer of GitHub and kept in the session until it ended, without a limit: a developer who lost the write access of the repository kept it here for as long as their cookie lived, and the flag was a boolean derived from has_commit_access rather than the value itself (e452f05a13). Ask GitHub again when the last answer is older than 15 minutes, once per request at most, and keep what the session knew when the API cannot be asked. The login is stored at sign-in for that call, the decision reads has_commit_access, and the older read_only flag of an existing session is still honoured until the next sign-in. Signed-off-by: William Desportes <williamdes@wdes.fr> Co-Authored-By: DeepSeek <noreply@deepseek.com> Commit: e29c9868f3511de9f37e8212fc11600e173e3ca6 https://github.com/phpmyadmin/error-reporting-server/commit/e29c9868f3511de9... Author: William Desportes <williamdes@wdes.fr> Date: 2026-11-09 (Mon, 11 November 2026) +01:00 Changed paths: A src/Github/GithubApi.php A tests/TestCase/Github/GithubApiTest.php M phpstan-baseline.neon M src/Controller/Component/GithubApiComponent.php M src/Middleware/AuthenticationMiddleware.php M tests/TestCase/Controller/Component/GithubApiComponentTest.php D src/Github/PermissionChecker.php D tests/TestCase/Github/PermissionCheckerTest.php Log Message: ----------- github: Share one API class between the controllers and the middleware The permission read of the middleware was a class of its own, with an HTTP client of its own beside the one GithubApiComponent built for every request: two places to change when the API moves, two clients, and two lists of the roles that may write. Move the client, the requests, the token exchange and the reading of a permission into App\Github\GithubApi, which builds its client once and keeps it. GithubApiComponent delegates to it for the controllers, and the middleware uses it directly: it is not a controller, so it cannot reach a component, but it can share the class behind it. canCommitTo() answers null when it cannot ask the API, so the middleware keeps the answer it already had, and the component turns that into the refusal a controller needs. A missing login or token is not asked at all, which the first version of cd5dd9e985 had and the extracted class dropped: it asked GitHub with an empty user and read the 404 as a refusal. The exchange of the code for a token no longer reads a "state" key of the configuration, which warned whenever GithubConfig did not carry one, and the component that 5fbb8c5ec9 gave its client is now the only place that builds one. Signed-off-by: William Desportes <williamdes@wdes.fr> Co-Authored-By: DeepSeek <noreply@deepseek.com>