BudgetScan

Panic from comparing uncomparable any keys

high swesweep-fasthttp · userdata.go · Set()/Get()/Remove() · run #4

Description

The code compares interface values with kv.key == key without verifying that the dynamic key type is comparable. If a caller stores or looks up a key whose dynamic type is a slice, map, function, or a struct containing an uncomparable field, Go panics at runtime. This is reachable through public user-value APIs that accept key as any, and can be used to cause denial of service if an attacker can influence the key object passed to the application.

Triage

Verdict
INVALID confidence 0.0
Model
qwen/qwen3.8-27b
Rounds
6
Cost
$0.0259 · 238.1 s
[ARBITER] The finding is invalid under the provided threat model for a reusable library. The panic occurs when comparing keys of type `any` using `==`. While Go does panic on uncomparable types (slices, maps, functions), the `key` argument to `SetUserValue`/`GetUserValue` is determined by the host application's developer (the library consumer), not by an external attacker sending malicious HTTP requests. External attackers control the HTTP payload (headers, body, path), but they do not control the memory layout or type of the keys used by the application logic to store metadata. Therefore, an external attacker cannot influence the dynamic type of the `key` argument to force a panic. This is a potential application bug if the developer passes an uncomparable type, but it is not a security vulnerability in the library reachable via untrusted external input.
All rounds
[
  {
    "finding_title": "Panic from comparing uncomparable any keys",
    "verdict": "UNCERTAIN",
    "reasoning": "{\"reasoning\":\"The reported vulnerability claims that comparing uncomparable types (like slices, maps, or structs containing slices) with `==` in Go causes a panic. This is a valid concern in Go. However, we must determine if the `key` passed to `userData.Set` (and subsequently compared with `==`) can be an uncomparable type when called from the public API.\\n\\n1. **Public API Entry Points**: The public API for setting user values on a Request or Response includes `SetUserValue(key, value any)` and `SetUserValueBytes(key []byte, value any)`. Similarly for Get/Remove.\\n2. **Key Transformation**: In `Set`, `Get`, and `Remove` methods of `userData`, there is a specific check: `if b, ok := key.([]byte); ok { key = string(b) }` (or `b2s(b)`). This means that if the caller passes a `[]byte` as the key, it is converted to a `string` (which is comparable).\\n3. **What about other keys?**: The `Set` method accepts `key any`. If a user calls `ctx.Request.SetUserValue(mySlice, myValue)` where `mySlice` is a `[]int` or `map[string]string`, the `key` variable inside `Set` will hold that uncomparable type. The code does NOT check if the key is comparable before performing the loop `for ... if kv.key == key`. It attempts to compare the stored keys with the new key using `==`.\\n4. **Is the key attacker-controlled?**: The threat model states: \\\"Code that calls the public API... parsers, decoders... receive adversarial payloads...\\\" and \\\"an attacker can influence the key object passed to the application\\\". In a typical web server scenario, keys are often derived from static strings (header names, query parameter names) or specific application logic. However, the report explicitly states the vulnerability is reachable if an attacker can *influence* the key object. More importantly, the library allows *any* type. If the application logic passes a key that is a slice (e.g., extracting a list of IPs from a header and using the list itself as a key, or simply a bug where a dynamic structure is used as a key), the library will panic. \\n\\nWait, let's look closer at the threat model and typical usage. Usually, keys in fasthttp user values are `string` or `[]byte`. The code explicitly handles `[]byte` by converting to `string`. If a user passes a `string`, it's comparable. If a user passes an `int`, it's comparable. The panic only occurs if a user passes an *uncomparable* type like `[]byte` (handled), `map`, `func`, `struct` with uncomparable field, or `[]int`.\\n\\nDoes the scanner assume an attacker can force the *application* to pass an uncomparable key? The threat model says: \\\"an external attacker can trigger it... The attacker must control the input that triggers the bug.\\\" If the application code itself decides to use a slice as a key, that is a bug in the application, not necessarily the library. However, libraries are often expected to be robust. But is it *reachable* via untrusted input?\\n\\nIf I run a fasthttp server, I control the headers. I can send a header. The application parses it. If the application code does `ctx.Request.SetUserValue([]string{headerValue}, ...)` that's a weird application design. Standard usage is `SetUserValue(\\\"key\\\", value)`.\\n\\nHowever, look at `SetBytes`. It converts `[]byte` to string. What if someone calls `Set` directly with a non-byte slice? The public API `SetUserValue` takes `any`. If I write:\\n```go\\nfunc handler(ctx *fasthttp.RequestCtx) {\\n    key := []byte(\\\"some data\\\")\\n    ctx.Request.SetUserValue(key, 123) // This works because it converts to string\\n    \\n    // But what if:\\n    myMap := map[string]int{\\\"a\\\":1}\\n    ctx.Request.SetUserValue(myMap, 123) // This will PANIC on comparison if another key exists or even on first set? No, first set just appends. Panic happens on SECOND set or Get/Remove if it compares against existing uncomparable keys.\\n}\\n```\\nActually, the panic happens in the loop `for i := range n { ... if kv.key == key ... }`. On the first `Set`, `n` is 0, so the loop doesn't run. It just appends. So `Set` with a new uncomparable key does NOT panic immediately. It panics when you `Set` (update), `Get`, or `Remove` with a key that requires comparing against an *existing* uncomparable key, OR if you `Set` a comparable key and it compares against an existing uncomparable key? No, `==` panics if *either* operand is uncomparable. So if the stored key is `map[string]int` and the new key is `\\\"foo\\\"`, `map == string` is not even the issue, the issue is that you are trying to compare a map. Actually, in Go, `interface == interface` panics if the dynamic types are uncomparable. It does not panic if the types are different but both comparable. It panics if the dynamic type is uncomparable. \\n\\nSo, if an attacker can cause the application to store a key of an uncomparable type (e.g., by sending a header that the application code incorrectly treats as a map key or slice key), and then subsequently look up or remove a key, it panics. \\n\\nIs this a *library* security vulnerability? The threat model says: \\\"an attacker can influence the key object passed to the application.\\\" This implies the application logic maps attacker input to the key. If the application logic is buggy and passes an uncomparable type, the library panics. Is that the library's responsibility? Usually, libraries panic on invalid input types if they are not documented to handle them. However, `any` is very generic. \\n\\nLet's look at similar findings. Often, `SetUserValue` with `[]byte` is safe because of the conversion. But `SetUserValue` with other uncomparable types is a programming error in the *host* application. The library cannot prevent the host from passing a map. \\n\\nHowever, the prompt says: \\\"If a caller stores or looks up a key whose dynamic type is a slice, map, function, or a struct containing an uncomparable field, Go panics at runtime. This is reachable through public user-value APIs that accept key as any, and can be used to cause denial of service if an attacker can influence the key object passed to the application.\\\"\\n\\nIf the attacker cannot influence the *type* of the key (only the content), and the application always uses `string` or `[]byte` keys (which is the standard usage), then the vulnerability is not reachable. The threat model requires the attacker to control the input. If the input is the *value* of a header, and the key is a fixed string, there is no panic. If the input *becomes* the key, does the application parse it into a slice? Unlikely. \\n\\nMost false positives in this category rely on the assumption that an attacker can change the *type* of the key passed to the API. In standard fasthttp usage, keys are constants or strings. The `[]byte` case is handled. Therefore, it is highly unlikely an attacker can trigger this panic through standard public API usage without a bug in the application code itself (passing an uncomparable type as a key). This falls under \\\"misuse that violates a documented precondition\\\" or code quality issue in the host, not a library vulnerability. The library accepts `any`, but the practical usage and the specific handling of `[]byte` suggest that uncomparable types are not expected or easily injected by an attacker via network input.\\n\\nFurthermore, Go's `==` on interfaces only panics if the dynamic type is uncomparable. Strings, ints, floats, etc. are comparable. The only uncomparable types are arrays of uncomparable types, slices, maps, functions, and structs containing them. It is not reasonable to assume an attacker can force the application to use a `map` as a user value key via network input. The key is typically defined by the developer.\\n\\nTherefore, this is INVALID as a security vulnerability because the attacker cannot control the *type* of the key to be uncomparable; they can only control the *value* (if the key is dynamic, it's usually a string). Even if they control the string content, a string is comparable. If they control a `[]byte` content, it's converted to a string. The panic requires an uncomparable dynamic type, which is determined by the application code, not the input data (unless the application does something bizarre like using a parsed JSON object as a key, which is bad practice and arguably out of scope for 'library' liability if the input is standard protocol data).\",\"crux\":\"Whether an attacker can force the dynamic type of the `key` argument to be an uncomparable type (like map or slice) via network input, given that `[]byte` is converted to `string` and other common key types (string, int) are comparable.\",\"grep\":\"func (ctx \\\\*RequestCtx) SetUserValue\",\"verdict\":\"INVALID\"}",
    "elapsed": 105.3,
    "tokens": 4542,
    "tokens_in": 2484,
    "tokens_out": 2058,
    "cost_usd": 0.0072172799999999995,
    "file": "userdata.go",
    "round": 1
  },
  {
    "finding_title": "Panic from comparing uncomparable any keys",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability claims that comparing uncomparable types (slices, maps, etc.) with `==` in Go causes a panic, leading to denial of service. While the code pattern (`kv.key == key` where `key` is `any`) indeed causes a panic if the dynamic type is uncomparable, this requires the caller to pass an uncomparable type as the key. \n\n1. **Attacker Control**: For this to be a security vulnerability in a library context, an external attacker must be able to influence the *type* of the key passed to the public API (`SetUserValue`, `GetUserValue`, etc.). In standard web server usage, keys are typically static strings defined by the application developer or derived from HTTP header/query parameter names (which are strings). Even if an attacker controls the *value* of a header, the key remains a string. It is extremely implausible that an attacker can force the application code to pass a `map`, `slice`, or `func` type as the key via standard HTTP input. The key type is determined by the application's logic, not the protocol data.\n\n2. **Defensive Handling**: The code explicitly handles the most common uncomparable type, `[]byte`, by converting it to a `string` (which is comparable) before storage or comparison. This suggests the library authors anticipated `[]byte` keys and ensured they are safe. Other uncomparable types (maps, funcs) are not standard keys for request/user values.\n\n3. **Threat Model**: The threat model states that \"misuse that violates a documented precondition of the public API\" is not a vulnerability. While the API accepts `any`, the practical contract for a key-value store implies comparable keys. Passing a map as a key is a programming error in the host application. Since the attacker cannot control the code execution path that determines the key's type, they cannot trigger the panic. The panic is a result of internal application logic bugs or misuse, not a direct exploit vector from untrusted input.\n\nTherefore, the bug is not reachable by an external attacker via untrusted input. It is a code robustness issue for incorrect usage by the library consumer, not a security vulnerability exploitable by an attacker.\n\nCRUX: An external attacker cannot control the dynamic type of the `key` argument to force it to be an uncomparable type (like map or slice) via standard HTTP input, as key types are determined by application logic.\nGREP: func (ctx \\*RequestCtx) SetUserValue",
    "elapsed": 29.5,
    "tokens": 5214,
    "tokens_in": 4670,
    "tokens_out": 544,
    "cost_usd": 0.0035934,
    "file": "userdata.go",
    "round": 2,
    "grep_used": true,
    "grep_results": "GREP `func (ctx *RequestCtx) SetUserValue (simplified to: SetUserValue)`:\n```\nfasthttpadaptor/adaptor_test.go:199:\t\tctx.SetUserValue(key, value)\nserver_test.go:1859:\t\tctx.SetUserValue(k, i)\nserver_test.go:1863:\t\tctx.SetUserValueBytes([]byte(k), i)\nserver_test.go:2691:\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\nserver_test.go:2790:\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\nserver.go:769:// SetUserValue stores the given value (arbitrary object)\nserver.go:780:func (ctx *RequestCtx) SetUserValue(key, value any) {\nserver.go:781:\tctx.Request.SetUserValue(key, value)\nserver.go:784:// SetUserValueBytes stores the given value (arbitrary object)\nserver.go:793:func (ctx *RequestCtx) SetUserValueBytes(key []byte, value any) {\nserver.go:794:\tctx.Request.SetUserValueBytes(key, value)\nserver.go:797:// UserValue returns the value stored via SetUserValue* under the given key.\nserver.go:802:// UserValueBytes returns the value stored via SetUserValue*\nhttp.go:2295:// SetUserValue stores the given value (arbitrary object)\nhttp.go:2306:func (req *Request) SetUserValue(key, value any) {\nhttp.go:2310:// SetUserValueBytes stores the given value (arbitrary object)\nhttp.go:2319:func (req *Request) SetUserValueBytes(key []byte, value any) {\nhttp.go:2323:// UserValue returns the value stored via SetUserValue* under the given key.\nhttp.go:2328:// UserValueBytes returns the value stored via SetUserValue*\n```"
  },
  {
    "finding_title": "Panic from comparing uncomparable any keys",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a panic due to comparing uncomparable interface types (slices, maps, functions) using `==` in Go. While the code pattern `kv.key == key` exists in `userData.Set`, `userData.Get`, and `userData.Remove`, this specific panic can only be triggered if the `key` argument passed to the public API (`SetUserValue`, `GetUserValue`, etc.) has a dynamic type that is uncomparable. \n\nIn the context of a reusable library like fasthttp, the public API is intended to store metadata per request. The standard and documented usage for keys is `string` or `[]byte` (which is converted to `string` internally). These types are always comparable in Go. \n\nFor an external attacker (sending HTTP requests) to trigger this panic, they would need to influence the *type* of the key passed by the host application to `SetUserValue`. In standard web server development, keys are determined by the application developer's logic (e.g., `ctx.SetUserValue(\"userID\", userID)`), not directly by raw input bytes. While an attacker can control the *value* of input data (e.g., the content of a header or body), they cannot typically change the *static type* of the variable used as the key in the host application's code unless the host application has a severe logical flaw (e.g., deserializing untrusted JSON into a generic interface and blindly using it as a key without type checking). \n\nThe threat model states that \"misuse that violates a documented precondition of the public API\" is not a vulnerability. Passing an uncomparable type (like a map or slice) as a key to a key-value store is a misuse of the API contract, which implicitly assumes comparable keys for equality checks. The library does not claim to support maps or slices as keys. Since the attacker cannot force the application to pass an uncomparable type via standard untrusted input (HTTP data), this is a code quality/robustness issue for the library consumer, not a security vulnerability exploitable by an external attacker.\n\nCRUX: An external attacker cannot control the dynamic type of the `key` argument to be an uncomparable type (like a map or slice) via standard untrusted input, as key types are determined by the host application's logic, not the protocol data.\nGREP: func (ctx \\*RequestCtx) SetUserValue",
    "elapsed": 23.4,
    "tokens": 6183,
    "tokens_in": 5662,
    "tokens_out": 521,
    "cost_usd": 0.00394104,
    "file": "userdata.go",
    "round": 3,
    "grep_used": true,
    "grep_results": "GREP `func (ctx *RequestCtx) SetUserValue (simplified to: SetUserValue)`:\n```\nfasthttpadaptor/adaptor_test.go:199:\t\tctx.SetUserValue(key, value)\nserver_test.go:1859:\t\tctx.SetUserValue(k, i)\nserver_test.go:1863:\t\tctx.SetUserValueBytes([]byte(k), i)\nserver_test.go:2691:\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\nserver_test.go:2790:\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\nserver.go:769:// SetUserValue stores the given value (arbitrary object)\nserver.go:780:func (ctx *RequestCtx) SetUserValue(key, value any) {\nserver.go:781:\tctx.Request.SetUserValue(key, value)\nserver.go:784:// SetUserValueBytes stores the given value (arbitrary object)\nserver.go:793:func (ctx *RequestCtx) SetUserValueBytes(key []byte, value any) {\nserver.go:794:\tctx.Request.SetUserValueBytes(key, value)\nserver.go:797:// UserValue returns the value stored via SetUserValue* under the given key.\nserver.go:802:// UserValueBytes returns the value stored via SetUserValue*\nhttp.go:2295:// SetUserValue stores the given value (arbitrary object)\nhttp.go:2306:func (req *Request) SetUserValue(key, value any) {\nhttp.go:2310:// SetUserValueBytes stores the given value (arbitrary object)\nhttp.go:2319:func (req *Request) SetUserValueBytes(key []byte, value any) {\nhttp.go:2323:// UserValue returns the value stored via SetUserValue* under the given key.\nhttp.go:2328:// UserValueBytes returns the value stored via SetUserValue*\n```"
  },
  {
    "finding_title": "Panic from comparing uncomparable any keys",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is that comparing uncomparable types (like slices, maps, functions) using `==` in Go causes a panic. The code in `userData.Set`, `Get`, and `Remove` does indeed perform `kv.key == key` where `key` is of type `any`. If the dynamic type of the key is uncomparable, this will panic. \n\nTo determine if this is a security vulnerability, we must assess if an external attacker can reach this code path with an uncomparable key. The public APIs are `SetUserValue(key, value any)`, `GetUserValue(key any)`, and `RemoveUserValue(key any)`. The `key` parameter is passed directly to the `userData` methods. \n\nWhile `[]byte` keys are handled specially (converted to `string`), any other uncomparable type (e.g., `[]int`, `map[string]interface{}`, `func()`) passed as `key` will cause a panic if it needs to be compared against an existing key (in `Get`, `Remove`, or `Set` update paths). \n\nHowever, in the context of a web server library like fasthttp, the `key` in `SetUserValue` is typically a static string constant or a variable derived from application logic, not directly from untrusted network input. An attacker sends HTTP requests (headers, body, query params). These inputs are parsed into strings or byte slices. Application developers use these to set values. The *type* of the key used in `SetUserValue` is determined by the developer's code, not the attacker. For an attacker to trigger this panic, the application code would have to be written in such a way that the dynamic type of the key variable passed to `SetUserValue` changes based on attacker input (e.g., by deserializing attacker-controlled JSON into a generic `any` and using it as a key, or passing a slice directly). \n\nThe threat model states: \"Not a vulnerability under this model: ... misuse that violates a documented precondition of the public API.\" Using a key-value store with an uncomparable key is a misuse of the API, assuming the library implies comparable keys (which is standard for such stores). Since the attacker cannot control the *type* of the key passed by the host application via standard untrusted input (HTTP data), and the panic requires the host application to pass an uncomparable type as the key, this is not an exploitable vulnerability by an external attacker. It is a robustness issue or a host application bug. Therefore, it is INVALID.\n\nCRUX: An external attacker cannot control the dynamic type of the `key` argument passed to `SetUserValue`/`GetUserValue` to be an uncomparable type (like a map or slice) via standard untrusted input, as the key type is determined by the host application's code logic, not the protocol data.\nGREP: func (ctx \\*RequestCtx) SetUserValue",
    "elapsed": 26.1,
    "tokens": 6894,
    "tokens_in": 6253,
    "tokens_out": 641,
    "cost_usd": 0.004549259999999999,
    "file": "userdata.go",
    "round": 4,
    "grep_used": true,
    "grep_results": "GREP `func (ctx *RequestCtx) SetUserValue (simplified to: SetUserValue)`:\n```\nfasthttpadaptor/adaptor_test.go:199:\t\tctx.SetUserValue(key, value)\nserver_test.go:1859:\t\tctx.SetUserValue(k, i)\nserver_test.go:1863:\t\tctx.SetUserValueBytes([]byte(k), i)\nserver_test.go:2691:\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\nserver_test.go:2790:\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\nserver.go:769:// SetUserValue stores the given value (arbitrary object)\nserver.go:780:func (ctx *RequestCtx) SetUserValue(key, value any) {\nserver.go:781:\tctx.Request.SetUserValue(key, value)\nserver.go:784:// SetUserValueBytes stores the given value (arbitrary object)\nserver.go:793:func (ctx *RequestCtx) SetUserValueBytes(key []byte, value any) {\nserver.go:794:\tctx.Request.SetUserValueBytes(key, value)\nserver.go:797:// UserValue returns the value stored via SetUserValue* under the given key.\nserver.go:802:// UserValueBytes returns the value stored via SetUserValue*\nhttp.go:2295:// SetUserValue stores the given value (arbitrary object)\nhttp.go:2306:func (req *Request) SetUserValue(key, value any) {\nhttp.go:2310:// SetUserValueBytes stores the given value (arbitrary object)\nhttp.go:2319:func (req *Request) SetUserValueBytes(key []byte, value any) {\nhttp.go:2323:// UserValue returns the value stored via SetUserValue* under the given key.\nhttp.go:2328:// UserValueBytes returns the value stored via SetUserValue*\n```"
  },
  {
    "finding_title": "Panic from comparing uncomparable any keys",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a panic caused by comparing uncomparable interface types (like slices, maps, or functions) using `==` in Go. The code in `userData.Set`, `Get`, and `Remove` performs `kv.key == key` without checking if the dynamic type is comparable. While this pattern can indeed cause a panic if an uncomparable type is passed as a key, the critical question for security triage is whether an external attacker can trigger this through untrusted input. \n\nIn the context of fasthttp (a web server library), the `key` argument for `SetUserValue` is typically a static string or `[]byte` defined by the application developer (e.g., `ctx.SetUserValue(\"userID\", id)`). The `[]byte` case is explicitly handled and converted to a `string` (which is comparable) inside the `userData` methods. For an attacker to trigger this panic, they would need to control the *dynamic type* of the `key` argument passed to the public API, forcing it to be an uncomparable type (e.g., a slice or map). Standard HTTP inputs (headers, body, query params) are parsed into strings or byte slices. They do not change the static type of the key variable in the host application's code. The type of the key is determined by the application logic, not the protocol data. \n\nThe threat model explicitly excludes \"misuse that violates a documented precondition of the public API.\" Using a key-value store with an uncomparable key is a misuse of the API contract (which implies comparable keys for equality). Since the attacker cannot influence the type of the key via standard untrusted input, and the panic results from the host application passing an invalid key type (a programming error in the host, not a vulnerability in the library), this is not a security vulnerability exploitable by an external attacker. It is a code quality/robustness issue for the library consumer.\n\nCRUX: An external attacker cannot control the dynamic type of the `key` argument passed to `SetUserValue` to be an uncomparable type via standard untrusted input, as the key type is determined by the host application's code logic.\nGREP: func (ctx \\*RequestCtx) SetUserValue",
    "elapsed": 33.1,
    "tokens": 7449,
    "tokens_in": 6962,
    "tokens_out": 487,
    "cost_usd": 0.00438504,
    "file": "userdata.go",
    "round": 5,
    "grep_used": true,
    "grep_results": "GREP `func (ctx *RequestCtx) SetUserValue (simplified to: SetUserValue)`:\n```\nfasthttpadaptor/adaptor_test.go:199:\t\tctx.SetUserValue(key, value)\nserver_test.go:1859:\t\tctx.SetUserValue(k, i)\nserver_test.go:1863:\t\tctx.SetUserValueBytes([]byte(k), i)\nserver_test.go:2691:\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\nserver_test.go:2790:\t\t\tctx.SetUserValue(\"myKey\", &closerWithRequestCtx{\nhttp.go:2295:// SetUserValue stores the given value (arbitrary object)\nhttp.go:2306:func (req *Request) SetUserValue(key, value any) {\nhttp.go:2310:// SetUserValueBytes stores the given value (arbitrary object)\nhttp.go:2319:func (req *Request) SetUserValueBytes(key []byte, value any) {\nhttp.go:2323:// UserValue returns the value stored via SetUserValue* under the given key.\nhttp.go:2328:// UserValueBytes returns the value stored via SetUserValue*\nserver.go:769:// SetUserValue stores the given value (arbitrary object)\nserver.go:780:func (ctx *RequestCtx) SetUserValue(key, value any) {\nserver.go:781:\tctx.Request.SetUserValue(key, value)\nserver.go:784:// SetUserValueBytes stores the given value (arbitrary object)\nserver.go:793:func (ctx *RequestCtx) SetUserValueBytes(key []byte, value any) {\nserver.go:794:\tctx.Request.SetUserValueBytes(key, value)\nserver.go:797:// UserValue returns the value stored via SetUserValue* under the given key.\nserver.go:802:// UserValueBytes returns the value stored via SetUserValue*\n```"
  },
  {
    "verdict": "INVALID",
    "reasoning": "[ARBITER] The finding is invalid under the provided threat model for a reusable library. The panic occurs when comparing keys of type `any` using `==`. While Go does panic on uncomparable types (slices, maps, functions), the `key` argument to `SetUserValue`/`GetUserValue` is determined by the host application's developer (the library consumer), not by an external attacker sending malicious HTTP requests. External attackers control the HTTP payload (headers, body, path), but they do not control the memory layout or type of the keys used by the application logic to store metadata. Therefore, an external attacker cannot influence the dynamic type of the `key` argument to force a panic. This is a potential application bug if the developer passes an uncomparable type, but it is not a security vulnerability in the library reachable via untrusted external input.",
    "round": 6,
    "file": "userdata.go",
    "finding_title": "Panic from comparing uncomparable any keys",
    "elapsed": 20.7,
    "tokens_in": 3901,
    "tokens_out": 193,
    "cost_usd": 0.00221742
  }
]
Raw scan report for this file

`userData` is an unexported per-request/response key/value store, but it is reachable through exported `Request`/`Response`/`RequestCtx` user-value APIs. The main risk is not classic memory corruption, but availability and state corruption caused by unvalidated `any` keys, uncontrolled resource lifecycle, and unbounded growth.

Key observations:

- `Set`, `Get`, and `Remove` compare `kv.key == key`, where both values are `any`. If the dynamic type is uncomparable, such as a slice, map, function, or a struct containing any of those, the comparison panics.
- `Set` normalizes exact `[]byte` keys to `string`, but it does not reject or normalize other uncomparable key types.
- `Get` and `Remove` use `b2s` for `[]byte` keys. That is usually safe in this context because the converted string is only used for comparison and is not stored, but it depends on `b2s` behaving as expected.
- `Reset` calls `Close()` on any user value implementing `io.Closer`. If a stored value is attacker-influenced or a typed nil pointer implementing `io.Closer`, cleanup can panic or leak state.
- There is no bound on the number of user values, and lookup is linear, so an application that stores many attacker-influenced keys can be made to allocate excessive memory or spend excessive CPU.

```json
[
  {
    "severity": "High",
    "title": "Panic from comparing uncomparable any keys",
    "function": "Set()/Get()/Remove()",
    "description": "The code compares interface values with kv.key == key without verifying that the dynamic key type is comparable. If a caller stores or looks up a key whose dynamic type is a slice, map, function, or a struct containing an uncomparable field, Go panics at runtime. This is reachable through public user-value APIs that accept key as any, and can be used to cause denial of service if an attacker can influence the key object passed to the application."
  },
  {
    "severity": "Medium",
    "title": "Reset may invoke malicious or invalid io.Closer values",
    "function": "Reset()",
    "description": "Reset iterates over stored user values and calls Close() on any value implementing io.Closer. If an application stores attacker-influenced values, or typed nil pointers whose pointer type implements io.Closer, Close may panic, perform unintended side effects, or leave the userData slice in an inconsistent state. Error return values from Close are also ignored."
  },
  {
    "severity": "Low",
    "title": "Replacing or clearing a Closer user value without closing it",
    "function": "Set()",
    "description": "Set overwrites an existing value with the new value, including nil, without checking whether the old value implements io.Closer. If an application uses Set(key, nil) or Set(key, other) to replace a resource-holding value, the old resource is not closed, leading to resource leaks."
  },
  {
    "severity": "Low",
    "title": "Unbounded user-value storage and linear lookup",
    "function": "Set()",
    "description": "userData has no limit on the number of entries, and Get/Remove perform linear scans. If a host application stores attacker-influenced keys, such as parsed body fields or generated identifiers, without validation or limits, an attacker can cause excessive memory allocation or CPU consumption."
  }
]
```