From d1e4e8748abb5d0da53a495c088ac1315935f611 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Wed, 9 Sep 2026 13:51:40 -0400 Subject: [PATCH 1/3] test against latest go --- .github/workflows/ci.yml | 12 ++++++------ go.mod | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85169a85..539c64bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,29 +23,29 @@ jobs: timeout-minutes: 15 strategy: matrix: - go-version: [1.25.x] + go-version: [1.26.x, 1.27.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7.0.0 with: go-version: ${{ matrix.go-version }} cache: true - name: golangci-lint - uses: golangci/golangci-lint-action@v9 + uses: golangci/golangci-lint-action@v9.3.0 with: - version: v2.7.2 + version: v2.13.2 - name: Build run: go build -v ./... - name: Run govulncheck - uses: golang/govulncheck-action@v1 + uses: golang/govulncheck-action@v1.1.0 with: go-version-input: ${{ matrix.go-version }} diff --git a/go.mod b/go.mod index 53d719d0..36402831 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/keybase/managed-bots -go 1.25.0 +go 1.26.0 -toolchain go1.25.5 +toolchain go1.27.1 require ( github.com/aws/aws-sdk-go-v2 v1.42.1 From cefa64491b60350cefea6aa429ebf7e9938c11c0 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Wed, 9 Sep 2026 13:51:46 -0400 Subject: [PATCH 2/3] run go fix --- base/git/git.go | 2 +- base/oauth.go | 3 +-- elastiwatch/elastiwatch/logs.go | 2 +- gitlabbot/gitlabbot/signing.go | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/base/git/git.go b/base/git/git.go index 46b535c6..190bbdc2 100644 --- a/base/git/git.go +++ b/base/git/git.go @@ -60,7 +60,7 @@ func FormatPushMsg(username string, repo string, branch string, numCommits int, } func formatCommitString(commit string, maxLen int) string { - firstLine := strings.Split(commit, "\n")[0] + firstLine, _, _ := strings.Cut(commit, "\n") if len(firstLine) > maxLen { firstLine = strings.TrimSpace(firstLine[:maxLen]) + "..." } diff --git a/base/oauth.go b/base/oauth.go index 519c5660..a61ad24b 100644 --- a/base/oauth.go +++ b/base/oauth.go @@ -101,8 +101,7 @@ func (o *OAuthHTTPSrv) getCallbackMsg(req OAuthRequest) (res chat1.MsgSummary, e // LogOAuthError logs an OAuth error, scrubbing any raw token-endpoint response // body. ErrorCode and ErrorDescription from structured OAuth errors are retained. func LogOAuthError(debug *DebugOutput, context string, err error) { - var retrieveErr *oauth2.RetrieveError - if errors.As(err, &retrieveErr) { + if retrieveErr, ok := errors.AsType[*oauth2.RetrieveError](err); ok { statusCode := 0 if retrieveErr.Response != nil { statusCode = retrieveErr.Response.StatusCode diff --git a/elastiwatch/elastiwatch/logs.go b/elastiwatch/elastiwatch/logs.go index 105fd0e6..4dcf10df 100644 --- a/elastiwatch/elastiwatch/logs.go +++ b/elastiwatch/elastiwatch/logs.go @@ -172,7 +172,7 @@ func (l *LogWatch) runOnce() { Params: opensearchapi.SearchParams{ Query: `NOT severity:debug AND @timestamp:[now-1m TO now]`, Sort: []string{"@timestamp:desc"}, - Size: opensearchapi.ToPointer(10000), + Size: new(10000), Pretty: true, }, }) diff --git a/gitlabbot/gitlabbot/signing.go b/gitlabbot/gitlabbot/signing.go index 80fd1d4f..8ee3b218 100644 --- a/gitlabbot/gitlabbot/signing.go +++ b/gitlabbot/gitlabbot/signing.go @@ -111,7 +111,7 @@ func verifyWebhookSignature( _, _ = mac.Write(payload) expected := []byte("v1," + base64.StdEncoding.EncodeToString(mac.Sum(nil))) - for _, signature := range strings.Fields(signatures) { + for signature := range strings.FieldsSeq(signatures) { if hmac.Equal([]byte(signature), expected) { return nil } From cb32d364075b03b89774a26ff9c1508f39a4ea6b Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Wed, 9 Sep 2026 13:58:58 -0400 Subject: [PATCH 3/3] fix lint issues - Add nolint directives for legitimate gosec findings - Replace WriteString(fmt.Sprintf(...)) with fmt.Fprintf for efficiency - All lint checks now pass cleanly --- elastiwatch/elastiwatch/handler.go | 2 +- elastiwatch/elastiwatch/logs.go | 4 ++-- gcalbot/gcalbot/event.go | 8 ++++---- gcalbot/gcalbot/webhook.go | 2 +- gitlabbot/gitlabbot/handler.go | 2 +- pollbot/pollbot/handler.go | 8 ++++---- triviabot/triviabot/session.go | 1 + webhookbot/webhookbot/handler.go | 2 +- zoombot/main.go | 1 + zoombot/zoombot/api.go | 1 + 10 files changed, 17 insertions(+), 14 deletions(-) diff --git a/elastiwatch/elastiwatch/handler.go b/elastiwatch/elastiwatch/handler.go index 86b47bb3..0205f319 100644 --- a/elastiwatch/elastiwatch/handler.go +++ b/elastiwatch/elastiwatch/handler.go @@ -64,7 +64,7 @@ func (h *Handler) handleDeferrals(ctx context.Context, convID chat1.ConvIDStr, _ return nil } for _, d := range deferrals { - body.WriteString(fmt.Sprintf("id: %d author: %s regex: %s (created: %v)\n", d.ID, d.Author, d.Regex, d.Ctime)) + fmt.Fprintf(&body, "id: %d author: %s regex: %s (created: %v)\n", d.ID, d.Author, d.Regex, d.Ctime) } h.ChatEcho(convID, "%s", body.String()) return nil diff --git a/elastiwatch/elastiwatch/logs.go b/elastiwatch/elastiwatch/logs.go index 4dcf10df..4b3455d9 100644 --- a/elastiwatch/elastiwatch/logs.go +++ b/elastiwatch/elastiwatch/logs.go @@ -81,12 +81,12 @@ func (l *LogWatch) alertFromChunk(c chunk) { func (l *LogWatch) alertEmail(subject string, chunks []chunk) { var body strings.Builder - body.WriteString(fmt.Sprintf("Email sent: %s", subject)) + fmt.Fprintf(&body, "Email sent: %s", subject) for _, c := range chunks { if c.Severity == "INFO" { continue } - body.WriteString(fmt.Sprintf("\n%s %d %s", c.Severity, c.Count, c.Message)) + fmt.Fprintf(&body, "\n%s %d %s", c.Severity, c.Count, c.Message) } l.ChatEcho(l.emailConvID, "```%s```", body.String()) } diff --git a/gcalbot/gcalbot/event.go b/gcalbot/gcalbot/event.go index b41f51f7..7350f3da 100644 --- a/gcalbot/gcalbot/event.go +++ b/gcalbot/gcalbot/event.go @@ -73,14 +73,14 @@ func FormatEvent( uri := strings.TrimPrefix(entryPoint.Uri, "https://") switch entryPoint.EntryPointType { case "video", "more": - conferenceData.WriteString(fmt.Sprintf("\n> Join online: %s", uri)) + fmt.Fprintf(&conferenceData, "\n> Join online: %s", uri) case "phone": - conferenceData.WriteString(fmt.Sprintf("\n> Join by phone: %s", entryPoint.Label)) + fmt.Fprintf(&conferenceData, "\n> Join by phone: %s", entryPoint.Label) if entryPoint.Pin != "" { - conferenceData.WriteString(fmt.Sprintf(" PIN: %s", entryPoint.Pin)) + fmt.Fprintf(&conferenceData, " PIN: %s", entryPoint.Pin) } case "sip": - conferenceData.WriteString(fmt.Sprintf("\n> Join by SIP: %s", entryPoint.Label)) + fmt.Fprintf(&conferenceData, "\n> Join by SIP: %s", entryPoint.Label) } } } diff --git a/gcalbot/gcalbot/webhook.go b/gcalbot/gcalbot/webhook.go index 91a74f62..e471d080 100644 --- a/gcalbot/gcalbot/webhook.go +++ b/gcalbot/gcalbot/webhook.go @@ -335,7 +335,7 @@ func (h *Handler) createEventChannel(ctx context.Context, account *Account, cale // pre-fill db with invites so we don't send old invites // there could be a race since this process can take up to a few seconds - // context.Background() because syncAllInvites is a background goroutine that outlives the request + //nolint:gosec // G118: Background context intentional - syncAllInvites is a background goroutine that outlives the request go h.syncAllInvites(account, srv, channelID, calendarID) return nil diff --git a/gitlabbot/gitlabbot/handler.go b/gitlabbot/gitlabbot/handler.go index c4663859..c9f39c45 100644 --- a/gitlabbot/gitlabbot/handler.go +++ b/gitlabbot/gitlabbot/handler.go @@ -162,7 +162,7 @@ func (h *Handler) handleListSubscriptions(ctx context.Context, msg chat1.MsgSumm var res strings.Builder for _, repo := range subscriptions { - res.WriteString(fmt.Sprintf("- *%s*\n", repo)) + fmt.Fprintf(&res, "- *%s*\n", repo) } h.ChatEcho(msg.ConvID, "%s", res.String()) return nil diff --git a/pollbot/pollbot/handler.go b/pollbot/pollbot/handler.go index 17504024..e4fbfc17 100644 --- a/pollbot/pollbot/handler.go +++ b/pollbot/pollbot/handler.go @@ -61,8 +61,8 @@ func (h *Handler) generateAnonymousPoll(ctx context.Context, convID chat1.ConvID promptMsgID := *sendRes.Result.MessageID var body strings.Builder for index, option := range options { - body.WriteString(fmt.Sprintf("\n%s *%s*\n%s\n", base.NumberToEmoji(index+1), option, - h.generateVoteLink(id, index+1))) + fmt.Fprintf(&body, "\n%s *%s*\n%s\n", base.NumberToEmoji(index+1), option, + h.generateVoteLink(id, index+1)) } h.ChatEcho(convID, "%s", body.String()) if sendRes, err = h.kbc.SendMessageByConvID(convID, "*Results*\n_No votes yet_"); err != nil { @@ -80,9 +80,9 @@ func (h *Handler) generateAnonymousPoll(ctx context.Context, convID chat1.ConvID func (h *Handler) generatePoll(convID chat1.ConvIDStr, prompt string, options []string) error { var body strings.Builder - body.WriteString(fmt.Sprintf("Poll: *%s*\n\n", prompt)) + fmt.Fprintf(&body, "Poll: *%s*\n\n", prompt) for index, option := range options { - body.WriteString(fmt.Sprintf("%s %s\n", base.NumberToEmoji(index+1), option)) + fmt.Fprintf(&body, "%s %s\n", base.NumberToEmoji(index+1), option) } body.WriteString("Tap a reaction below to register your vote!") sendRes, err := h.kbc.SendMessageByConvID(convID, "%s", body.String()) diff --git a/triviabot/triviabot/session.go b/triviabot/triviabot/session.go index 6b15e8a9..b330894f 100644 --- a/triviabot/triviabot/session.go +++ b/triviabot/triviabot/session.go @@ -46,6 +46,7 @@ type question struct { func newQuestion(aq apiQuestion) question { a := append([]string{aq.CorrectAnswer}, aq.IncorrectAnswers...) + //nolint:gosec // G404: Using math/rand for trivia game answer shuffling, not cryptography rand.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] }) correctAnswer := 0 for index, answer := range a { diff --git a/webhookbot/webhookbot/handler.go b/webhookbot/webhookbot/handler.go index d22acdee..44b5b00a 100644 --- a/webhookbot/webhookbot/handler.go +++ b/webhookbot/webhookbot/handler.go @@ -99,7 +99,7 @@ func (h *Handler) handleList(ctx context.Context, _ string, msg chat1.MsgSummary } var body strings.Builder for _, hook := range hooks { - body.WriteString(fmt.Sprintf("%s, %s\n", hook.Name, h.formURL(hook.ID))) + fmt.Fprintf(&body, "%s, %s\n", hook.Name, h.formURL(hook.ID)) } if _, err := h.kbc.SendMessageByTlfName(msg.Sender.Username, "%s", body.String()); err != nil { h.Debug("handleList: failed to send hook: %s", err) diff --git a/zoombot/main.go b/zoombot/main.go index 15b739a8..1237b3e2 100644 --- a/zoombot/main.go +++ b/zoombot/main.go @@ -118,6 +118,7 @@ func (s *BotServer) Go() (err error) { config := &oauth2.Config{ ClientID: credentials.ClientID, ClientSecret: credentials.ClientSecret, + //nolint:gosec // G101: False positive - these are public OAuth endpoint URLs, not credentials Endpoint: oauth2.Endpoint{ AuthURL: "https://zoom.us/oauth/authorize", TokenURL: "https://zoom.us/oauth/token", diff --git a/zoombot/zoombot/api.go b/zoombot/zoombot/api.go index c4f13ed7..e062b0f3 100644 --- a/zoombot/zoombot/api.go +++ b/zoombot/zoombot/api.go @@ -182,6 +182,7 @@ func GetUser(client *http.Client, userID string) (*GetUserResponse, error) { func CreateMeeting(client *http.Client, userID string, request *CreateMeetingRequest) (*CreateMeetingResponse, error) { apiURL := fmt.Sprintf("%s/users/%s/meetings", apiBaseURLV2, userID) + //nolint:gosec // G117: False positive - Password field is a legitimate Zoom API meeting password field, not hardcoded credentials payload, err := json.Marshal(request) if err != nil { return nil, err