diff --git a/changelog.d/1280-text-issue-fix.md b/changelog.d/1280-text-issue-fix.md index d13f3f9cc..5e5474967 100644 --- a/changelog.d/1280-text-issue-fix.md +++ b/changelog.d/1280-text-issue-fix.md @@ -1 +1,3 @@ -- Bound untrusted display text at output boundaries (asturksever, #1280). +- Keep outside text in voice and MCP tool results to one line of 180 + characters: ALPR tags, station names and other display fields (asturksever, + #1280). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 809048675..7e2470098 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -137,16 +137,13 @@ Stale fallback runs per response after the reservation is released, and disk writes finish asynchronously. Settled and rejected reservations are removed only while they still own their map entry. -External ALPR tags, surveillance-tool display fields, voice context text and -compact analyst strings collapse controls and whitespace before a final -180-character cap (scene labels retain their 120-character cap). Empty ALPR -values remain null and Unicode text is preserved without splitting a surrogate -pair. Only display fields are normalized: canonical analyst identifiers -(`id`, `layerKey`, `icao24`, `mmsi`, `noradId`, `registration`, `callsign`) -pass through unchanged, because bikeshare uses the raw station name as its ID -and numbered voice references resolve by that key. These fields remain -untrusted source data in structured tool output; text normalization does not -establish trust or eliminate prompt injection. +External ALPR tags, surveillance-tool fields, voice context text and compact +analyst strings become one line of at most 180 characters (scene labels: 120). +Empty ALPR values stay null, and the cap never splits a surrogate pair. +Canonical analyst identifiers such as `id` and `routeId` stay unchanged, because +voice follow-ups match on them and bikeshare uses the raw station name as its +ID. The text is still untrusted: normalization does not prevent prompt +injection. Terrain height requests accept at most 64 points, matching the browser service's existing batch size and one bounded upstream chunk. Larger requests return HTTP diff --git a/src/data/displayText.js b/src/data/displayText.js index 1138c4cc0..fd0fe4eba 100644 --- a/src/data/displayText.js +++ b/src/data/displayText.js @@ -1,4 +1,4 @@ -/** Bounded, single-line external display text; it remains untrusted source data. */ +/** One-line text capped at `maxLength` without splitting a surrogate pair. Still untrusted. */ export function normalizeDisplayText(value, maxLength = 180) { if (value == null || typeof value === 'object' || typeof value === 'function') return ''; diff --git a/src/voice/gevActions.js b/src/voice/gevActions.js index 617f51e02..2d9334bc8 100644 --- a/src/voice/gevActions.js +++ b/src/voice/gevActions.js @@ -5353,9 +5353,9 @@ const ANALYST_IDENTITY_FIELDS = Object.freeze([ ]); /** - * Analyst item keys that identify a record rather than describe it: the - * referent registry, track_entity and locateLayerEntity match on them, so - * display-text normalization must leave them byte-for-byte unchanged. + * Keys that identify an analyst record. The referent registry, track_entity, + * locateLayerEntity and follow-up filters match on them, so they skip + * display-text normalization. */ const ANALYST_CANONICAL_FIELDS = new Set([ 'layerKey', @@ -5405,9 +5405,7 @@ function compactAnalystItem(r) { } else compact[key] = r[key]; } if (present(r.ageHours)) compact.ageHours = r.ageHours; - // Display text only: canonical keys are lookup identities (bikeshare uses - // the raw station name as its id), so the referent registry and - // locateLayerEntity must see them exactly as the layer reports them. + // Canonical keys stay raw: bikeshare's id is the raw station name. for (const key of Object.keys(compact)) { if (typeof compact[key] === 'string' && !ANALYST_CANONICAL_FIELDS.has(key)) compact[key] = normalizeDisplayText(compact[key]);