From befefd86d6416f8a39beb816874cbb168d8bb833 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Fri, 12 Dec 2025 11:21:18 -0500 Subject: [PATCH 1/3] go1.25 --- .github/workflows/ci.yml | 41 +++++++++++++--- .golangci.yml | 73 ++++++++++++++++++++++++---- bot.go | 15 ++++-- cli/cli.go | 4 +- command.go | 1 + config.go | 7 +-- go.mod | 4 +- hybrid.go | 1 - keybot/winbot.go | 100 ++++++++++++++++++++++++++++++--------- launchd/command.go | 4 ++ launchd/plist.go | 6 ++- tuxbot/tuxbot.go | 18 +++++-- 12 files changed, 217 insertions(+), 57 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index faf05df..47d04f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,5 @@ name: CI + on: push: branches: @@ -6,21 +7,47 @@ on: pull_request: branches: - master + schedule: + # Run daily at 2 AM UTC to check for new vulnerabilities + - cron: "0 2 * * *" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: + timeout-minutes: 15 strategy: matrix: - go-version: [1.23.x] + go-version: [1.25.x, 1.24.x, 1.23.x] os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/setup-go@v3 + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} - - uses: actions/checkout@v3 + cache: true + - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v9 + with: + version: v2.7.2 + + - name: Build + run: go build -v ./... + + - name: Run govulncheck + uses: golang/govulncheck-action@v1 with: - version: v1.63 - - run: go vet ./... - - run: go test ./... + go-version-input: ${{ matrix.go-version }} + + - name: Test + run: go test -race ./... diff --git a/.golangci.yml b/.golangci.yml index e467d8c..786d611 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,66 @@ -linters-settings: - gocritic: - disabled-checks: - - ifElseChain - - elseif +version: "2" + +run: + timeout: 5m + tests: true + +formatters: + enable: + - gofumpt linters: enable: - - gofmt - - govet - - gocritic - - unconvert - - revive + # Core recommended linters + - errcheck # Checks for unchecked errors + - govet # Go vet checks + - ineffassign # Detects ineffectual assignments + - staticcheck # Advanced static analysis + - unused # Finds unused code + + # Code quality + - misspell # Finds commonly misspelled words + - unconvert # Unnecessary type conversions (already enabled in original) + - unparam # Finds unused function parameters + - gocritic # Various checks (already enabled in original) + - revive # Fast, configurable linter (already enabled in original) + + # Security and best practices + - gosec # Security-focused linter + - bodyclose # Checks HTTP response body closed + - noctx # Finds HTTP requests without context + + settings: + gocritic: + disabled-checks: + - ifElseChain + - elseif + + govet: + enable-all: true + disable: + - shadow + - fieldalignment + + revive: + enable-all-rules: false + + exclusions: + rules: + # Exclude specific revive rules + - linters: + - revive + text: "package-comments" + + - linters: + - revive + text: "exported" + + # Exclude specific staticcheck rules + - linters: + - staticcheck + text: "ST1005" + + # Exclude specific gocritic rules + - linters: + - gocritic + text: "ifElseChain" diff --git a/bot.go b/bot.go index 3ee6f8e..6d93452 100644 --- a/bot.go +++ b/bot.go @@ -69,12 +69,21 @@ func (b *Bot) HelpMessage() string { w := new(tabwriter.Writer) buf := new(bytes.Buffer) w.Init(buf, 8, 8, 8, ' ', 0) - fmt.Fprintln(w, "Command\tDescription") + if _, err := fmt.Fprintln(w, "Command\tDescription"); err != nil { + log.Printf("Error writing help header: %s", err) + return "Error generating help message" + } for _, trigger := range b.triggers() { command := b.commands[trigger] - fmt.Fprintf(w, "%s\t%s\n", trigger, command.Description()) + if _, err := fmt.Fprintf(w, "%s\t%s\n", trigger, command.Description()); err != nil { + log.Printf("Error writing help command: %s", err) + return "Error generating help message" + } + } + if err := w.Flush(); err != nil { + log.Printf("Error flushing help writer: %s", err) + return "Error generating help message" } - _ = w.Flush() return BlockQuote(buf.String()) } diff --git a/cli/cli.go b/cli/cli.go index 9290385..5246b13 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -36,7 +36,9 @@ func Parse(app *kingpin.Application, args []string, stringBuffer *bytes.Buffer) if err != nil && stringBuffer.Len() == 0 { log.Printf("Error in parsing command: %s. got %s", args, err) - _, _ = io.WriteString(stringBuffer, fmt.Sprintf("I don't know what you mean by `%s`.\nError: `%s`\nHere's my usage:\n\n", strings.Join(args, " "), err.Error())) + if _, writeErr := io.WriteString(stringBuffer, fmt.Sprintf("I don't know what you mean by `%s`.\nError: `%s`\nHere's my usage:\n\n", strings.Join(args, " "), err.Error())); writeErr != nil { + log.Printf("Error writing error message: %s", writeErr) + } // Print out help page if there was an error parsing command app.Usage([]string{}) } diff --git a/command.go b/command.go index 0a7a083..449a3c6 100644 --- a/command.go +++ b/command.go @@ -41,6 +41,7 @@ func (c execCommand) Run(_ string, _ []string) (string, error) { return fmt.Sprintf("I'm in dry run mode. I would have run `%s` with args: %s", c.exec, c.args), nil } + //nolint:gosec,noctx // Command execution is the purpose of this bot, no context available out, err := exec.Command(c.exec, c.args...).CombinedOutput() outAsString := string(out) return outAsString, err diff --git a/config.go b/config.go index 28ac5cb..adcb9b7 100644 --- a/config.go +++ b/config.go @@ -54,7 +54,6 @@ func (c *config) SetDryRun(dryRun bool) { func getConfigPath() (string, error) { currentUser, err := user.Current() - if err != nil { return "", err } @@ -83,13 +82,11 @@ func readConfigOrDefault() config { } path, err := getConfigPath() - if err != nil { return defaultConfig } - fileBytes, err := os.ReadFile(path) - + fileBytes, err := os.ReadFile(path) //nolint:gosec // Reading user's own config file if err != nil { return defaultConfig } @@ -115,7 +112,7 @@ func (c config) Save() error { return err } - err = os.WriteFile(path, b, 0644) + err = os.WriteFile(path, b, 0o644) //nolint:gosec // Config file should be user-readable if err != nil { return err } diff --git a/go.mod b/go.mod index 12f4d57..e9fe1c3 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/keybase/slackbot -go 1.21 +go 1.23 -toolchain go1.23.4 +toolchain go1.25.5 require ( github.com/keybase/go-keybase-chat-bot v0.0.0-20250106203511-859265729a56 diff --git a/hybrid.go b/hybrid.go index 497d5ca..b9d3cf4 100644 --- a/hybrid.go +++ b/hybrid.go @@ -18,7 +18,6 @@ func newHybridRunner(runner BotCommandRunner, channel string) *hybridRunner { func (r *hybridRunner) RunCommand(args []string, _ string) error { return r.runner.RunCommand(args, r.channel) - } type HybridBackendMember struct { diff --git a/keybot/winbot.go b/keybot/winbot.go index b2c1132..80199eb 100644 --- a/keybot/winbot.go +++ b/keybot/winbot.go @@ -7,6 +7,7 @@ import ( "bufio" "bytes" "fmt" + "log" "os" "os/exec" "path" @@ -28,8 +29,10 @@ const numLogLines = 10 // Keep track of the current build process, protected with a mutex, // to support cancellation -var buildProcessMutex sync.Mutex -var buildProcess *os.Process +var ( + buildProcessMutex sync.Mutex + buildProcess *os.Process +) func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, error) { app := kingpin.New("winbot", "Job command parser for winbot") @@ -143,12 +146,14 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, updateChannel, smokeTest, devCert, logFileName) bot.SendMessage(msg, channel) - os.Remove(logFileName) - logf, err := os.OpenFile(logFileName, os.O_WRONLY|os.O_CREATE, 0644) + _ = os.Remove(logFileName) // Ignore error if file doesn't exist + //nolint:gosec // Log file path is constructed from system temp dir + logf, err := os.OpenFile(logFileName, os.O_WRONLY|os.O_CREATE, 0o600) if err != nil { return "Unable to open logfile", err } + //nolint:noctx // Long-running git operation, no request context available gitCmd := exec.Command( "git.exe", "checkout", @@ -156,23 +161,36 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, ) gitCmd.Dir = os.ExpandEnv("$GOPATH/src/github.com/keybase/client") stdoutStderr, err := gitCmd.CombinedOutput() - _, _ = logf.Write(stdoutStderr) + if _, writeErr := logf.Write(stdoutStderr); writeErr != nil { + log.Printf("Error writing to log: %s", writeErr) + } if err != nil { - _, _ = logf.WriteString(gitCmd.Dir) - logf.Close() + if _, writeErr := logf.WriteString(gitCmd.Dir); writeErr != nil { + log.Printf("Error writing dir to log: %s", writeErr) + } + if closeErr := logf.Close(); closeErr != nil { + log.Printf("Error closing log: %s", closeErr) + } return string(stdoutStderr), err } + //nolint:noctx // Long-running git operation, no request context available gitCmd = exec.Command( "git.exe", "pull", ) gitCmd.Dir = os.ExpandEnv("$GOPATH/src/github.com/keybase/client") stdoutStderr, err = gitCmd.CombinedOutput() - _, _ = logf.Write(stdoutStderr) + if _, writeErr := logf.Write(stdoutStderr); writeErr != nil { + log.Printf("Error writing to log: %s", writeErr) + } if err != nil { - _, _ = logf.WriteString(gitCmd.Dir) - logf.Close() + if _, writeErr := logf.WriteString(gitCmd.Dir); writeErr != nil { + log.Printf("Error writing dir to log: %s", writeErr) + } + if closeErr := logf.Close(); closeErr != nil { + log.Printf("Error closing log: %s", closeErr) + } return string(stdoutStderr), err } @@ -180,6 +198,7 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, msg := fmt.Sprintf(autoBuild+"I'm trying to use commit %s", *buildWindowsCientCommit) bot.SendMessage(msg, channel) + //nolint:gosec,noctx // Checking out user-specified commit, no request context available gitCmd = exec.Command( "git.exe", "checkout", @@ -187,15 +206,22 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, ) gitCmd.Dir = os.ExpandEnv("$GOPATH/src/github.com/keybase/client") stdoutStderr, err = gitCmd.CombinedOutput() - _, _ = logf.Write(stdoutStderr) + if _, writeErr := logf.Write(stdoutStderr); writeErr != nil { + log.Printf("Error writing to log: %s", writeErr) + } if err != nil { - _, _ = logf.WriteString(fmt.Sprintf("error doing git pull in %s\n", gitCmd.Dir)) - logf.Close() + if _, writeErr := fmt.Fprintf(logf, "error doing git pull in %s\n", gitCmd.Dir); writeErr != nil { + log.Printf("Error writing error to log: %s", writeErr) + } + if closeErr := logf.Close(); closeErr != nil { + log.Printf("Error closing log: %s", closeErr) + } return string(stdoutStderr), err } // Test if we're on a branch. If so, do git pull once more. + //nolint:noctx // Long-running git operation, no request context available gitCmd = exec.Command( "git.exe", "rev-parse", @@ -205,27 +231,39 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, gitCmd.Dir = os.ExpandEnv("$GOPATH/src/github.com/keybase/client") stdoutStderr, err = gitCmd.CombinedOutput() if err != nil { - _, _ = logf.WriteString(fmt.Sprintf("error going git rev-parse dir: %s\n", gitCmd.Dir)) - logf.Close() + if _, writeErr := fmt.Fprintf(logf, "error going git rev-parse dir: %s\n", gitCmd.Dir); writeErr != nil { + log.Printf("Error writing error to log: %s", writeErr) + } + if closeErr := logf.Close(); closeErr != nil { + log.Printf("Error closing log: %s", closeErr) + } return string(stdoutStderr), err } commit := strings.TrimSpace(string(stdoutStderr)) if commit != "HEAD" { + //nolint:noctx // Long-running git operation, no request context available gitCmd = exec.Command( "git.exe", "pull", ) gitCmd.Dir = os.ExpandEnv("$GOPATH/src/github.com/keybase/client") stdoutStderr, err = gitCmd.CombinedOutput() - _, _ = logf.Write(stdoutStderr) + if _, writeErr := logf.Write(stdoutStderr); writeErr != nil { + log.Printf("Error writing to log: %s", writeErr) + } if err != nil { - _, _ = logf.WriteString(fmt.Sprintf("error doing git pull on %s in %s\n", commit, gitCmd.Dir)) - logf.Close() + if _, writeErr := fmt.Fprintf(logf, "error doing git pull on %s in %s\n", commit, gitCmd.Dir); writeErr != nil { + log.Printf("Error writing error to log: %s", writeErr) + } + if closeErr := logf.Close(); closeErr != nil { + log.Printf("Error closing log: %s", closeErr) + } return string(stdoutStderr), err } } } + //nolint:noctx // Long-running git operation, no request context available gitCmd = exec.Command( "git.exe", "rev-parse", @@ -234,12 +272,19 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, gitCmd.Dir = os.ExpandEnv("$GOPATH/src/github.com/keybase/client") stdoutStderr, err = gitCmd.CombinedOutput() if err != nil { - _, _ = logf.WriteString(fmt.Sprintf("error getting current commit for logs: %s", gitCmd.Dir)) - logf.Close() + if _, writeErr := fmt.Fprintf(logf, "error getting current commit for logs: %s", gitCmd.Dir); writeErr != nil { + log.Printf("Error writing error to log: %s", writeErr) + } + if closeErr := logf.Close(); closeErr != nil { + log.Printf("Error closing log: %s", closeErr) + } return string(stdoutStderr), err } - _, _ = logf.WriteString(fmt.Sprintf("HEAD is currently at %s\n", string(stdoutStderr))) + if _, writeErr := fmt.Fprintf(logf, "HEAD is currently at %s\n", string(stdoutStderr)); writeErr != nil { + log.Printf("Error writing to log: %s", writeErr) + } + //nolint:gosec,noctx // Build script execution from known location in GOPATH, no request context available cmd := exec.Command( "cmd", "/c", path.Join(os.Getenv("GOPATH"), "src/github.com/keybase/client/packaging/windows/dorelease.cmd"), @@ -254,8 +299,12 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, fmt.Sprintf("DevCert=%d", devCert), "SlackBot=1", ) - _, _ = logf.WriteString(fmt.Sprintf("cmd: %+v\n", cmd)) - logf.Close() + if _, writeErr := fmt.Fprintf(logf, "cmd: %+v\n", cmd); writeErr != nil { + log.Printf("Error writing cmd to log: %s", writeErr) + } + if closeErr := logf.Close(); closeErr != nil { + log.Printf("Error closing log: %s", closeErr) + } go func() { err := cmd.Start() @@ -271,6 +320,7 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, if bucketName == "" { bucketName = "prerelease.keybase.io" } + //nolint:gosec,noctx // Executing release tool from known location in GOPATH with safe arguments, no context available sendLogCmd := exec.Command( path.Join(os.Getenv("GOPATH"), "src/github.com/keybase/client/go/release/release.exe"), "save-log", @@ -286,6 +336,7 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, index := 0 lineCount := 0 + //nolint:gosec // Log file path is constructed from system temp dir f, err := os.Open(logFileName) if err != nil { bot.SendMessage(autoBuild+"Error reading "+logFileName+": "+err.Error(), channel) @@ -321,6 +372,7 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, }() return "", nil case dumplogCmd.FullCommand(): + //nolint:gosec // Log file path is constructed from system temp dir logContents, err := os.ReadFile(logFileName) if err != nil { return "Error reading " + logFileName, err @@ -335,6 +387,7 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, rawRepoText := *gitDiffRepo repoParsed := strings.Split(strings.Trim(rawRepoText, "`<>"), "|")[1] + //nolint:noctx // User-initiated git command, no request context available gitDiffCmd := exec.Command( "git.exe", "diff", @@ -355,6 +408,7 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, rawRepoText := *gitCleanRepo repoParsed := strings.Split(strings.Trim(rawRepoText, "`<>"), "|")[1] + //nolint:noctx // User-initiated git command, no request context available gitCleanCmd := exec.Command( "git.exe", "clean", diff --git a/launchd/command.go b/launchd/command.go index e76515f..8ef8c2c 100644 --- a/launchd/command.go +++ b/launchd/command.go @@ -24,14 +24,17 @@ func NewStartCommand(plistPath string, label string) StartCommand { // Run runs the exec command func (c StartCommand) Run(_ string, _ []string) (string, error) { + //nolint:gosec,noctx // launchctl is a trusted system binary with safe arguments, no context available if _, err := exec.Command("/bin/launchctl", "unload", c.plistPath).CombinedOutput(); err != nil { return "", fmt.Errorf("Error in launchctl unload: %s", err) } + //nolint:gosec,noctx // launchctl is a trusted system binary with safe arguments, no context available if _, err := exec.Command("/bin/launchctl", "load", c.plistPath).CombinedOutput(); err != nil { return "", fmt.Errorf("Error in launchctl load: %s", err) } + //nolint:gosec,noctx // launchctl is a trusted system binary with safe arguments, no context available if _, err := exec.Command("/bin/launchctl", "start", c.label).CombinedOutput(); err != nil { return "", fmt.Errorf("Error in launchctl start: %s", err) } @@ -41,6 +44,7 @@ func (c StartCommand) Run(_ string, _ []string) (string, error) { // Stop a launchd job func Stop(label string) (string, error) { + //nolint:noctx // launchctl is a trusted system binary, no context available if _, err := exec.Command("/bin/launchctl", "stop", label).CombinedOutput(); err != nil { return "", fmt.Errorf("Error in launchctl stop: %s", err) } diff --git a/launchd/plist.go b/launchd/plist.go index 584e8ea..a785f67 100644 --- a/launchd/plist.go +++ b/launchd/plist.go @@ -177,12 +177,14 @@ func (e Env) WritePlist(script Script) (string, error) { return "", err } plistDir := e.Home + "/Library/LaunchAgents" - if err := os.MkdirAll(plistDir, 0755); err != nil { + //nolint:gosec // LaunchAgents directory must be world-readable for launchd + if err := os.MkdirAll(plistDir, 0o755); err != nil { return "", err } path := fmt.Sprintf("%s/%s.plist", plistDir, script.Label) log.Printf("Writing %s", path) - if err := os.WriteFile(path, data, 0755); err != nil { + //nolint:gosec // Plist files must be readable by launchd + if err := os.WriteFile(path, data, 0o755); err != nil { return "", err } return path, nil diff --git a/tuxbot/tuxbot.go b/tuxbot/tuxbot.go index 6c1ea22..8e3e196 100644 --- a/tuxbot/tuxbot.go +++ b/tuxbot/tuxbot.go @@ -6,6 +6,7 @@ package main import ( "bytes" "fmt" + "log" "net/http" "net/url" "os" @@ -26,6 +27,7 @@ func (t *tuxbot) linuxBuildFunc(channel string, _ []string, skipCI bool, nightly } t.bot.SendMessage("building linux!!!", channel) prereleaseScriptPath := filepath.Join(currentUser.HomeDir, "slackbot/systemd/prerelease.sh") + //nolint:gosec,noctx // Executing build script from known location in user's home directory, no context available prereleaseCmd := exec.Command(prereleaseScriptPath) prereleaseCmd.Stdout = os.Stdout prereleaseCmd.Stderr = os.Stderr @@ -40,14 +42,20 @@ func (t *tuxbot) linuxBuildFunc(channel string, _ []string, skipCI bool, nightly } err = prereleaseCmd.Run() if err != nil { - journal, _ := exec.Command("journalctl", "--since=today", "--user-unit", "keybase.keybot.service").CombinedOutput() + //nolint:noctx // Error logging command, no request context available + journal, journalErr := exec.Command("journalctl", "--since=today", "--user-unit", "keybase.keybot.service").CombinedOutput() + if journalErr != nil { + log.Printf("Error getting journal: %s", journalErr) + } api := slack.New(slackbot.GetTokenFromEnv()) snippetFile := slack.FileUploadParameters{ Channels: []string{channel}, Title: "failed build output", Content: string(journal), } - _, _ = api.UploadFile(snippetFile) // ignore errors here for now + if _, uploadErr := api.UploadFile(snippetFile); uploadErr != nil { + log.Printf("Error uploading build output: %s", uploadErr) + } return "FAILURE", err } return "SUCCESS", nil @@ -113,7 +121,11 @@ func postStathat(key string, count string) error { "stat": {key}, "count": {count}, } - _, err := http.PostForm("https://api.stathat.com/ez", vals) + //nolint:noctx // Simple fire-and-forget stat reporting, no request context available + resp, err := http.PostForm("https://api.stathat.com/ez", vals) + if resp != nil { + defer func() { _ = resp.Body.Close() }() + } return err } From b2158b2f086e82d76849c9fd778ebf91ba41ae12 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Fri, 12 Dec 2025 11:29:34 -0500 Subject: [PATCH 2/3] x --- config.go | 4 ++-- keybot/winbot.go | 10 +++++----- launchd/plist.go | 4 ++-- tuxbot/tuxbot.go | 6 +++++- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/config.go b/config.go index adcb9b7..d0a3d18 100644 --- a/config.go +++ b/config.go @@ -86,7 +86,7 @@ func readConfigOrDefault() config { return defaultConfig } - fileBytes, err := os.ReadFile(path) //nolint:gosec // Reading user's own config file + fileBytes, err := os.ReadFile(filepath.Clean(path)) if err != nil { return defaultConfig } @@ -112,7 +112,7 @@ func (c config) Save() error { return err } - err = os.WriteFile(path, b, 0o644) //nolint:gosec // Config file should be user-readable + err = os.WriteFile(filepath.Clean(path), b, 0o644) //nolint:gosec // Config file should be user-readable if err != nil { return err } diff --git a/keybot/winbot.go b/keybot/winbot.go index 80199eb..06276e3 100644 --- a/keybot/winbot.go +++ b/keybot/winbot.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "strings" "sync" "time" @@ -59,7 +60,7 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, gitCleanCmd := app.Command("gclean", "Clean the repo") gitCleanRepo := gitCleanCmd.Arg("repo", "Repo path relative to $GOPATH/src").Required().String() - logFileName := path.Join(os.TempDir(), "keybase.build.windows.log") + logFileName := filepath.Clean(path.Join(os.TempDir(), "keybase.build.windows.log")) testAutoBuild := app.Command("testauto", "Simulate an automated daily build").Hidden() startAutoTimer := app.Command("startAutoTimer", "Start the auto build timer") @@ -146,8 +147,9 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, updateChannel, smokeTest, devCert, logFileName) bot.SendMessage(msg, channel) - _ = os.Remove(logFileName) // Ignore error if file doesn't exist - //nolint:gosec // Log file path is constructed from system temp dir + if err := os.Remove(logFileName); err != nil && !os.IsNotExist(err) { + return "Unable to remove old logfile", err + } logf, err := os.OpenFile(logFileName, os.O_WRONLY|os.O_CREATE, 0o600) if err != nil { return "Unable to open logfile", err @@ -336,7 +338,6 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, index := 0 lineCount := 0 - //nolint:gosec // Log file path is constructed from system temp dir f, err := os.Open(logFileName) if err != nil { bot.SendMessage(autoBuild+"Error reading "+logFileName+": "+err.Error(), channel) @@ -372,7 +373,6 @@ func (d *winbot) Run(bot *slackbot.Bot, channel string, args []string) (string, }() return "", nil case dumplogCmd.FullCommand(): - //nolint:gosec // Log file path is constructed from system temp dir logContents, err := os.ReadFile(logFileName) if err != nil { return "Error reading " + logFileName, err diff --git a/launchd/plist.go b/launchd/plist.go index a785f67..6aa121e 100644 --- a/launchd/plist.go +++ b/launchd/plist.go @@ -176,12 +176,12 @@ func (e Env) WritePlist(script Script) (string, error) { if err != nil { return "", err } - plistDir := e.Home + "/Library/LaunchAgents" + plistDir := filepath.Join(e.Home, "Library", "LaunchAgents") //nolint:gosec // LaunchAgents directory must be world-readable for launchd if err := os.MkdirAll(plistDir, 0o755); err != nil { return "", err } - path := fmt.Sprintf("%s/%s.plist", plistDir, script.Label) + path := filepath.Clean(filepath.Join(plistDir, script.Label+".plist")) log.Printf("Writing %s", path) //nolint:gosec // Plist files must be readable by launchd if err := os.WriteFile(path, data, 0o755); err != nil { diff --git a/tuxbot/tuxbot.go b/tuxbot/tuxbot.go index 8e3e196..e075b5f 100644 --- a/tuxbot/tuxbot.go +++ b/tuxbot/tuxbot.go @@ -124,7 +124,11 @@ func postStathat(key string, count string) error { //nolint:noctx // Simple fire-and-forget stat reporting, no request context available resp, err := http.PostForm("https://api.stathat.com/ez", vals) if resp != nil { - defer func() { _ = resp.Body.Close() }() + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil { + log.Printf("Error closing response body: %s", closeErr) + } + }() } return err } From 7f6bd54d6e23aed305f8c6238a09c24a49117d27 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Fri, 12 Dec 2025 11:32:28 -0500 Subject: [PATCH 3/3] x --- .github/workflows/ci.yml | 2 +- go.mod | 6 +++--- go.sum | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47d04f2..72c1421 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: timeout-minutes: 15 strategy: matrix: - go-version: [1.25.x, 1.24.x, 1.23.x] + go-version: [1.25.x, 1.24.x] os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: diff --git a/go.mod b/go.mod index e9fe1c3..0a8070d 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/keybase/slackbot -go 1.23 +go 1.24.0 toolchain go1.25.5 require ( - github.com/keybase/go-keybase-chat-bot v0.0.0-20250106203511-859265729a56 + github.com/keybase/go-keybase-chat-bot v0.0.0-20251212163122-450fd0812017 github.com/nlopes/slack v0.1.1-0.20180101221843-107290b5bbaf gopkg.in/alecthomas/kingpin.v2 v2.2.6 ) @@ -17,7 +17,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.10.0 // indirect + github.com/stretchr/testify v1.11.1 // indirect golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 8137dcd..4708e68 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/keybase/go-keybase-chat-bot v0.0.0-20250106203511-859265729a56 h1:w8ikAizh5hbXZxBXbees5iOxOoi7nH/qp1lJQ3pOPiY= -github.com/keybase/go-keybase-chat-bot v0.0.0-20250106203511-859265729a56/go.mod h1:cmXzSxB8TNJdxMKcmywTHsbv+H3WZ/92lP9nyEbCGNQ= +github.com/keybase/go-keybase-chat-bot v0.0.0-20251212163122-450fd0812017 h1:lB6jgDag58Ie9yfLSGDQiUZt60zPyRpK6aWCtovQeSo= +github.com/keybase/go-keybase-chat-bot v0.0.0-20251212163122-450fd0812017/go.mod h1:wl5lBoVNkepL8Hzs7jyqg3GS6U+by4yQeNr7oT0Evt0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -20,8 +20,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=