fix: disable dangerous deserialization by default in FAISS component (#11999)

* fix: disable dangerous deserialization by default in FAISS component

Change the default value of allow_dangerous_deserialization from True
to False to prevent remote code execution via malicious pickle files.

This addresses a security vulnerability where an attacker could upload
a crafted pickle file and trigger arbitrary code execution when the
FAISS component loads the index.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix: set allow_dangerous_deserialization to false in Nvidia Remix starter project and add regression test

- Changed allow_dangerous_deserialization default from true to false in
  Nvidia Remix.json starter project to match the FAISS component security fix
- Added regression tests to ensure the default value does not revert to True

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* fix: skip FAISS test gracefully when langchain_community is not installed

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Janardan Singh Kavia <janardankavia@ibm.com>
This commit is contained in:
Ram Gopal Srikar Katakam
2026-03-25 15:27:32 -04:00
committed by GitHub
parent 792808b1d5
commit 313eff264e
4 changed files with 49 additions and 12 deletions

View File

@ -1979,7 +1979,7 @@
"legacy": false,
"lf_version": "1.4.2",
"metadata": {
"code_hash": "2bd7a064d724",
"code_hash": "3e55e36d0692",
"dependencies": {
"dependencies": [
{
@ -2025,7 +2025,7 @@
"advanced": true,
"display_name": "Allow Dangerous Deserialization",
"dynamic": false,
"info": "Set to True to allow loading pickle files from untrusted sources. Only enable this if you trust the source of the data.",
"info": "Set to True to allow loading pickle files. WARNING: Only enable this if you trust the source of the data. Malicious pickle files can execute arbitrary code on your system.",
"list": false,
"list_add_label": "Add More",
"name": "allow_dangerous_deserialization",
@ -2036,7 +2036,7 @@
"tool_mode": false,
"trace_as_metadata": true,
"type": "bool",
"value": true
"value": false
},
"code": {
"advanced": true,
@ -2054,7 +2054,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "from pathlib import Path\n\nfrom langchain_community.vectorstores import FAISS\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.io import BoolInput, HandleInput, IntInput, StrInput\nfrom lfx.schema.data import Data\n\n\nclass FaissVectorStoreComponent(LCVectorStoreComponent):\n \"\"\"FAISS Vector Store with search capabilities.\"\"\"\n\n display_name: str = \"FAISS\"\n description: str = \"FAISS Vector Store with search capabilities\"\n name = \"FAISS\"\n icon = \"FAISS\"\n\n inputs = [\n StrInput(\n name=\"index_name\",\n display_name=\"Index Name\",\n value=\"langflow_index\",\n ),\n StrInput(\n name=\"persist_directory\",\n display_name=\"Persist Directory\",\n info=\"Path to save the FAISS index. It will be relative to where Langflow is running.\",\n ),\n *LCVectorStoreComponent.inputs,\n BoolInput(\n name=\"allow_dangerous_deserialization\",\n display_name=\"Allow Dangerous Deserialization\",\n info=\"Set to True to allow loading pickle files from untrusted sources. \"\n \"Only enable this if you trust the source of the data.\",\n advanced=True,\n value=True,\n ),\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n ]\n\n @staticmethod\n def resolve_path(path: str) -> str:\n \"\"\"Resolve the path relative to the Langflow root.\n\n Args:\n path: The path to resolve\n Returns:\n str: The resolved path as a string\n \"\"\"\n return str(Path(path).resolve())\n\n def get_persist_directory(self) -> Path:\n \"\"\"Returns the resolved persist directory path or the current directory if not set.\"\"\"\n if self.persist_directory:\n return Path(self.resolve_path(self.persist_directory))\n return Path()\n\n @check_cached_vector_store\n def build_vector_store(self) -> FAISS:\n \"\"\"Builds the FAISS object.\"\"\"\n path = self.get_persist_directory()\n path.mkdir(parents=True, exist_ok=True)\n\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n documents.append(_input)\n\n faiss = FAISS.from_documents(documents=documents, embedding=self.embedding)\n faiss.save_local(str(path), self.index_name)\n return faiss\n\n def search_documents(self) -> list[Data]:\n \"\"\"Search for documents in the FAISS vector store.\"\"\"\n path = self.get_persist_directory()\n index_path = path / f\"{self.index_name}.faiss\"\n\n if not index_path.exists():\n vector_store = self.build_vector_store()\n else:\n vector_store = FAISS.load_local(\n folder_path=str(path),\n embeddings=self.embedding,\n index_name=self.index_name,\n allow_dangerous_deserialization=self.allow_dangerous_deserialization,\n )\n\n if not vector_store:\n msg = \"Failed to load the FAISS index.\"\n raise ValueError(msg)\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n docs = vector_store.similarity_search(\n query=self.search_query,\n k=self.number_of_results,\n )\n return docs_to_data(docs)\n return []\n"
"value": "from pathlib import Path\n\nfrom langchain_community.vectorstores import FAISS\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.io import BoolInput, HandleInput, IntInput, StrInput\nfrom lfx.schema.data import Data\n\n\nclass FaissVectorStoreComponent(LCVectorStoreComponent):\n \"\"\"FAISS Vector Store with search capabilities.\"\"\"\n\n display_name: str = \"FAISS\"\n description: str = \"FAISS Vector Store with search capabilities\"\n name = \"FAISS\"\n icon = \"FAISS\"\n\n inputs = [\n StrInput(\n name=\"index_name\",\n display_name=\"Index Name\",\n value=\"langflow_index\",\n ),\n StrInput(\n name=\"persist_directory\",\n display_name=\"Persist Directory\",\n info=\"Path to save the FAISS index. It will be relative to where Langflow is running.\",\n ),\n *LCVectorStoreComponent.inputs,\n BoolInput(\n name=\"allow_dangerous_deserialization\",\n display_name=\"Allow Dangerous Deserialization\",\n info=\"Set to True to allow loading pickle files. WARNING: Only enable this if you trust the source \"\n \"of the data. Malicious pickle files can execute arbitrary code on your system.\",\n advanced=True,\n value=False,\n ),\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n ]\n\n @staticmethod\n def resolve_path(path: str) -> str:\n \"\"\"Resolve the path relative to the Langflow root.\n\n Args:\n path: The path to resolve\n Returns:\n str: The resolved path as a string\n \"\"\"\n return str(Path(path).resolve())\n\n def get_persist_directory(self) -> Path:\n \"\"\"Returns the resolved persist directory path or the current directory if not set.\"\"\"\n if self.persist_directory:\n return Path(self.resolve_path(self.persist_directory))\n return Path()\n\n @check_cached_vector_store\n def build_vector_store(self) -> FAISS:\n \"\"\"Builds the FAISS object.\"\"\"\n path = self.get_persist_directory()\n path.mkdir(parents=True, exist_ok=True)\n\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n documents.append(_input)\n\n faiss = FAISS.from_documents(documents=documents, embedding=self.embedding)\n faiss.save_local(str(path), self.index_name)\n return faiss\n\n def search_documents(self) -> list[Data]:\n \"\"\"Search for documents in the FAISS vector store.\"\"\"\n path = self.get_persist_directory()\n index_path = path / f\"{self.index_name}.faiss\"\n\n if not index_path.exists():\n vector_store = self.build_vector_store()\n else:\n vector_store = FAISS.load_local(\n folder_path=str(path),\n embeddings=self.embedding,\n index_name=self.index_name,\n allow_dangerous_deserialization=self.allow_dangerous_deserialization,\n )\n\n if not vector_store:\n msg = \"Failed to load the FAISS index.\"\n raise ValueError(msg)\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n docs = vector_store.similarity_search(\n query=self.search_query,\n k=self.number_of_results,\n )\n return docs_to_data(docs)\n return []\n"
},
"embedding": {
"_input_type": "HandleInput",

View File

@ -29,7 +29,7 @@
"icon": "FAISS",
"legacy": false,
"metadata": {
"code_hash": "2bd7a064d724",
"code_hash": "3e55e36d0692",
"dependencies": {
"dependencies": [
{
@ -85,7 +85,7 @@
"advanced": true,
"display_name": "Allow Dangerous Deserialization",
"dynamic": false,
"info": "Set to True to allow loading pickle files from untrusted sources. Only enable this if you trust the source of the data.",
"info": "Set to True to allow loading pickle files. WARNING: Only enable this if you trust the source of the data. Malicious pickle files can execute arbitrary code on your system.",
"list": false,
"list_add_label": "Add More",
"name": "allow_dangerous_deserialization",
@ -98,7 +98,7 @@
"trace_as_metadata": true,
"track_in_telemetry": true,
"type": "bool",
"value": true
"value": false
},
"code": {
"advanced": true,
@ -116,7 +116,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "from pathlib import Path\n\nfrom langchain_community.vectorstores import FAISS\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.io import BoolInput, HandleInput, IntInput, StrInput\nfrom lfx.schema.data import Data\n\n\nclass FaissVectorStoreComponent(LCVectorStoreComponent):\n \"\"\"FAISS Vector Store with search capabilities.\"\"\"\n\n display_name: str = \"FAISS\"\n description: str = \"FAISS Vector Store with search capabilities\"\n name = \"FAISS\"\n icon = \"FAISS\"\n\n inputs = [\n StrInput(\n name=\"index_name\",\n display_name=\"Index Name\",\n value=\"langflow_index\",\n ),\n StrInput(\n name=\"persist_directory\",\n display_name=\"Persist Directory\",\n info=\"Path to save the FAISS index. It will be relative to where Langflow is running.\",\n ),\n *LCVectorStoreComponent.inputs,\n BoolInput(\n name=\"allow_dangerous_deserialization\",\n display_name=\"Allow Dangerous Deserialization\",\n info=\"Set to True to allow loading pickle files from untrusted sources. \"\n \"Only enable this if you trust the source of the data.\",\n advanced=True,\n value=True,\n ),\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n ]\n\n @staticmethod\n def resolve_path(path: str) -> str:\n \"\"\"Resolve the path relative to the Langflow root.\n\n Args:\n path: The path to resolve\n Returns:\n str: The resolved path as a string\n \"\"\"\n return str(Path(path).resolve())\n\n def get_persist_directory(self) -> Path:\n \"\"\"Returns the resolved persist directory path or the current directory if not set.\"\"\"\n if self.persist_directory:\n return Path(self.resolve_path(self.persist_directory))\n return Path()\n\n @check_cached_vector_store\n def build_vector_store(self) -> FAISS:\n \"\"\"Builds the FAISS object.\"\"\"\n path = self.get_persist_directory()\n path.mkdir(parents=True, exist_ok=True)\n\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n documents.append(_input)\n\n faiss = FAISS.from_documents(documents=documents, embedding=self.embedding)\n faiss.save_local(str(path), self.index_name)\n return faiss\n\n def search_documents(self) -> list[Data]:\n \"\"\"Search for documents in the FAISS vector store.\"\"\"\n path = self.get_persist_directory()\n index_path = path / f\"{self.index_name}.faiss\"\n\n if not index_path.exists():\n vector_store = self.build_vector_store()\n else:\n vector_store = FAISS.load_local(\n folder_path=str(path),\n embeddings=self.embedding,\n index_name=self.index_name,\n allow_dangerous_deserialization=self.allow_dangerous_deserialization,\n )\n\n if not vector_store:\n msg = \"Failed to load the FAISS index.\"\n raise ValueError(msg)\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n docs = vector_store.similarity_search(\n query=self.search_query,\n k=self.number_of_results,\n )\n return docs_to_data(docs)\n return []\n"
"value": "from pathlib import Path\n\nfrom langchain_community.vectorstores import FAISS\n\nfrom lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom lfx.helpers.data import docs_to_data\nfrom lfx.io import BoolInput, HandleInput, IntInput, StrInput\nfrom lfx.schema.data import Data\n\n\nclass FaissVectorStoreComponent(LCVectorStoreComponent):\n \"\"\"FAISS Vector Store with search capabilities.\"\"\"\n\n display_name: str = \"FAISS\"\n description: str = \"FAISS Vector Store with search capabilities\"\n name = \"FAISS\"\n icon = \"FAISS\"\n\n inputs = [\n StrInput(\n name=\"index_name\",\n display_name=\"Index Name\",\n value=\"langflow_index\",\n ),\n StrInput(\n name=\"persist_directory\",\n display_name=\"Persist Directory\",\n info=\"Path to save the FAISS index. It will be relative to where Langflow is running.\",\n ),\n *LCVectorStoreComponent.inputs,\n BoolInput(\n name=\"allow_dangerous_deserialization\",\n display_name=\"Allow Dangerous Deserialization\",\n info=\"Set to True to allow loading pickle files. WARNING: Only enable this if you trust the source \"\n \"of the data. Malicious pickle files can execute arbitrary code on your system.\",\n advanced=True,\n value=False,\n ),\n HandleInput(name=\"embedding\", display_name=\"Embedding\", input_types=[\"Embeddings\"]),\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Results\",\n info=\"Number of results to return.\",\n advanced=True,\n value=4,\n ),\n ]\n\n @staticmethod\n def resolve_path(path: str) -> str:\n \"\"\"Resolve the path relative to the Langflow root.\n\n Args:\n path: The path to resolve\n Returns:\n str: The resolved path as a string\n \"\"\"\n return str(Path(path).resolve())\n\n def get_persist_directory(self) -> Path:\n \"\"\"Returns the resolved persist directory path or the current directory if not set.\"\"\"\n if self.persist_directory:\n return Path(self.resolve_path(self.persist_directory))\n return Path()\n\n @check_cached_vector_store\n def build_vector_store(self) -> FAISS:\n \"\"\"Builds the FAISS object.\"\"\"\n path = self.get_persist_directory()\n path.mkdir(parents=True, exist_ok=True)\n\n # Convert DataFrame to Data if needed using parent's method\n self.ingest_data = self._prepare_ingest_data()\n\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n documents.append(_input)\n\n faiss = FAISS.from_documents(documents=documents, embedding=self.embedding)\n faiss.save_local(str(path), self.index_name)\n return faiss\n\n def search_documents(self) -> list[Data]:\n \"\"\"Search for documents in the FAISS vector store.\"\"\"\n path = self.get_persist_directory()\n index_path = path / f\"{self.index_name}.faiss\"\n\n if not index_path.exists():\n vector_store = self.build_vector_store()\n else:\n vector_store = FAISS.load_local(\n folder_path=str(path),\n embeddings=self.embedding,\n index_name=self.index_name,\n allow_dangerous_deserialization=self.allow_dangerous_deserialization,\n )\n\n if not vector_store:\n msg = \"Failed to load the FAISS index.\"\n raise ValueError(msg)\n\n if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():\n docs = vector_store.similarity_search(\n query=self.search_query,\n k=self.number_of_results,\n )\n return docs_to_data(docs)\n return []\n"
},
"embedding": {
"_input_type": "HandleInput",
@ -118890,6 +118890,6 @@
"num_components": 360,
"num_modules": 97
},
"sha256": "75bc084b1d3f8e8a508e5883ff630167793df395ed3a574d109b2d35e908a1b2",
"sha256": "a4a0efff75f73264ab584dc97581a97d489df3c6e10a2cb2e08f31c8e1049638",
"version": "0.4.0"
}

View File

@ -31,10 +31,10 @@ class FaissVectorStoreComponent(LCVectorStoreComponent):
BoolInput(
name="allow_dangerous_deserialization",
display_name="Allow Dangerous Deserialization",
info="Set to True to allow loading pickle files from untrusted sources. "
"Only enable this if you trust the source of the data.",
info="Set to True to allow loading pickle files. WARNING: Only enable this if you trust the source "
"of the data. Malicious pickle files can execute arbitrary code on your system.",
advanced=True,
value=True,
value=False,
),
HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
IntInput(

View File

@ -0,0 +1,37 @@
"""Regression tests for FaissVectorStoreComponent security defaults."""
import pytest
pytest.importorskip("langchain_community")
from lfx.components.FAISS.faiss import FaissVectorStoreComponent
from lfx.io import BoolInput
class TestFaissVectorStoreComponentDefaults:
"""Regression tests ensuring FAISS component security defaults do not regress."""
def test_allow_dangerous_deserialization_defaults_to_false(self):
"""Regression test: allow_dangerous_deserialization must default to False.
This guards against accidentally re-enabling unsafe pickle deserialization
(RCE vulnerability PVR0699083). Do not change this default to True.
"""
input_def = next(
inp
for inp in FaissVectorStoreComponent.inputs
if isinstance(inp, BoolInput) and inp.name == "allow_dangerous_deserialization"
)
assert input_def.value is False, (
"allow_dangerous_deserialization must default to False to prevent RCE via malicious pickle files. "
"See security issue PVR0699083."
)
def test_allow_dangerous_deserialization_is_advanced(self):
"""allow_dangerous_deserialization should be an advanced field to reduce accidental enabling."""
input_def = next(
inp
for inp in FaissVectorStoreComponent.inputs
if isinstance(inp, BoolInput) and inp.name == "allow_dangerous_deserialization"
)
assert input_def.advanced is True