44 captureRevealedSimKeys ,
55 extractRevealedSimKeys ,
66 extractRevealedSimKeysFromBlocks ,
7+ type RevealedSimKey ,
8+ type RevealedSimKeysByMessage ,
79 redactSensitiveContent ,
810 restoreRevealedSimKeysForMessage ,
911 toolResultForModel ,
@@ -16,6 +18,8 @@ const redacted = `<credential>${JSON.stringify({ type: 'sim_key', redacted: true
1618const placeholder = `<credential>${ JSON . stringify ( { type : 'sim_key' } ) } </credential>`
1719const credentialBatch = ( items : unknown [ ] ) => `<credential>${ JSON . stringify ( items ) } </credential>`
1820
21+ const keys = ( ...values : string [ ] ) : RevealedSimKey [ ] => values . map ( ( value ) => ( { value } ) )
22+
1923const apiKeyBlock = ( key : string ) => ( {
2024 type : 'tool_call' as const ,
2125 toolCall : { name : 'generate_api_key' , result : { success : true , output : { id : 'k1' , key } } } ,
@@ -25,13 +29,13 @@ describe('sim-key-redaction', () => {
2529 describe ( 'extractRevealedSimKeys' , ( ) => {
2630 it ( 'returns sim_key values in document order' , ( ) => {
2731 const text = `first ${ credential ( 'sk-sim-A' ) } mid ${ credential ( 'sk-sim-B' ) } `
28- expect ( extractRevealedSimKeys ( text ) ) . toEqual ( [ 'sk-sim-A' , 'sk-sim-B' ] )
32+ expect ( extractRevealedSimKeys ( text ) ) . toEqual ( keys ( 'sk-sim-A' , 'sk-sim-B' ) )
2933 } )
3034
3135 it ( 'skips redacted entries and non-sim_key tags' , ( ) => {
3236 const link = `<credential>${ JSON . stringify ( { value : 'https://x' , type : 'link' , provider : 'slack' } ) } </credential>`
3337 const text = `${ link } ${ credential ( 'sk-sim-A' ) } ${ redacted } `
34- expect ( extractRevealedSimKeys ( text ) ) . toEqual ( [ 'sk-sim-A' ] )
38+ expect ( extractRevealedSimKeys ( text ) ) . toEqual ( keys ( 'sk-sim-A' ) )
3539 } )
3640
3741 it ( 'returns sim_key values from a mixed credential-card array' , ( ) => {
@@ -40,7 +44,7 @@ describe('sim-key-redaction', () => {
4044 { type : 'sim_key' , value : 'sk-sim-A' } ,
4145 { type : 'sim_key' , value : 'sk-sim-B' } ,
4246 ] )
43- expect ( extractRevealedSimKeys ( batch ) ) . toEqual ( [ 'sk-sim-A' , 'sk-sim-B' ] )
47+ expect ( extractRevealedSimKeys ( batch ) ) . toEqual ( keys ( 'sk-sim-A' , 'sk-sim-B' ) )
4448 } )
4549 } )
4650
@@ -50,6 +54,27 @@ describe('sim-key-redaction', () => {
5054 const batch = credentialBatch ( [ link , { type : 'sim_key' , value : 'sk-sim-secret' } ] )
5155 expect ( redactSensitiveContent ( batch ) ) . toBe ( credentialBatch ( [ link , { type : 'sim_key' } ] ) )
5256 } )
57+
58+ it ( 'keeps the workspace target an organization chat needs and strips only the value' , ( ) => {
59+ const single = `<credential>${ JSON . stringify ( { type : 'sim_key' , workspaceId : 'ws-1' , value : 'sk-sim-secret' } ) } </credential>`
60+ expect ( redactSensitiveContent ( single ) ) . toBe (
61+ `<credential>${ JSON . stringify ( { type : 'sim_key' , workspaceId : 'ws-1' } ) } </credential>`
62+ )
63+ const batch = credentialBatch ( [
64+ { type : 'sim_key' , workspaceId : 'ws-1' , value : 'sk-sim-secret' , extra : 'dropped' } ,
65+ ] )
66+ expect ( redactSensitiveContent ( batch ) ) . toBe (
67+ credentialBatch ( [ { type : 'sim_key' , workspaceId : 'ws-1' } ] )
68+ )
69+ } )
70+
71+ it . each ( [ ' ws-1' , 'w' . repeat ( 257 ) ] ) (
72+ 'saves no workspace target the renderer would reject: %j' ,
73+ ( workspaceId ) => {
74+ const tag = `<credential>${ JSON . stringify ( { type : 'sim_key' , workspaceId, value : 'sk-sim-secret' } ) } </credential>`
75+ expect ( redactSensitiveContent ( tag ) ) . toBe ( placeholder )
76+ }
77+ )
5378 } )
5479
5580 describe ( 'toolResultForModel' , ( ) => {
@@ -80,7 +105,7 @@ describe('sim-key-redaction', () => {
80105 it ( 'pulls generate_api_key output keys in block order' , ( ) => {
81106 expect (
82107 extractRevealedSimKeysFromBlocks ( [ apiKeyBlock ( 'sk-sim-A' ) , apiKeyBlock ( 'sk-sim-B' ) ] )
83- ) . toEqual ( [ 'sk-sim-A' , 'sk-sim-B' ] )
108+ ) . toEqual ( keys ( 'sk-sim-A' , 'sk-sim-B' ) )
84109 } )
85110
86111 it ( 'skips redacted markers and unrelated tools' , ( ) => {
@@ -92,7 +117,7 @@ describe('sim-key-redaction', () => {
92117 } ,
93118 apiKeyBlock ( 'sk-sim-A' ) ,
94119 ]
95- expect ( extractRevealedSimKeysFromBlocks ( blocks ) ) . toEqual ( [ 'sk-sim-A' ] )
120+ expect ( extractRevealedSimKeysFromBlocks ( blocks ) ) . toEqual ( keys ( 'sk-sim-A' ) )
96121 } )
97122
98123 it ( 'returns nothing for empty/undefined block lists' , ( ) => {
@@ -103,55 +128,55 @@ describe('sim-key-redaction', () => {
103128
104129 describe ( 'captureRevealedSimKeys' , ( ) => {
105130 it ( 'records new keys under each provided key' , ( ) => {
106- const cache = new Map < string , string [ ] > ( )
131+ const cache = new Map < string , RevealedSimKey [ ] > ( )
107132 captureRevealedSimKeys ( cache , [ 'msg-1' , 'req-1' ] , credential ( 'sk-sim-A' ) )
108- expect ( cache . get ( 'msg-1' ) ) . toEqual ( [ 'sk-sim-A' ] )
109- expect ( cache . get ( 'req-1' ) ) . toEqual ( [ 'sk-sim-A' ] )
133+ expect ( cache . get ( 'msg-1' ) ) . toEqual ( keys ( 'sk-sim-A' ) )
134+ expect ( cache . get ( 'req-1' ) ) . toEqual ( keys ( 'sk-sim-A' ) )
110135 } )
111136
112137 it ( 'extends but never shrinks the captured list across calls' , ( ) => {
113- const cache = new Map < string , string [ ] > ( )
138+ const cache = new Map < string , RevealedSimKey [ ] > ( )
114139 captureRevealedSimKeys (
115140 cache ,
116141 [ 'msg-1' ] ,
117142 `${ credential ( 'sk-sim-A' ) } ${ credential ( 'sk-sim-B' ) } `
118143 )
119144 captureRevealedSimKeys ( cache , [ 'msg-1' ] , credential ( 'sk-sim-A' ) )
120- expect ( cache . get ( 'msg-1' ) ) . toEqual ( [ 'sk-sim-A' , 'sk-sim-B' ] )
145+ expect ( cache . get ( 'msg-1' ) ) . toEqual ( keys ( 'sk-sim-A' , 'sk-sim-B' ) )
121146 } )
122147
123148 it ( 'skips undefined keys without throwing' , ( ) => {
124- const cache = new Map < string , string [ ] > ( )
149+ const cache = new Map < string , RevealedSimKey [ ] > ( )
125150 captureRevealedSimKeys ( cache , [ 'msg-1' , undefined ] , credential ( 'sk-sim-A' ) )
126- expect ( cache . get ( 'msg-1' ) ) . toEqual ( [ 'sk-sim-A' ] )
151+ expect ( cache . get ( 'msg-1' ) ) . toEqual ( keys ( 'sk-sim-A' ) )
127152 expect ( cache . size ) . toBe ( 1 )
128153 } )
129154
130155 it ( 'ignores content with no credential tag' , ( ) => {
131- const cache = new Map < string , string [ ] > ( )
156+ const cache = new Map < string , RevealedSimKey [ ] > ( )
132157 captureRevealedSimKeys ( cache , [ 'msg-1' ] , 'plain assistant text' )
133158 expect ( cache . has ( 'msg-1' ) ) . toBe ( false )
134159 } )
135160
136161 it ( 'sources the key from the generate_api_key tool result (model text is a redacted placeholder)' , ( ) => {
137- const cache = new Map < string , string [ ] > ( )
162+ const cache = new Map < string , RevealedSimKey [ ] > ( )
138163 captureRevealedSimKeys ( cache , [ 'msg-1' , 'req-1' ] , `Here is your key: ${ redacted } ` , [
139164 apiKeyBlock ( 'sk-sim-fromtool' ) ,
140165 ] )
141- expect ( cache . get ( 'msg-1' ) ) . toEqual ( [ 'sk-sim-fromtool' ] )
142- expect ( cache . get ( 'req-1' ) ) . toEqual ( [ 'sk-sim-fromtool' ] )
166+ expect ( cache . get ( 'msg-1' ) ) . toEqual ( keys ( 'sk-sim-fromtool' ) )
167+ expect ( cache . get ( 'req-1' ) ) . toEqual ( keys ( 'sk-sim-fromtool' ) )
143168 } )
144169
145170 it ( 'prefers tool-result keys over any inline content values' , ( ) => {
146- const cache = new Map < string , string [ ] > ( )
171+ const cache = new Map < string , RevealedSimKey [ ] > ( )
147172 captureRevealedSimKeys ( cache , [ 'msg-1' ] , credential ( 'sk-content' ) , [ apiKeyBlock ( 'sk-tool' ) ] )
148- expect ( cache . get ( 'msg-1' ) ) . toEqual ( [ 'sk-tool' ] )
173+ expect ( cache . get ( 'msg-1' ) ) . toEqual ( keys ( 'sk-tool' ) )
149174 } )
150175 } )
151176
152177 describe ( 'restoreRevealedSimKeysForMessage' , ( ) => {
153178 it ( 'substitutes the live key back into a redacted message' , ( ) => {
154- const cache = new Map < string , string [ ] > ( [ [ 'msg-1' , [ 'sk-sim-A' ] ] ] )
179+ const cache = new Map < string , RevealedSimKey [ ] > ( [ [ 'msg-1' , keys ( 'sk-sim-A' ) ] ] )
155180 const msg : ChatMessage = {
156181 id : 'msg-1' ,
157182 role : 'assistant' ,
@@ -165,7 +190,7 @@ describe('sim-key-redaction', () => {
165190 } )
166191
167192 it ( 'fills a value-less {"type":"sim_key"} placeholder (no redacted flag needed)' , ( ) => {
168- const cache = new Map < string , string [ ] > ( [ [ 'msg-1' , [ 'sk-sim-A' ] ] ] )
193+ const cache = new Map < string , RevealedSimKey [ ] > ( [ [ 'msg-1' , keys ( 'sk-sim-A' ) ] ] )
169194 const msg : ChatMessage = {
170195 id : 'msg-1' ,
171196 role : 'assistant' ,
@@ -178,7 +203,7 @@ describe('sim-key-redaction', () => {
178203 } )
179204
180205 it ( 'fills value-less and redacted placeholders positionally in one message' , ( ) => {
181- const cache = new Map < string , string [ ] > ( [ [ 'msg-1' , [ 'sk-sim-A' , 'sk-sim-B' ] ] ] )
206+ const cache = new Map < string , RevealedSimKey [ ] > ( [ [ 'msg-1' , keys ( 'sk-sim-A' , 'sk-sim-B' ) ] ] )
182207 const msg : ChatMessage = {
183208 id : 'msg-1' ,
184209 role : 'assistant' ,
@@ -193,7 +218,7 @@ describe('sim-key-redaction', () => {
193218 it ( 'fills sim_key rows positionally inside a mixed credential-card array' , ( ) => {
194219 const link = { type : 'link' , value : 'https://x' , provider : 'slack' }
195220 const batch = credentialBatch ( [ link , { type : 'sim_key' } , { type : 'sim_key' } ] )
196- const cache = new Map < string , string [ ] > ( [ [ 'msg-1' , [ 'sk-sim-A' , 'sk-sim-B' ] ] ] )
221+ const cache = new Map < string , RevealedSimKey [ ] > ( [ [ 'msg-1' , keys ( 'sk-sim-A' , 'sk-sim-B' ) ] ] )
197222 const msg : ChatMessage = { id : 'msg-1' , role : 'assistant' , content : batch }
198223
199224 expect ( restoreRevealedSimKeysForMessage ( msg , cache ) . content ) . toBe (
@@ -205,8 +230,37 @@ describe('sim-key-redaction', () => {
205230 )
206231 } )
207232
233+ it ( 'binds the filled key to the workspace it was created in, not the tag target' , ( ) => {
234+ const cache : RevealedSimKeysByMessage = new Map ( )
235+ const tag = `<credential>${ JSON . stringify ( { type : 'sim_key' , workspaceId : 'ws-other' } ) } </credential>`
236+ captureRevealedSimKeys ( cache , [ 'msg-1' ] , tag , [
237+ {
238+ toolCall : {
239+ name : 'generate_api_key' ,
240+ result : { success : true , output : { key : 'sk-sim-A' , workspaceId : 'ws-1' } } ,
241+ } ,
242+ } ,
243+ ] )
244+ const msg : ChatMessage = { id : 'msg-1' , role : 'assistant' , content : tag }
245+
246+ expect ( restoreRevealedSimKeysForMessage ( msg , cache ) . content ) . toBe (
247+ `<credential>${ JSON . stringify ( { value : 'sk-sim-A' , type : 'sim_key' , workspaceId : 'ws-1' } ) } </credential>`
248+ )
249+ } )
250+
251+ it ( 'keeps the tag workspace when the creation result carries none' , ( ) => {
252+ const cache : RevealedSimKeysByMessage = new Map ( )
253+ const tag = `<credential>${ JSON . stringify ( { type : 'sim_key' , workspaceId : 'ws-tag' } ) } </credential>`
254+ captureRevealedSimKeys ( cache , [ 'msg-1' ] , tag , [ apiKeyBlock ( 'sk-sim-A' ) ] )
255+ const msg : ChatMessage = { id : 'msg-1' , role : 'assistant' , content : tag }
256+
257+ expect ( restoreRevealedSimKeysForMessage ( msg , cache ) . content ) . toBe (
258+ `<credential>${ JSON . stringify ( { value : 'sk-sim-A' , type : 'sim_key' , workspaceId : 'ws-tag' } ) } </credential>`
259+ )
260+ } )
261+
208262 it ( 'substitutes multiple keys in stream order' , ( ) => {
209- const cache = new Map < string , string [ ] > ( [ [ 'msg-1' , [ 'sk-sim-A' , 'sk-sim-B' ] ] ] )
263+ const cache = new Map < string , RevealedSimKey [ ] > ( [ [ 'msg-1' , keys ( 'sk-sim-A' , 'sk-sim-B' ) ] ] )
210264 const msg : ChatMessage = {
211265 id : 'msg-1' ,
212266 role : 'assistant' ,
@@ -219,7 +273,7 @@ describe('sim-key-redaction', () => {
219273 } )
220274
221275 it ( 'leaves a redacted tag in place if no live value is captured for that slot' , ( ) => {
222- const cache = new Map < string , string [ ] > ( [ [ 'msg-1' , [ 'sk-sim-A' ] ] ] )
276+ const cache = new Map < string , RevealedSimKey [ ] > ( [ [ 'msg-1' , keys ( 'sk-sim-A' ) ] ] )
223277 const msg : ChatMessage = {
224278 id : 'msg-1' ,
225279 role : 'assistant' ,
@@ -230,7 +284,7 @@ describe('sim-key-redaction', () => {
230284 } )
231285
232286 it ( 'returns the same message reference when nothing to restore' , ( ) => {
233- const cache = new Map < string , string [ ] > ( )
287+ const cache = new Map < string , RevealedSimKey [ ] > ( )
234288 const msg : ChatMessage = {
235289 id : 'msg-1' ,
236290 role : 'assistant' ,
@@ -240,7 +294,7 @@ describe('sim-key-redaction', () => {
240294 } )
241295
242296 it ( 'does nothing for user messages' , ( ) => {
243- const cache = new Map < string , string [ ] > ( [ [ 'msg-1' , [ 'sk-sim-A' ] ] ] )
297+ const cache = new Map < string , RevealedSimKey [ ] > ( [ [ 'msg-1' , keys ( 'sk-sim-A' ) ] ] )
244298 const msg : ChatMessage = {
245299 id : 'msg-1' ,
246300 role : 'user' ,
@@ -250,7 +304,7 @@ describe('sim-key-redaction', () => {
250304 } )
251305
252306 it ( 'threads the cursor across separate content blocks so each block gets its matching key' , ( ) => {
253- const cache = new Map < string , string [ ] > ( [ [ 'msg-1' , [ 'sk-sim-A' , 'sk-sim-B' ] ] ] )
307+ const cache = new Map < string , RevealedSimKey [ ] > ( [ [ 'msg-1' , keys ( 'sk-sim-A' , 'sk-sim-B' ) ] ] )
254308 const msg : ChatMessage = {
255309 id : 'msg-1' ,
256310 role : 'assistant' ,
@@ -269,9 +323,9 @@ describe('sim-key-redaction', () => {
269323 } )
270324
271325 it ( 'isolates revealed values by message id (multiple keys across messages)' , ( ) => {
272- const cache = new Map < string , string [ ] > ( [
273- [ 'msg-1' , [ 'sk-sim-A' ] ] ,
274- [ 'msg-2' , [ 'sk-sim-B' ] ] ,
326+ const cache = new Map < string , RevealedSimKey [ ] > ( [
327+ [ 'msg-1' , keys ( 'sk-sim-A' ) ] ,
328+ [ 'msg-2' , keys ( 'sk-sim-B' ) ] ,
275329 ] )
276330 const msg1 : ChatMessage = { id : 'msg-1' , role : 'assistant' , content : redacted }
277331 const msg2 : ChatMessage = { id : 'msg-2' , role : 'assistant' , content : redacted }
0 commit comments