Fix G118 false positive on cancel called inside goroutine closure (#1592)

The isCancelCalled function did not handle *ssa.MakeClosure, so when
a cancel function was captured as a free variable in a closure
(e.g. go func() { cancel() }()), gosec could not see the call inside
the closure body and reported a false positive.

Add a MakeClosure case that follows the cancel value into the closure's
FreeVars, allowing the existing call-detection logic to find the
invocation.

Fixes #1590
This commit is contained in:
Cosmin Cojocar
2026-03-09 15:25:30 +01:00
committed by GitHub
parent cbf46b8771
commit 59a9da022f
2 changed files with 27 additions and 0 deletions
+11
View File
@@ -725,6 +725,17 @@ func isCancelCalled(cancelValue ssa.Value, allFuncs []*ssa.Function) bool {
if r.X == current {
queue = append(queue, r)
}
case *ssa.MakeClosure:
// The cancel value is captured as a free variable in a closure.
// Find the corresponding FreeVar inside the closure body and
// follow it so that calls within the closure are detected.
if fn, ok := r.Fn.(*ssa.Function); ok {
for i, binding := range r.Bindings {
if binding == current && i < len(fn.FreeVars) {
queue = append(queue, fn.FreeVars[i])
}
}
}
case *ssa.Return:
// Cancel function is returned to the caller — responsibility
// is transferred; treat as "called".
+16
View File
@@ -1551,5 +1551,21 @@ func initDatabase(ctx context.Context) (*sql.DB, func(), error) {
}
return db, cancelFunc, nil
}
`}, 0, gosec.NewConfig()},
// Safe: cancel called inside goroutine closure (issue #1590)
{[]string{`
package main
import (
"context"
)
func main() {
_, cancel := context.WithCancel(context.Background())
go func() {
cancel()
}()
}
`}, 0, gosec.NewConfig()},
}