From 4178940d392ca8536378dacfbb5ea28f6de5cdaf Mon Sep 17 00:00:00 2001 From: gastown/crew/gus Date: Tue, 30 Dec 2025 23:23:12 -0800 Subject: [PATCH] Fix SQL injection and refresh scheduling in convoy panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add convoyID validation with regex pattern ^hq-[a-zA-Z0-9-]+$ to prevent SQL injection in getTrackedIssueStatus (gt-ur4c4) - Fix duplicate refresh scheduling: tick schedules fetch, fetch schedules next tick (gt-yqfrx) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/tui/feed/convoy.go | 10 ++++++++++ internal/tui/feed/model.go | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/tui/feed/convoy.go b/internal/tui/feed/convoy.go index 94f499b5..32390b57 100644 --- a/internal/tui/feed/convoy.go +++ b/internal/tui/feed/convoy.go @@ -6,6 +6,7 @@ import ( "fmt" "os/exec" "path/filepath" + "regexp" "sort" "strings" "time" @@ -13,6 +14,9 @@ import ( "github.com/charmbracelet/lipgloss" ) +// convoyIDPattern validates convoy IDs to prevent SQL injection +var convoyIDPattern = regexp.MustCompile(`^hq-[a-zA-Z0-9-]+$`) + // Convoy represents a convoy's status for the dashboard type Convoy struct { ID string `json:"id"` @@ -145,9 +149,15 @@ type trackedStatus struct { // getTrackedIssueStatus queries tracked issues and their status func getTrackedIssueStatus(beadsDir, convoyID string) []trackedStatus { + // Validate convoyID to prevent SQL injection + if !convoyIDPattern.MatchString(convoyID) { + return nil + } + dbPath := filepath.Join(beadsDir, "beads.db") // Query tracked dependencies from SQLite + // convoyID is validated above to match ^hq-[a-zA-Z0-9-]+$ cmd := exec.Command("sqlite3", "-json", dbPath, fmt.Sprintf(`SELECT depends_on_id FROM dependencies WHERE issue_id = '%s' AND type = 'tracks'`, convoyID)) diff --git a/internal/tui/feed/model.go b/internal/tui/feed/model.go index 26ae107d..8767c30b 100644 --- a/internal/tui/feed/model.go +++ b/internal/tui/feed/model.go @@ -190,11 +190,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case convoyUpdateMsg: if msg.state != nil { + // Fresh data arrived - update state and schedule next tick m.convoyState = msg.state m.updateViewContent() + cmds = append(cmds, m.convoyRefreshTick()) + } else { + // Tick fired - fetch new data + cmds = append(cmds, m.fetchConvoys()) } - // Schedule next refresh - cmds = append(cmds, m.fetchConvoys(), m.convoyRefreshTick()) case tickMsg: cmds = append(cmds, tick())