ScuttleBot
fix: bot review — nil dereference in scroll/scribe, steward mute mode (#76-82) Audit of all 10 system bots found 3 bugs: - scroll: nil dereference on e.Source in PRIVMSG handler (crash on malformed IRC messages) - scribe: same nil dereference bug - steward: used +q (channel owner in Ergo) instead of extended ban m:nick!*@* for muting, same bug warden had All other bots (oracle, warden, snitch, sentinel, herald, systembot, auditbot) passed review — proper nil checks, mutex protection, error handling, +B mode, and INVITE handling all correct.
Commit
1b7cb63ff2f0285b9c62e0906e5ba2382696302e8e608fb1af39e53bf11b9caa
Parent
30e53110edb0698…
3 files changed
+1
-1
+1
-1
+3
-3
+1
-1
| --- internal/bots/scribe/scribe.go | ||
| +++ internal/bots/scribe/scribe.go | ||
| @@ -79,11 +79,11 @@ | ||
| 79 | 79 | } |
| 80 | 80 | }) |
| 81 | 81 | |
| 82 | 82 | // Log PRIVMSG — the agent message stream. |
| 83 | 83 | c.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, e girc.Event) { |
| 84 | - if len(e.Params) < 1 { | |
| 84 | + if len(e.Params) < 1 || e.Source == nil { | |
| 85 | 85 | return |
| 86 | 86 | } |
| 87 | 87 | channel := e.Params[0] |
| 88 | 88 | if !strings.HasPrefix(channel, "#") { |
| 89 | 89 | return // ignore DMs to scribe itself |
| 90 | 90 |
| --- internal/bots/scribe/scribe.go | |
| +++ internal/bots/scribe/scribe.go | |
| @@ -79,11 +79,11 @@ | |
| 79 | } |
| 80 | }) |
| 81 | |
| 82 | // Log PRIVMSG — the agent message stream. |
| 83 | c.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, e girc.Event) { |
| 84 | if len(e.Params) < 1 { |
| 85 | return |
| 86 | } |
| 87 | channel := e.Params[0] |
| 88 | if !strings.HasPrefix(channel, "#") { |
| 89 | return // ignore DMs to scribe itself |
| 90 |
| --- internal/bots/scribe/scribe.go | |
| +++ internal/bots/scribe/scribe.go | |
| @@ -79,11 +79,11 @@ | |
| 79 | } |
| 80 | }) |
| 81 | |
| 82 | // Log PRIVMSG — the agent message stream. |
| 83 | c.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, e girc.Event) { |
| 84 | if len(e.Params) < 1 || e.Source == nil { |
| 85 | return |
| 86 | } |
| 87 | channel := e.Params[0] |
| 88 | if !strings.HasPrefix(channel, "#") { |
| 89 | return // ignore DMs to scribe itself |
| 90 |
+1
-1
| --- internal/bots/scroll/scroll.go | ||
| +++ internal/bots/scroll/scroll.go | ||
| @@ -95,11 +95,11 @@ | ||
| 95 | 95 | b.log.Info("scroll connected", "channels", b.channels, "chathistory", hasCH) |
| 96 | 96 | }) |
| 97 | 97 | |
| 98 | 98 | // Only respond to DMs — ignore anything in a channel. |
| 99 | 99 | c.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, e girc.Event) { |
| 100 | - if len(e.Params) < 1 { | |
| 100 | + if len(e.Params) < 1 || e.Source == nil { | |
| 101 | 101 | return |
| 102 | 102 | } |
| 103 | 103 | target := e.Params[0] |
| 104 | 104 | if strings.HasPrefix(target, "#") { |
| 105 | 105 | return // channel message, ignore |
| 106 | 106 |
| --- internal/bots/scroll/scroll.go | |
| +++ internal/bots/scroll/scroll.go | |
| @@ -95,11 +95,11 @@ | |
| 95 | b.log.Info("scroll connected", "channels", b.channels, "chathistory", hasCH) |
| 96 | }) |
| 97 | |
| 98 | // Only respond to DMs — ignore anything in a channel. |
| 99 | c.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, e girc.Event) { |
| 100 | if len(e.Params) < 1 { |
| 101 | return |
| 102 | } |
| 103 | target := e.Params[0] |
| 104 | if strings.HasPrefix(target, "#") { |
| 105 | return // channel message, ignore |
| 106 |
| --- internal/bots/scroll/scroll.go | |
| +++ internal/bots/scroll/scroll.go | |
| @@ -95,11 +95,11 @@ | |
| 95 | b.log.Info("scroll connected", "channels", b.channels, "chathistory", hasCH) |
| 96 | }) |
| 97 | |
| 98 | // Only respond to DMs — ignore anything in a channel. |
| 99 | c.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, e girc.Event) { |
| 100 | if len(e.Params) < 1 || e.Source == nil { |
| 101 | return |
| 102 | } |
| 103 | target := e.Params[0] |
| 104 | if strings.HasPrefix(target, "#") { |
| 105 | return // channel message, ignore |
| 106 |
+3
-3
| --- internal/bots/steward/steward.go | ||
| +++ internal/bots/steward/steward.go | ||
| @@ -278,12 +278,12 @@ | ||
| 278 | 278 | b.log.Info("steward warn", "nick", nick, "channel", channel, "reason", reason) |
| 279 | 279 | } |
| 280 | 280 | } |
| 281 | 281 | |
| 282 | 282 | func (b *Bot) mute(c *girc.Client, nick, channel string, d time.Duration) { |
| 283 | - // +q (quiet) mode — supported by Ergo. | |
| 284 | - c.Cmd.Mode(channel, "+q", nick) | |
| 283 | + // Extended ban m: to mute — agent stays in channel but cannot speak. | |
| 284 | + c.Cmd.Mode(channel, "+b", "m:"+nick+"!*@*") | |
| 285 | 285 | key := channel + ":" + nick |
| 286 | 286 | b.mu.Lock() |
| 287 | 287 | b.mutes[key] = time.Now().Add(d) |
| 288 | 288 | b.mu.Unlock() |
| 289 | 289 | b.announce(c, fmt.Sprintf("muted %s in %s for %s", nick, channel, d.Round(time.Second))) |
| @@ -291,11 +291,11 @@ | ||
| 291 | 291 | b.log.Info("steward mute", "nick", nick, "channel", channel, "duration", d) |
| 292 | 292 | } |
| 293 | 293 | } |
| 294 | 294 | |
| 295 | 295 | func (b *Bot) unmute(c *girc.Client, nick, channel string) { |
| 296 | - c.Cmd.Mode(channel, "-q", nick) | |
| 296 | + c.Cmd.Mode(channel, "-b", "m:"+nick+"!*@*") | |
| 297 | 297 | key := channel + ":" + nick |
| 298 | 298 | b.mu.Lock() |
| 299 | 299 | delete(b.mutes, key) |
| 300 | 300 | b.mu.Unlock() |
| 301 | 301 | b.announce(c, fmt.Sprintf("unmuted %s in %s", nick, channel)) |
| 302 | 302 |
| --- internal/bots/steward/steward.go | |
| +++ internal/bots/steward/steward.go | |
| @@ -278,12 +278,12 @@ | |
| 278 | b.log.Info("steward warn", "nick", nick, "channel", channel, "reason", reason) |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | func (b *Bot) mute(c *girc.Client, nick, channel string, d time.Duration) { |
| 283 | // +q (quiet) mode — supported by Ergo. |
| 284 | c.Cmd.Mode(channel, "+q", nick) |
| 285 | key := channel + ":" + nick |
| 286 | b.mu.Lock() |
| 287 | b.mutes[key] = time.Now().Add(d) |
| 288 | b.mu.Unlock() |
| 289 | b.announce(c, fmt.Sprintf("muted %s in %s for %s", nick, channel, d.Round(time.Second))) |
| @@ -291,11 +291,11 @@ | |
| 291 | b.log.Info("steward mute", "nick", nick, "channel", channel, "duration", d) |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | func (b *Bot) unmute(c *girc.Client, nick, channel string) { |
| 296 | c.Cmd.Mode(channel, "-q", nick) |
| 297 | key := channel + ":" + nick |
| 298 | b.mu.Lock() |
| 299 | delete(b.mutes, key) |
| 300 | b.mu.Unlock() |
| 301 | b.announce(c, fmt.Sprintf("unmuted %s in %s", nick, channel)) |
| 302 |
| --- internal/bots/steward/steward.go | |
| +++ internal/bots/steward/steward.go | |
| @@ -278,12 +278,12 @@ | |
| 278 | b.log.Info("steward warn", "nick", nick, "channel", channel, "reason", reason) |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | func (b *Bot) mute(c *girc.Client, nick, channel string, d time.Duration) { |
| 283 | // Extended ban m: to mute — agent stays in channel but cannot speak. |
| 284 | c.Cmd.Mode(channel, "+b", "m:"+nick+"!*@*") |
| 285 | key := channel + ":" + nick |
| 286 | b.mu.Lock() |
| 287 | b.mutes[key] = time.Now().Add(d) |
| 288 | b.mu.Unlock() |
| 289 | b.announce(c, fmt.Sprintf("muted %s in %s for %s", nick, channel, d.Round(time.Second))) |
| @@ -291,11 +291,11 @@ | |
| 291 | b.log.Info("steward mute", "nick", nick, "channel", channel, "duration", d) |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | func (b *Bot) unmute(c *girc.Client, nick, channel string) { |
| 296 | c.Cmd.Mode(channel, "-b", "m:"+nick+"!*@*") |
| 297 | key := channel + ":" + nick |
| 298 | b.mu.Lock() |
| 299 | delete(b.mutes, key) |
| 300 | b.mu.Unlock() |
| 301 | b.announce(c, fmt.Sprintf("unmuted %s in %s", nick, channel)) |
| 302 |