@@ -189,7 +189,7 @@ pub fn is_concrete_env_file_name(name: &str) -> bool {
189189 is_env_file_name ( name) && !is_template_env_file_name ( name)
190190}
191191
192- pub fn looks_like_secret ( value : & str ) -> bool {
192+ pub fn looks_like_secret ( key : & str , value : & str ) -> bool {
193193 if value. is_empty ( ) {
194194 return false ;
195195 }
@@ -198,10 +198,11 @@ pub fn looks_like_secret(value: &str) -> bool {
198198 return false ;
199199 }
200200
201- value. len ( ) >= 16
202- && value
203- . chars ( )
204- . all ( |character| character. is_ascii_alphanumeric ( ) || "/_+=-" . contains ( character) )
201+ if has_high_confidence_secret_value ( value) {
202+ return true ;
203+ }
204+
205+ is_secret_like_env_key ( key)
205206}
206207
207208fn split_env_assignment ( line : & str ) -> Option < ( & str , & str ) > {
@@ -280,6 +281,89 @@ fn is_placeholder_value(value: &str) -> bool {
280281 . unwrap_or ( false )
281282}
282283
284+ fn has_high_confidence_secret_value ( value : & str ) -> bool {
285+ let lower = value. to_ascii_lowercase ( ) ;
286+
287+ value. contains ( "-----BEGIN PRIVATE KEY-----" )
288+ || lower. starts_with ( "sk_live_" )
289+ || lower. starts_with ( "sk_test_" )
290+ || lower. starts_with ( "ghp_" )
291+ || lower. starts_with ( "github_pat_" )
292+ || lower. starts_with ( "xoxb-" )
293+ || lower. starts_with ( "xoxp-" )
294+ || lower. starts_with ( "xoxa-" )
295+ || ( value. len ( ) == 20
296+ && value. starts_with ( "AKIA" )
297+ && value
298+ . chars ( )
299+ . all ( |character| character. is_ascii_uppercase ( ) || character. is_ascii_digit ( ) ) )
300+ || ( value. len ( ) >= 35
301+ && value. starts_with ( "AIza" )
302+ && value
303+ . chars ( )
304+ . all ( |character| character. is_ascii_alphanumeric ( ) || "-_" . contains ( character) ) )
305+ }
306+
307+ fn is_secret_like_env_key ( key : & str ) -> bool {
308+ let segments = env_key_segments ( key) ;
309+ if segments. is_empty ( ) {
310+ return false ;
311+ }
312+
313+ if contains_adjacent_segments ( & segments, "PRIVATE" , "KEY" )
314+ || contains_service_key_segments ( & segments)
315+ {
316+ return true ;
317+ }
318+
319+ if segments. iter ( ) . any ( |segment| {
320+ matches ! (
321+ segment. as_str( ) ,
322+ "TOKEN" | "SECRET" | "PASSWORD" | "PASSWD" | "PWD"
323+ )
324+ } ) {
325+ return true ;
326+ }
327+
328+ if contains_adjacent_segments ( & segments, "API" , "KEY" )
329+ || contains_adjacent_segments ( & segments, "ACCESS" , "KEY" )
330+ {
331+ return !is_public_key_identifier ( & segments) ;
332+ }
333+
334+ false
335+ }
336+
337+ fn env_key_segments ( key : & str ) -> Vec < String > {
338+ key. split ( |character : char | !character. is_ascii_alphanumeric ( ) )
339+ . filter ( |segment| !segment. is_empty ( ) )
340+ . map ( |segment| segment. to_ascii_uppercase ( ) )
341+ . collect ( )
342+ }
343+
344+ fn contains_adjacent_segments ( segments : & [ String ] , left : & str , right : & str ) -> bool {
345+ segments
346+ . windows ( 2 )
347+ . any ( |window| window[ 0 ] == left && window[ 1 ] == right)
348+ }
349+
350+ fn contains_service_key_segments ( segments : & [ String ] ) -> bool {
351+ segments
352+ . windows ( 2 )
353+ . any ( |window| window[ 0 ] == "SERVICE" && window[ 1 ] == "KEY" )
354+ || segments. windows ( 3 ) . any ( |window| {
355+ window[ 0 ] == "SERVICE"
356+ && matches ! ( window[ 1 ] . as_str( ) , "ROLE" | "ACCOUNT" )
357+ && window[ 2 ] == "KEY"
358+ } )
359+ }
360+
361+ fn is_public_key_identifier ( segments : & [ String ] ) -> bool {
362+ contains_adjacent_segments ( segments, "PUBLIC" , "KEY" )
363+ || contains_adjacent_segments ( segments, "ANON" , "KEY" )
364+ || contains_adjacent_segments ( segments, "CLIENT" , "ID" )
365+ }
366+
283367fn normalize_env_template_source_group ( group : EnvTemplateSourceGroup ) -> EnvTemplateSourceGroup {
284368 let mut unique_keys = group
285369 . keys
0 commit comments