Skip to content

fix: safe attribute construction for data-ol-link-track in 4 templates - #13187

Merged
mekarpeles merged 1 commit into
masterfrom
13186/fix-unsafe-tracking-attribute-templates
Jul 21, 2026
Merged

fix: safe attribute construction for data-ol-link-track in 4 templates#13187
mekarpeles merged 1 commit into
masterfrom
13186/fix-unsafe-tracking-attribute-templates

Conversation

@mekarpeles

@mekarpeles mekarpeles commented Jul 21, 2026

Copy link
Copy Markdown
Member

Closes #13186. Follow-up to #12985's review, which found the same unsafe pattern in modal_links.html and flagged it out of scope. A full sweep during that review turned up 3 more instances of the identical bug.

Problem

openlibrary/templates/type/edition/modal_links.html, title_and_author.html, home/onboarding_card.html, and lib/header_dropdown.html each built their tracking attribute (data-ol-link-track) via Python %-string formatting, then spliced the whole attr="value" fragment into the tag as a single unquoted token.

The real, confirmed mechanism (not tag/script breakout β€” Open Library's render_template pipeline already HTML-escapes <, >, &, " for a bare $var): a value containing a space injects a real new HTML attribute, because the escaped " characters become inert &quot; text rather than actual quote delimiters once escaped, effectively making the "quoted" attribute unquoted. E.g. ga_data = "x onmouseover=alert(document.cookie)//" renders as:

<a data-ol-link-track="x" onmouseover="alert(document.cookie)//">

Confirmed via a real test render against the prior code (see the added test file β€” verified red against old code, green against the fix, not just reasoned about). None of today's real callers pass anything but hardcoded string literals, so this isn't attacker-reachable through any live code path right now β€” same defense-in-depth situation Follow.html was in before #12985.

Fix

Same pattern as #12985: place the value inside a static, template-source literal quote character via $if var: attr="$var", instead of building the whole attr="value" fragment in Python and splicing it in unquoted. This can't be broken out of regardless of what characters (including whitespace) the value contains.

Testing

  • openlibrary/tests/test_link_track_attribute_escaping.py β€” new tests for all 4 fixes, each proving the injection is blocked with a real payload (x onmouseover=alert(document.cookie)//) rendered through the actual template engine and parsed with BeautifulSoup. Verified red against the pre-fix code, green after.
  • modal_links.html/title_and_author.html duplicate an internal icon_link() helper not reachable with dynamic data through any real top-level call site today, so those two are covered via a small dedicated test fixture (openlibrary/templates/tests/icon_link_pattern_check.html) that mirrors the exact fixed pattern.
  • Full openlibrary/tests/test_templates.py suite (769 tests) green. Broader regression sweep (openlibrary/tests/, openlibrary/plugins/upstream/tests/, openlibrary/plugins/openlibrary/tests/, 1920+ tests) green aside from 3 pre-existing, unrelated failures (missing site context, reproducible in isolation on a clean checkout, nothing to do with any file this PR touches).
  • Browser-verified: home page (onboarding carousel), header nav (hamburger dropdown), and a work page (icon links: Share/Review/Notes) all render correctly post-fix β€” screenshots taken, no visual regressions, all data-ol-link-track attributes present with the expected values.
  • A11y check skipped, noting explicitly rather than silently: openlibrary/scripts/a11y/pa11y_runner.py referenced in internal docs doesn't exist in this codebase currently, so it couldn't be run. Risk is low regardless β€” this change only alters how an existing non-semantic analytics attribute is constructed internally; no DOM structure, semantics, or interactive elements changed.
  • No visual changes β€” this only changes how the attribute is constructed internally, not any class, structure, or rendered content.

modal_links.html, title_and_author.html, onboarding_card.html, and
header_dropdown.html each built the tracking attribute via %-string
formatting spliced into the tag unquoted. Templetor's bare $var escapes
<, >, &, and " but not whitespace, so a tracking value containing a
space injects a real new HTML attribute (e.g. onmouseover=...) since
the escaped quote characters become inert &quot; text, not real
delimiters. Confirmed exploitable against the prior code with a real
render + injection payload before applying this fix.

Mirrors the fix already applied to Follow.html in #12985: place the
value inside a static, template-source quote character via
$if var: attr="$var" instead of building the whole fragment in Python.
@mekarpeles
mekarpeles marked this pull request as ready for review July 21, 2026 04:45
@github-actions
github-actions Bot requested a review from openlibrary-bot July 21, 2026 04:45
@mekarpeles
mekarpeles merged commit 3bc1c1a into master Jul 21, 2026
13 checks passed
@mekarpeles
mekarpeles deleted the 13186/fix-unsafe-tracking-attribute-templates branch July 21, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant