Problem/Motivation

Follow-up to #3625451: Search strings are output unescaped on the Zeitgeist report page (5.x) and #3625471: Search strings are output unescaped on the Zeitgeist report page (6.x): the 7.x multicolumn report outputs recorded search strings unescaped. ReportMulticolumn::page() passes them to '#theme' => 'table', which prints cell contents verbatim, so any visitor with search content can inject markup that runs in the browser of a user with access site reports viewing the report.

Drupal 7 is unsupported, so this is handled in the public queue. This is expected to be the last Drupal 7 release, so the change also fixes every other defect found while reviewing the branch.

Other security-relevant defects:

  • The inline separator of both blocks is output unfiltered. It is editable with administer search, which is not a restricted permission, and shown to every visitor.
  • A search longer than 80 characters makes the search form fail with an HTTP 500 error, for any visitor: the value exceeds the search column, and MySQL runs in strict mode under Drupal 7.

Other defects:

  • The "Top searches" block ignored the "validate search strings" setting.
  • The "Latest searches" block escaped search strings twice when showing their category.
  • rel="nofollow" was never emitted: both blocks passed it outside the link attributes.
  • The "Top searches" block was also cached by core, per role, which Zeitgeist does not clear when a search is recorded.
  • The settings form could not be saved with its defaults: the cache durations default to 0, which their validation rejected.
  • Enabling Zeitgeist together with X Autoload failed with "Call to undefined function xautoload()", and uninstalling it with X Autoload disabled failed on missing classes.
  • Uninstalling left every Zeitgeist variable behind.
  • Update 7001 cleared the nonexistent bootstrap cache bin.
  • Quarter spans started on the wrong month from April on.
  • The Views report linked to search/[category]/..., an unreplaced token, and stripped markup from the searches in its links.
  • Every view on the site got a "Zeitgeist settings" contextual link.
  • The default view granted access with search content.
  • Without Database logging, the Zeitgeist report menu entry was broken.
  • A non-numeric day count in the report path caused a TypeError on PHP 8.
  • Every recorded search rewrote a variable, flushing the bootstrap variable cache.
  • The settings form rebuilt all module data on each display.
  • PHP 8.1 and 8.2 deprecations: float array keys in the multicolumn report, ${} string interpolation.

Steps to reproduce

  1. Enable search and Zeitgeist, grant search content to anonymous users, and choose the "multicolumn" report page type.
  2. As an anonymous user, submit the search form with <b>test</b>.
  3. As an administrator, visit admin/reports/search/zeitgeist: "test" is rendered in bold, meaning the markup was output as HTML instead of text.

Proposed resolution

  • Escape search strings with check_plain() in the multicolumn report.
  • Filter the block separators with filter_xss_admin().
  • Truncate recorded searches to the column lengths.
  • Fix the other defects listed above.
  • Views: a field handler links each search to itself, replacing the path rewrite.
  • Updates: 7002 stops core from caching the "Top searches" block; 7003 points administrators to the issue about Zeitgeist's future, #3625488: Future of Zeitgeist after Drupal 7.
  • Documentation: README.md and INSTALL.md updated, with the missing Views and X Autoload prerequisites.

Remaining tasks

  • Review and commit to 7.x-1.x.
  • Release 7.x-1.3.

User interface changes

  • Cache durations of 0, meaning no caching, can be saved.
  • The Zeitgeist settings and report pages show a notice pointing to #3625488: Future of Zeitgeist after Drupal 7.
  • Views: the Search field gets a "Link this field to the search it records" option.

API changes

Statistics::getLatestSearches() keys are now always safe HTML. BlockInterface declares settings().

Data model changes

Updates 7002 and 7003. Searches from the search page now record the search path, like the search block does, instead of the module name; both are identical for core searches.

AI disclosure

Fixes developed with Claude Opus 5.5.

Comments

fgm created an issue. See original summary.

fgm’s picture

Issue summary: View changes

  • fgm committed 4c3dcaf1 on 7.x-1.x
    fix: #3625486 Search strings are output unescaped on the Zeitgeist...
fgm’s picture

Status: Active » Fixed

Fixed in 7.x-1.3

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.