@@ -7,6 +7,22 @@ import {
77} from "./sanitize-for-prompt.js" ;
88import { buildAgentSystemPrompt } from "./system-prompt.js" ;
99
10+ function hasLoneSurrogate ( value : string ) : boolean {
11+ for ( let index = 0 ; index < value . length ; index += 1 ) {
12+ const code = value . charCodeAt ( index ) ;
13+ if ( code >= 0xd800 && code <= 0xdbff ) {
14+ const next = value . charCodeAt ( index + 1 ) ;
15+ if ( next < 0xdc00 || next > 0xdfff ) {
16+ return true ;
17+ }
18+ index += 1 ;
19+ } else if ( code >= 0xdc00 && code <= 0xdfff ) {
20+ return true ;
21+ }
22+ }
23+ return false ;
24+ }
25+
1026describe ( "sanitizeForPromptLiteral (OC-19 hardening)" , ( ) => {
1127 it ( "strips ASCII control chars (CR/LF/NUL/tab)" , ( ) => {
1228 expect ( sanitizeForPromptLiteral ( "/tmp/a\nb\rc\x00d\te" ) ) . toBe ( "/tmp/abcde" ) ;
@@ -89,6 +105,17 @@ describe("wrapPromptDataBlock", () => {
89105 expect ( block ) . toContain ( "\nabcd\n" ) ;
90106 expect ( block ) . not . toContain ( "\nabcdef\n" ) ;
91107 } ) ;
108+
109+ it ( "does not split surrogate pairs when applying max char limits" , ( ) => {
110+ const block = wrapPromptDataBlock ( {
111+ label : "Data" ,
112+ text : `${ "a" . repeat ( 3 ) } 😀tail` ,
113+ maxChars : 4 ,
114+ } ) ;
115+
116+ expect ( block ) . toContain ( `\n${ "a" . repeat ( 3 ) } \n` ) ;
117+ expect ( hasLoneSurrogate ( block ) ) . toBe ( false ) ;
118+ } ) ;
92119} ) ;
93120
94121describe ( "wrapUntrustedPromptDataBlock" , ( ) => {
0 commit comments