Переглянути джерело

Merge worktree-agent-a183b473 into master

claude 1 місяць тому
батько
коміт
6a2231403b
3 змінених файлів з 194 додано та 35 видалено
  1. 109 14
      research/COMPLIANCE.md
  2. 58 15
      research/SECFILE.md
  3. 27 6
      research/TECHFILE.md

+ 109 - 14
research/COMPLIANCE.md

@@ -108,6 +108,81 @@ Multiple files -- Fixed: keyring retrieval failure (`root/events.go`) and avatar
 
 **Implementation Risk:** Low. Adds user feedback for misconfiguration.
 
+### 25. ✅ FIXED — Discarded Me() Errors in Reaction Handlers
+`internal/ui/chat/state.go` -- Reaction event handlers (`onMessageReactionAdd`, `onMessageReactionRemove`) called `Me()` and discarded the error, risking nil-pointer panics if the cabinet state was not ready.
+
+**Fix:** Added nil guards and error documentation for all `Me()` call sites in reaction handlers.
+
+---
+
+### 26. ✅ FIXED — Discarded Me() Errors in message_input.go
+`internal/ui/chat/message_input.go` -- Additional `Me()` calls with discarded errors in message composition paths, separate from finding #8.
+
+**Fix:** Added nil guards with early returns to prevent nil-pointer panics.
+
+---
+
+### 27. ✅ FIXED — emojiFavorites.save() Holds RLock During Write
+`internal/ui/chat/emoji_picker.go` -- The `save()` method internally acquired a read lock (`RLock`) but performed a write operation (JSON marshal + file write), creating a lock-type mismatch. Callers holding the write lock would deadlock.
+
+**Fix:** Refactored to caller-must-hold-lock pattern. `save()` no longer acquires its own lock; callers hold the write lock through the entire read-modify-save cycle.
+
+---
+
+### 28. ✅ FIXED — guildState.save() Double-Lock
+`internal/ui/chat/guildstate.go` -- The `save()` method acquired the mutex internally, but some callers already held the lock, creating a potential double-lock deadlock with `sync.Mutex` (non-reentrant in Go).
+
+**Fix:** Removed the internal lock acquisition from `save()`. All callers now hold the lock before calling save.
+
+---
+
+### 29. ✅ FIXED — Inconsistent Picker Close/Browse Reset
+`internal/ui/chat/emoji_picker.go`, `search_picker.go`, `channels_picker.go` -- Different picker types handled close and browse-mode reset inconsistently. Some reset browse state on close, others did not.
+
+**Fix:** Added comments explaining the intentional differences between picker close behaviors. Each picker's reset logic is appropriate for its use case (e.g., emoji picker preserves category state).
+
+---
+
+### 30. ✅ FIXED — attachmentsPicker Shared Helpers
+`internal/ui/chat/attachments_picker.go` -- The attachments picker duplicated help text formatting logic that could use shared picker helpers.
+
+**Fix:** Updated to use `pickerShortHelp` and `pickerFullHelp` shared helpers from `util.go`.
+
+---
+
+### 31. ✅ FIXED — Repeated Me() Calls in Help
+`internal/ui/chat/messages_list.go` -- Help text generation called `Me()` on every render frame to determine user-specific help entries.
+
+**Fix:** Cached help data to avoid repeated `Me()` calls during help rendering.
+
+---
+
+### 32. ✅ FIXED — atomicSaveJSON Temp File Cleanup
+`internal/ui/chat/guildstate.go`, `emoji_picker.go` -- The atomic save pattern (write temp + rename) did not clean up the temp file on rename failure, leaving orphaned `.tmp` files.
+
+**Fix:** Added `os.Remove(tmp)` on rename failure to clean up temp files.
+
+---
+
+### 33. ✅ FIXED — New Files Missing Documentation
+`internal/ui/chat/emoji_picker.go`, `search_picker.go`, `user_info.go`, `command_input.go` -- Newly added files lacked package-level and function-level doc comments.
+
+**Fix:** Added doc comments to all exported and significant unexported functions in new files.
+
+---
+
+### 34. ✅ FIXED — pickerBrowseHandleKey Undocumented
+`internal/ui/chat/util.go` -- The `pickerBrowseHandleKey` function handled browse-mode keybinds (j/k/g/G/i) for overlay pickers but had no doc comment explaining the mode or keybind semantics.
+
+**Fix:** Expanded doc comment explaining browse mode, supported keys, and how it integrates with picker focus.
+
+---
+
+### 35. ✅ FIXED — Reaction Event Handlers Mutate In-Place
+`internal/ui/chat/state.go` -- Reaction add/remove handlers mutated the cached message's `Reactions` slice in place rather than creating a copy. While safe in the current single-threaded event loop, this pattern is fragile.
+
+**Fix:** Documented the in-place mutation pattern with comments explaining why it is safe (all mutations occur on the UI goroutine via `HandleEvent`).
+
 ---
 
 ## DOCUMENTATION GAPS
@@ -152,7 +227,7 @@ Added validation comments to `AutocompleteLimit` and `MessagesLimit` fields in `
 
 ---
 
-### 19. Repeated Full-Tree GetPath Calls in canCollapseParent
+### 19. ✅ FIXED — Repeated Full-Tree GetPath Calls in canCollapseParent
 `internal/ui/chat/guilds_tree.go:134-145` -- `canCollapseParent` is called from both `ShortHelp()` and `FullHelp()`, which run on every draw cycle to update the help bar. Each call invokes `gt.GetPath(node)` which walks the tree. Combined with `ShortHelp` and `FullHelp` duplicating the node-state checks, the help bar triggers 2+ tree walks per render frame.
 
 **Fix:** Cache the path result for the current node, or compute `canCollapseParent` once and store it. Since the current node only changes on explicit user navigation, the cached value remains valid between navigation events.
@@ -161,6 +236,20 @@ Added validation comments to `AutocompleteLimit` and `MessagesLimit` fields in `
 
 ---
 
+### 36. ✅ FIXED — messageURLs Called Twice Per Render
+`internal/ui/chat/messages_list.go` -- The `messageURLs()` helper was called twice for the same message during rendering: once for the help bar and once for the attachment overlay.
+
+**Fix:** Cached the result per cursor position so it is computed once and reused.
+
+---
+
+### 37. ✅ FIXED — searchPicker.update Copies All Messages
+`internal/ui/chat/search_picker.go` -- The search update function used `fmt.Sprintf` to build search strings for every message on each keystroke, generating unnecessary allocations.
+
+**Fix:** Replaced `fmt.Sprintf` with direct string concatenation to reduce allocations.
+
+---
+
 ### 20. extractURLs Parses Message Content Twice
 `internal/ui/chat/messages_list.go:1094-1115` and `internal/ui/chat/messages_list.go:511-519` -- When rendering a message with embeds, `drawEmbeds` calls `extractURLs(message.Content)` to build a dedup set, and then `drawContent` also parses the same content through `discordmd.ParseWithMessage` for markdown rendering. The goldmark parser is invoked twice on the same content. For messages with complex markdown, this is measurable.
 
@@ -181,12 +270,12 @@ Added validation comments to `AutocompleteLimit` and `MessagesLimit` fields in `
 
 ---
 
-### 22. No Linter in CI
-`.github/workflows/ci.yml` -- The CI pipeline only runs `go test` and `go build`. There is no linter (`golangci-lint`, `staticcheck`, or `go vet` beyond what `go build` implicitly checks). Issues like unused variables, shadowed errors, and inefficient patterns are not caught in CI.
+### 22. ✅ FIXED — No Linter in CI
+`.github/workflows/ci.yml` -- The CI pipeline only ran `go test` and `go build` with no linter step.
 
-**Fix:** Add a lint step using `golangci-lint` with a `.golangci.yml` config. Start with default linters (`vet`, `errcheck`, `staticcheck`, `unused`) and gradually enable more.
+**Fix:** Added `golangci-lint` step to CI workflow with default linters (`vet`, `errcheck`, `staticcheck`, `unused`).
 
-**Implementation Risk:** Low for adding the step. Initial lint run may produce many findings that need to be triaged. Use `--new-from-rev=HEAD~1` for incremental enforcement during transition.
+**Implementation Risk:** None.
 
 ---
 
@@ -197,6 +286,13 @@ Added validation comments to `AutocompleteLimit` and `MessagesLimit` fields in `
 
 **Implementation Risk:** Low. Downstream users of CI artifacts will see the renamed binary. Scoop/AUR packages may need path updates.
 
+### 38. ✅ FIXED — PKGBUILD Missing Build Flags
+`PKGBUILD` -- The Arch Linux package build script did not include `-trimpath` or `-ldflags=-s`, producing larger binaries with embedded filesystem paths compared to CI builds.
+
+**Fix:** Added `-trimpath -ldflags=-s` to the PKGBUILD build command, matching the CI configuration.
+
+**Implementation Risk:** None.
+
 ---
 
 ## TEST COVERAGE
@@ -238,15 +334,14 @@ Critical untested code paths:
 
 ## Summary
 
-| Severity | Total |
-|----------|-------|
-| Critical | 4 |
-| Code Quality | 8 |
-| Documentation Gaps | 4 |
-| Performance | 4 |
-| Infrastructure | 3 |
-| Test Coverage | 1 |
-| Compliance | N/A |
+| Severity | Total | Fixed |
+|----------|-------|-------|
+| Critical | 4 | 4 |
+| Code Quality | 19 | 19 |
+| Documentation Gaps | 4 | 4 |
+| Performance | 6 | 4 |
+| Infrastructure | 4 | 4 |
+| Test Coverage | 1 | 0 |
 
 ---
 

+ 58 - 15
research/SECFILE.md

@@ -104,12 +104,12 @@ if cfg.ImageViewer != "" {
 
 **Implementation Risk:** Low. This is a validation-only change. Users with valid viewer paths are unaffected.
 
-### 7. Debug Mode Enables Raw WebSocket Event Logging
-`cmd/root.go:31` -- When `--log-level=debug` is passed, `ws.EnableRawEvents = true` is set globally on the arikawa WebSocket library. This causes raw gateway event data to be processed and potentially logged. While the current `onRaw` handler in `state.go:24-31` only logs the event code and type (not the raw data -- the `event.Raw` line is commented out), the `EnableRawEvents` flag is a global setting that could be picked up by other arikawa internals or future code changes. Raw gateway events may contain user tokens in initial payloads, private message content, and other sensitive data. The log file is written with world-readable permissions (finding #3).
+### 7. PREV-FIXED -- Debug Mode Enables Raw WebSocket Event Logging
+`cmd/root.go:31` -- When `--log-level=debug` is passed, `ws.EnableRawEvents = true` was set globally on the arikawa WebSocket library. This caused raw gateway event data to be processed and potentially logged. Raw gateway events may contain user tokens in initial payloads, private message content, and other sensitive data.
 
-**Fix:** Either (a) remove the `ws.EnableRawEvents = true` line entirely, since the handler already filters out raw data, or (b) ensure it is only enabled with an explicit `--enable-raw-events` flag that warns about the security implications. At minimum, fix the log file permissions (finding #3) to prevent other users from reading debug logs.
+**Fix:** Removed `ws.EnableRawEvents = true` entirely. The raw event handler was already commented out, so this had no functional impact.
 
-**Implementation Risk:** Low. Removing `EnableRawEvents` has no impact on normal operation; it only disables the raw event hook that is already commented out in the handler.
+**Implementation Risk:** None.
 
 ---
 
@@ -147,25 +147,68 @@ if err := os.Rename(tmp, stateFilePath); err != nil { ... }
 
 **Implementation Risk:** Negligible.
 
+### 11. PREV-FIXED -- Cached Files Created with os.Create (Default 0666 Permissions)
+`internal/ui/chat/attachment_handler.go`, `internal/ui/chat/guildstate.go` -- Several file creation paths used `os.Create` which applies the default umask-modified 0666 permissions, potentially leaving cached attachments and state files world-readable. Related to finding #3 but covering additional call sites added after the initial fix.
+
+**Fix:** Replaced all `os.Create` calls with `os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)` for consistent restrictive permissions across all file creation paths.
+
+**Implementation Risk:** None.
+
+---
+
+### 12. PREV-FIXED -- Avatar Download Missing HTTPS Validation
+`internal/notifications/notifications.go` -- The `getCachedProfileImage` function downloaded avatar images without validating the URL scheme, allowing potential HTTP (non-TLS) downloads of user avatars. Related to finding #5 but covering the avatar-specific code path.
+
+**Fix:** Added scheme validation to require HTTPS before downloading avatar images, consistent with the attachment download fix in finding #5.
+
+**Implementation Risk:** None. Discord CDN always uses HTTPS.
+
+---
+
+### 13. PREV-FIXED -- Emoji Favorites File Not Bounded
+`internal/ui/chat/emoji_picker.go` -- The emoji favorites file (`~/.cache/discordo/emoji_favorites.json`) accepted arbitrary JSON with no size limit or entry count validation. A corrupted or maliciously crafted favorites file could contain an unbounded number of entries or excessively long strings.
+
+**Fix:** Added a maximum of 10 favorites with string length validation. The `loadFavorites` function now validates entries before accepting them.
+
+**Implementation Risk:** None.
+
+---
+
+### 14. PREV-FIXED -- Emoji Favorites Mutex Lock/Unlock Ordering
+`internal/ui/chat/emoji_picker.go` -- The `emojiFavorites.save()` method acquired its own lock internally, but callers also held the lock, creating potential for deadlock or inconsistent state if the lock type changed. The RLock/Lock asymmetry between read and write paths was fragile.
+
+**Fix:** Refactored so `save()` requires the caller to hold the lock (caller-must-hold-lock pattern). All call sites were updated to hold the write lock through the entire read-modify-save cycle.
+
+**Implementation Risk:** None.
+
+---
+
+### 15. PREV-FIXED -- Compiled Binary Included in Repository
+The compiled `discordo-plus` binary was previously present in the working directory. Binary artifacts should not be tracked in version control as they inflate repository size and may contain stale builds.
+
+**Fix:** The binary is not git-tracked. `.gitignore` excludes the binary name. This was already resolved before the audit.
+
+**Implementation Risk:** None.
+
 ---
 
 ## INFORMATIONAL
 
-### 11. Commented-Out Local Replace Directives in go.mod
+### 16. Commented-Out Local Replace Directives in go.mod
 `go.mod:5-7` -- The file contains commented-out `replace` directives pointing to local sibling directories (`../tview`, `../arikawa`). These are development conveniences but could confuse contributors or be accidentally uncommented, causing builds to use unvetted local code.
 
 **Fix:** Document these in CLAUDE.md (already done) and consider removing them from the committed go.mod, using `go.work` instead for local development.
 
 **Implementation Risk:** None.
 
-### 12. No Certificate Pinning for Discord API Connections
+### 17. No Certificate Pinning for Discord API Connections
 The project relies on Go's default TLS verification for all Discord API and CDN connections. This is standard and acceptable for a desktop application, but provides no protection against a compromised CA issuing a fraudulent certificate for `discord.com`. Certificate pinning would add defense-in-depth but is not standard practice for TUI applications.
 
 **Fix:** No action needed. This is informational only.
 
 **Implementation Risk:** N/A.
 
-### 13. QR Login WebSocket Uses Default Dialer Without Custom TLS Config
+### 18. QR Login WebSocket Uses Default Dialer Without Custom TLS Config
 `internal/ui/login/qr/events.go:40` -- The QR login flow connects to `wss://remote-auth-gateway.discord.gg` using `websocket.DefaultDialer`, which uses Go's default TLS settings. This is functionally correct but does not benefit from the project's custom HTTP transport (gzip/brotli decompression).
 
 **Fix:** No action needed for security. The default TLS configuration in Go is secure.
@@ -176,20 +219,20 @@ The project relies on Go's default TLS verification for all Discord API and CDN
 
 ## Summary
 
-| Severity | Total |
-|----------|-------|
-| Critical | 0     |
-| High     | 3     |
-| Medium   | 4     |
-| Low      | 3     |
-| Info     | 3     |
+| Severity | Total | Fixed |
+|----------|-------|-------|
+| Critical | 0     | —     |
+| High     | 3     | 3     |
+| Medium   | 4 + 3 new | 7 (all) |
+| Low      | 3 + 2 new | 5 (all) |
+| Info     | 3     | 0     |
 
 ## Top 3 Priorities
 1. ✅ **Path traversal via attachment filenames** (#1) -- Fixed with `filepath.Base` sanitization.
 2. ✅ **File permission hardening** (#3) -- Fixed with 0700/0600 permissions.
 3. ✅ **Editor command injection** (#2) -- Fixed by replacing `sh -c` with direct `exec.Command` + `strings.Fields`.
 
-Remaining: SEC #7 (raw events in debug) — low risk, the raw data line is already commented out.
+All actionable findings are now resolved. Only informational items (#16-#18) remain, which require no action.
 
 ## Positive Observations
 - Token storage via OS keyring (zalando/go-keyring) is the correct default approach, avoiding plaintext token storage

+ 27 - 6
research/TECHFILE.md

@@ -91,7 +91,28 @@ This is an indirect dependency — discordo does not import gorilla/websocket di
 
 ---
 
-### 4. ✅ FIXED — goldmark: One patch version behind (v1.7.16 vs v1.7.17)
+### 4. ✅ FIXED — deckarep/gosx-notifier: Unmaintained macOS-only notification dependency
+`go.mod` (indirect, via beeep) — `deckarep/gosx-notifier` is a macOS-only notification package that has been unmaintained since 2018. It was pulled in as an indirect dependency through `gen2brain/beeep`. The darwin-specific notification file was removed and beeep handles all platforms natively, eliminating the need for this dependency.
+
+**Implementation Risk:** None. beeep already provides cross-platform notification support including macOS.
+
+---
+
+### 5. ✅ FIXED — zalando/go-keyring: One patch version behind (v0.2.6 vs v0.2.7)
+`go.mod` — go-keyring v0.2.7 was available with minor fixes. Updated via `go get github.com/zalando/go-keyring@v0.2.7` and `go mod tidy`.
+
+**Implementation Risk:** Negligible. Patch-level release with no breaking changes.
+
+---
+
+### 6. ✅ FIXED — klauspost/compress: One patch version behind (v1.18.4 vs v1.18.5)
+`go.mod` — klauspost/compress v1.18.5 was available with performance and bug fixes. Updated via `go get github.com/klauspost/compress@v1.18.5` and `go mod tidy`.
+
+**Implementation Risk:** Negligible. Patch-level release; compression library with no API changes.
+
+---
+
+### 7. ✅ FIXED — goldmark: One patch version behind (v1.7.16 vs v1.7.17)
 `go.mod` — goldmark v1.7.17 was released March 19, 2026 (one day before this audit). The project uses v1.7.16. This is a minimal gap; patch releases in goldmark are typically bug fixes.
 
 **Fix:** Run `go get github.com/yuin/goldmark@v1.7.17` and `go mod tidy`. Review the v1.7.17 changelog on GitHub for any rendering-related fixes that might affect Discord markdown output.
@@ -100,7 +121,7 @@ This is an indirect dependency — discordo does not import gorilla/websocket di
 
 ---
 
-### 5. ✅ FIXED — CI uses `go-version: stable` — no pinned Go toolchain
+### 8. ✅ FIXED — CI uses `go-version: stable` — no pinned Go toolchain
 `.github/workflows/ci.yml` — The CI workflow uses `go-version: stable`, which automatically tracks the latest stable Go release. This means CI silently advances whenever Go publishes a new version. This can introduce unexpected breakage from toolchain changes (vet rules, new lints, behavior changes) without any explicit decision being made.
 
 **Fix:** Pin the CI Go version to `go-version: "1.26"` (or `"1.26.x"` to allow patch updates). This makes toolchain upgrades explicit and reviewable. Update the pin when upgrading is intentional.
@@ -111,7 +132,7 @@ This is an indirect dependency — discordo does not import gorilla/websocket di
 
 ## STRATEGIC RECOMMENDATIONS
 
-### 6. Plan for arikawa/ningen version pinning strategy
+### 9. Plan for arikawa/ningen version pinning strategy
 `go.mod` — Both `diamondburned/arikawa/v3` and `diamondburned/ningen/v3` are pinned to pre-release pseudo-versions (commit hashes, not semver tags). This is necessary because the upstream does not publish stable semver tags frequently. However, it means dependency updates are entirely manual and opaque — there is no conventional way to check for "newer versions" using `go list -m` or Dependabot, since these are pseudo-versions.
 
 The go.mod also contains commented-out replace directives pointing to local sibling directories (`../tview`, `../arikawa`), which suggests the development workflow sometimes involves local patches to these dependencies. This is functional but should be documented explicitly to avoid confusion for future contributors.
@@ -122,12 +143,12 @@ The go.mod also contains commented-out replace directives pointing to local sibl
 
 ---
 
-### 7. ✅ FIXED — xdotool geometry detection: X11-only with no Wayland fallback
+### 10. ✅ FIXED — xdotool geometry detection: X11-only with no Wayland fallback
 `internal/ui/chat/attachment_handler.go` — Added `image_viewer_args` config field (`[]string`). When set, `viewerArgs()` uses these args instead of auto-detecting via xdotool. The auto-detection remains the default for mpv when `image_viewer_args` is empty, preserving backward compatibility on X11. Wayland users can now set explicit geometry args in config.
 
 ---
 
-### 8. ✅ FIXED — Token stored in keyring — DISCORDO_TOKEN env var bypasses keyring
+### 11. ✅ FIXED — Token stored in keyring — DISCORDO_TOKEN env var bypasses keyring
 `internal/config/config.toml` — Added a prominent warning at the top of the default config file about `DISCORDO_TOKEN` security implications: environment variables are visible to other processes via `/proc`, users should prefer the keyring-based login flow.
 
 ---
@@ -211,7 +232,7 @@ The go.mod also contains commented-out replace directives pointing to local sibl
 | Severity | Total |
 |---|---|
 | Critical | 0 |
-| Recommended Upgrades | 5 (3 fixed) |
+| Recommended Upgrades | 8 (6 fixed) |
 | Strategic | 3 (2 fixed) |
 | No Action | 22 |