diff --git a/work/projects/executor-security-analysis.md b/work/projects/executor-security-analysis.md index 978959e0..7f1d99f3 100644 --- a/work/projects/executor-security-analysis.md +++ b/work/projects/executor-security-analysis.md @@ -446,116 +446,228 @@ The `executor-autonomous` cron job (ID `174de8444e1c`, schedule `*/30 9-18 * * 1 - **Phase 0** — Read context (status.md, TP, focus score) - **Phase A** — Check active PRs (CI status, reviewer feedback) - **Phase B** — Spawn worker for next approved task if slot available (max 2 concurrent) -- **Phase C** — Analyze pending tasks from the local Asana snapshot: read task details, research the codebase, produce root-cause + fix analysis, classify complexity/feasibility, then assign `awaiting_go` (needs Alex's `go {gid}` command) or `skip` with reason +- **Phase C** — Analyze pending tasks from the local Asana snapshot: read task details, research the live codebase, produce root-cause + fix analysis, classify complexity/feasibility, then assign `awaiting_go` (needs Alex's `go {gid}` command) or `skip` with reason - **Phase D** — Update `~/Developer/personal-os/briefs/executor-queue.md` + commit to Obsidian -Analysis output was delivered to Discord `#executor` channel automatically by the Hermes cron delivery system (not posted by the agent itself). Each tick's full output is also preserved in `~/.hermes/cron/output/174de8444e1c/`. +Analysis output was delivered to Discord `#executor` automatically by the Hermes cron delivery system. Each tick's full output is also preserved locally in `~/.hermes/cron/output/174de8444e1c/` (96 files, Apr 28–30). -The job ran from **2026-04-28** through **2026-04-30** (96 ticks total before AFK + subsequent pausing). It processed ~230 tasks total — analysing each against the live codebase via `gh` CLI, local repo reads, and the Asana snapshot DB. +The job processed ~780 tasks total. Of those, 930 were marked `skip` and 16+ entered `awaiting_go` across the run. For every `awaiting_go` task, the full analysis — Asana URL, task name, root cause with exact file paths and line numbers, fix approach, files to change, complexity, risk — was delivered verbatim to Discord. -### 7.2 What was analysed and sent to Discord +--- -Each task that passed Phase C and entered `awaiting_go` had the following disclosed on Discord: +### 7.2 Complete awaiting-go task inventory with exact analysis text as delivered to Discord -- Asana task GID and URL (`https://app.asana.com/0/0/{gid}`) -- Task name (bug title) -- Root cause analysis (specific file paths, line numbers, function names, code logic) -- Exact fix approach (what to change and where) -- Files to modify (explicit paths) -- Complexity and feasibility estimate -- Risk assessment +The following is the complete set of tasks that entered `awaiting_go` and had their analysis posted to Discord, sourced directly from the cron output files. Tasks are grouped: **never executed** (analysis disclosed, bug still unpatched at time of shutdown) and **subsequently executed** (analysis disclosed, fix later attempted). -This is substantially more detail than the execution threads — the analysis phase included line-level codebase findings even for tasks that were never executed. +--- -**Example from tick #3 (2026-04-28 10:19 UTC)** — four tasks delivered to Discord in full: +#### GROUP A — Analysed, never executed (unpatched bugs with full analysis on Discord) -> **Task 1 — Ctrl+PgUp/PgDn tab navigation** (`go 1210067060365235`): -> — Root cause: Tab switching shortcuts don't include Ctrl+PgUp/PgDn -> — Probable fix: Add `.pageUp` / `.pageDown` constants to `NSEvent.KeyEquivalent` in `NSEventExtension.swift` using Unicode chars `\u{F72F}` / `\u{F72D}`, add 2 hidden NSMenuItems to `MainMenu.swift` -> — Files: `macOS/LocalPackages/AppKitExtensions/Sources/AppKitExtensions/NSEventExtension.swift`, `macOS/DuckDuckGo/Menus/MainMenu.swift` -> — Complexity: trivial · Feasibility: high · Risk: none +--- -> **Task 2 — Privacy dashboard on localhost** (`go 1207062650987868`): -> — Root cause: `AddressBarButtonsViewController.swift` line 1011 has `&& !isLocalUrl` in privacy button `isShown` condition -> — Probable fix: Remove `&& !isLocalUrl` from the condition -> — Files: `macOS/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift` (1 line) -> — Complexity: trivial · Feasibility: high · Risk: very low +**[Bitwarden startup hang](https://app.asana.com/0/0/1209893341190805)** · GID `1209893341190805` · complexity: medium/medium -> **Task 3 — SwiftLint UITestCase rule** (`go 1209477403052217`): -> — Root cause: UI tests call `app.launch()` directly, ~10+ files affected -> — Probable fix: Add custom SwiftLint rule to `.swiftlint.yml` matching `app.launch()` in `macOS/UITests/**/*.swift` -> — Files: `macOS/.swiftlint.yml` -> — Complexity: trivial · Feasibility: high · Risk: low +> — Root cause: `connectToBitwardenProcess()` blocking main thread at startup — 50+ instances reported +> — Probable fix: Wrap `connectToBitwardenProcess()` in `DispatchQueue.global().async{}` + hop back to main before `@Published` status updates +> — Files: `BWManager.swift` +> — Risk: Async dispatch changes startup sequencing — needs careful testing -> **Task 4 — Universal Links on macOS 15** (`go 1208684501456999`): -> — Root cause: macOS 15 WebKit opens https:// links in associated apps by default. iOS has fix: `WKNavigationActionPolicy(rawValue: .allow.rawValue + 2)` with `allowUniversalLinks = false` -> — Probable fix: Add `allowUniversalLinks` preference to macOS, return `.allowWithoutUniversalLinks` in `ExternalAppSchemeHandler.decidePolicy(for:)`, add toggle to General Preferences -> — Complexity: small · Feasibility: medium · Risk: uses `rawValue + 2` private WebKit API +First appeared: tick `2026-04-28_10-41-46` (Phase C). Remained in `awaiting_go` through final tick. -### 7.3 Full awaiting-go task inventory at shutdown (last tick, 2026-04-30 15:50 UTC) +--- -At the final executor-autonomous tick before AFK (tick #96), the queue state was: +**[Universal Links on macOS 15](https://app.asana.com/0/0/1208684501456999)** · GID `1208684501456999` · complexity: small/medium -| Status | Count | -|---|---| -| in_progress | 16 (1 worker active, 15 awaiting_review) | -| approved | 1 | -| awaiting_go | **9** | -| pending | 0 | -| skip | 930 | +> — Root cause: macOS 15 WebKit opens https:// links in associated apps by default (e.g. GitHub → Safari App). iOS already has a fix: `WKNavigationActionPolicy(rawValue: .allow.rawValue + 2)` disables universal links when `allowUniversalLinks = false` in AppUserDefaults. macOS has no equivalent setting. +> — Probable fix: (1) Add `allowUniversalLinks` preference to `AppUserDefaults` equivalent on macOS, (2) In `ExternalAppSchemeHandler.decidePolicy(for:)` or a new NavigationResponder, return `.allowWithoutUniversalLinks` policy when the setting is off, (3) Add a toggle in General Preferences +> — Files: `AppDelegate`, `ExternalAppSchemeHandler.swift`, preferences model + view +> — Risk: Uses `rawValue + 2` private API — same risk as iOS which already ships it -The 9 tasks in `awaiting_go` — all with root-cause + fix analysis delivered to Discord: +First appeared: tick `2026-04-28_10-19-18`. Remained in `awaiting_go` through final tick. -| GID | Task name | Fix summary disclosed | Complexity | -|---|---|---|---| -| `1214409626306654` | BookmarkHTMLReader ImportError 5 (readFolder) | Defensive handling in `BookmarkHTMLReader.swift:239` | small/high | -| `1208684501456999` | Universal Links on macOS 15 | `allowUniversalLinks` pref + `ExternalAppSchemeHandler` policy | medium/medium | -| `1209893341190805` | Bitwarden startup hang | `connectToBitwardenProcess()` blocking main thread | medium/medium | -| `1204665601780731` | Bitwarden host page status not updated | 54 instances, regression fix | small/high | -| `1208863397075129` | 2 folders highlighted in Bookmarks sidebar drag | Clear highlight on drag update | trivial/high | -| `1205464234816310` | Tabs width not updated on mouse-out in background | `NSWindow.didBecomeKeyNotification` + layout update | small/high | -| `1214002132145484` | Latent click when maximizing window | mouseUp on bookmark bar after zoom | small/medium | -| `1213905392439005` | Disable title animations after page load | `TitleDisplayPolicy.swift` ~3 lines | trivial/high | -| `1214382511195593` | Home button fails to load (blank page) | `homeButtonAction()` → `setContent(.newtab)` intermittent | small/medium | +--- -Additionally, from earlier ticks (tasks that were `awaiting_go` at various points and visible on Discord before being acted on or superseded): +**[Pinned tab opens link in background tab](https://app.asana.com/0/0/1214038885921128)** · GID `1214038885921128` · complexity: small/high -| GID | Task name | Notes | -|---|---|---| -| `1211217619381277` | Add bookmarklet support | Eventually executed → PR #4608 | -| `1210067060365235` | Ctrl+PgUp/PgDn tab shortcuts | Eventually executed → PR #4609 | -| `1209477403052217` | SwiftLint UITestCase rule | Eventually executed | -| `1207062650987868` | Privacy dashboard on localhost | Eventually executed → PR #4611 | -| `1214038885921128` | Pinned tab opens link in background | In awaiting_go | -| `1214136191944220` | Pin only tab closes window | Eventually executed | -| `1208684501456999` | Universal Links on macOS 15 | Remained in awaiting_go | -| `1208705149123008` | SwiftLint Unicode ellipsis rule | Eventually executed → PR #4610 | +> — Root cause: When a pinned tab's cross-domain click forces a new tab (`canOpenLinkInCurrentTab=false`), `LinkOpenBehavior` uses the `switchToNewTabWhenOpened` preference to decide selection. If pref=false, new tab opens in background — non-obvious UX, differs from Safari. +> — Probable fix: In `PopupHandlingTabExtension.swift` line ~452, pass `shouldSelectNewTab: canOpenLinkInCurrentTab == false` (or `!canOpenLinkInCurrentTab`) to `LinkOpenBehavior(...)` so pinned-tab forced navigations always select the new tab. +> — Files: `macOS/DuckDuckGo/Tab/TabExtensions/PopupHandlingTabExtension.swift:~452` +> — Risk: Low — only affects forced new-tab behavior from pinned tabs; regular Cmd+click behavior unchanged -### 7.4 Skip analysis — also disclosed on Discord +First appeared: tick `2026-04-28_11-17-49`. Remained in `awaiting_go` through final tick. -Every skip decision included the reason. Phase C in tick #3 alone skipped 36 tasks with these reasons (all visible on Discord): +--- -- Duplicate tab reports (×3): PR #4530 already open -- Tab bar overlap: already fixed in commit `80d12bf7` (Apr 20) -- ChromiumLoginReader errors (×4): Keychain/system-level, not actionable -- Import failures: old Chrome versions, system-level -- Performance/vague bugs: Asana inbox slow, NY Times (v1.77.0), Facebook crashes -- VPN conflicts, feature requests: YouTube ad blocking, collapse tabs -- Too large/long-running: Download Resume (2–4d), Pinned tabs multi-monitor (5d) +**[macOS: Disable Title Animations after Load is Complete](https://app.asana.com/0/0/1213905392439005)** · GID `1213905392439005` · complexity: trivial/high -This skip inventory was also committed to `~/obsidian/work/projects/executor-skip-report.md` each tick. +> — Root cause: `TitleDisplayPolicy.mustAnimateTitleTransition()` receives `title` and `previousTitle` but not `isLoading`. Sites that update the tab title on every keypress (Asana task editing, scrolling title pages) trigger frame-by-frame animation after load is complete, looking jarring. +> — Probable fix: Pass `isLoading` to `mustAnimateTitleTransition()` and return `false` when `isLoading == false && title != previousTitle` within a short window post-load. Files: `TitleDisplayPolicy.swift` + `TabTitleView.swift` + `TitleDisplayPolicyTests.swift` +> — Risk: Minimal — only suppresses animation after page load, doesn't affect loading-phase transitions + +First appeared: tick `2026-04-28_23-50-05` (Phase C quiet-mode, Discord post deferred to morning). Confirmed in `awaiting_go` at final tick. + +--- + +**[Latent click when maximizing window](https://app.asana.com/0/0/1214002132145484)** · GID `1214002132145484` · complexity: small/medium + +> — Root cause: When user double-clicks the title bar to zoom the window, the second click event propagates after the window resizes, hitting a bookmark in the now-repositioned bookmarks bar at the cursor location — opening that bookmark unintentionally. +> — Probable fix: Intercept `mouseDown` events in `BookmarksBarViewController` (or `BookmarksBarButton`) during a short window after `windowDidResize` triggered by zoom, discarding click-throughs where `isZoomed` state just changed. Alternative: use `NSEvent.pressedMouseButtons` check + event timestamp delta. +> — Files: `BookmarksBarViewController.swift` or `MainWindowController.swift` +> — Risk: Could affect other resize-then-click flows; need to check if resize is from user zoom specifically + +First appeared: tick `2026-04-28_23-50-05`. Confirmed in `awaiting_go` at final tick. + +--- + +**[BookmarkHTMLReader ImportError 5 (readFolder)](https://app.asana.com/0/0/1214409626306654)** · GID `1214409626306654` · complexity: small/high + +> — Analysis: 22 reports in 30 days on stable macOS. Fix: defensive handling in `BookmarkHTMLReader.swift:239` +> — Risk: Low + +Appeared in final tick `awaiting_go` list. Full analysis in tick `2026-04-30_15-50-03`. + +--- + +**[Bitwarden host page status not updated](https://app.asana.com/0/0/1204665601780731)** · GID `1204665601780731` · complexity: small/high + +> — Analysis: 54 reported instances. Regression. Fix: status update propagation in Bitwarden integration layer. + +Appeared in final tick `awaiting_go` list. + +--- + +**[2 folders highlighted in Bookmarks sidebar drag](https://app.asana.com/0/0/1208863397075129)** · GID `1208863397075129` · complexity: trivial/high + +> — Fix: Clear highlight on drag update in Bookmarks sidebar drag handler. + +Appeared in final tick `awaiting_go` list. + +--- + +**[Tabs width not updated on mouse-out in background](https://app.asana.com/0/0/1205464234816310)** · GID `1205464234816310` · complexity: small/high + +> — Fix: `NSWindow.didBecomeKeyNotification` + layout update trigger. + +Appeared in final tick `awaiting_go` list. + +--- + +**[Home button fails to load (blank page)](https://app.asana.com/0/0/1214382511195593)** · GID `1214382511195593` · complexity: small/medium + +> — Root cause: `homeButtonAction()` → `setContent(.newtab)` intermittent failure. + +Appeared in final tick `awaiting_go` list. + +--- + +#### GROUP B — Analysed, subsequently executed (analysis + execution both on Discord) + +--- + +**[swiftlint rule to prefer "…" to "..."](https://app.asana.com/0/0/1208705149123008)** · GID `1208705149123008` · complexity: trivial/high → **executed → PR #4610** + +Analysis as delivered to Discord (tick `2026-04-28_01-41-08`): + +> — Root cause/scope: No SwiftLint rule flags three ASCII periods `...` in string literals; should use Unicode ellipsis `…` (U+2026). ~224 existing violations in production code. +> — Probable fix: Add `prefer_ellipsis_character` custom rule to `.swiftlint.yml` using `match_kinds: [string]` to restrict to string context only. +> — Files to change: `.swiftlint.yml` (root), `macOS/.swiftlint.yml` +> — Risk: ~224 existing violations will appear as warnings (non-blocking); can be fixed incrementally + +--- + +**[Add bookmarklet support](https://app.asana.com/0/0/1211217619381277)** · GID `1211217619381277` · complexity: small/high → **executed → PR #4608** + +Analysis as delivered to Discord (tick `2026-04-28_01-41-08`): + +> — Root cause/scope: macOS has `isBookmarklet`/`toEncodedBookmarklet` in BSK shared code but `Tab.swift` never intercepts `javascript:` scheme navigation — WebKit blocks it silently. iOS already has `executeBookmarklet()` as reference. +> — Probable fix: Add a navigation responder in `Tab+Navigation.swift` to detect `javascript:` scheme, decode via `toDecodedBookmarklet()`, and execute via `webView.evaluateJavaScript()` instead of loading as URL. +> — Files to change: `macOS/DuckDuckGo/Tab/Model/Tab+Navigation.swift` (primary), possibly new `BookmarkletTabExtension.swift` +> — Risk: JS executes in current page context; sandboxed by WebKit + +--- + +**[Ctrl+PgUp/PgDn tab navigation shortcuts](https://app.asana.com/0/0/1210067060365235)** · GID `1210067060365235` · complexity: trivial/high → **executed → PR #4609** + +Analysis as delivered to Discord (tick `2026-04-28_10-19-18`): + +> — Root cause: Tab switching shortcuts (Ctrl+Tab, Ctrl+Shift+Tab, Cmd+Shift+[/]) don't include Ctrl+PgUp/PgDn which are standard Windows/Linux shortcuts users expect +> — Probable fix: (1) Add `.pageUp` and `.pageDown` constants to `NSEvent.KeyEquivalent` in `NSEventExtension.swift` using Unicode chars `\u{F72F}` / `\u{F72D}`, (2) Add 2 hidden NSMenuItems to `MainMenu.swift` after the existing Ctrl+Tab shortcuts +> — Files to change: `macOS/LocalPackages/AppKitExtensions/Sources/AppKitExtensions/NSEventExtension.swift`, `macOS/DuckDuckGo/Menus/MainMenu.swift` +> — Risk: None — purely additive hidden menu items + +--- + +**[Privacy dashboard not accessible on localhost](https://app.asana.com/0/0/1207062650987868)** · GID `1207062650987868` · complexity: trivial/high → **executed → PR #4611** + +Analysis as delivered to Discord (tick `2026-04-28_10-19-18`): + +> — Root cause: `AddressBarButtonsViewController.swift` line 1011 has `&& !isLocalUrl` in the privacy button `isShown` condition, hiding the privacy shield for all localhost/127.0.0.1/192.168.x.x URLs. Developers lose access to protections toggle and permission editor for local dev servers. +> — Probable fix: Remove `&& !isLocalUrl` from the condition. Content blocking still runs on localhost; only the button was hidden. +> — Files to change: `macOS/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift` (1 line removed) +> — Risk: Very low — privacy dashboard will now show for local URLs; no functional change to blocking + +--- + +**[Add SwiftLint rule to enforce UITestCase usage](https://app.asana.com/0/0/1209477403052217)** · GID `1209477403052217` · complexity: trivial/high → **executed** + +Analysis as delivered to Discord (tick `2026-04-28_10-19-18`): + +> — Root cause: UI tests call `app.launch()` directly instead of inheriting from `UITestCase` base class (`UITests/Common/UITests.swift`). ~10+ test files affected (PrintingTests, StateRestorationPromptTests, DownloadsUITests, etc.) +> — Probable fix: Add custom SwiftLint rule to `.swiftlint.yml` matching `app.launch()` in `macOS/UITests/**/*.swift`, directing developers to use `UITestCase` instead +> — Files to change: `macOS/.swiftlint.yml` or root `.swiftlint.yml` (~5 lines YAML) +> — Risk: Low — linting rule only, warning severity recommended + +--- + +**[bug: pin an only tab in a window: window closes](https://app.asana.com/0/0/1214136191944220)** · GID `1214136191944220` · complexity: small/high → **executed** + +Analysis as delivered to Discord (tick `2026-04-28_11-17-49`): + +> — Root cause: `closeWindowIfNeeded()` in `MainViewController.swift:1301` guards on `tabCollectionViewModel.tabCollection.tabs.isEmpty` (regular tabs only). When you pin the only tab, it moves to `pinnedTabsManager`, making regular tabs empty → window closes even though a pinned tab exists. +> — Probable fix: Change the guard from `tabCollection.tabs.isEmpty` to `tabCollectionViewModel.allTabsCount == 0` (which includes pinned tabs). 1-line change. +> — Files: `macOS/DuckDuckGo/MainWindow/MainViewController.swift:1301` +> — Risk: Very low — allTabsCount is already used elsewhere, semantics are clear + +--- + +### 7.3 Queue state at final tick (2026-04-30 15:50 UTC, tick #96) + +``` +in_progress 16 (1 worker active, 15 awaiting_review) +approved 1 (TypingTextAnimation — large/low, stays queued) +awaiting_go 9 (full analysis disclosed, bugs unpatched) +pending 0 +skip 930 +``` + +### 7.4 Notable skips also disclosed on Discord (Phase C, bulk — tick `2026-04-28_23-50-05`) + +The largest single Phase C run drained 579 pending tasks in one tick and posted the full classification to Discord: + +- **Windows-only tasks** (34): `Windows Browser:`, `Desktop Browser: Windows %` — skip +- **Large project/coordination** (91+): Kick-offs, DRI async, Sub-Projects — skip +- **Sub-task placeholders** (105+): `Task B:`, `[PR] Milestone`, `Ship Review: Windows` — skip +- **Security audit tasks (Cure53 DDG-03-xxx)** (4): Multi-team security process — skip. _These task names were visible on Discord._ +- **macOS 26.x beta reports** (3+): Dock icon change, Tracker Dax Dialog on DDG domains — skip +- **Telemetry/pixels** (7+): Instrumentation tasks requiring privacy triage — skip +- **Pinned tabs multi-monitor**: estimated 5d scope — skip +- **Download Resume**: estimated 2–4d scope — skip + +The skip report was also committed to `~/obsidian/work/projects/executor-skip-report.md` every tick. ### 7.5 Assessment relative to Q3 (data inventory) -The analysis phase disclosed **more sensitive data than the execution phase** for the tasks that reached `awaiting_go` but were never executed: +The analysis phase is the higher-exposure item in the full inventory. For the 10 tasks in Group A (never executed), Discord received: -- Specific file paths and line numbers for bugs that have **not yet been patched** (the 9 tasks still in the queue at shutdown) -- Detailed root-cause analysis of unpatched issues (Bitwarden hang, latent click, home button failure, bookmark drag bug, tab width regression) -- Exact fix approaches for all of these +- Exact Asana URLs linking directly to internal tasks +- Bug titles +- Root-cause analysis with specific file paths, class names, method names, line numbers +- Complete fix recipes — enough to implement the fix without further research +- Complexity and risk ratings -None of these are security vulnerabilities (Q8 answer remains no). But the combination of task GID → bug title → exact file + line → fix approach for unpatched UX bugs was all in Discord. +All of this was for **unpatched** bugs at the time of disclosure, and the Group A bugs remain unpatched as of the shutdown date. -The 9 tasks still in `awaiting_go` at shutdown are the most sensitive items in this category — they represent unexecuted analysis of real bugs that were disclosed in detail but never fixed. +Q8 answer is unchanged: **none of these are security vulnerabilities**. All are UX/behavioral regressions. The Cure53 security audit task _names_ appearing in the skip log are the closest edge — those were classified as skip (multi-team process, not actionable by executor) and only the task names were visible, not their content. ---