@@ -253,8 +253,7 @@ impl TraceFilterEngine {
253253 value_default = rule_value;
254254 }
255255 if !rule. value_patterns . is_empty ( ) {
256- let mut merged =
257- Vec :: with_capacity ( rule. value_patterns . len ( ) + patterns. len ( ) ) ;
256+ let mut merged = Vec :: with_capacity ( rule. value_patterns . len ( ) + patterns. len ( ) ) ;
258257 merged. extend ( rule. value_patterns . iter ( ) . cloned ( ) ) ;
259258 merged. extend ( patterns. into_iter ( ) ) ;
260259 patterns = merged;
@@ -404,10 +403,20 @@ impl ScopeContext {
404403 }
405404 } ) ;
406405
407- let module_name = relative_path
406+ let mut module_name = relative_path
408407 . as_deref ( )
409408 . and_then ( |rel| module_from_relative ( rel) . map ( |cow| cow. into_owned ( ) ) ) ;
410409
410+ if module_name. is_none ( ) {
411+ module_name = absolute_path
412+ . as_deref ( )
413+ . and_then ( |abs| module_from_relative ( abs) . map ( |cow| cow. into_owned ( ) ) ) ;
414+ }
415+
416+ if module_name. is_none ( ) && !qualname. is_empty ( ) {
417+ module_name = Some ( qualname. to_string ( ) ) ;
418+ }
419+
411420 let object_name = module_name
412421 . as_ref ( )
413422 . map ( |module| format ! ( "{}.{}" , module, qualname) )
@@ -473,12 +482,15 @@ fn module_from_relative(relative: &str) -> Option<Cow<'_, str>> {
473482 if relative. is_empty ( ) {
474483 return None ;
475484 }
476- let trimmed = relative. trim_start_matches ( "./" ) ;
485+ let trimmed = relative. trim_start_matches ( "./" ) . trim_start_matches ( '/' ) ;
477486 let without_suffix = trimmed. strip_suffix ( ".py" ) . unwrap_or ( trimmed) ;
478487 if without_suffix. is_empty ( ) {
479488 return None ;
480489 }
481- let mut parts: Vec < & str > = without_suffix. split ( '/' ) . collect ( ) ;
490+ let mut parts: Vec < & str > = without_suffix
491+ . split ( '/' )
492+ . filter ( |segment| !segment. is_empty ( ) )
493+ . collect ( ) ;
482494 if let Some ( last) = parts. last ( ) . copied ( ) {
483495 if last == "__init__" {
484496 parts. pop ( ) ;
@@ -509,6 +521,67 @@ mod tests {
509521 use std:: io:: Write ;
510522 use tempfile:: tempdir;
511523
524+ #[ test]
525+ fn builtin_redactions_apply_without_project_filter ( ) -> RecorderResult < ( ) > {
526+ use std:: path:: PathBuf ;
527+
528+ const BUILTIN_LABEL : & str = "builtin-default" ;
529+ const BUILTIN_FILTER : & str =
530+ include_str ! ( "../../resources/trace_filters/builtin_default.toml" ) ;
531+
532+ let config =
533+ TraceFilterConfig :: from_inline_and_paths ( & [ ( BUILTIN_LABEL , BUILTIN_FILTER ) ] , & [ ] ) ?;
534+ assert_eq ! ( config. sources( ) . len( ) , 1 ) ;
535+
536+ let mut inline_source = config. sources ( ) [ 0 ] . clone ( ) ;
537+ inline_source. project_root = PathBuf :: from ( "." ) ;
538+
539+ let rules = compile_rules ( config. rules ( ) ) ;
540+ let mut value_default = config. default_value_action ( ) ;
541+ let mut patterns: Vec < CompiledValuePattern > = Vec :: new ( ) ;
542+
543+ let temp = tempdir ( ) . expect ( "temp dir" ) ;
544+ let script_path = temp. path ( ) . join ( "app.py" ) ;
545+ fs:: write ( & script_path, "print('placeholder')\n " ) . expect ( "create script file" ) ;
546+ let script_path = script_path. to_string_lossy ( ) . to_string ( ) ;
547+
548+ let context = ScopeContext :: derive ( & script_path, "leak" , & [ inline_source] ) ;
549+ assert ! (
550+ context. relative_path. is_none( ) ,
551+ "expected unresolved relative path"
552+ ) ;
553+ assert ! (
554+ context. module_name. is_some( ) ,
555+ "module name should be derived despite missing project filter"
556+ ) ;
557+
558+ for rule in rules. iter ( ) {
559+ if rule. matches ( & context) {
560+ if let Some ( rule_value) = rule. value_default {
561+ value_default = rule_value;
562+ }
563+ if !rule. value_patterns . is_empty ( ) {
564+ let mut merged = Vec :: with_capacity ( rule. value_patterns . len ( ) + patterns. len ( ) ) ;
565+ merged. extend ( rule. value_patterns . iter ( ) . cloned ( ) ) ;
566+ merged. extend ( patterns. into_iter ( ) ) ;
567+ patterns = merged;
568+ }
569+ }
570+ }
571+
572+ let policy = ValuePolicy :: new ( value_default, patterns. into ( ) ) ;
573+
574+ assert_eq ! (
575+ policy. decide( ValueKind :: Local , "password" ) ,
576+ ValueAction :: Redact
577+ ) ;
578+ assert_eq ! (
579+ policy. decide( ValueKind :: Arg , "password" ) ,
580+ ValueAction :: Redact
581+ ) ;
582+ Ok ( ( ) )
583+ }
584+
512585 #[ test]
513586 fn caches_resolution_and_applies_value_patterns ( ) -> RecorderResult < ( ) > {
514587 let ( config, file_path) = filter_with_pkg_rule (
@@ -705,10 +778,7 @@ mod tests {
705778 policy. decide( ValueKind :: Local , "secret_token" ) ,
706779 ValueAction :: Allow
707780 ) ;
708- assert_eq ! (
709- policy. decide( ValueKind :: Local , "other" ) ,
710- ValueAction :: Allow
711- ) ;
781+ assert_eq ! ( policy. decide( ValueKind :: Local , "other" ) , ValueAction :: Allow ) ;
712782 Ok ( ( ) )
713783 } )
714784 }
0 commit comments