Automatic Metadata Scrubbing for Clipboard Files in Python
A background daemon for Windows intercepts files in the clipboard immediately after copying and removes their metadata before pasting. This eliminates the need for manual processing of images, documents, and videos using ExifTool or system utilities. Processing occurs locally without external data transfer, with protection against corruption and symlink attacks.
Working with the Clipboard via Win32 API
Clipboard monitoring is implemented by a ClipboardMonitor thread that polls the clipboard every 500 ms. The key format is CF_HDROP, which contains a list of file paths.
Issues with pywin32:
- In older versions,
GetClipboardData(CF_HDROP)returns a pointer toDROPFILES, requiringDragQueryFileWfromshell32.dllviactypes. - In newer versions, it returns a list of strings, but PyInstaller or DLL mismatches can break this behavior.
Check code:
if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_HDROP):
file_list = win32clipboard.GetClipboardData(win32clipboard.CF_HDROP)
Path Validation for Security
Before processing, paths undergo strict checks in validate_path:
- Absolute path.
- Comparison of original inode and resolved_path (to block symlinks).
- Exclusion of directories in
WINDIR. - Limit on the number of files (protection against mass processing).
This prevents symlink attacks and processing of system directories. Additionally, resolved_path.is_file() is checked to exclude folders.
Processing Architecture: In-Place Overwriting
Files are overwritten at their original path without substitution in the clipboard—this simplifies handling Windows memory locks. Sequence:
- Copying a file to the clipboard.
- Detecting
CF_HDROP. - Validation and passing to
MetadataScrubber. - Overwriting with the clean version.
Image Scrubbing: Atomic Replacement
Support for JPEG, PNG, TIFF, BMP. Removes EXIF data including geolocation, device, and software information.
Strategy—temporary file for atomicity:
with Image.open(file_path) as img:
data = list(img.getdata())
image_without_exif = Image.new(img.mode, img.size)
image_without_exif.putdata(data)
temp_path = file_path.with_suffix(file_path.suffix + '.temp')
image_without_exif.save(temp_path, format=format_name)
shutil.move(str(temp_path), str(file_path))
Ensures integrity: pasting receives either the original or the scrubbed file.
DOCX: Direct XML Editing
python-docx changes properties but not Track Changes and comments. Solution—parsing _blob parts:
xml = part._blob.decode('utf-8')
if 'w:revision' in xml or 'w:ins' in xml or 'w:del' in xml:
xml = xml.replace('w:revision', 'w:revision_removed')\
.replace('w:ins ', 'w:p ')\
.replace('w:del ', 'w:p ')
part._blob = xml.encode('utf-8')
A hack with replace removes revision tags at the root, making the document final for Word. Risk—complex tables.
PDF and Video: Basic Support
PDF via pypdf:
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
writer.add_metadata({})
Rebuilding without metadata. Video (MP4, MKV, MOV)—basic atom scrubbing; full scrubbing requires ffmpeg.
Issues with the System Tray
Avoiding notifications due to Shell_NotifyIcon errors:
- Incorrect HWND lifecycle leads to
DestroyWindowfailures. - COM objects crash when closing Tkinter.
Solution—removed the module for background stability. Polling at 500 ms results in 0% CPU usage when idle.
Key Points
- Automatic metadata scrubbing (EXIF, Track Changes, geolocation) when copying to the clipboard.
- Protection against symlink attacks and system paths through validation.
- Atomic file overwriting without risk of corruption.
- Local processing in Python (PIL, pywin32, pypdf) without external servers.
- Optimized clipboard monitoring with minimal overhead.
— Editorial Team
No comments yet.