security: CSRF protection for state-changing requests (high risk)#693
security: CSRF protection for state-changing requests (high risk)#693anonymoususer72041 wants to merge 29 commits intoopencats:masterfrom
Conversation
|
Security heads-up @RussH: this affects all deployments and can allow unauthorized write actions in an authenticated session. Please prioritize review and consider a patch release after merge. |
|
Thanks for putting the work into this.. I definitely agree that hardening the project against CSRF is a high priority. I did want to mention that SameSite=Strict cookie protections are already in place, but I recognize that moving actions from GET to POST and adding proper tokens is an enhancement. I’m currently in the middle of migrating our CI/CD from Travis-CI over to GitHub Actions. Since this PR touches so many files and changes how the app handles requests, it’s almost certainly going to break the current automated tests. I need to get the new GitHub Actions pipeline stable first so I have a reliable way to test these changes. Once that's up and running, I'll be able to check the build status here and see what needs to be tweaked to get this merged. I appreciate the patience while I get the infrastructure sorted out! |
|
Thanks @RussH – understood on the CI migration.
In the current master codebase SameSite=Strict is not actually applied. Also, Strict has real UX trade-offs compared to Lax: users coming in via external links (email, chat, ticketing systems, bookmarks) or certain SSO/OAuth redirect flows can appear "logged out" because the session cookie won’t be sent on that cross-site entry. Since this PR migrates legacy state-changing GET endpoints to POST and enforces server-side CSRF token validation on write actions, I’d suggest using SameSite=Lax as the default baseline. |
|
I’ve pushed the PHPUnit unit tests covering the CSRF token core logic ( Before I invest time into Behat coverage, I’d like to align with what you expect here, @RussH – this can range from a minimal set of representative scenarios to a much broader (and potentially flaky/slow) UI smoke suite. Options I see:
Do you have a preference on:
Once aligned, I’m happy to implement the Behat part accordingly. |
|
It's definitely going to be a massive improvement over current. However it's not quite right yet - there's some oddities, I’m seeing a regression with bulk actions after this change. Q? : are we now sending p as JSON in some paths where the receiver expects serialized params? That could explain foreach() warnings / huge “allow all” queries and the 500. |
|
I was able to trace this to inconsistent DataGrid parameter encoding in bulk/quick-action flows. In particular, the "selected rows" payload (dynamicArgument/exportIDs) could be interpreted incorrectly, which then caused the DataGrid to fall back to an "allow all" query with maxResults=100000000, leading to warnings and eventually a 500 under a 128M memory_limit. I pushed a fix that:
|
The toolbar integration is based on legacy Firefox XUL/XPI extensions and targets an ecosystem that is no longer supported by modern Firefox. Keeping it increases maintenance burden and attack surface without providing practical value for most deployments.
|
Once #715 lands into master, CI should not fail anymore. |
|
@anonymoususer72041 |
|
@RussH done |
|
While testing this PR locally I hit what looks like a related regression with bulk “add to list” flows: JobOrders datagrid → select multiple → “Add to static list”: the popup opens but no lists are offered (empty list selector). Feels similar to the earlier multi-select/list modal issues we’ve seen. Also seeing a bunch of noise in error_log during normal navigation: [Tue Feb 17 14:41:28.524893 2026] [php7:warn] [pid 5533:tid 5533] [client 127.0.0.1:56026] PHP Warning: mysqli_fetch_assoc() expects parameter 1 to be mysqli_result, bool given in /srv/http/lib/DatabaseConnection.php on line 321, referer: http://127.0.0.1/ The session warning may be a red herring, but the mysqli_fetch_assoc(bool) warnings look like a query failing somewhere and returning false. Can you take a look at the JobOrders “add to list” popup (multi-select) and the query failure warnings and see if they can be addressed in this PR before merging? I need to add some behat testing for the lists, we seem to trip over this frequently - CI/CD tests pass, but a little real world testing fails. |
Summary
This PR adds CSRF protection for state-changing actions by introducing a CSRF token mechanism and enforcing server-side validation on write operations (including relevant AJAX endpoints).
As part of this work, legacy endpoints that performed state changes via GET were migrated to POST to make the write surface explicit and allow consistent token validation.
Motivation
OpenCATS includes multiple workflows where authenticated users trigger actions that modify data. Without CSRF protection, any installation is affected (including internal-only and local deployments): if a user is logged in, a crafted third-party page, link, or even an HTML-capable email/attachment opened in a browser context can trigger unintended state-changing requests in the user's session.
This is a high-impact issue because it requires no access to the OpenCATS instance itself – only that a legitimate user visits attacker-controlled content while authenticated. Adding CSRF defenses (and migrating legacy state-changing GET actions to POST where needed) significantly reduces the risk of unauthorized changes and brings OpenCATS in line with modern baseline web security expectations.
Given the potential for unauthorized write actions (including changes to privacy-sensitive candidate data and – depending on the victim's privileges – administrative settings), I suggest prioritizing review and publishing a patch release after merge.