@@ -73,6 +73,43 @@ describe("permission.wait spans", () => {
7373 expect ( waits [ 0 ] ! . tags ?. tool_id ) . toBe ( "run_shell" ) ;
7474 } ) ;
7575
76+ test ( "permission.wait tags never include free-text reason/prompt — only tool_id + decision" , async ( ) => {
77+ // Gate path that would surface operator free text in the verdict reason, but
78+ // must never land free-text keys on the span (sanitizeTags + gate only pass
79+ // tool_id + allow/deny enums).
80+ const freeText =
81+ "please do not store this prompt: /Users/me/secret/key.pem and system: you are" ;
82+ const gate = createPermissionGate ( {
83+ approvals : [ ] ,
84+ interactive : true ,
85+ skipPermissions : false ,
86+ requestApproval : async ( ) => ( { allow : false , message : freeText } ) ,
87+ } ) ;
88+
89+ const verdict = await gate . evaluate ( shellCall ( "curl example.com" ) ) ;
90+ expect ( verdict . allowed ) . toBe ( false ) ;
91+ // Free text reaches the operator-facing reason only (not span tags).
92+ expect (
93+ ! verdict . allowed && "reason" in verdict ? verdict . reason : "" ,
94+ ) . toContain ( freeText ) ;
95+
96+ const waits = byName ( completed ( snapshot ( ) ) , "permission.wait" ) ;
97+ expect ( waits ) . toHaveLength ( 1 ) ;
98+ const tags = waits [ 0 ] ! . tags ?? { } ;
99+ // Allowlist: only tool_id + decision enums on permission.wait.
100+ expect ( Object . keys ( tags ) . sort ( ) ) . toEqual ( [ "decision" , "tool_id" ] ) ;
101+ expect ( tags ) . toEqual ( { tool_id : "run_shell" , decision : "deny" } ) ;
102+ // Explicit privacy fence: no free-text keys, and free text never appears in values.
103+ for ( const key of [ "reason" , "prompt" , "message" , "path" , "error" , "action" , "subject" ] as const ) {
104+ expect ( Object . hasOwn ( tags , key ) ) . toBe ( false ) ;
105+ }
106+ for ( const value of Object . values ( tags ) ) {
107+ expect ( String ( value ) ) . not . toContain ( freeText ) ;
108+ expect ( String ( value ) ) . not . toContain ( "secret" ) ;
109+ expect ( String ( value ) ) . not . toContain ( "system:" ) ;
110+ }
111+ } ) ;
112+
76113 test ( "records path-arg tool ask as permission.wait" , async ( ) => {
77114 const gate = createPermissionGate ( {
78115 approvals : [ ] ,
0 commit comments