fix(web): render <strong> in /me/activity hero subtitle instead of escaping it (#312)
The subtitle was built by ~-concatenating a Markup operand
(user.email | e) with HTML string literals. Under autoescaping,
Jinja2's markup_join escapes every non-Markup part once it hits a
Markup operand — so the literal <strong> tags became <strong>
and the page showed literal "<strong>...</strong>" text around the
email. The | safe in _page_hero.html was too late to undo it.
Switch to {% set %}...{% endset %} block capture: the literal
<strong> stays HTML while {{ user.email }} is still autoescaped.
Regression test asserts the tags render and a hostile email stays
escaped.
This commit is contained in:
parent
a1c7849b3e
commit
8b5b0f8ef5
3 changed files with 26 additions and 1 deletions
|
|
@ -10,6 +10,15 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- `/me/activity` hero subtitle showed literal `<strong>…</strong>` tags
|
||||
around the user's email instead of rendering them bold. The subtitle
|
||||
was built by `~`-concatenating a `Markup` operand (`user.email | e`)
|
||||
with HTML string literals, which made Jinja2's `markup_join` escape
|
||||
the literal tags too. Switched to `{% set %}…{% endset %}` block
|
||||
capture so the literal `<strong>` stays HTML while the email is still
|
||||
autoescaped.
|
||||
|
||||
### Internal
|
||||
- CI test suite sharded for speed. The `test` job in `.github/workflows/ci.yml` is now a `test-shard` matrix — 4 parallel jobs via `pytest-split`, balanced by a committed `.test_durations` file — aggregated into a single `test` status check so branch protection needs no change. The duplicate full-suite `test` job in `release.yml` is removed (it re-ran the same ~10 min suite a second time on every push to main/feature branches); `release.yml` is now image-build only, with the advisory ruff/mypy steps moved to a lean `lint` job in `ci.yml`. Net: ~10 min → ~3 min wall-clock per push, and the suite runs once instead of twice. Adds `pytest-split` to the `dev` extra.
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,10 @@
|
|||
<div class="activity-page">
|
||||
{% set page_hero_eyebrow = "Profile" %}
|
||||
{% set page_hero_title = "My activity" %}
|
||||
{% set page_hero_subtitle = "Sessions, token usage, data access, and sync activity for <strong>" ~ (user.email | e) ~ "</strong>." %}
|
||||
{# Block-capture so the literal <strong> stays HTML while {{ user.email }}
|
||||
is still autoescaped. `~`-concatenating a Markup operand (user.email | e)
|
||||
made Jinja2's markup_join escape the literal tags too. #}
|
||||
{% set page_hero_subtitle %}Sessions, token usage, data access, and sync activity for <strong>{{ user.email }}</strong>.{% endset %}
|
||||
{% include "_page_hero.html" %}
|
||||
|
||||
<nav class="tab-strip" role="tablist" aria-label="Activity sections">
|
||||
|
|
|
|||
|
|
@ -422,6 +422,19 @@ class TestAdminRoleGuards:
|
|||
assert r.status_code == 200
|
||||
assert b"My activity" in r.content
|
||||
|
||||
def test_me_activity_hero_renders_strong_email_unescaped(self, web_client, analyst_cookie):
|
||||
"""Regression: the /me/activity hero subtitle embeds the user's email
|
||||
in <strong> tags. Building it via `~` concatenation with a Markup
|
||||
operand (`user.email | e`) made Jinja2's markup_join escape the
|
||||
literal tags too, so the page showed literal "<strong>...</strong>"
|
||||
text. The subtitle must render real <strong> HTML while still
|
||||
escaping the email itself."""
|
||||
r = web_client.get("/me/activity", cookies=analyst_cookie, follow_redirects=False)
|
||||
assert r.status_code == 200
|
||||
body = r.text
|
||||
assert "activity for <strong>analyst@test.com</strong>." in body
|
||||
assert "activity for <strong>" not in body
|
||||
|
||||
def test_profile_session_download_returns_file_for_owner(self, web_client, analyst_cookie, tmp_path, monkeypatch):
|
||||
"""Authenticated owner can fetch their own jsonl with proper Content-Disposition."""
|
||||
# The seeded analyst is "analyst1" (per conftest.seeded_app).
|
||||
|
|
|
|||
Loading…
Reference in a new issue