Skip to content

Use prepared statements for Manticore queries in the web UI - #526

Merged
jsuto merged 2 commits into
masterfrom
manticore-prepared-statements
Nov 10, 2026
Merged

jsuto merged 2 commits into
masterfrom
manticore-prepared-statements

Conversation

@jsuto

@jsuto jsuto commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

Rewrites every $this->sphx->query in the web UI to bind its values instead of interpolating them, and stops the full-text search expression from breaking out of the per-user address filter.

SQL injection fixes

  • model/search/search.php
    • The id: expert search term and extra_folders were inserted raw into id IN (...) / folder IN (...). They are now parsed to integers and bound. If no valid value is left, they match nothing.
    • The search text (MATCH(?)), direction, size (operator checked against a strict pattern, number bound), the session folder list, tag/note uid and search text, and all ID lookups are now bound. LIMIT and OFFSET are cast to integers.
    • Removed the unused sphx_query session variable.
  • model/folder/folder.php: the folder update took id and folder_id straight from POST. Both are now bound.
  • model/search/message.php: tag and note lookups are bound.
  • model/audit/audit.php: action, meta_id and the search text are bound.
  • system/database/sphinx.php: integers are bound as PDO::PARAM_INT, so they aren't quoted in LIMIT or integer attribute comparisons. ATTR_EMULATE_PREPARES is now set explicitly.

Bug fix

can_download() and can_restore() passed 3 values for 2 placeholders, so the query always failed and MAX_DOWNLOAD_PER_HOUR / MAX_RESTORE_PER_HOUR were never enforced.

Full-text search operator fix

For non-auditor users, the query is ( user search ) & <address filter>. A search such as x ) | ( y could reorder that expression and get around the address filter. An unclosed quote or a \ before a parenthesis could do the same. The new fixup_match_grouping() removes backslashes, takes parentheses out of quoted phrases, drops unmatched ), and closes open quotes and parentheses before the query is wrapped.

Folder restriction

extra_folders from the request is now limited to the folders the user may access, using the same rule as the folder picker: auditors may search any folder, other users only the folders in their session.

Other

  • Manticore image in docker/docker-compose.yaml bumped to 29.9.0.

Test plan

  • php -l on the changed files (PHP 8.3)
  • phpunit tests/EmailTest.php: 19 tests OK, including 10 new fixup_match_grouping cases. The full suite has 8 ParseMessageTest failures, and the same 8 fail on master.
  • The branch's Sphinx driver against manticoresearch/manticore:29.9.0: bound ints, MATCH(?), IN (?,?), size > ?, quotes in the search text, a sanitized breakout attempt (the address filter still applies) and UPDATE ... SET folder=? all work. A quoted int (uid='5') is rejected by Manticore 29.9, which confirms that the PARAM_INT binding is needed.
  • Docker integration tests (tests/run.sh) in CI
  • Manual check in the web UI: expert search with id:, size:>1M, d:inbound, tag:, note:, folder restrictions, the audit search and CSV export, and the download limit

🤖 Generated with Claude Code

jsuto and others added 2 commits November 10, 2026 06:17
- Bind all user and session supplied values in SphinxQL queries
  instead of interpolating them; this fixes SQL injection via the
  id: search term, extra_folders and the folder update request
- Bind integers as PDO::PARAM_INT in the Sphinx driver, so they are
  not quoted in LIMIT and integer attribute comparisons
- Fix can_download() and can_restore(): they passed 3 values for 2
  placeholders, so the query always failed and the per-hour limits
  were never enforced
- Keep the user part of the full-text query self-contained, so it
  can't escape the group AND-ed with the email address filter
- Bump the Manticore image in docker-compose.yaml to 29.9.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The extra_folders request parameter was not checked against the
folders the user may access. Apply the same rule as the folder
picker: auditors may search any folder, other users only those in
their session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jsuto
jsuto merged commit 147d3d3 into master Nov 10, 2026
4 checks passed
@jsuto
jsuto deleted the manticore-prepared-statements branch November 10, 2026 05:55
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.

1 participant