Nil-pointer panic in JWT tokenizer when Jsonnet claims mapper returns non-object `claims`
Describe the bug
On the JWT session-tokenization path, a Jsonnet claims mapper whose output has no object-valued claims field is supposed to produce a controlled ErrBadRequest ("the claims mapper must return a claims object"). Instead the error-reporting code itself dereferences a nil error, panicking the request.
In session/tokenizer.go, after EvaluateAnonymousSnippet succeeds the block-local err is nil. The missing-claims branch then passes that nil err straight into the telemetry helper:
evaluatedClaims := gjson.Get(evaluated, "claims")
if !evaluatedClaims.IsObject() {
trace.SpanFromContext(ctx).AddEvent(events.NewJsonnetMappingFailed(ctx, err, ...)) // err is nil here
...
}NewJsonnetMappingFailed routes the error through attrErrorReason → reasonForError, which calls err.Error() with no nil guard:
func reasonForError(err error) string {
...
return err.Error() // nil error interface -> panic
}errors.As(nil, ...) returns false safely, so execution falls through to err.Error() on the nil interface and panics. There is no recover() on this path, so Go's net/http per-request recovery aborts the connection instead of returning the intended HTTP 400.
Introduced by: #4409 ("feat: emit events on jsonnet failure when templating a jwt", merged 2025-05-12). That PR added two AddEvent(NewJsonnetMappingFailed(...)) calls; the first passes a genuine non-nil error, the second (missing-claims branch) passes the nil err.
Reproduction
- Configure JWT session tokenization with a Jsonnet claims mapper (
ClaimsMapperURL) whose output is valid JSON but has no object-valuedclaimsfield, e.g.{ "sub": "x" }(noclaimskey) or{ "claims": "not-an-object" }. - Exercise the tokenized session flow (e.g.
whoamiwith a tokenized session template) so evaluation reaches the missing-claims branch insession/tokenizer.go. - Observe a nil-pointer panic in
reasonForError(x/events/events.go) instead of the documented bad-request response.
Expected behavior
The missing-claims branch returns the documented ErrBadRequest explaining the mapper must return a claims object.
Suggested fix
Construct the missing-claims error before emitting the event and pass that (non-nil) error to NewJsonnetMappingFailed, returning the same error; or make reasonForError nil-safe (if err == nil { return "" }). The first keeps the intended controlled 400 path intact.
Environment: current master (HEAD 783f90e at time of report); code path present since #4409.
Found while testing Ito, an automated code-review tool, against recently-merged PRs. It's free for open source. Sharing this because it looked like a real bug worth fixing, not to sell anything: https://app.ito.ai/share/0cc3f8b0-2faf-488e-a6cb-c1a1e6bf3691?tab=details
Source: ory/kratos