mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-27 18:39:00 +08:00
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:
committed by
GitHub
parent
792808b1d5
commit
313eff264e
@ -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",
|
||||
|
||||
@ -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"
|
||||
}
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
Reference in New Issue
Block a user