{"record":{"id":"5cbc2f07b68df43f","repo":"JuliusBrussee/caveman","slug":"asgi-context-resolver-or-request-bounds-are-invalid","errorCode":null,"errorMessage":"ASGI context resolver or request bounds are invalid","messagePattern":"ASGI context resolver or request bounds are invalid","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/asgi.py","lineNumber":70,"sourceCode":"\nclass CavemanASGIMiddleware:\n    \"\"\"Place inside authentication and original-content guard middleware.\n\n    resolve_context may decline by returning None; existing application routing\n    and auth then run unchanged. It must use authenticated server state, not\n    namespace/session headers. This middleware never handles an auth rejection.\n    \"\"\"\n    def __init__(self, app, *, runtime: AsyncMiddlewareRuntime,\n                 routes: Mapping[str, Protocol],\n                 resolve_context: Callable[[ASGIScope], ASGIContext | None | Awaitable[ASGIContext | None]],\n                 max_body_bytes: int = 2 << 20, max_request_chunks: int = 256):\n        if not isinstance(runtime, AsyncMiddlewareRuntime):\n            raise TypeError(\"ASGI requires AsyncMiddlewareRuntime\")\n        if not routes or any(not isinstance(path, str) or not path.startswith(\"/\") or \"*\" in path or \"?\" in path\n                             or protocol not in (\"openai-chat\", \"openai-responses\", \"anthropic-messages\") for path, protocol in routes.items()):\n            raise ValueError(\"Configure exact POST paths and native LLM protocols\")\n        if not callable(resolve_context) or not 1 <= max_body_bytes <= 2 << 20 or not 1 <= max_request_chunks <= 4096:\n            raise ValueError(\"ASGI context resolver or request bounds are invalid\")\n        self.app, self.runtime = app, runtime\n        self.routes, self.resolve_context = dict(routes), resolve_context\n        self.max_body_bytes, self.max_request_chunks = max_body_bytes, max_request_chunks\n        self._version_supported = matches_framework((\"fastapi\", \"0.141\", \"1\"), (\"starlette\", \"1.6\", \"2\"))\n        if not self._version_supported and runtime.mode != \"off\":\n            runtime.decline(\"unsupported_version\")\n\n    async def __call__(self, scope, receive, send):\n        if owner.get() is not None:\n            return await self.app(scope, receive, send)\n\n        protocol = self.routes.get(scope.get(\"path\"))\n        async def passthrough(reader, reason):\n            if scope.get(\"type\") != \"http\" or scope.get(\"method\") != \"POST\" or protocol is None:\n                self.runtime.report(None, reason=reason, adapter=\"asgi\")\n                return await self.app(scope, reader, send)\n            # This exact inference route still owns its native request when\n            # projection is disabled or declined. A nested adapter must not","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/asgi.py#L52-L88","documentation":"CavemanASGIMiddleware validates the resolve_context callable and the request-size bounds in __init__. resolve_context must be callable, max_body_bytes must be between 1 and 2 MiB (2<<20), and max_request_chunks between 1 and 4096. This guards against a resolver that can never authenticate context and against buffer bounds that would either disable body buffering entirely or allow unbounded memory growth.","triggerScenarios":"Passing resolve_context=None or a non-callable; passing max_body_bytes=0 or > 2097152 (e.g. 10<<20 to 'raise the limit'); passing max_request_chunks=0 or > 4096; passing these as strings or misremembering which keyword takes which bound.","commonSituations":"Developers try to lift the 2 MiB body cap for large LLM payloads (e.g. big batched requests), copy a config where bounds were supplied as env-var strings, or refactor and swap resolve_context for a non-callable object holding the resolver.","solutions":["Keep max_body_bytes within 1..2097152; if payloads exceed 2 MiB, split or compress them upstream rather than raising the bound","Keep max_request_chunks within 1..4096","Ensure resolve_context is a callable (sync or async) accepting one ASGI scope argument; wrap it in a function if it is a method-like object","Check for values passed as strings from environment/config; convert with int() before constructing"],"exampleFix":"// before\nmw = CavemanASGIMiddleware(app, runtime=rt, routes=routes, resolve_context=None, max_body_bytes=16<<20)\n// after\nmw = CavemanASGIMiddleware(app, runtime=rt, routes=routes, resolve_context=my_resolver, max_body_bytes=2<<20)","handlingStrategy":"validation","validationCode":"assert callable(resolve_context), 'resolve_context must be callable'\nassert 1 <= max_body_bytes <= 2 << 20, f'max_body_bytes out of range: {max_body_bytes}'\nassert 1 <= max_request_chunks <= 4096, f'max_request_chunks out of range: {max_request_chunks}'","typeGuard":null,"tryCatchPattern":"try:\n    mw = CavemanASGIMiddleware(app, runtime=rt, routes=routes, resolve_context=resolver, max_body_bytes=body_cap, max_request_chunks=chunks)\nexcept ValueError as e:\n    logging.critical('invalid ASGI middleware config: %s', e)\n    raise","preventionTips":["Coerce config values from env/JSON with int() before passing them","Remember the 2 MiB hard ceiling; design payloads to fit rather than raising bounds","Keep the resolver a plain function so it is always callable"],"tags":["python","asgi","configuration","validation","argument-out-of-range"],"backgroundTag":"invalid-argument-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}