Full codebase audit: 21 security/robustness fixes, 328 tests across 4 packages

Security & economy:
- Credit()/Debit() reject non-positive amounts (closes infinite-money exploit)
- Lottery ticket purchase uses in-transaction count check (closes TOCTOU race)
- Arena bail channel ownership prevents double-close panic
- Entry.ID validated before exec.Command in fetch-esteemed
- Internal errors no longer leaked to users (forex, esteemed)

Robustness:
- safeGo() panic recovery added to 17 goroutine launch sites across 14 plugins
- db.Close() added and called on shutdown
- Miniflux mutex pattern fixed (snapshot-under-lock)
- Silently ignored DB/JSON errors now logged (lottery_db)

Performance:
- Added indexes: idx_arena_runs_user(user_id, status), idx_euro_bal_user(user_id)

UX:
- Empty !buy args shows usage instead of "No item matching ''"
- "Failed to load character" now suggests !adventure

Test coverage:
- internal/util/parser_test.go (19 tests: XP, levels, progress bar, commands, parsing)
- internal/crypto/crypto_test.go (12 tests: encrypt/decrypt, HMAC, ParseKey)
- internal/plugin/helpers_test.go (30 tests: formatNumber, calc, lottery, chat perks)
- internal/plugin/audit_fixes_test.go (25 tests: safeGo, bail channels, error leaks, overflow)
- internal/db/db_test.go (4 tests: Init, Close, schema indexes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-04-08 17:04:40 -07:00
parent 68b2f8b7a5
commit e459b6e78d
34 changed files with 2443 additions and 486 deletions

View File

@@ -73,7 +73,7 @@ func (p *ForexPlugin) OnMessage(ctx MessageContext) error {
// API-calling subcommands run async
switch sub {
case "rate", "report":
go func() {
safeGo("forex-handler", func() {
var err error
if sub == "rate" {
err = p.cmdRate(ctx, parts[1:])
@@ -83,7 +83,7 @@ func (p *ForexPlugin) OnMessage(ctx MessageContext) error {
if err != nil {
slog.Error("forex: handler error", "err", err)
}
}()
})
return nil
default:
return p.SendReply(ctx.RoomID, ctx.EventID, fmt.Sprintf("Unknown subcommand `%s`. Try `!fx help`.", parts[0]))
@@ -110,7 +110,8 @@ func (p *ForexPlugin) cmdRate(ctx MessageContext, args []string) error {
currencies := fxParseCurrencies(args)
rates, err := p.fxLiveRates(currencies)
if err != nil {
return p.SendReply(ctx.RoomID, ctx.EventID, fmt.Sprintf("Could not fetch rates: %v", err))
slog.Error("forex: fetch rates failed", "err", err)
return p.SendReply(ctx.RoomID, ctx.EventID, "Could not fetch rates. Try again shortly.")
}
var lines []string
@@ -172,7 +173,8 @@ func fxIsSupported(cur string) bool {
func (p *ForexPlugin) cmdPairRateOrReport(ctx MessageContext, pair *fxPair, full bool) error {
currentRate, err := p.fxLivePairRate(pair)
if err != nil {
return p.SendReply(ctx.RoomID, ctx.EventID, fmt.Sprintf("Could not fetch rates: %v", err))
slog.Error("forex: fetch rates failed", "err", err)
return p.SendReply(ctx.RoomID, ctx.EventID, "Could not fetch rates. Try again shortly.")
}
sig, err := fxComputePairSignal(pair, currentRate)
@@ -239,7 +241,8 @@ func (p *ForexPlugin) cmdReport(ctx MessageContext, args []string) error {
currencies := fxParseCurrencies(args)
rates, err := p.fxLiveRates(currencies)
if err != nil {
return p.SendReply(ctx.RoomID, ctx.EventID, fmt.Sprintf("Could not fetch rates: %v", err))
slog.Error("forex: fetch rates failed", "err", err)
return p.SendReply(ctx.RoomID, ctx.EventID, "Could not fetch rates. Try again shortly.")
}
var sections []string