krz/octosentry

macOS menu bar app to monitor GitHub security alerts github macos menubar security

Commit 146ea30e5d

146ea30e5d8d99fc6e70483c8bba46ae1fddf0fa

parent: 739e452413

Unsigned

cmc <hello@cleberg.net> · 2026-07-25 21:47 UTC

Normalize repo watch-list entries in addRepo

Store the canonical "owner/repo" reconstructed from the parsed parts
instead of the raw input, so stray slashes (e.g. "owner/repo/") can't
create an entry that silently 404s on refresh. Also compare
case-insensitively for duplicates, since GitHub owner/repo names are
case-insensitive.

Bumps marketing version to 1.0.1.

Layout: unified · split

octosentry.xcodeproj/project.pbxproj +4 −4
@@ -396,7 +396,7 @@
396 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 396 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
397 CODE_SIGN_STYLE = Automatic; 397 CODE_SIGN_STYLE = Automatic;
398 COMBINE_HIDPI_IMAGES = YES; 398 COMBINE_HIDPI_IMAGES = YES;
399 CURRENT_PROJECT_VERSION = 1; 399 CURRENT_PROJECT_VERSION = 2;
400 DEVELOPMENT_TEAM = ZCNAX3VL9D; 400 DEVELOPMENT_TEAM = ZCNAX3VL9D;
401 ENABLE_APP_SANDBOX = YES; 401 ENABLE_APP_SANDBOX = YES;
402 ENABLE_HARDENED_RUNTIME = YES; 402 ENABLE_HARDENED_RUNTIME = YES;
@@ -412,7 +412,7 @@
412 "$(inherited)", 412 "$(inherited)",
413 "@executable_path/../Frameworks", 413 "@executable_path/../Frameworks",
414 ); 414 );
415 MARKETING_VERSION = 1.0.0; 415 MARKETING_VERSION = 1.0.1;
416 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.octosentry; 416 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.octosentry;
417 PRODUCT_NAME = "$(TARGET_NAME)"; 417 PRODUCT_NAME = "$(TARGET_NAME)";
418 REGISTER_APP_GROUPS = YES; 418 REGISTER_APP_GROUPS = YES;
@@ -432,7 +432,7 @@
432 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 432 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
433 CODE_SIGN_STYLE = Automatic; 433 CODE_SIGN_STYLE = Automatic;
434 COMBINE_HIDPI_IMAGES = YES; 434 COMBINE_HIDPI_IMAGES = YES;
435 CURRENT_PROJECT_VERSION = 1; 435 CURRENT_PROJECT_VERSION = 2;
436 DEVELOPMENT_TEAM = ZCNAX3VL9D; 436 DEVELOPMENT_TEAM = ZCNAX3VL9D;
437 ENABLE_APP_SANDBOX = YES; 437 ENABLE_APP_SANDBOX = YES;
438 ENABLE_HARDENED_RUNTIME = YES; 438 ENABLE_HARDENED_RUNTIME = YES;
@@ -448,7 +448,7 @@
448 "$(inherited)", 448 "$(inherited)",
449 "@executable_path/../Frameworks", 449 "@executable_path/../Frameworks",
450 ); 450 );
451 MARKETING_VERSION = 1.0.0; 451 MARKETING_VERSION = 1.0.1;
452 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.octosentry; 452 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.octosentry;
453 PRODUCT_NAME = "$(TARGET_NAME)"; 453 PRODUCT_NAME = "$(TARGET_NAME)";
454 REGISTER_APP_GROUPS = YES; 454 REGISTER_APP_GROUPS = YES;
octosentry/SecurityEventStore.swift +11 −3
@@ -121,13 +121,21 @@ final class SecurityEventStore {
121 watchListErrorMessage = "Enter a repo as \"owner/repo\"." 121 watchListErrorMessage = "Enter a repo as \"owner/repo\"."
122 return 122 return
123 } 123 }
124 // Store the canonical "owner/repo" rather than the raw input, so
125 // stray slashes (e.g. "owner/repo/") can't produce a malformed
126 // entry that silently 404s on refresh.
127 let repoFullName = "\(parts[0])/\(parts[1])"
124 128
125 var state = await persistenceStore.load() 129 var state = await persistenceStore.load()
126 guard !state.watchedRepos.contains(trimmed) else { 130 // GitHub owner/repo names are case-insensitive, so treat entries
127 watchListErrorMessage = "\(trimmed) is already watched." 131 // that differ only in case as the same watched repo.
132 guard !state.watchedRepos.contains(where: {
133 $0.caseInsensitiveCompare(repoFullName) == .orderedSame
134 }) else {
135 watchListErrorMessage = "\(repoFullName) is already watched."
128 return 136 return
129 } 137 }
130 state.watchedRepos.append(trimmed) 138 state.watchedRepos.append(repoFullName)
131 await persistenceStore.save(state) 139 await persistenceStore.save(state)
132 watchedRepos = state.watchedRepos 140 watchedRepos = state.watchedRepos
133 141