{"id":"GHSA-w2v2-5662-8v5q","kind":"vulnerability","aliases":["GHSA-w2v2-5662-8v5q"],"related":[],"title":"Rust gRPC Generate accepts out-of-vocabulary prompt token IDs and can terminate EngineCore","description":"## Summary\n\nThe Rust gRPC `Generate` and `GenerateStream` service accepts pre-tokenized prompt IDs from the protobuf `TokenIds` field and forwards them to `TextLlm`/`EngineCore` without checking them against the served model or tokenizer vocabulary.\n\nThis is the gRPC sibling of the public Rust HTTP `/inference/v1/generate` issue fixed in PR `#45441`: a single prompt token ID such as `9000000` can reach the engine embedding lookup, trigger a fatal EngineCore failure, and leave the server unhealthy. PR `#45441` adds validation for the HTTP `/inference/v1/generate` route, but it does not touch `rust/src/server/src/grpc/*`.\n\n## Technical Details\n\n`rust/proto/vllm_grpc.proto` exposes pre-tokenized prompts:\n\n```proto\noneof prompt {\n  string text = 3;\n  TokenIds token_ids = 4;\n}\n```\n\n`rust/src/server/src/grpc/convert.rs` maps those IDs directly:\n\n```rust\nSome(pb::generate_request::Prompt::TokenIds(ids)) => Prompt::TokenIds(ids.ids),\n```\n\nThe gRPC handlers then submit the converted `TextRequest`:\n\n```rust\nlet text_request =\n    convert::to_text_request(proto_req, false, self.state.served_model_names())?;\nlet stream = self.state.chat.text().generate(text_request).await;\n```\n\nFor pre-tokenized prompts, `rust/src/text/src/lib.rs` intentionally bypasses tokenization:\n\n```rust\nPrompt::TokenIds(token_ids) => token_ids,\n```\n\n`rust/src/model/src/request.rs` forwards the IDs into the engine request:\n\n```rust\nprompt_token_ids: Some(prompt_token_ids),\n```\n\nThere is no gRPC-side equivalent of the token-id range validators used by the Rust OpenAI routes.\n\nThe Rust OpenAI validation layer already treats out-of-vocabulary token IDs as invalid client input. `rust/src/server/src/routes/openai/utils/token_ids.rs` rejects prompt token IDs, `allowed_token_ids`, and `logit_bias` keys outside the relevant vocabulary bounds.\n\nPR `#44680` added that validator family to `/v1/completions` and `/v1/chat/completions`. PR `#45441` extends the same validator family to the Rust HTTP `/inference/v1/generate` route after demonstrating that an OOV `token_ids` prompt kills EngineCore. The gRPC service still lacks the same pre-submit guard.\n\n## Impact\n\nAn attacker who can reach a vLLM Rust gRPC endpoint can submit a `Generate` or `GenerateStream` request with an out-of-vocabulary `prompt.token_ids` value. The request is accepted by the gRPC frontend and submitted to the shared text generation path. The same invalid token-id sink is documented publicly in PR `#45441` as aborting EngineCore for the Rust HTTP sibling route.\n\nExpected impact is a single-request denial of service for deployments exposing the Rust gRPC service. Fixed behavior should be an `INVALID_ARGUMENT` gRPC status before the request is submitted to the engine.\n\nSuggested severity: High if the gRPC endpoint is network-reachable without an additional deployment-layer credential requirement.\n\nSuggested CVSS v3.1:\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` = 7.5\n\nIf maintainers model the gRPC surface as reachable only to already-authorized clients, the same issue becomes:\n\n`CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H` = 6.5\n\nSuggested CWEs:\n\n- `CWE-20`: Improper Input Validation\n- `CWE-400`: Uncontrolled Resource Consumption\n\n## Suggested Fix\n\nApply the same token-id range validation used by the Rust OpenAI and HTTP generate routes before either gRPC handler calls `state.chat.text().generate()`.\n\nThe gRPC fix should validate:\n\n- `GenerateRequest.prompt.token_ids` against the same prompt bound used by PR `#45441`, namely the union of tokenizer and model vocabulary bounds.\n- `DecodingParameters.allowed_token_ids` against tokenizer vocabulary.\n- `DecodingParameters.logit_bias` keys against model vocabulary.\n- `ResponseOptions.prompt_candidates.token_ids` and `ResponseOptions.output_candidates.token_ids`, if they are lowered into `logprob_token_ids`, against the same model/tokenizer bounds used by the corresponding HTTP route.\n\nAdd tests for both unary `Generate` and streaming `GenerateStream` that assert OOV token IDs return `INVALID_ARGUMENT` before any engine request is submitted.\n\n## Appendix: Duplicate Research\n\nI checked:\n\n- Public PR `#44680`: validates Rust `/v1/completions` and `/v1/chat/completions`; changed files do not touch gRPC.\n- Public PR `#45441`: validates Rust HTTP `/inference/v1/generate`; changed files do not touch gRPC.\n- GitHub issue/PR searches for `grpc token_ids out-of-vocab`, `grpc Generate token_ids EngineCore`, and `grpc token_ids validation`: no public gRPC duplicate found.\n- vLLM security advisories matching `grpc`, `token_ids`, `out-of-vocab`, `EngineCore`, or `Generate`: no gRPC duplicate found.\n- Local prior vLLM submissions: none cover Rust gRPC prompt token-id range validation.\n\n## Affected versions\n\n- Package: `vllm` (PyPI).\n- Affected versions: `>= 0.22.0, < 0.24.0`.\n- First stable patched version: `0.24.0`.\n\nAdvisory fixed in [#45685](https://github.com/vllm-project/vllm/pull/45685).","summary":"The Rust gRPC Generate and GenerateStream endpoints in vLLM pass pre-tokenized prompt token IDs to the engine without checking them against the vocabulary. A single out-of-range token ID can crash EngineCore and leave the server unhealthy, a denial of service.","severity":"medium","cvss":5.9,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"vllm","fixed":["0.24.0"],"affected":">= 0.22.0, < 0.24.0","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["ai-tooling","cwe-20","cwe-400","grpc","python","self-hosted","vllm"],"not_affected_if":["auth-required-in-front","grpc-not-exposed","version-outside-range"],"how_to_check":"- Check the installed vLLM version with `pip show vllm`; versions `>= 0.22.0, < 0.24.0` are affected.\n- Check whether the Rust gRPC service is enabled and reachable from untrusted networks; the advisory describes it as a network-reachable surface.","fix":"- `vllm` (PyPI): upgrade to `0.24.0`.","status":"open","published_at":"2026-10-09T07:49:41+00:00","first_seen_at":"2026-11-09T22:53:23+00:00","triaged_at":"2026-11-09T23:01:02+00:00","updated_at":"2026-11-09T23:01:02+00:00","category":"ai-tooling","confidence":0.8,"triage":{"at":"2026-11-09T23:01:02Z","model":"claude-haiku-5-5","output":{"fix":"- `vllm` (PyPI): upgrade to `0.24.0`.","reach":"network","title":"vLLM: out-of-vocabulary token IDs over gRPC can crash the inference engine","impact":"medium","summary":"The Rust gRPC Generate and GenerateStream endpoints in vLLM pass pre-tokenized prompt token IDs to the engine without checking them against the vocabulary. A single out-of-range token ID can crash EngineCore and leave the server unhealthy, a denial of service.","audience":"developers","category":"ai-tooling","nicknames":[],"who_cares":["vllm","python","grpc","self-hosted"],"confidence":0.8,"fix_status":"available","needs_auth":"unknown","worst_case":"a single request crashes the engine process and takes the server down","analyst_note":"","how_to_check":"- Check the installed vLLM version with `pip show vllm`; versions `>= 0.22.0, < 0.24.0` are affected.\n- Check whether the Rust gRPC service is enabled and reachable from untrusted networks; the advisory describes it as a network-reachable surface.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["version-outside-range","grpc-not-exposed","auth-required-in-front"],"who_is_affected":"Teams running vLLM 0.22.0 up to but not including 0.24.0 with the Rust gRPC service exposed to clients. The advisory says the flaw is reachable over the network and needs no credentials unless a deployment adds its own access control in front of the gRPC endpoint. Deployments that serve only the Python HTTP API or keep the gRPC service on a trusted internal network are less exposed.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"medium"},"cost_usd":0.000973,"input_tokens":6636,"output_tokens":618,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-20':33B,536C '-400':35B,541C '-8':20A,26A '/av':493C,523C '/inference/v1/generate':132C,169C,347C '/v1/chat/completions':335C '/v1/completions':333C '3':190C '3.1':492C,522C '4':194C '44680':327C '45441':137C,163C,337C,425C,586C '5662':19A,25A '6.5':532C '7.5':502C '9000000':145C 'abort':427C 'accept':4A,95C,400C 'add':164C,328C,632C 'addit':482C 'ai':30B 'ai-tool':29B 'allow':314C 'alreadi':297C,514C 'already-author':513C 'appli':547C 'argument':458C 'assert':642C 'attack':372C 'author':515C 'await':239C 'becom':520C 'behavior':453C 'bias':319C,603C 'bound':325C,582C,595C,625C 'bypass':247C 'call':568C 'check':62B,113C 'client':307C,516C 'consumpt':544C 'convert':217C,223C 'correspond':629C 'crash':76B 'credenti':486C 'cvss':489C,491C,521C 'cwe':32B,34B,535C,540C 'cwes':534C 'decodingparameters.allowed':596C 'decodingparameters.logit':602C 'demonstr':350C 'denial':84B,442C 'deploy':446C,484C 'deployment-lay':483C 'detail':178C 'direct':199C 'document':421C 'either':565C 'embed':150C 'endpoint':48B,380C,475C 'engin':60B,149C,262C,468C 'enginecor':15A,77B,111C,155C,358C,428C 'equival':278C 'expect':435C 'expos':180C,447C 'extend':338C 'failur':156C 'fals':229C 'famili':331C,342C 'fatal':154C 'field':105C 'fix':134C,452C,546C,573C 'forward':107C,257C 'frontend':404C 'generat':3A,45B,91C,203C,236C,384C,411C,562C,570C,637C 'generaterequest.prompt.token':576C 'generatestream':47B,93C,386C,640C 'ghsa':17A,23A 'ghsa-w2v2':16A,22A 'grpc':2A,36B,44B,90C,125C,212C,276C,360C,379C,403C,450C,459C,474C,507C,566C,572C 'grpc-side':275C 'guard':369C 'h':501C,531C 'handler':213C,567C 'high':471C 'host':40B 'http':131C,168C,346C,432C,561C,630C 'id':11A,57B,74B,100C,142C,193C,198C,207C,253C,255C,259C,267C,271C,283C,304C,313C,316C,355C,395C,418C,552C,577C,598C,609C,612C,620C 'ids.ids':210C 'impact':370C,436C 'improp':537C 'input':308C,538C 'intent':246C 'invalid':306C,415C,457C 'issu':133C,519C 'key':320C,604C 'kill':357C 'l/pr':495C,525C 'l/ui':526C 'lack':363C 'layer':296C,485C 'leav':79B,158C 'let':220C,233C 'logit':318C 'logprob':618C 'lookup':151C 'lower':616C 'maintain':504C 'map':196C 'model':118C,231C,505C,593C,606C 'model/tokenizer':624C 'n/a':500C,530C 'n/ac':494C,524C 'n/i':499C,529C 'n/s':497C,527C 'n/ui':496C 'name':232C,587C 'network':478C 'network-reach':477C 'oneof':186C 'oov':353C 'openai':290C,294C,559C 'out-of-rang':69B 'out-of-vocabulari':5A,299C,390C 'outsid':321C 'pass':51B 'path':412C 'pb':202C 'pr':136C,162C,326C,336C,424C,585C 'pre':53B,97C,182C,242C,367C 'pre-submit':366C 'pre-token':52B,96C,181C,241C 'prompt':9A,55B,99C,140C,184C,187C,205C,208C,244C,250C,265C,269C,311C,356C,581C 'prompt.token':394C 'proto':185C,227C 'protobuf':103C 'public':129C,422C 'python':37B 'rang':72B,284C,553C 'reach':147C,375C 'reachabl':479C,510C 'reject':310C 'relev':323C 'req':228C 'request':204C,222C,226C,238C,263C,387C,398C,441C,463C 'requir':487C 'resourc':543C 'responseoptions.output_candidates.token':611C 'responseoptions.prompt_candidates.token':608C 'rout':170C,291C,348C,434C,563C,631C 'rust':1A,43B,89C,130C,200C,219C,249C,264C,289C,293C,345C,378C,431C,449C,558C 'rust/proto/vllm_grpc.proto':179C 'rust/src/model/src/request.rs':256C 'rust/src/server/src/grpc':176C 'rust/src/server/src/grpc/convert.rs':195C 'rust/src/server/src/routes/openai/utils/token_ids.rs':309C 'rust/src/text/src/lib.rs':245C 'self':39B 'self-host':38B 'self.state.chat.text':235C 'self.state.served':230C 'serv':117C 'server':81B,160C 'servic':86B,94C,361C,444C,451C 'sever':470C 'share':409C 'sibl':126C,433C 'side':277C 'singl':68B,139C,440C 'single-request':439C 'sink':419C 'state.chat.text':569C 'status':460C 'still':362C 'stream':234C,639C 'string':188C 'submit':215C,368C,382C,406C,465C 'suggest':469C,488C,533C,545C 'summari':87C 'surfac':508C 'technic':177C 'termin':14A 'test':633C 'text':189C,221C,225C,237C,410C 'textllm':110C 'textrequest':218C 'token':10A,54B,56B,73B,98C,120C,141C,183C,192C,243C,248C,252C,254C,266C,270C,282C,303C,312C,315C,354C,417C,551C,591C,597C,600C,619C 'token-id':281C,416C,550C 'tokenid':104C,191C,206C,209C,251C 'tool':31B 'touch':175C 'treat':298C 'trigger':152C 'u/c':498C,528C 'unari':636C 'uncontrol':542C 'unhealthi':82B,161C 'union':589C 'use':286C,555C,583C,626C 'v3.1':490C 'v5q':21A,27A 'valid':165C,285C,295C,330C,341C,539C,554C,575C 'valu':396C 'vllm':28A,41B,50B,377C 'vocabulari':8A,66B,121C,302C,324C,393C,594C,601C,607C 'w2v2':18A,24A 'without':61B,112C,480C","nicknames":[],"mitigations":[],"who_affected":"Teams running vLLM 0.22.0 up to but not including 0.24.0 with the Rust gRPC service exposed to clients. The advisory says the flaw is reachable over the network and needs no credentials unless a deployment adds its own access control in front of the gRPC endpoint. Deployments that serve only the Python HTTP API or keep the gRPC service on a trusted internal network are less exposed.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["vllm"],"ptokens":["vllm"],"labels":["aitooling","cwe20","cwe400","grpc","medium","pypi","python","selfhosted","vllm"],"campaign_type":null,"search_a":"'-8':20A,26A '5662':19A,25A 'accept':4A 'enginecor':15A 'generat':3A 'ghsa':17A,23A 'ghsa-w2v2':16A,22A 'grpc':2A 'id':11A 'out-of-vocabulari':5A 'prompt':9A 'rust':1A 'termin':14A 'token':10A 'v5q':21A,27A 'vllm':28A 'vocabulari':8A 'w2v2':18A,24A","search_b":"'-20':33B '-400':35B 'ai':30B 'ai-tool':29B 'check':62B 'crash':76B 'cwe':32B,34B 'denial':84B 'endpoint':48B 'engin':60B 'enginecor':77B 'generat':45B 'generatestream':47B 'grpc':36B,44B 'host':40B 'id':57B,74B 'leav':79B 'out-of-rang':69B 'pass':51B 'pre':53B 'pre-token':52B 'prompt':55B 'python':37B 'rang':72B 'rust':43B 'self':39B 'self-host':38B 'server':81B 'servic':86B 'singl':68B 'token':54B,56B,73B 'tool':31B 'unhealthi':82B 'vllm':41B,50B 'vocabulari':66B 'without':61B","search_c":"'-20':536C '-400':541C '/av':493C,523C '/inference/v1/generate':132C,169C,347C '/v1/chat/completions':335C '/v1/completions':333C '3':190C '3.1':492C,522C '4':194C '44680':327C '45441':137C,163C,337C,425C,586C '6.5':532C '7.5':502C '9000000':145C 'abort':427C 'accept':95C,400C 'add':164C,328C,632C 'addit':482C 'allow':314C 'alreadi':297C,514C 'already-author':513C 'appli':547C 'argument':458C 'assert':642C 'attack':372C 'author':515C 'await':239C 'becom':520C 'behavior':453C 'bias':319C,603C 'bound':325C,582C,595C,625C 'bypass':247C 'call':568C 'check':113C 'client':307C,516C 'consumpt':544C 'convert':217C,223C 'correspond':629C 'credenti':486C 'cvss':489C,491C,521C 'cwe':535C,540C 'cwes':534C 'decodingparameters.allowed':596C 'decodingparameters.logit':602C 'demonstr':350C 'denial':442C 'deploy':446C,484C 'deployment-lay':483C 'detail':178C 'direct':199C 'document':421C 'either':565C 'embed':150C 'endpoint':380C,475C 'engin':149C,262C,468C 'enginecor':111C,155C,358C,428C 'equival':278C 'expect':435C 'expos':180C,447C 'extend':338C 'failur':156C 'fals':229C 'famili':331C,342C 'fatal':154C 'field':105C 'fix':134C,452C,546C,573C 'forward':107C,257C 'frontend':404C 'generat':91C,203C,236C,384C,411C,562C,570C,637C 'generaterequest.prompt.token':576C 'generatestream':93C,386C,640C 'grpc':90C,125C,212C,276C,360C,379C,403C,450C,459C,474C,507C,566C,572C 'grpc-side':275C 'guard':369C 'h':501C,531C 'handler':213C,567C 'high':471C 'http':131C,168C,346C,432C,561C,630C 'id':100C,142C,193C,198C,207C,253C,255C,259C,267C,271C,283C,304C,313C,316C,355C,395C,418C,552C,577C,598C,609C,612C,620C 'ids.ids':210C 'impact':370C,436C 'improp':537C 'input':308C,538C 'intent':246C 'invalid':306C,415C,457C 'issu':133C,519C 'key':320C,604C 'kill':357C 'l/pr':495C,525C 'l/ui':526C 'lack':363C 'layer':296C,485C 'leav':158C 'let':220C,233C 'logit':318C 'logprob':618C 'lookup':151C 'lower':616C 'maintain':504C 'map':196C 'model':118C,231C,505C,593C,606C 'model/tokenizer':624C 'n/a':500C,530C 'n/ac':494C,524C 'n/i':499C,529C 'n/s':497C,527C 'n/ui':496C 'name':232C,587C 'network':478C 'network-reach':477C 'oneof':186C 'oov':353C 'openai':290C,294C,559C 'out-of-vocabulari':299C,390C 'outsid':321C 'path':412C 'pb':202C 'pr':136C,162C,326C,336C,424C,585C 'pre':97C,182C,242C,367C 'pre-submit':366C 'pre-token':96C,181C,241C 'prompt':99C,140C,184C,187C,205C,208C,244C,250C,265C,269C,311C,356C,581C 'prompt.token':394C 'proto':185C,227C 'protobuf':103C 'public':129C,422C 'rang':284C,553C 'reach':147C,375C 'reachabl':479C,510C 'reject':310C 'relev':323C 'req':228C 'request':204C,222C,226C,238C,263C,387C,398C,441C,463C 'requir':487C 'resourc':543C 'responseoptions.output_candidates.token':611C 'responseoptions.prompt_candidates.token':608C 'rout':170C,291C,348C,434C,563C,631C 'rust':89C,130C,200C,219C,249C,264C,289C,293C,345C,378C,431C,449C,558C 'rust/proto/vllm_grpc.proto':179C 'rust/src/model/src/request.rs':256C 'rust/src/server/src/grpc':176C 'rust/src/server/src/grpc/convert.rs':195C 'rust/src/server/src/routes/openai/utils/token_ids.rs':309C 'rust/src/text/src/lib.rs':245C 'self.state.chat.text':235C 'self.state.served':230C 'serv':117C 'server':160C 'servic':94C,361C,444C,451C 'sever':470C 'share':409C 'sibl':126C,433C 'side':277C 'singl':139C,440C 'single-request':439C 'sink':419C 'state.chat.text':569C 'status':460C 'still':362C 'stream':234C,639C 'string':188C 'submit':215C,368C,382C,406C,465C 'suggest':469C,488C,533C,545C 'summari':87C 'surfac':508C 'technic':177C 'test':633C 'text':189C,221C,225C,237C,410C 'textllm':110C 'textrequest':218C 'token':98C,120C,141C,183C,192C,243C,248C,252C,254C,266C,270C,282C,303C,312C,315C,354C,417C,551C,591C,597C,600C,619C 'token-id':281C,416C,550C 'tokenid':104C,191C,206C,209C,251C 'touch':175C 'treat':298C 'trigger':152C 'u/c':498C,528C 'unari':636C 'uncontrol':542C 'unhealthi':161C 'union':589C 'use':286C,555C,583C,626C 'v3.1':490C 'valid':165C,285C,295C,330C,341C,539C,554C,575C 'valu':396C 'vllm':377C 'vocabulari':121C,302C,324C,393C,594C,601C,607C 'without':112C,480C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-10-09T07:49:41+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-w2v2-5662-8v5q","url":"https://github.com/vllm-project/vllm/security/advisories/GHSA-w2v2-5662-8v5q","severity":"medium","published_at":"2026-10-09T07:49:41+00:00","modified_at":"2026-10-27T22:09:23+00:00","first_seen_at":"2026-11-09T22:53:23+00:00","fetched_at":"2026-11-09T22:53:23+00:00","note":null}],"documents":[{"id":47332,"url":"https://github.com/vllm-project/vllm/security/advisories/GHSA-w2v2-5662-8v5q","title":"Vendor advisory: github.com","kind":"vendor","source":"nvd","publisher":"vendor","text":null,"published_at":"2026-10-09T07:49:41+00:00","relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}