Skip to content

fix(sessions): sanitize Unicode in session titles like tags#1067

Open
zied-jlassi wants to merge 1 commit into
anthropics:mainfrom
zied-jlassi:fix/sanitize-unicode-title-parity
Open

fix(sessions): sanitize Unicode in session titles like tags#1067
zied-jlassi wants to merge 1 commit into
anthropics:mainfrom
zied-jlassi:fix/sanitize-unicode-title-parity

Conversation

@zied-jlassi

@zied-jlassi zied-jlassi commented Jun 24, 2026

Copy link
Copy Markdown

What

tag_session / tag_session_via_store run a tag through _sanitize_unicode (NFKC + strip format/bidi/zero-width categories) before persisting it. The twin custom-title fields do not: rename_session, rename_session_via_store, and fork_session's title only .strip().

A title and a tag are both user-controlled metadata that list_sessions surfaces identically. As a result a title carrying a bidi override (U+202E) or zero-width chars (U+200B, BOM) is persisted and rendered unsanitized, while the exact same string used as a tag is cleaned. The fork path additionally derives a title from the source transcript (customTitle / aiTitle / first prompt) and copied it through verbatim — so untrusted transcript content reached the displayed title.

This is a consistency/hardening fix (low severity — title-spoofing in terminal/UI listings), not an exploitable vulnerability.

Change

Apply _sanitize_unicode(...).strip() at the three custom-title sites so they match the tag path:

  • rename_session, rename_session_via_store
  • fork_session (both the explicit title and the transcript-derived title)

Behavior notes (matching tag_session): a title that is only invisible characters now raises title must be non-empty; NFKC normalization now applies to titles as it already does to tags.

 src/claude_agent_sdk/_internal/session_mutations.py | 17 ++++--
 tests/test_session_mutations.py                     | 80 +++++++++++++++++++

Tests

Added, mirroring the existing tag sanitization tests:

  • test_unicode_sanitization + test_sanitization_rejects_pure_invisible (rename)
  • test_fork_title_unicode_sanitized (explicit fork title)
  • test_fork_derived_title_unicode_sanitized (title derived from an unsanitized source transcript)

Verified against the repo toolchain (in an isolated container):

  • uvx ruff check src/ tests/ and ruff format --check -> clean
  • uv run mypy src/ -> Success, no issues in 24 source files
  • uv run pytest -> 986 passed, 5 skipped

Disclosure

AI-assisted (analysis + implementation), reviewed by me. Commit is DCO signed-off.

tag_session/tag_session_via_store run titles through _sanitize_unicode
(NFKC + strip format/bidi/zero-width chars) before persisting, but the
twin custom-title fields did not: rename_session, rename_session_via_store
and fork_session only .strip()ed. A title and a tag are both
user-controlled metadata surfaced identically by list_sessions, so a
bidi-override or zero-width title was persisted and rendered unsanitized
while the same string as a tag was cleaned.

The fork case also derives a title from the source transcript
(customTitle / aiTitle / first prompt) — content that may not be
trusted — and previously copied it through verbatim.

Apply _sanitize_unicode(...).strip() at all three sites so the
custom-title path matches the tag path (an all-invisible title now
raises 'title must be non-empty', consistent with tag_session). NFKC
normalization now applies to titles as it already does to tags.

Tests: rename sanitization + pure-invisible rejection, explicit fork
title sanitization, and derived fork title sanitization (untrusted
transcript content).

Signed-off-by: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com>
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