Skip to content

Full project audit + fixes for all High/Medium findings#6

Merged
Robertg761 merged 8 commits into
mainfrom
claude/project-audit-cpwfp8
Jul 19, 2026
Merged

Full project audit + fixes for all High/Medium findings#6
Robertg761 merged 8 commits into
mainfrom
claude/project-audit-cpwfp8

Conversation

@Robertg761

Copy link
Copy Markdown
Owner

Summary

A full audit of the project (report committed as docs/AUDIT-2026-07-17.md), followed by fixes for every High and Medium finding, one commit per area:

  1. Audit report — 1 critical, 9 high, ~20 medium findings with file:line references, verified-sound areas, and test-coverage gaps.
  2. Pop.Core — fail-soft settings load (broad exception handling + corrupt-file backup to *.corrupt), value clamping via AppSettings.Normalized(), serialized saves, diagnostics formatter no longer throws on colliding truncated keys, Ctrl vertical-flick no longer produces a horizontal snap, threading contracts documented. New tests for each behavior.
  3. Windows host — real PerMonitorV2 DPI awareness via app.manifest (the csproj property had no effect on WPF); window moves via SetWindowPos + SWP_ASYNCWINDOWPOS so a hung target app can't freeze system mouse input and get the LL hook silently removed; single-instance mutex; global exception handlers logging to host-errors.log; UpdateService.Dispose race fixed; IStartupRegistration now reports success/read-back.
  4. macOS hostcritical fix: ScreenCoordinator no longer double-flips coordinates (CGEvent locations and AX rects are already top-left global; only NSScreen frames are flipped, pivoting on the primary screen height). This fixes inverted title-bar hit-testing, snapped windows landing ~55 px low, and inverted vertical velocity. Also: event tap re-enables after tapDisabledByTimeout/ByUserInput; the update download is moved to a stable path synchronously before URLSession deletes its temp file; all six NativeAOT exports get exception barriers; Accessibility revocation is detected at runtime.
  5. Linux host — polling loop is exception-guarded (one throw no longer silently kills snapping); process-wide XSetErrorHandler (a window closed mid-drag can no longer exit() the process); multi-monitor support via Xinerama (gated: single-head setups keep the exact previous path; missing libXinerama degrades gracefully); restore-state dictionary race fixed; save-path crash fixed; shutdown use-after-free on the X Display fixed; missing gdbus handled; non-UI settings fields preserved on save.
  6. CI/releasepermissions: contents: read on CI, a Linux build leg (Linux host breakage is now caught at PR time), release concurrency group, Linux assets preserved on release re-runs, appimagetool pinned to an immutable tag with optional sha256 verification, AppRun exit-code bug fixed.

Not addressed (deliberately)

  • Code signing/notarization — requires certificates and repo secrets.
  • Reordering release-linux to run before the release is public — a pipeline restructure best done separately; the new CI Linux leg mitigates by blocking broken Linux builds pre-merge.
  • Low-severity audit findings (documented in the report).

Verification

Changes were written via careful static review — the authoring environment could not run .NET or Swift toolchains, so CI on this PR is the compile/test verification across ubuntu, windows, and macos legs. The macOS coordinate fix should additionally get a manual smoke test on real hardware (drag a window by its title bar, flick left/right, confirm it lands flush with the menu-bar edge) before the next release.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RhoT77dxUk7AmRRUX3XFvV


Generated by Claude Code

claude added 7 commits July 17, 2026 17:04
Consolidated audit of Pop.Core, the Windows/macOS/Linux hosts, the
release pipeline, and repo hygiene: 1 critical, 9 high, ~20 medium
findings with file:line references, verified-sound areas, test
coverage gaps, and prioritized recommendations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhoT77dxUk7AmRRUX3XFvV
…Ctrl vertical flick

- JsonSettingsStore.LoadAsync now catches IO/access errors (not just
  JsonException) and backs up corrupt settings to *.corrupt before
  falling back to defaults; SaveAsync is serialized behind a gate to
  stop concurrent writers racing File.Replace/File.Move.
- AppSettings.Normalized() clamps velocity/dominance/glide to safe
  ranges; applied on load so a hand-edited settings.json cannot defeat
  the dominance gate or produce negative animation durations.
- DiagnosticsLogFormatter builds its field map with last-writer-wins so
  keys that collide after 48-char truncation no longer throw on the
  drag hot path.
- SnapDecider rejects a Ctrl vertical flick that has no monitor
  above/below instead of snapping a horizontal half by landing X.
- WindowAnimator clamps the short-duration branch to non-negative.
- DragSession.ResetDragOrigin clears stale release state; documented
  the non-thread-safe contract on DragSession and IDragTracker.
- Added tests for corrupt-file backup, settings clamping, colliding
  diagnostic keys, and the vertical-flick rejection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhoT77dxUk7AmRRUX3XFvV
…ce, safety nets

- app.manifest declares PerMonitorV2 dpiAwareness so coordinate math is
  correct on mixed-DPI multi-monitor setups (the csproj
  ApplicationHighDpiMode property had no effect on this WPF app).
- Win32WindowMover repositions via SetWindowPos with SWP_ASYNCWINDOWPOS
  instead of MoveWindow, so a move never blocks on a hung target
  window's message loop — including the in-drag restore that runs on
  the low-level mouse hook thread, which the OS would otherwise time out
  and silently unhook.
- Added a Local\ named mutex single-instance guard in Main so a manual
  launch plus the Run-key entry no longer install two hooks/tray icons.
- Global DispatcherUnhandledException / AppDomain / unobserved-task
  handlers fail soft and append to host-errors.log instead of crashing
  the tray-only process.
- UpdateService.Dispose is idempotent and waits for the background loop;
  in-flight checks catch ObjectDisposedException; OnUpdateStateChanged
  guards a null Application.Current / shutting-down dispatcher; the
  Check-For-Updates handler swallows cancel/dispose races.
- SetWindowsHookEx marshals the Win32 error into the failure message.
- IStartupRegistration now returns success and can read back OS state;
  WindowsStartupRegistration implements both and no longer throws, and a
  failed registration write reverts the setting with a user error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhoT77dxUk7AmRRUX3XFvV
…OT barriers, AX monitor

- ScreenCoordinator no longer double-flips: CGEvent locations and AX
  window rects are already top-left global coordinates and now pass
  through unflipped; only NSScreen (Cocoa, bottom-left) frames are
  flipped, pivoting on the primary screen height. This fixes inverted
  title-bar hit-testing, wrong snap Y placement, and inverted vertical
  velocity that mis-targeted stacked monitors.
- GlobalDragTracker re-enables the CGEvent tap on tapDisabledByTimeout /
  tapDisabledByUserInput instead of going permanently deaf after one
  slow AX inspection.
- The URLSession download delegate moves the temp archive to a stable
  path synchronously before the main-actor hop, so prepareUpdate no
  longer races URLSession deleting the file (updates were failing).
- All six [UnmanagedCallersOnly] bridge exports wrap their body so a
  managed exception returns a safe default instead of fail-fasting the
  NativeAOT process.
- PopRuntimeController polls Accessibility state so a mid-session
  revocation stops tracking and updates the menu instead of showing a
  stale 'Granted'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhoT77dxUk7AmRRUX3XFvV
…ave crash

- X11PollingDragTracker wraps its loop body so an exception recovers
  instead of faulting the task and silently ending all snapping.
- X11DisplayConnection installs a process-wide XSetErrorHandler that
  swallows async protocol errors (BadWindow from a window closed
  mid-drag) instead of letting Xlib exit(); Dispose is now idempotent
  and exposes IsDisposed.
- X11WindowInspector resolves per-monitor geometry via Xinerama when
  more than one head is present (falling back to whole-display and to a
  single head, so single-monitor behaviour is unchanged and a missing
  libXinerama degrades gracefully).
- X11WindowMover checks IsDisposed under the display lock so a move in
  flight during shutdown can't send on a freed Display.
- LinuxPopHost guards _snapRestoreStates with a lock (poll thread vs
  async completion) and persists settings before adopting them in
  memory.
- SettingsWindow preserves contract fields it doesn't expose (e.g.
  LaunchAtStartup) via 'with', and its async-void Save handler catches
  exceptions instead of crashing the app.
- KWinWaylandIntegration handles a missing gdbus binary per its
  allowFailure contract instead of throwing at spawn time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhoT77dxUk7AmRRUX3XFvV
…ion, pinned tool

- ci.yml declares least-privilege 'contents: read' and now builds
  Pop.App.Linux on the ubuntu leg, so Linux host breakage is caught at
  PR time instead of only during post-release packaging.
- release.yml gains a concurrency group so two quick version bumps can't
  race the update-feed push and release-asset create/delete.
- Both release asset-cleanup steps now preserve Pop-linux-x64-* assets,
  so re-running a release for an existing tag no longer deletes the
  published Linux downloads.
- package-linux-release.sh pins appimagetool to an immutable release tag
  instead of the moving 'continuous' tag and verifies its checksum when
  APPIMAGETOOL_SHA256 is provided (warns otherwise).
- Fixed the AppRun launcher so a failed non-terminal start reports the
  real exit code instead of always 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhoT77dxUk7AmRRUX3XFvV
- WindowsStartupRegistration needs an explicit 'using System.IO;' — the
  WPF SDK removes System.IO from implicit usings, unlike the plain SDK
  projects where the same catch-filter pattern compiles.
- Suppress WFAC010 in the csproj with a comment: the analyzer suggests
  ApplicationHighDpiMode, but that only works via the WinForms
  ApplicationConfiguration bootstrap this WPF app never calls; the
  manifest dpiAwareness element is what WPF honors.
- Use Assert.Single per xUnit2013 in the new diagnostics test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhoT77dxUk7AmRRUX3XFvV

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b270de3d6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/package-linux-release.sh Outdated
APPIMAGETOOL_TAG="${APPIMAGETOOL_TAG:-13}"
curl --fail --location --retry 3 --retry-delay 2 \
"https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" \
"https://github.com/AppImage/AppImageKit/releases/download/${APPIMAGETOOL_TAG}/appimagetool-x86_64.AppImage" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point curl at an existing tag-13 asset

The Release Linux workflow invokes this script on a fresh Ubuntu runner, so this download path is taken whenever the cached tool is absent. With the default APPIMAGETOOL_TAG=13, this URL is 404 because the AppImageKit tag 13 release publishes obsolete-appimagetool-x86_64.AppImage, not appimagetool-x86_64.AppImage (checked https://github.com/AppImage/AppImageKit/releases/tag/13); curl --help all also confirms --fail fails on HTTP errors, so set -e aborts packaging before the AppImage output is produced.

Useful? React with 👍 / 👎.

Review feedback: AppImageKit is deprecated and its tag-13 release
assets were renamed with an 'obsolete-' prefix, so the previously
pinned URL can 404 on a fresh runner. Try the renamed asset first,
the pre-rename name second, and the maintained AppImage/appimagetool
repo's continuous build as a last resort, logging which source was
used. APPIMAGETOOL_URL forces a specific source and APPIMAGETOOL_SHA256
still enforces integrity of whatever is downloaded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhoT77dxUk7AmRRUX3XFvV
@Robertg761
Robertg761 merged commit bafafb9 into main Jul 19, 2026
3 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba66c004b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

notify "Pop is running. Launch output is logged to $log_path."

if "$POP_BIN" "$@" >> "$log_path" 2>&1; then
"$POP_BIN" "$@" >> "$log_path" 2>&1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep AppRun from exiting before failure handling

When the AppImage is launched without a terminal and Pop exits non-zero, the generated AppRun has set -euo pipefail, so the shell aborts immediately at this command instead of reaching exit_code=$? and the "could not start" notification below. That leaves desktop launches showing the earlier "Pop is running" notification with no failure notification or log pointer even though startup failed.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants