From 00427d204c02decf584128b83cb3ba18176b43b8 Mon Sep 17 00:00:00 2001 From: sauyon Date: Wed, 17 Jun 2026 19:51:40 -0700 Subject: [PATCH 1/2] Go: Model `log/slog` as a logging sink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standard-library structured logger `log/slog` (Go 1.21+) was not modeled, so `go/log-injection` and `go/clear-text-logging` were blind to any code that logs through it. Model its logging functions and `*slog.Logger` methods — `Debug`, `Info`, `Warn`, `Error`, their `Context` variants, and `Log`/`LogAttrs` — as `log-injection` sinks (the kind that feeds `LoggerCall`, powering both queries). Adds `log/slog` cases to the `LoggerCall` library test. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../change-notes/2026-06-17-model-log-slog.md | 8 ++++ go/ql/lib/ext/log.slog.model.yml | 29 ++++++++++++++ .../semmle/go/concepts/LoggerCall/go.mod | 2 +- .../semmle/go/concepts/LoggerCall/main.go | 2 + .../semmle/go/concepts/LoggerCall/slog.go | 39 +++++++++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 go/ql/lib/change-notes/2026-06-17-model-log-slog.md create mode 100644 go/ql/lib/ext/log.slog.model.yml create mode 100644 go/ql/test/library-tests/semmle/go/concepts/LoggerCall/slog.go diff --git a/go/ql/lib/change-notes/2026-06-17-model-log-slog.md b/go/ql/lib/change-notes/2026-06-17-model-log-slog.md new file mode 100644 index 000000000000..06bba53a6ed1 --- /dev/null +++ b/go/ql/lib/change-notes/2026-06-17-model-log-slog.md @@ -0,0 +1,8 @@ +--- +category: minorAnalysis +--- +* Added models for the `log/slog` package (Go 1.21+). Its logging functions and + `*slog.Logger` methods (`Debug`/`Info`/`Warn`/`Error`, their `Context` + variants, and `Log`/`LogAttrs`) are now recognized as logging sinks, so the + `go/log-injection` and `go/clear-text-logging` queries cover code that logs + through `slog`. diff --git a/go/ql/lib/ext/log.slog.model.yml b/go/ql/lib/ext/log.slog.model.yml new file mode 100644 index 000000000000..3283492c226e --- /dev/null +++ b/go/ql/lib/ext/log.slog.model.yml @@ -0,0 +1,29 @@ +extensions: + - addsTo: + pack: codeql/go-all + extensible: sinkModel + data: + # Package-level convenience functions (msg string, args ...any). + - ["log/slog", "", False, "Debug", "", "", "Argument[0..1]", "log-injection", "manual"] + - ["log/slog", "", False, "Info", "", "", "Argument[0..1]", "log-injection", "manual"] + - ["log/slog", "", False, "Warn", "", "", "Argument[0..1]", "log-injection", "manual"] + - ["log/slog", "", False, "Error", "", "", "Argument[0..1]", "log-injection", "manual"] + # Context variants (ctx, msg string, args ...any). + - ["log/slog", "", False, "DebugContext", "", "", "Argument[1..2]", "log-injection", "manual"] + - ["log/slog", "", False, "InfoContext", "", "", "Argument[1..2]", "log-injection", "manual"] + - ["log/slog", "", False, "WarnContext", "", "", "Argument[1..2]", "log-injection", "manual"] + - ["log/slog", "", False, "ErrorContext", "", "", "Argument[1..2]", "log-injection", "manual"] + # Log/LogAttrs (ctx, level, msg string, args/attrs ...). + - ["log/slog", "", False, "Log", "", "", "Argument[2..3]", "log-injection", "manual"] + - ["log/slog", "", False, "LogAttrs", "", "", "Argument[2..3]", "log-injection", "manual"] + # Methods on *slog.Logger. + - ["log/slog", "Logger", True, "Debug", "", "", "Argument[0..1]", "log-injection", "manual"] + - ["log/slog", "Logger", True, "Info", "", "", "Argument[0..1]", "log-injection", "manual"] + - ["log/slog", "Logger", True, "Warn", "", "", "Argument[0..1]", "log-injection", "manual"] + - ["log/slog", "Logger", True, "Error", "", "", "Argument[0..1]", "log-injection", "manual"] + - ["log/slog", "Logger", True, "DebugContext", "", "", "Argument[1..2]", "log-injection", "manual"] + - ["log/slog", "Logger", True, "InfoContext", "", "", "Argument[1..2]", "log-injection", "manual"] + - ["log/slog", "Logger", True, "WarnContext", "", "", "Argument[1..2]", "log-injection", "manual"] + - ["log/slog", "Logger", True, "ErrorContext", "", "", "Argument[1..2]", "log-injection", "manual"] + - ["log/slog", "Logger", True, "Log", "", "", "Argument[2..3]", "log-injection", "manual"] + - ["log/slog", "Logger", True, "LogAttrs", "", "", "Argument[2..3]", "log-injection", "manual"] diff --git a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/go.mod b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/go.mod index 0d3c053e7fe2..f5319354dc88 100644 --- a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/go.mod +++ b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/go.mod @@ -1,6 +1,6 @@ module codeql-go-tests/concepts/loggercall -go 1.15 +go 1.21 require ( github.com/golang/glog v1.2.5 diff --git a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/main.go b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/main.go index 688c59bc2eac..ae3699c19661 100644 --- a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/main.go +++ b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/main.go @@ -2,10 +2,12 @@ package main const fmt = "formatted %s string" const text = "test" +const key = "key" var v []byte func main() { glogTest(len(v)) stdlib() + slogTest() } diff --git a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/slog.go b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/slog.go new file mode 100644 index 000000000000..89a4e4dac1f8 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/slog.go @@ -0,0 +1,39 @@ +package main + +import ( + "context" + "log/slog" +) + +func slogTest() { + ctx := context.Background() + var logger *slog.Logger + + // Methods on *slog.Logger: Debug/Info/Warn/Error(msg string, args ...any). + logger.Debug(text) // $ logger=text + logger.Info(text) // $ logger=text + logger.Warn(text) // $ logger=text + logger.Error(text) // $ logger=text + logger.Info(text, key, v) // $ logger=text logger=key logger=v + + // Context variants: (ctx, msg string, args ...any). + logger.DebugContext(ctx, text) // $ logger=text + logger.InfoContext(ctx, text) // $ logger=text + logger.WarnContext(ctx, text) // $ logger=text + logger.ErrorContext(ctx, text) // $ logger=text + logger.InfoContext(ctx, text, key, v) // $ logger=text logger=key logger=v + + // Log/LogAttrs: (ctx, level, msg string, args/attrs ...). + logger.Log(ctx, slog.LevelInfo, text, key, v) // $ logger=text logger=key logger=v + logger.LogAttrs(ctx, slog.LevelInfo, text) // $ logger=text + + // Package-level convenience functions. + slog.Debug(text) // $ logger=text + slog.Info(text) // $ logger=text + slog.Warn(text) // $ logger=text + slog.Error(text) // $ logger=text + slog.Info(text, key, v) // $ logger=text logger=key logger=v + slog.InfoContext(ctx, text, key, v) // $ logger=text logger=key logger=v + slog.Log(ctx, slog.LevelInfo, text) // $ logger=text + slog.LogAttrs(ctx, slog.LevelInfo, text) // $ logger=text +} From b7ef551b528b6881a6f9f4006accd6d037d9a158 Mon Sep 17 00:00:00 2001 From: sauyon Date: Wed, 17 Jun 2026 20:27:00 -0700 Subject: [PATCH 2/2] Address review: exercise variadic args/attrs in slog Log/LogAttrs tests Copilot review on #22004: the Log/LogAttrs test cases didn't pass any variadic args/attrs, so the Argument[..3] portion of the sink range was untested. Pass an ...any arg to slog.Log/Logger.Log and a slog.Attr to slog.LogAttrs/Logger.LogAttrs, with inline expectations asserting they're captured as logged components. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../semmle/go/concepts/LoggerCall/slog.go | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/slog.go b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/slog.go index 89a4e4dac1f8..63bb0a817925 100644 --- a/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/slog.go +++ b/go/ql/test/library-tests/semmle/go/concepts/LoggerCall/slog.go @@ -8,32 +8,33 @@ import ( func slogTest() { ctx := context.Background() var logger *slog.Logger + var attr slog.Attr // Methods on *slog.Logger: Debug/Info/Warn/Error(msg string, args ...any). - logger.Debug(text) // $ logger=text - logger.Info(text) // $ logger=text - logger.Warn(text) // $ logger=text - logger.Error(text) // $ logger=text + logger.Debug(text) // $ logger=text + logger.Info(text) // $ logger=text + logger.Warn(text) // $ logger=text + logger.Error(text) // $ logger=text logger.Info(text, key, v) // $ logger=text logger=key logger=v // Context variants: (ctx, msg string, args ...any). - logger.DebugContext(ctx, text) // $ logger=text - logger.InfoContext(ctx, text) // $ logger=text - logger.WarnContext(ctx, text) // $ logger=text - logger.ErrorContext(ctx, text) // $ logger=text + logger.DebugContext(ctx, text) // $ logger=text + logger.InfoContext(ctx, text) // $ logger=text + logger.WarnContext(ctx, text) // $ logger=text + logger.ErrorContext(ctx, text) // $ logger=text logger.InfoContext(ctx, text, key, v) // $ logger=text logger=key logger=v // Log/LogAttrs: (ctx, level, msg string, args/attrs ...). - logger.Log(ctx, slog.LevelInfo, text, key, v) // $ logger=text logger=key logger=v - logger.LogAttrs(ctx, slog.LevelInfo, text) // $ logger=text + logger.Log(ctx, slog.LevelInfo, text, key, v) // $ logger=text logger=key logger=v + logger.LogAttrs(ctx, slog.LevelInfo, text, attr) // $ logger=text logger=attr // Package-level convenience functions. - slog.Debug(text) // $ logger=text - slog.Info(text) // $ logger=text - slog.Warn(text) // $ logger=text - slog.Error(text) // $ logger=text - slog.Info(text, key, v) // $ logger=text logger=key logger=v - slog.InfoContext(ctx, text, key, v) // $ logger=text logger=key logger=v - slog.Log(ctx, slog.LevelInfo, text) // $ logger=text - slog.LogAttrs(ctx, slog.LevelInfo, text) // $ logger=text + slog.Debug(text) // $ logger=text + slog.Info(text) // $ logger=text + slog.Warn(text) // $ logger=text + slog.Error(text) // $ logger=text + slog.Info(text, key, v) // $ logger=text logger=key logger=v + slog.InfoContext(ctx, text, key, v) // $ logger=text logger=key logger=v + slog.Log(ctx, slog.LevelInfo, text, key, v) // $ logger=text logger=key logger=v + slog.LogAttrs(ctx, slog.LevelInfo, text, attr) // $ logger=text logger=attr }