@@ -727,6 +727,120 @@ func TestGetFindingId(t *testing.T) {
727727 })
728728}
729729
730+ func TestIsSecretFromConfluenceResourceIdentifier (t * testing.T ) {
731+ tests := []struct {
732+ name string
733+ ruleID string
734+ line string
735+ match string
736+ want bool
737+ }{
738+ {
739+ name : "matches ri:secret attribute with quoted value" ,
740+ ruleID : rules .GenericApiKeyID ,
741+ line : `<ri:attachment ri:secret="12345" />` ,
742+ match : `secret="12345"` ,
743+ want : true ,
744+ },
745+ {
746+ name : "matches with extra whitespace and self-closing tag" ,
747+ ruleID : rules .GenericApiKeyID ,
748+ line : `<ri:attachment ri:secret="12345"/>` ,
749+ match : `secret="12345"` ,
750+ want : true ,
751+ },
752+ {
753+ name : "no match when value format differs (expects exact literal)" ,
754+ ruleID : rules .GenericApiKeyID ,
755+ line : `<ri:attachment ri:secret="12345" />` ,
756+ match : `secret=12345` ,
757+ want : false ,
758+ },
759+ {
760+ name : "no match when value appears in a different attribute" ,
761+ ruleID : rules .GenericApiKeyID ,
762+ line : `<ri:attachment ri:filename="secret=12345" />` ,
763+ match : `secret=12345` ,
764+ want : false ,
765+ },
766+ {
767+ name : "no match when ri: prefixes the element name (not an attribute)" ,
768+ ruleID : rules .GenericApiKeyID ,
769+ line : `<ri:secret value="x">` ,
770+ match : `secret` ,
771+ want : false ,
772+ },
773+ {
774+ name : "no match when text is outside any tag" ,
775+ ruleID : rules .GenericApiKeyID ,
776+ line : `ri:secret=12345` ,
777+ match : `secret=12345` ,
778+ want : false ,
779+ },
780+ {
781+ name : "no match for xri: prefixed attribute" ,
782+ ruleID : rules .GenericApiKeyID ,
783+ line : `<ri:attachment xri:secret="12345" />` ,
784+ match : `secret="12345"` ,
785+ want : false ,
786+ },
787+ {
788+ name : "no match when rule ID is not generic-api-key does not apply" ,
789+ ruleID : "some-other-rule" ,
790+ line : `<ri:attachment ri:secret="12345" />` ,
791+ match : `secret="12345"` ,
792+ want : false ,
793+ },
794+ }
795+
796+ for _ , tt := range tests {
797+ t .Run (tt .name , func (t * testing.T ) {
798+ got := isSecretFromConfluenceResourceIdentifier (tt .ruleID , tt .line , tt .match )
799+ assert .Equal (t , tt .want , got , "ruleID=%q, line=%q, match=%q" , tt .ruleID , tt .line , tt .match )
800+ })
801+ }
802+ }
803+
804+ // if any of these tests fails, we should review isSecretFromConfluenceResourceIdentifier and/or generic-api-key rule
805+ func TestDetectWithConfluenceMetadata (t * testing.T ) {
806+ secretsCases := []struct {
807+ Content string
808+ Name string
809+ ShouldFind bool
810+ }{
811+ {
812+ Content : "<ri:user ri:userkey=\" 8a7f808362ce64321162ceb20e64321a\" >" ,
813+ Name : "should not detect from confluence userkey metadata" ,
814+ ShouldFind : false ,
815+ },
816+ }
817+
818+ detector , err := Init (& EngineConfig {})
819+ if err != nil {
820+ t .Fatal (err )
821+ }
822+
823+ for _ , secret := range secretsCases {
824+ t .Run (secret .Name , func (t * testing.T ) {
825+ secretsChan := make (chan * secrets.Secret , 1 )
826+ c := plugins.ConfluencePlugin {}
827+ err = detector .DetectFragment (item {content : & secret .Content }, secretsChan , c .GetName ())
828+ if err != nil {
829+ return
830+ }
831+ close (secretsChan )
832+
833+ s := <- secretsChan
834+
835+ if secret .ShouldFind {
836+ assert .Equal (t , s .LineContent , secret .Content )
837+ } else {
838+ assert .Nil (t , s )
839+ }
840+ })
841+ }
842+ }
843+
730844type item struct {
731845 content * string
732846 id string
0 commit comments