krz/octosentry

macOS menu bar app to monitor GitHub security alerts

clone: git clone https://gitbay.org/krz/octosentry.git

522d04a8233f66b81abcae4dd04df02d342f35be

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-07-25T21:47:21Z

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.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
 octosentry.xcodeproj/project.pbxproj |  8 ++++----
 octosentry/SecurityEventStore.swift  | 14 +++++++++++---
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/octosentry.xcodeproj/project.pbxproj b/octosentry.xcodeproj/project.pbxproj
index d8edb63..c19ebc7 100644
--- a/octosentry.xcodeproj/project.pbxproj
+++ b/octosentry.xcodeproj/project.pbxproj
@@ -396,7 +396,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 2;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_APP_SANDBOX = YES;
 				ENABLE_HARDENED_RUNTIME = YES;
@@ -412,7 +412,7 @@
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 				);
-				MARKETING_VERSION = 1.0.0;
+				MARKETING_VERSION = 1.0.1;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.octosentry;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				REGISTER_APP_GROUPS = YES;
@@ -432,7 +432,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 2;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_APP_SANDBOX = YES;
 				ENABLE_HARDENED_RUNTIME = YES;
@@ -448,7 +448,7 @@
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 				);
-				MARKETING_VERSION = 1.0.0;
+				MARKETING_VERSION = 1.0.1;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.octosentry;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				REGISTER_APP_GROUPS = YES;
diff --git a/octosentry/SecurityEventStore.swift b/octosentry/SecurityEventStore.swift
index 8f3d0f5..bb92af9 100644
--- a/octosentry/SecurityEventStore.swift
+++ b/octosentry/SecurityEventStore.swift
@@ -121,13 +121,21 @@ final class SecurityEventStore {
             watchListErrorMessage = "Enter a repo as \"owner/repo\"."
             return
         }
+        // Store the canonical "owner/repo" rather than the raw input, so
+        // stray slashes (e.g. "owner/repo/") can't produce a malformed
+        // entry that silently 404s on refresh.
+        let repoFullName = "\(parts[0])/\(parts[1])"
 
         var state = await persistenceStore.load()
-        guard !state.watchedRepos.contains(trimmed) else {
-            watchListErrorMessage = "\(trimmed) is already watched."
+        // GitHub owner/repo names are case-insensitive, so treat entries
+        // that differ only in case as the same watched repo.
+        guard !state.watchedRepos.contains(where: {
+            $0.caseInsensitiveCompare(repoFullName) == .orderedSame
+        }) else {
+            watchListErrorMessage = "\(repoFullName) is already watched."
             return
         }
-        state.watchedRepos.append(trimmed)
+        state.watchedRepos.append(repoFullName)
         await persistenceStore.save(state)
         watchedRepos = state.watchedRepos