CVE-2026-54234: Vllm Denial of Service Vulnerability

HIGH
Advisory Identifier: GHSA-8wr5-jm2h-8r4f
CVSS 7.5
CWE-20
Published
Advisory Summary

CVE-2026-54234 is a HIGH (CVSS 7.5) denial of service vulnerability (CWE-20: Improper Input Validation) affecting Vllm libraries in the PyPI ecosystem. The issue involves vllm has remote dos via invalid recovered token reinjection. Remediation is available by upgrading affected packages to version 0.24.0.

CVE-2026-54234 at a Glance

CVE Identifier CVE-2026-54234
GHSA Identifier GHSA-8wr5-jm2h-8r4f
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-20: Improper Input Validation
Affected Product Vllm
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-54234

To remediate this vulnerability, upgrade all affected Vllm dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

vllm fixed: 0.24.0
pip install vllm>=0.24.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show vllm

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-54234, inspect your installed packages using pip:

pip show vllm

Analyze the terminal output to locate any version of Vllm in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.24.0), your application is affected.

Note: The pip show vllm command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-20: Improper Input Validation, where the product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly. In Vllm, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary A frontend-legal multi-request speculative workload can make vLLM produce an out-of-vocabulary recovered token equal to `vocab_size`, convert that value to `-1` when choosing the next live token for a request, and then feed that `-1` back into the next drafter input ids. On Qwen3 GPTQ this reaches the worker-side drafting / attention path and crashes the engine with a GPU `device-side assert`. The same issue is reachable through the public gRPC request surface by sending a specific overlapping `Generate` / `Abort` sequence. ## Impact - A remote client that can send public gRPC generation requests can crash the shared vLLM engine worker - The triggering request sequence aborts concurrent requests and prevents later requests from completing until the worker is restarted - In shared deployments, this is a service-wide denial of service for other clients, not just a failure isolated to the attacking requests - The failure is reproducible, so repeated request sequences can sustain the outage ## Affected version - Confirmed on vLLM `0.17.1` - Earlier and later versions have not been checked yet in this report ## Repro model - Official Hugging Face repo: - [`Qwen/Qwen3-0.6B-GPTQ-Int8`](https://huggingface.co/Qwen/Qwen3-0.6B-GPTQ-Int8) - Anyone wants to reproduce the bug with my PoC scripts should download `Qwen3-0.6B-GPTQ-Int8` first ## Trigger chain 1. A legal multi-request speculative workload keeps structured-output state, speculative decoding, overlap, and request cancellation active in the same live engine. 2. During rejection sampling, vLLM produces a recovered token equal to the model `vocab_size` boundary value. 3. That recovered token appears in position 0 of the sampled speculative row for a live request. The same row also contains trailing padding entries equal to `-1`, but those padding entries are not the key fault by themselves. 4. The next-token preparation step treats the position-0 recovered token as the real next token for that request and converts that out-of-vocabulary value to `-1`. 5. The drafter writes that converted `-1` back into the live next-step input-id row for the request. 6. The drafting / embedding / attention path later consumes that live invalid token and the worker crashes on GPU. ## Details ### Simple example The important distinction is: - trailing `-1` values in a speculative row can be ordinary padding - the bug appears when the first live token for a request becomes `151936 == vocab_size`, and that live token is then converted into `-1` In simplified form, the bad transition looks like this: ```text sampled speculative row: [151936, -1, -1, -1, ...] ``` At this point, the trailing `-1` values are only padding. The critical problem is that the first position holds `151936`, which is out of vocabulary and is being treated as the request's real next token. Then vLLM prepares the next-token buffer: ```text next_token_ids: [-1, ...] ``` Finally, that converted `-1` is written back into the live model input ids: ```text input_ids_after: [-1, 0, 0, 0, ...] ``` The crash happens because the live next token became `-1` and was later consumed by the drafting / embedding / attention path, not merely because the speculative row contained padded `-1` entries. ### Trigger path in code 1. The workload is frontend-legal. The requests use normal `SamplingParams` features such as structured outputs, `stop`, `bad_words`, `min_tokens`, and streaming overlap. No malformed token-id list is required at the request boundary. 2. In speculative decoding, the rejection sampler can generate recovered tokens when drafted tokens are rejected. ```python # vllm/v1/sample/rejection_sampler.py def sample_recovered_tokens(...): recovered_token_ids = torch.empty_like(draft_token_ids) sample_recovered_tokens_kernel[(batch_size, max_spec_len)](...) return recovered_token_ids ``` On the verified Qwen3 run, the recovered-token trace shows `recovered_token_ids[0] = 151936`, which is exactly `vocab_size` for this checkpoint. 3. The speculative proposer then prepares the next-token row from the sampled speculative row. ```python # vllm/v1/spec_decode/eagle.py def prepare_next_token_ids_padded(...): ... eagle_prepare_next_token_padded_kernel[grid]( sampled_token_ids, discard_request_mask, backup_tokens_gpu, next_token_ids, valid_sampled_tokens_count, gpu_input_batch.vocab_size, ... ) return next_token_ids, valid_sampled_tokens_count ``` In the verified trace, this step receives a sampled row beginning with `151936`, followed by `-1` padding. The important point is that `151936` occupies the first live token position for the request. This step then produces `next_token_ids[0] = -1`, meaning the live next token for the request has been converted to `-1`. 4. The drafter then rotates the draft input ids and inserts those `next_token_ids` back into the live input-id buffer. ```python # vllm/v1/spec_decode/eagle.py def set_inputs_first_pass(...): ... self.input_ids[token_indices_to_sample] = next_token_ids ``` In the verified trace, this produces `input_ids_after[0] = -1`. 5. The model-side embed path later consumes those input ids. ```python # vllm/model_executor/models/qwen2.py def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor: return self.embed_tokens(input_ids) ``` In the verified trace, this is the first point where the converted `-1` becomes visible as a real model input. The bug is not merely that the sampled speculative row contained padding `-1`; the bug is that the live next token for the request became `-1` and was written back into input ids. 6. After that point, the visible sink depends on timing and backend state. On the attached Qwen3 reproducer, the engine commonly dies later in the drafting / attention path with `CUDA error: device-side assert triggered`, for example under `flash_attn_varlen_func(...)`. ### Local script breakdown `repro_g4_recovered_minus1_local.py` is a standalone local reproducer. - It reads the Qwen3 checkpoint path from `VLLM_POC_G4_MODEL` or the built-in `/path/to/qwen3` placeholder - It creates `EngineCore` directly without any external helper dependency - It submits one fixed multi-request workload that preserves the same overlap and speculative-decoding state needed for the bug - It writes: - `request_payloads.json` - `repro_config.json` - `timeline.json` - `responses.json` - `error.txt` - `recovered_chain_trace.jsonl` - `recovered_chain_trace.jsonl` is the key attribution artifact. It records the recovered-token chain directly from the standalone reproducer ### gRPC script breakdown `repro_g4_recovered_minus1_grpc.py` is a standalone public gRPC reproducer. - It reads the Qwen3 checkpoint path from `VLLM_POC_G4_MODEL` or the built-in `/path/to/qwen3` placeholder - It starts a temporary `vllm.entrypoints.grpc_server` process - It sends only public `Generate` and `Abort` RPCs - It submits one fixed overlapping request sequence that preserves the same speculative-decoding state needed for the bug - After the crash window, it sends one more public `Generate` probe request to confirm that later gRPC requests also fail after the worker dies - It writes: - `request_payloads.json` - `timeline.json` - `server_command.json` - `responses.json` - `post_crash_probe.json` - `server.stdout.log` - `server.stderr.log` ## Observed result Local repro typically ends with: - a recovered-token trace showing: - `sample_recovered_tokens_return -> recovered_token_ids[0] = 151936` - `prepare_next_token_ids_padded -> next_token_ids[0] = -1` - `set_inputs_first_pass -> input_ids_after[0] = -1` - `embed_input_ids_out_of_range -> input_ids[0] = -1` - `CUDA error: device-side assert triggered` - a fatal engine-side failure gRPC repro typically ends with: - the triggering gRPC requests failing with `INTERNAL: EngineCore encountered an issue. See stack trace (above) for the root cause.` - server logs showing the worker dies with `CUDA error: device-side assert triggered` - a later public probe request also failing after the worker is dead This demonstrates that the issue is reachable through the public gRPC request surface, not only through a local reproducer. ## Log snippets ### Local recovered-chain trace ```text sample_recovered_tokens_return: recovered_token_ids = [151936, ...] vocab_size = 151936 prepare_next_token_ids_padded: sampled_token_ids_head = [[151936, -1, -1, ...], ...] next_token_ids = [-1, ...] set_inputs_first_pass: input_ids_after = [-1, 0, 0, 0, ...] embed_input_ids_out_of_range: input_ids = [-1, 0, 0, 0, ...] ``` ### gRPC server log ```text torch.AcceleratorError: CUDA error: device-side assert triggered ... vllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue. See stack trace (above) for the root cause. ... Error in Generate for request post_crash_probe vllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue. See stack trace (above) for the root cause. ``` ## Root cause This is a speculative-decoding state-handling bug, not an invalid frontend token-id input bug. The root cause is that a recovered speculative token can become equal to `vocab_size`, then be selected as the live next token for a request, then be converted to `-1`, and that converted `-1` is still written back into live drafter input ids and later consumed by the drafting / embedding / attention path. For the Qwen3 checkpoint used here: - `151936 == vocab_size` This value should be described as the model `vocab_size` boundary value, not as a legal token id. ## Attachments The attached bundle for this report should contain: - `repro_g4_recovered_minus1_local.py` - `repro_g4_recovered_minus1_grpc.py` These two standalone scripts are sufficient to reproduce the issue and its public gRPC reachability. ## Fix A fix for this vulnerability has been merged in: https://github.com/vllm-project/vllm/pull/44744

CVE-2026-54234 Affected Vllm Versions

1 component(s)
Fixed Versions:
0.24.0
Product / Package Ecosystem Affected Versions Fixed Version
vllm
PyPI
≥ 0.17.1
< 0.24.0
0.24.0

References

Frequently Asked Questions

What is CVE-2026-54234?
CVE-2026-54234 is a security vulnerability in Vllm involving vLLM has Remote DoS via Invalid Recovered Token Reinjection under CWE-20: Improper Input Validation.
Which Vllm versions are affected?
Affected versions include releases prior to 0.24.0 across affected packages: vllm.
What version fixes CVE-2026-54234?
Upgrading to 0.24.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is vllm affected?
Yes, vllm is affected in releases prior to 0.24.0. Upgrade your dependency to 0.24.0 or later.
What is the CVSS score of CVE-2026-54234?
CVE-2026-54234 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-54234?
CVE-2026-54234 is classified under CWE-20: Improper Input Validation (The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly.).
How can I check PyPI dependencies for CVE-2026-54234?
Run the dependency tree command: 'pip show vllm'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-8wr5-jm2h-8r4f",
  "modified" : "2026-07-17T17:08:03Z",
  "published" : "2026-07-17T17:08:03Z",
  "aliases" : [ "CVE-2026-54234" ],
  "summary" : "vLLM has Remote DoS via Invalid Recovered Token Reinjection",
  "details" : "## Summary\n\nA frontend-legal multi-request speculative workload can make vLLM produce an out-of-vocabulary recovered token equal to `vocab_size`, convert that value to `-1` when choosing the next live token for a request, and then feed that `-1` back into the next drafter input ids. On Qwen3 GPTQ this reaches the worker-side drafting / attention path and crashes the engine with a GPU `device-side assert`.\n\nThe same issue is reachable through the public gRPC request surface by sending a specific overlapping `Generate` / `Abort` sequence.\n\n## Impact\n\n- A remote client that can send public gRPC generation requests can crash the\n  shared vLLM engine worker\n- The triggering request sequence aborts concurrent requests and prevents later\n  requests from completing until the worker is restarted\n- In shared deployments, this is a service-wide denial of service for other\n  clients, not just a failure isolated to the attacking requests\n- The failure is reproducible, so repeated request sequences can sustain the\n  outage\n\n## Affected version\n\n- Confirmed on vLLM `0.17.1`\n- Earlier and later versions have not been checked yet in this report\n\n## Repro model\n\n- Official Hugging Face repo:\n  - [`Qwen/Qwen3-0.6B-GPTQ-Int8`](https://huggingface.co/Qwen/Qwen3-0.6B-GPTQ-Int8)\n- Anyone wants to reproduce the bug with my PoC scripts should download `Qwen3-0.6B-GPTQ-Int8` first\n\n## Trigger chain\n\n1. A legal multi-request speculative workload keeps structured-output state,\n   speculative decoding, overlap, and request cancellation active in the same\n   live engine.\n2. During rejection sampling, vLLM produces a recovered token equal to the\n   model `vocab_size` boundary value.\n3. That recovered token appears in position 0 of the sampled speculative row\n   for a live request. The same row also contains trailing padding entries\n   equal to `-1`, but those padding entries are not the key fault by\n   themselves.\n4. The next-token preparation step treats the position-0 recovered token as the\n   real next token for that request and converts that out-of-vocabulary value\n   to `-1`.\n5. The drafter writes that converted `-1` back into the live next-step input-id\n   row for the request.\n6. The drafting / embedding / attention path later consumes that live invalid\n   token and the worker crashes on GPU.\n\n## Details\n\n### Simple example\n\nThe important distinction is:\n\n- trailing `-1` values in a speculative row can be ordinary padding\n- the bug appears when the first live token for a request becomes\n  `151936 == vocab_size`, and that live token is then converted into `-1`\n\nIn simplified form, the bad transition looks like this:\n\n```text\nsampled speculative row:\n[151936, -1, -1, -1, ...]\n```\n\nAt this point, the trailing `-1` values are only padding. The critical problem\nis that the first position holds `151936`, which is out of vocabulary and is\nbeing treated as the request's real next token.\n\nThen vLLM prepares the next-token buffer:\n\n```text\nnext_token_ids:\n[-1, ...]\n```\n\nFinally, that converted `-1` is written back into the live model input ids:\n\n```text\ninput_ids_after:\n[-1, 0, 0, 0, ...]\n```\n\nThe crash happens because the live next token became `-1` and was later consumed by the drafting / embedding / attention path, not merely because the speculative row contained padded `-1` entries.\n\n### Trigger path in code\n\n1. The workload is frontend-legal. The requests use normal `SamplingParams`\n   features such as structured outputs, `stop`, `bad_words`, `min_tokens`, and\n   streaming overlap. No malformed token-id list is required at the request\n   boundary.\n2. In speculative decoding, the rejection sampler can generate recovered tokens\n   when drafted tokens are rejected.\n   ```python\n   # vllm/v1/sample/rejection_sampler.py\n   def sample_recovered_tokens(...):\n       recovered_token_ids = torch.empty_like(draft_token_ids)\n       sample_recovered_tokens_kernel[(batch_size, max_spec_len)](...)\n       return recovered_token_ids\n   ```\n   On the verified Qwen3 run, the recovered-token trace shows\n   `recovered_token_ids[0] = 151936`, which is exactly `vocab_size` for this\n   checkpoint.\n3. The speculative proposer then prepares the next-token row from the sampled\n   speculative row.\n   ```python\n   # vllm/v1/spec_decode/eagle.py\n   def prepare_next_token_ids_padded(...):\n       ...\n       eagle_prepare_next_token_padded_kernel[grid](\n           sampled_token_ids,\n           discard_request_mask,\n           backup_tokens_gpu,\n           next_token_ids,\n           valid_sampled_tokens_count,\n           gpu_input_batch.vocab_size,\n           ...\n       )\n       return next_token_ids, valid_sampled_tokens_count\n   ```\n   In the verified trace, this step receives a sampled row beginning with\n   `151936`, followed by `-1` padding. The important point is that `151936`\n   occupies the first live token position for the request. This step then\n   produces `next_token_ids[0] = -1`, meaning the live next token for the\n   request has been converted to `-1`.\n4. The drafter then rotates the draft input ids and inserts those\n   `next_token_ids` back into the live input-id buffer.\n   ```python\n   # vllm/v1/spec_decode/eagle.py\n   def set_inputs_first_pass(...):\n       ...\n       self.input_ids[token_indices_to_sample] = next_token_ids\n   ```\n   In the verified trace, this produces `input_ids_after[0] = -1`.\n5. The model-side embed path later consumes those input ids.\n   ```python\n   # vllm/model_executor/models/qwen2.py\n   def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:\n       return self.embed_tokens(input_ids)\n   ```\n   In the verified trace, this is the first point where the converted `-1`\n   becomes visible as a real model input. The bug is not merely that the\n   sampled speculative row contained padding `-1`; the bug is that the live\n   next token for the request became `-1` and was written back into input ids.\n6. After that point, the visible sink depends on timing and backend state. On\n   the attached Qwen3 reproducer, the engine commonly dies later in the\n   drafting / attention path with `CUDA error: device-side assert triggered`,\n   for example under `flash_attn_varlen_func(...)`.\n\n### Local script breakdown\n\n`repro_g4_recovered_minus1_local.py` is a standalone local reproducer.\n\n- It reads the Qwen3 checkpoint path from `VLLM_POC_G4_MODEL` or the built-in\n  `/path/to/qwen3` placeholder\n- It creates `EngineCore` directly without any external helper dependency\n- It submits one fixed multi-request workload that preserves the same overlap\n  and speculative-decoding state needed for the bug\n- It writes:\n  - `request_payloads.json`\n  - `repro_config.json`\n  - `timeline.json`\n  - `responses.json`\n  - `error.txt`\n  - `recovered_chain_trace.jsonl`\n- `recovered_chain_trace.jsonl` is the key attribution artifact. It records the\n  recovered-token chain directly from the standalone reproducer\n\n### gRPC script breakdown\n\n`repro_g4_recovered_minus1_grpc.py` is a standalone public gRPC reproducer.\n\n- It reads the Qwen3 checkpoint path from `VLLM_POC_G4_MODEL` or the built-in\n  `/path/to/qwen3` placeholder\n- It starts a temporary `vllm.entrypoints.grpc_server` process\n- It sends only public `Generate` and `Abort` RPCs\n- It submits one fixed overlapping request sequence that preserves the same\n  speculative-decoding state needed for the bug\n- After the crash window, it sends one more public `Generate` probe request to\n  confirm that later gRPC requests also fail after the worker dies\n- It writes:\n  - `request_payloads.json`\n  - `timeline.json`\n  - `server_command.json`\n  - `responses.json`\n  - `post_crash_probe.json`\n  - `server.stdout.log`\n  - `server.stderr.log`\n\n## Observed result\n\nLocal repro typically ends with:\n\n- a recovered-token trace showing:\n  - `sample_recovered_tokens_return -> recovered_token_ids[0] = 151936`\n  - `prepare_next_token_ids_padded -> next_token_ids[0] = -1`\n  - `set_inputs_first_pass -> input_ids_after[0] = -1`\n  - `embed_input_ids_out_of_range -> input_ids[0] = -1`\n- `CUDA error: device-side assert triggered`\n- a fatal engine-side failure\n\ngRPC repro typically ends with:\n\n- the triggering gRPC requests failing with\n  `INTERNAL: EngineCore encountered an issue. See stack trace (above) for the root cause.`\n- server logs showing the worker dies with\n  `CUDA error: device-side assert triggered`\n- a later public probe request also failing after the worker is dead\n\nThis demonstrates that the issue is reachable through the public gRPC request surface, not only through a local reproducer.\n\n## Log snippets\n\n### Local recovered-chain trace\n\n```text\nsample_recovered_tokens_return:\n  recovered_token_ids = [151936, ...]\n  vocab_size = 151936\n\nprepare_next_token_ids_padded:\n  sampled_token_ids_head = [[151936, -1, -1, ...], ...]\n  next_token_ids = [-1, ...]\n\nset_inputs_first_pass:\n  input_ids_after = [-1, 0, 0, 0, ...]\n\nembed_input_ids_out_of_range:\n  input_ids = [-1, 0, 0, 0, ...]\n```\n\n### gRPC server log\n\n```text\ntorch.AcceleratorError: CUDA error: device-side assert triggered\n...\nvllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue. See stack trace (above) for the root cause.\n...\nError in Generate for request post_crash_probe\nvllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue. See stack trace (above) for the root cause.\n```\n\n## Root cause\n\nThis is a speculative-decoding state-handling bug, not an invalid frontend token-id input bug.\n\nThe root cause is that a recovered speculative token can become equal to `vocab_size`, then be selected as the live next token for a request, then be converted to `-1`, and that converted `-1` is still written back into live drafter input ids and later consumed by the drafting / embedding / attention path.\n\nFor the Qwen3 checkpoint used here:\n\n- `151936 == vocab_size`\n\nThis value should be described as the model `vocab_size` boundary value, not as a legal token id.\n\n## Attachments\n\nThe attached bundle for this report should contain:\n\n- `repro_g4_recovered_minus1_local.py`\n- `repro_g4_recovered_minus1_grpc.py`\n\nThese two standalone scripts are sufficient to reproduce the issue and its public gRPC reachability.\n\n## Fix\n\nA fix for this vulnerability has been merged in: https://github.com/vllm-project/vllm/pull/44744",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "vllm"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.17.1"
      }, {
        "fixed" : "0.24.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/vllm-project/vllm/security/advisories/GHSA-8wr5-jm2h-8r4f"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-54234"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/vllm-project/vllm/pull/44744"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/vllm-project/vllm/commit/8a5cf1ccd65e8ac7635c402c1ec0b08988bc26ca"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/vllm-project/vllm"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-20" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-17T17:08:03Z",
    "nvd_published_at" : "2026-07-06T21:16:56Z"
  }
}