@@ -161,6 +161,57 @@ export function wrapExternalContent({ text, source, user, channel, threadTs }) {
161161 ] . join ( "\n" ) ;
162162}
163163
164+ // ── Outbound Redaction / Leak Prevention ───────────────────────────────────
165+
166+ const OUTBOUND_BLOCK_PATTERNS = [
167+ { pattern : / \/ p r o c \/ ( s e l f | \d + ) \/ e n v i r o n \b / i, reason : "proc-environ-path" } ,
168+ { pattern : / \/ p r o c \/ ( s e l f | \d + ) \/ c m d l i n e \b / i, reason : "proc-cmdline-path" } ,
169+ { pattern : / \0 [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * = [ ^ \0 ] { 0 , 200 } \0 / , reason : "nul-delimited-env-dump" } ,
170+ ] ;
171+
172+ const OUTBOUND_REDACT_PATTERNS = [
173+ { pattern : / \b x o x [ b a p r s ] - [ 0 - 9 A - Z a - z - ] { 12 , } \b / g, replacement : "[REDACTED_SLACK_TOKEN]" , reason : "slack-token" } ,
174+ { pattern : / \b g h [ p o u s r ] _ [ A - Z a - z 0 - 9 _ ] { 20 , } \b / g, replacement : "[REDACTED_GITHUB_TOKEN]" , reason : "github-token" } ,
175+ { pattern : / \b g i t h u b _ p a t _ [ A - Z a - z 0 - 9 _ ] { 20 , } \b / g, replacement : "[REDACTED_GITHUB_TOKEN]" , reason : "github-token" } ,
176+ { pattern : / \b s k - [ A - Z a - z 0 - 9 ] { 20 , } \b / g, replacement : "[REDACTED_API_KEY]" , reason : "openai-key" } ,
177+ { pattern : / \b A K I A [ A - Z 0 - 9 ] { 16 } \b / g, replacement : "[REDACTED_AWS_KEY]" , reason : "aws-access-key" } ,
178+ { pattern : / \b ( (?: S E C R E T | T O K E N | P A S S W O R D | P A S S | A P I (?: _ | - ) ? K E Y | A C C E S S (?: _ | - ) ? K E Y | P R I V A T E (?: _ | - ) ? K E Y | S E S S I O N | C O O K I E | B E A R E R | S L A C K | G I T H U B | O P E N A I | A N T H R O P I C | G E M I N I | A W S ) [ A - Z 0 - 9 _ - ] * ) = [ ^ \s \n \r \0 ] { 1 , 400 } / gi, replacement : "$1=[REDACTED_ENV]" , reason : "sensitive-env-assignment" } ,
179+ ] ;
180+
181+ const OUTBOUND_BLOCK_FALLBACK = "I found potentially sensitive runtime data and omitted it. I can still help with the task if you share only the non-sensitive details." ;
182+
183+ export function sanitizeOutboundText ( input ) {
184+ let text = typeof input === "string" ? input : String ( input ) ;
185+ const reasons = [ ] ;
186+
187+ for ( const rule of OUTBOUND_BLOCK_PATTERNS ) {
188+ if ( rule . pattern . test ( text ) ) {
189+ reasons . push ( rule . reason ) ;
190+ }
191+ }
192+
193+ if ( reasons . length > 0 ) {
194+ return {
195+ text : OUTBOUND_BLOCK_FALLBACK ,
196+ redacted : true ,
197+ blocked : true ,
198+ reasons,
199+ } ;
200+ }
201+
202+ let redacted = false ;
203+ for ( const rule of OUTBOUND_REDACT_PATTERNS ) {
204+ const next = text . replace ( rule . pattern , rule . replacement ) ;
205+ if ( next !== text ) {
206+ redacted = true ;
207+ reasons . push ( rule . reason ) ;
208+ text = next ;
209+ }
210+ }
211+
212+ return { text, redacted, blocked : false , reasons } ;
213+ }
214+
164215// ── Access Control ──────────────────────────────────────────────────────────
165216
166217/**
0 commit comments