From db7dc2bd4e5b666bb29bb6b3c14596c4ed693a9f Mon Sep 17 00:00:00 2001 From: Milan Hoppe Date: Fri, 3 Jul 2026 04:21:29 +0200 Subject: [PATCH] Fix clipboard privacy and safety issues Three fixes for handling of potentially sensitive clipboard data: - Stop logging clipping contents on paste. pasteFromStack wrote the first 50 characters of every pasted clipping to the unified log via NSLog in release builds, where other processes, Console, and sysdiagnose bundles can read it. Short secrets (passwords, tokens) leaked in full. - Honor the nspasteboard.org skip types independently of the 'Don't copy from password fields' toggle. The ConcealedType/ TransientType/AutoGeneratedType check was nested inside the skipPasswordFields conditional, so unchecking that one box silently disabled all password-manager protection. The revealPasteboardTypes side effect also no longer runs (with a store save) on every poll. - Fix invalid -setValue: calls (missing forKey:) in setSavePreference: that would raise unrecognized-selector and leave the iCloud-sync/ save-preference interlock unapplied. Co-Authored-By: Claude Fable 5 --- AppController.m | 8 +++++--- FlycutOperator.m | 40 ++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/AppController.m b/AppController.m index 3898ab0..e492dbd 100755 --- a/AppController.m +++ b/AppController.m @@ -885,7 +885,9 @@ - (void)pasteFromStack NSLog(@"pasteFromStack called"); NSString *content = [flycutOperator getPasteFromStackPosition]; if ( nil != content ) { - NSLog(@"Content found, adding to pasteboard and preparing to paste: %@", [content substringToIndex:MIN(content.length, 50)]); + // Never log clipping contents; NSLog output lands in the unified log, which + // other processes and diagnostics can read. + NSLog(@"Content found, adding to pasteboard and preparing to paste"); [self addClipToPasteboard:content]; [self performSelector:@selector(hideApp) withObject:nil afterDelay:0.2]; [self performSelector:@selector(fakeCommandV) withObject:nil afterDelay:0.5]; @@ -1418,11 +1420,11 @@ - (IBAction)setSavePreference:(id)sender if ( [alert runModal] == NSAlertFirstButtonReturn ) { - [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO]]; + [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO] forKey:@"syncClippingsViaICloud"]; } else { - [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:2]]; + [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:2] forKey:@"savePreference"]; } [alert release]; } diff --git a/FlycutOperator.m b/FlycutOperator.m index ca5c581..bfc5069 100644 --- a/FlycutOperator.m +++ b/FlycutOperator.m @@ -362,31 +362,35 @@ -(NSString*)getClipFromCount:(int)indexInt -(BOOL)shouldSkip:(NSString *)contents ofType:(NSString *)type fromAvailableTypes:(NSArray *)availableTypes { - // Check to see if we are skipping passwords based on length and characters. - if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"skipPasswordFields"] ) + // Check to see if they want a little help figuring out what types to enter. + if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"revealPasteboardTypes"] ) { - // Check to see if they want a little help figuring out what types to enter. - if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"revealPasteboardTypes"] ) - [clippingStore addClipping:type ofType:type fromAppLocalizedName:@"Flycut" fromAppBundleURL:nil atTimestamp:0]; + [clippingStore addClipping:type ofType:type fromAppLocalizedName:@"Flycut" fromAppBundleURL:nil atTimestamp:0]; [self actionAfterListModification]; + } - __block bool skipClipping = NO; + __block bool skipClipping = NO; - // Check the array of types to skip. - if ( availableTypes && [[NSUserDefaults standardUserDefaults] boolForKey:@"skipPboardTypes"] ) - { - NSSet *typesToSkip = [NSSet setWithArray: [[[[NSUserDefaults standardUserDefaults] stringForKey:@"skipPboardTypesList"] stringByReplacingOccurrencesOfString:@" " withString:@""] componentsSeparatedByString: @","]]; - NSSet *pasteBoardTypes = [NSSet setWithArray: availableTypes]; + // Check the array of types to skip. This honors the nspasteboard.org conventions + // (ConcealedType etc.), so it must apply independently of the password-field + // heuristics toggle below. + if ( availableTypes && [[NSUserDefaults standardUserDefaults] boolForKey:@"skipPboardTypes"] ) + { + NSSet *typesToSkip = [NSSet setWithArray: [[[[NSUserDefaults standardUserDefaults] stringForKey:@"skipPboardTypesList"] stringByReplacingOccurrencesOfString:@" " withString:@""] componentsSeparatedByString: @","]]; + NSSet *pasteBoardTypes = [NSSet setWithArray: availableTypes]; - if ( [pasteBoardTypes intersectsSet: typesToSkip] ) - { - skipClipping = YES; - }; + if ( [pasteBoardTypes intersectsSet: typesToSkip] ) + { + skipClipping = YES; + }; - } - if (skipClipping) - return YES; + } + if (skipClipping) + return YES; + // Check to see if we are skipping passwords based on length and characters. + if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"skipPasswordFields"] ) + { // Check the array of lengths to skip for suspicious strings. if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"skipPasswordLengths"] ) {