Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified packages/core/src/__tests__/foreign-session.test.ts
Binary file not shown.
47 changes: 47 additions & 0 deletions packages/core/src/__tests__/session-name.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { normalizeUserSessionName } from '../session-name.js';

describe('normalizeUserSessionName', () => {
it('strips the deprecated Cf bidi-adjacent controls U+206A-206F (#3823)', () => {
// The session-name surface shares one sanitizer with foreign-session, so a
// gap in the character class reaches user-visible names too: two names
// differing only by one of these render identically but compare unequal.
for (let cp = 0x206a; cp <= 0x206f; cp += 1) {
const ch = String.fromCodePoint(cp);
const result = normalizeUserSessionName(`alice${ch}bob`);
assert.equal(result.ok, true, `U+${cp.toString(16).toUpperCase()} must normalize`);
if (!result.ok) continue;
assert.equal(
result.value.includes(ch),
false,
`must strip U+${cp.toString(16).toUpperCase()} from a session name`,
);
}
});

it('leaves ordinary names untouched', () => {
const result = normalizeUserSessionName('release triage');
assert.equal(result.ok, true);
if (result.ok) assert.equal(result.value, 'release triage');
});
});
5 changes: 3 additions & 2 deletions packages/core/src/foreign-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,13 @@ export function isSafeForeignId(value: unknown): value is string {
* appear in a verbatim-rendered id, and that the display sanitizer strips.
* Kept as one source of truth so the id guard and the sanitizer cannot drift.
* Covers: C0/C1 controls, ALM (U+061C), bidi marks + embeddings/overrides/
* isolates (U+200E/200F, U+202A-202E, U+2066-2069), zero-width joiners +
* isolates (U+200E/200F, U+202A-202E, U+2066-2069), the deprecated Cf
* bidi-adjacent controls (U+206A-206F, #3823), zero-width joiners +
* invisible operators (U+200B-200D, U+2060-2064), the BOM (U+FEFF), and any
* whitespace.
*/
const FOREIGN_UNSAFE_CHARS =
/[\u0000-\u001F\u007F\u0080-\u009F\u061C\u200B-\u200F\u2060-\u2064\u2066-\u2069\u202A-\u202E\uFEFF\s]/;
/[\u0000-\u001F\u007F\u0080-\u009F\u061C\u200B-\u200F\u2060-\u2064\u2066-\u206F\u202A-\u202E\uFEFF\s]/;

/* ------------------------------------------------------------------ *
* Claude Code transcript records
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/text-sanitize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ const CONTROL_CHARS_REGEX = /[\u0000-\u001F\u007F-\u009F]/g;
// U+200F RLM (right-to-left mark)
// U+202A LRE, U+202B RLE, U+202C PDF, U+202D LRO, U+202E RLO
// U+2066 LRI, U+2067 RLI, U+2068 FSI, U+2069 PDI
const BIDI_FORMAT_REGEX = /[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g;
// U+206A INHIBIT SYMMETRIC SWAPPING, U+206B ACTIVATE SYMMETRIC SWAPPING,
// U+206C INHIBIT ARABIC FORM SHAPING, U+206D ACTIVATE ARABIC FORM SHAPING,
// U+206E NATIONAL DIGIT SHAPES, U+206F NOMINAL DIGIT SHAPES
// (deprecated Cf bidi-adjacent controls; invisible and not whitespace, so
// the `\s+` collapse never removed them — #3823)
const BIDI_FORMAT_REGEX = /[\u061C\u200E\u200F\u202A-\u202E\u2066-\u206F]/g;

// Zero-width / invisible format characters. Removed entirely (no replacement)
// because they're meant to be invisible and replacing with space would inject
Expand Down