@@ -637,15 +637,18 @@ export class SafetyGuard extends BaseGuard {
637637 // The value part excludes benign placeholder labels ("password:
638638 // required", "api_key: not set") while still matching real credentials
639639 // (Sentry/Greptile P1, PR #34). Mirrors safety_guard.py HARMFUL_PATTERNS.
640+ // Each exemption alternative must match the ENTIRE value token
641+ // ((?=\s|$) not \b) — otherwise "password=required-secret" bypasses
642+ // (Greptile/CodeRabbit P1, PR #34).
640643 private static HARMFUL_PATTERNS = [
641- / p a s s w o r d \s * [ = : ] \s * (? ! (?: r e q u i r e d | o p t i o n a l | n o n e | n u l l | r e d a c t e d | o m i t t e d | p l a c e h o l d e r | i n v a l i d | e x p i r e d | n o t [ _ \s ] ? (?: s e t | p r o v i d e d ) | n \/ ? a ) \b | \* { 3 , } | x { 3 , } ) \S + / i,
642- / a p i [ _ - ] ? k e y \s * [ = : ] \s * (? ! (?: r e q u i r e d | o p t i o n a l | n o n e | n u l l | r e d a c t e d | o m i t t e d | p l a c e h o l d e r | i n v a l i d | e x p i r e d | n o t [ _ \s ] ? (?: s e t | p r o v i d e d ) | n \/ ? a ) \b | \* { 3 , } | x { 3 , } ) \S + / i,
643- / s e c r e t \s * [ = : ] \s * (? ! (?: r e q u i r e d | o p t i o n a l | n o n e | n u l l | r e d a c t e d | o m i t t e d | p l a c e h o l d e r | i n v a l i d | e x p i r e d | n o t [ _ \s ] ? (?: s e t | p r o v i d e d ) | n \/ ? a ) \b | \* { 3 , } | x { 3 , } ) \S + / i,
644+ / p a s s w o r d \s * [ = : ] \s * (? ! (?: r e q u i r e d | o p t i o n a l | n o n e | n u l l | r e d a c t e d | o m i t t e d | p l a c e h o l d e r | i n v a l i d | e x p i r e d | n o t [ _ \s ] ? (?: s e t | p r o v i d e d ) | n \/ ? a ) (? = \s | $ ) | \* { 3 , } (? = \s | $ ) | x { 3 , } (? = \s | $ ) ) \S + / i,
645+ / a p i [ _ - ] ? k e y \s * [ = : ] \s * (? ! (?: r e q u i r e d | o p t i o n a l | n o n e | n u l l | r e d a c t e d | o m i t t e d | p l a c e h o l d e r | i n v a l i d | e x p i r e d | n o t [ _ \s ] ? (?: s e t | p r o v i d e d ) | n \/ ? a ) (? = \s | $ ) | \* { 3 , } (? = \s | $ ) | x { 3 , } (? = \s | $ ) ) \S + / i,
646+ / s e c r e t \s * [ = : ] \s * (? ! (?: r e q u i r e d | o p t i o n a l | n o n e | n u l l | r e d a c t e d | o m i t t e d | p l a c e h o l d e r | i n v a l i d | e x p i r e d | n o t [ _ \s ] ? (?: s e t | p r o v i d e d ) | n \/ ? a ) (? = \s | $ ) | \* { 3 , } (? = \s | $ ) | x { 3 , } (? = \s | $ ) ) \S + / i,
644647 // Value-aware label form (same placeholder exemption as above) —
645648 // "private[_-]?key" bare-matching blocked benign labels such as
646649 // "private_key: not set" (Greptile P1, PR #34). [\s_-]? also catches
647650 // the spaced "private key: <value>" form. Mirrors safety_guard.py.
648- / p r i v a t e [ \s _ - ] ? k e y \s * [ = : ] \s * (? ! (?: r e q u i r e d | o p t i o n a l | n o n e | n u l l | r e d a c t e d | o m i t t e d | p l a c e h o l d e r | i n v a l i d | e x p i r e d | n o t [ _ \s ] ? (?: s e t | p r o v i d e d ) | n \/ ? a ) \b | \* { 3 , } | x { 3 , } ) \S + / i,
651+ / p r i v a t e [ \s _ - ] ? k e y \s * [ = : ] \s * (? ! (?: r e q u i r e d | o p t i o n a l | n o n e | n u l l | r e d a c t e d | o m i t t e d | p l a c e h o l d e r | i n v a l i d | e x p i r e d | n o t [ _ \s ] ? (?: s e t | p r o v i d e d ) | n \/ ? a ) (? = \s | $ ) | \* { 3 , } (? = \s | $ ) | x { 3 , } (? = \s | $ ) ) \S + / i,
649652 // Generic PEM header: BEGIN [TYPE] PRIVATE KEY — covers RSA/DSA/EC
650653 // plus generic "BEGIN PRIVATE KEY", OPENSSH and ENCRYPTED variants
651654 // that were missed (CodeRabbit, PR #34). Python applies re.I to all
@@ -681,19 +684,30 @@ export class SafetyGuard extends BaseGuard {
681684 { error : String ( err ) } ,
682685 ) ;
683686 }
684- const issues : string [ ] = [ ] ;
687+ // Python parity: issues is a uniform array of
688+ // {type, severity, details} objects for BOTH paths — the error path
689+ // (all issues, errors AND warnings) and the warning-only path
690+ // (Sentry, PR #34: PII used to be plain strings here and verbose
691+ // {type: 'PII detected: email', details: []} objects there).
692+ const issues : Array < { type : string ; severity : string ; details : string [ ] } > = [ ] ;
685693 // Error-severity issues are COLLECTED across checks — matching
686694 // Python, which appends injection and harmful findings together and
687695 // reports the total (CodeAnt nitpick, PR #34: returning on the first
688696 // harmful pattern discarded PII and other diagnostics).
689697 const errorIssues : Array < { type : string ; severity : string ; details : string [ ] } > = [ ] ;
690698
691699 if ( this . checkPii ) {
700+ // Python parity: one {type:'pii', severity:'warning'} entry whose
701+ // details carry the matched PII types (email, phone, ...).
702+ const piiTypes : string [ ] = [ ] ;
692703 for ( const [ type , pattern ] of Object . entries ( SafetyGuard . PII_PATTERNS ) ) {
693704 if ( pattern . test ( content ) ) {
694- issues . push ( `PII detected: ${ type } ` ) ;
705+ piiTypes . push ( type ) ;
695706 }
696707 }
708+ if ( piiTypes . length > 0 ) {
709+ issues . push ( { type : 'pii' , severity : 'warning' , details : piiTypes } ) ;
710+ }
697711 }
698712
699713 if ( this . checkInjection ) {
@@ -704,7 +718,9 @@ export class SafetyGuard extends BaseGuard {
704718 }
705719 }
706720 if ( injections . length > 0 ) {
707- errorIssues . push ( { type : 'injection' , severity : 'error' , details : injections } ) ;
721+ const entry = { type : 'injection' , severity : 'error' , details : injections } ;
722+ issues . push ( entry ) ;
723+ errorIssues . push ( entry ) ;
708724 }
709725 }
710726
@@ -718,7 +734,9 @@ export class SafetyGuard extends BaseGuard {
718734 }
719735 }
720736 if ( harmful . length > 0 ) {
721- errorIssues . push ( { type : 'harmful' , severity : 'error' , details : harmful } ) ;
737+ const entry = { type : 'harmful' , severity : 'error' , details : harmful } ;
738+ issues . push ( entry ) ;
739+ errorIssues . push ( entry ) ;
722740 }
723741 }
724742
@@ -729,16 +747,16 @@ export class SafetyGuard extends BaseGuard {
729747 // which returns details={'issues': issues} with everything
730748 // (Sentry, PR #34: PII warnings were discarded when a
731749 // critical issue co-existed).
732- {
733- issues : errorIssues . concat (
734- issues . map ( ( w ) => ( { type : w , severity : 'warning' , details : [ ] } ) ) ,
735- ) ,
736- } ,
750+ { issues } ,
737751 ) ;
738752 }
739753
740754 if ( issues . length > 0 ) {
741- return this . failResult ( `Safety issues detected: ${ issues . join ( ', ' ) } ` , { issues } , 'warning' ) ;
755+ return this . failResult (
756+ `Safety warnings: ${ issues . length } warning(s)` ,
757+ { issues } ,
758+ 'warning' ,
759+ ) ;
742760 }
743761
744762 return this . passResult ( 'All safety checks passed' ) ;
0 commit comments