3434 'total_processed' : 0 ,
3535 'strikes_added' : 0 ,
3636 'downloads_removed' : 0 ,
37+ 'malicious_removed' : 0 ,
3738 'items_ignored' : 0 ,
3839 'api_calls_made' : 0 ,
3940 'errors_encountered' : 0 ,
@@ -49,6 +50,7 @@ def reset_session_stats():
4950 'total_processed' : 0 ,
5051 'strikes_added' : 0 ,
5152 'downloads_removed' : 0 ,
53+ 'malicious_removed' : 0 ,
5254 'items_ignored' : 0 ,
5355 'api_calls_made' : 0 ,
5456 'errors_encountered' : 0 ,
@@ -133,6 +135,39 @@ def generate_item_hash(item):
133135 hash_input = f"{ item ['name' ]} _{ item ['size' ]} "
134136 return hashlib .md5 (hash_input .encode ('utf-8' )).hexdigest ()
135137
138+ def check_for_malicious_files (item , settings ):
139+ """Check if download contains malicious file types"""
140+ if not settings .get ('malicious_file_detection' , False ):
141+ return False , None
142+
143+ # Use user-defined malicious extensions from settings
144+ malicious_extensions = settings .get ('malicious_extensions' , [
145+ '.lnk' , '.exe' , '.bat' , '.cmd' , '.scr' , '.pif' , '.com' ,
146+ '.zipx' , '.jar' , '.vbs' , '.js' , '.jse' , '.wsf' , '.wsh'
147+ ])
148+
149+ # Use user-defined suspicious patterns from settings
150+ suspicious_patterns = settings .get ('suspicious_patterns' , [
151+ 'password.txt' , 'readme.txt' , 'install.exe' , 'setup.exe' ,
152+ 'keygen' , 'crack' , 'patch.exe' , 'activator'
153+ ])
154+
155+ item_name = item .get ('name' , '' ).lower ()
156+
157+ # Check for malicious extensions in the title/name
158+ for ext in malicious_extensions :
159+ if ext .lower () in item_name :
160+ swaparr_logger .warning (f"Malicious file detected in '{ item_name } ': contains { ext } " )
161+ return True , f"Contains malicious file type: { ext } "
162+
163+ # Check for suspicious patterns
164+ for pattern in suspicious_patterns :
165+ if pattern .lower () in item_name :
166+ swaparr_logger .warning (f"Suspicious content detected in '{ item_name } ': contains { pattern } " )
167+ return True , f"Contains suspicious content: { pattern } "
168+
169+ return False , None
170+
136171def parse_time_string_to_seconds (time_string ):
137172 """Parse a time string like '2h', '30m', '1d' to seconds"""
138173 if not time_string :
@@ -444,6 +479,35 @@ def process_stalled_downloads(app_name, instance_name, instance_data, settings):
444479 item_state = "Monitoring (Queued)"
445480 continue
446481
482+ # Check for malicious files FIRST - immediate removal without strikes
483+ is_malicious , malicious_reason = check_for_malicious_files (item , settings )
484+ if is_malicious :
485+ swaparr_logger .error (f"MALICIOUS CONTENT DETECTED: { item ['name' ]} - { malicious_reason } " )
486+
487+ if not settings .get ("dry_run" , False ):
488+ if delete_download (app_name , instance_data ["api_url" ], instance_data ["api_key" ], item ["id" ], True ):
489+ swaparr_logger .info (f"Successfully removed malicious download: { item ['name' ]} " )
490+
491+ # Mark as removed to prevent reappearance
492+ removed_items [item_hash ] = {
493+ "name" : item ["name" ],
494+ "removed_time" : datetime .utcnow ().isoformat (),
495+ "reason" : f"Malicious: { malicious_reason } " ,
496+ "size" : item ["size" ]
497+ }
498+ save_removed_items (app_name , removed_items )
499+
500+ item_state = f"REMOVED (Malicious: { malicious_reason } )"
501+
502+ # Track malicious removal statistics
503+ SWAPARR_STATS ['malicious_removed' ] = SWAPARR_STATS .get ('malicious_removed' , 0 ) + 1
504+ increment_swaparr_stat ("malicious_removals" , 1 )
505+ else :
506+ swaparr_logger .info (f"DRY RUN: Would remove malicious download: { item ['name' ]} - { malicious_reason } " )
507+ item_state = f"Would Remove (Malicious: { malicious_reason } )"
508+
509+ continue # Skip to next item - don't process further
510+
447511 # Initialize strike count if not already in strike data
448512 if item_id not in strike_data :
449513 strike_data [item_id ] = {
0 commit comments