fix(security): confine AssemblyAI audio file access#14037
Conversation
WalkthroughChangesAssemblyAI audio resolution
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant AssemblyAITranscriptionJobCreator
participant StorageService
participant AssemblyAIProvider
AssemblyAITranscriptionJobCreator->>StorageService: Resolve and verify audio_file
StorageService-->>AssemblyAITranscriptionJobCreator: Return opened audio handle or failure
AssemblyAITranscriptionJobCreator->>AssemblyAIProvider: Submit transcription with audio handle
AssemblyAIProvider-->>AssemblyAITranscriptionJobCreator: Return transcript_id or error
🚥 Pre-merge checks | ✅ 8 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (8 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Test Coverage AdvisorNo source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/lfx/tests/unit/components/test_assemblyai_start_transcript.py (1)
63-105: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd a test for a non-existent file within the trusted namespace.
The parametrized cases verify that untrusted paths return
"Error: Audio file not found", but no test verifies this error for a file that simply doesn't exist inside an allowed namespace. Adding this case would confirm the "not found" error path works independently of the trust-boundary check and aligns with the PR objective to preserve existing error behavior.💚 Suggested additional test
+def test_rejects_missing_file_in_trusted_namespace(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + flow_id = "flow-123" + user_id = "user-456" + storage_root = tmp_path / "storage" + audio_file = storage_root / flow_id / "missing.mp3" + # Note: file is intentionally NOT created + + submitted_audio: list[str] = [] + _patch_assemblyai(monkeypatch, submitted_audio) + monkeypatch.setattr( + assemblyai_start_transcript, + "get_storage_service", + lambda: _storage_service(storage_root), + raising=False, + ) + + result = _component(audio_file, flow_id, user_id).create_transcription_job() + + assert submitted_audio == [] + assert result.data == {"error": "Error: Audio file not found"}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lfx/tests/unit/components/test_assemblyai_start_transcript.py` around lines 63 - 105, Add a parametrized case to test_rejects_untrusted_audio_paths_before_provider_submission for a path inside the trusted flow namespace that does not exist. Keep the existing setup and assertions, ensuring the provider receives no submission and create_transcription_job returns {"error": "Error: Audio file not found"} for this missing trusted file.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lfx/src/lfx/_assets/component_index.json`:
- Line 7137: Update AssemblyAITranscriptionJobCreator.create_transcription_job
and _resolve_uploaded_audio_file so validation and upload use immutable,
securely controlled content rather than passing the user-controlled path to
Transcriber.submit. Copy the validated audio into a private temporary file or
provide a securely opened file handle, ensure the temporary resource is cleaned
up, and pass that protected resource to submit while preserving the existing URL
and error behavior.
In `@src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py`:
- Around line 140-146: Validate or normalize namespace IDs before constructing
trusted roots in the assemblyai transcript storage-path logic, ensuring absolute
and traversal values cannot escape the intended storage directory while
preserving valid flow_id and user_id behavior. Apply the equivalent change to
the embedded starter-project copy at
src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py#L140-L146
and src/backend/base/langflow/initial_setup/starter_projects/Meeting
Summary.json#L2639.
---
Nitpick comments:
In `@src/lfx/tests/unit/components/test_assemblyai_start_transcript.py`:
- Around line 63-105: Add a parametrized case to
test_rejects_untrusted_audio_paths_before_provider_submission for a path inside
the trusted flow namespace that does not exist. Keep the existing setup and
assertions, ensuring the provider receives no submission and
create_transcription_job returns {"error": "Error: Audio file not found"} for
this missing trusted file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: eef4b928-4378-479b-8f8f-b50741c3a210
📒 Files selected for processing (4)
src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.jsonsrc/lfx/src/lfx/_assets/component_index.jsonsrc/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.pysrc/lfx/tests/unit/components/test_assemblyai_start_transcript.py
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## release-1.10.3 #14037 +/- ##
=================================================
Coverage ? 58.77%
=================================================
Files ? 2309
Lines ? 220760
Branches ? 31302
=================================================
Hits ? 129751
Misses ? 89475
Partials ? 1534
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Adam-Aghili
left a comment
There was a problem hiding this comment.
LGTM other than code rabbit comment. Will likely need o regenerate components after fix
|
@CodeRabbit full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py (1)
149-197: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSolid layered defense; consider extracting helpers to tame complexity.
The logic correctly: (1) filters namespace IDs, (2) verifies each resolved namespace dir stays under its resolved parent (guards against a symlinked namespace directory), (3) strict-resolves and checks containment before opening, and (4) re-resolves +
os.fstat/os.path.samestatafteropen()to catch a swap between the initial check and the open call. This closes the gap flagged in prior review rounds.The function is doing a lot in one place (trusted-root computation, the repeated
any(... .is_relative_to(root) for root in trusted_roots)pattern used twice, and the open+verify dance). Extracting_compute_trusted_roots()and_is_within_trusted_roots(path, trusted_roots)helpers would reduce duplication and make each piece independently unit-testable.♻️ Suggested extraction
+ def _compute_trusted_roots(self, namespace_ids: set[str], storage_service) -> list[Path]: + trusted_roots = [] + for namespace_id in namespace_ids: + namespace_path = Path(storage_service.build_full_path(namespace_id, "")) + storage_root = namespace_path.parent.resolve() + trusted_root = namespace_path.resolve() + if trusted_root.is_relative_to(storage_root): + trusted_roots.append(trusted_root) + return trusted_roots + + `@staticmethod` + def _is_within_trusted_roots(path: Path, trusted_roots: list[Path]) -> bool: + return any(path.is_relative_to(root) for root in trusted_roots) + def _open_uploaded_audio_file(self) -> BinaryIO | None: ... - trusted_roots = [] - for namespace_id in namespace_ids: - namespace_path = Path(storage_service.build_full_path(namespace_id, "")) - storage_root = namespace_path.parent.resolve() - trusted_root = namespace_path.resolve() - if trusted_root.is_relative_to(storage_root): - trusted_roots.append(trusted_root) + trusted_roots = self._compute_trusted_roots(namespace_ids, storage_service)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py` around lines 149 - 197, Refactor _open_uploaded_audio_file by extracting trusted-root construction into a _compute_trusted_roots helper and the repeated containment check into an _is_within_trusted_roots(path, trusted_roots) helper. Reuse the containment helper for both the initial resolved path and the post-open current path, while preserving the existing namespace validation, race checks, cleanup, and return behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lfx/src/lfx/_assets/component_index.json`:
- Line 7137: In AssemblyAITranscriptionJobCreator.create_transcription_job,
correct the warning text to use “Both an audio file and an audio URL were
specified.” After updating the embedded source, regenerate the affected metadata
hash.
---
Nitpick comments:
In `@src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py`:
- Around line 149-197: Refactor _open_uploaded_audio_file by extracting
trusted-root construction into a _compute_trusted_roots helper and the repeated
containment check into an _is_within_trusted_roots(path, trusted_roots) helper.
Reuse the containment helper for both the initial resolved path and the
post-open current path, while preserving the existing namespace validation, race
checks, cleanup, and return behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e5c21a05-e13f-439e-aa19-a93c16d62830
📒 Files selected for processing (4)
src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.jsonsrc/lfx/src/lfx/_assets/component_index.jsonsrc/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.pysrc/lfx/tests/unit/components/test_assemblyai_start_transcript.py
| "title_case": false, | ||
| "type": "code", | ||
| "value": "from pathlib import Path\n\nimport assemblyai as aai\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DropdownInput, FileInput, MessageTextInput, Output, SecretStrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\n\n\nclass AssemblyAITranscriptionJobCreator(Component):\n display_name = \"AssemblyAI Start Transcript\"\n description = \"Create a transcription job for an audio file using AssemblyAI with advanced options\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n required=True,\n ),\n FileInput(\n name=\"audio_file\",\n display_name=\"Audio File\",\n file_types=[\n \"3ga\",\n \"8svx\",\n \"aac\",\n \"ac3\",\n \"aif\",\n \"aiff\",\n \"alac\",\n \"amr\",\n \"ape\",\n \"au\",\n \"dss\",\n \"flac\",\n \"flv\",\n \"m4a\",\n \"m4b\",\n \"m4p\",\n \"m4r\",\n \"mp3\",\n \"mpga\",\n \"ogg\",\n \"oga\",\n \"mogg\",\n \"opus\",\n \"qcp\",\n \"tta\",\n \"voc\",\n \"wav\",\n \"wma\",\n \"wv\",\n \"webm\",\n \"mts\",\n \"m2ts\",\n \"ts\",\n \"mov\",\n \"mp2\",\n \"mp4\",\n \"m4p\",\n \"m4v\",\n \"mxf\",\n ],\n info=\"The audio file to transcribe\",\n required=True,\n ),\n MessageTextInput(\n name=\"audio_file_url\",\n display_name=\"Audio File URL\",\n info=\"The URL of the audio file to transcribe (Can be used instead of a File)\",\n advanced=True,\n ),\n DropdownInput(\n name=\"speech_model\",\n display_name=\"Speech Model\",\n options=[\n \"best\",\n \"nano\",\n ],\n value=\"best\",\n info=\"The speech model to use for the transcription\",\n advanced=True,\n ),\n BoolInput(\n name=\"language_detection\",\n display_name=\"Automatic Language Detection\",\n info=\"Enable automatic language detection\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"language_code\",\n display_name=\"Language\",\n info=(\n \"\"\"\n The language of the audio file. Can be set manually if automatic language detection is disabled.\n See https://www.assemblyai.com/docs/getting-started/supported-languages \"\"\"\n \"for a list of supported language codes.\"\n ),\n advanced=True,\n ),\n BoolInput(\n name=\"speaker_labels\",\n display_name=\"Enable Speaker Labels\",\n info=\"Enable speaker diarization\",\n ),\n MessageTextInput(\n name=\"speakers_expected\",\n display_name=\"Expected Number of Speakers\",\n info=\"Set the expected number of speakers (optional, enter a number)\",\n advanced=True,\n ),\n BoolInput(\n name=\"punctuate\",\n display_name=\"Punctuate\",\n info=\"Enable automatic punctuation\",\n advanced=True,\n value=True,\n ),\n BoolInput(\n name=\"format_text\",\n display_name=\"Format Text\",\n info=\"Enable text formatting\",\n advanced=True,\n value=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcript ID\", name=\"transcript_id\", method=\"create_transcription_job\"),\n ]\n\n def create_transcription_job(self) -> Data:\n aai.settings.api_key = self.api_key\n\n # Convert speakers_expected to int if it's not empty\n speakers_expected = None\n if self.speakers_expected and self.speakers_expected.strip():\n try:\n speakers_expected = int(self.speakers_expected)\n except ValueError:\n self.status = \"Error: Expected Number of Speakers must be a valid integer\"\n return Data(data={\"error\": \"Error: Expected Number of Speakers must be a valid integer\"})\n\n language_code = self.language_code or None\n\n config = aai.TranscriptionConfig(\n speech_model=self.speech_model,\n language_detection=self.language_detection,\n language_code=language_code,\n speaker_labels=self.speaker_labels,\n speakers_expected=speakers_expected,\n punctuate=self.punctuate,\n format_text=self.format_text,\n )\n\n audio = None\n if self.audio_file:\n if self.audio_file_url:\n logger.warning(\"Both an audio file an audio URL were specified. The audio URL was ignored.\")\n\n # Check if the file exists\n if not Path(self.audio_file).exists():\n self.status = \"Error: Audio file not found\"\n return Data(data={\"error\": \"Error: Audio file not found\"})\n audio = self.audio_file\n elif self.audio_file_url:\n audio = self.audio_file_url\n else:\n self.status = \"Error: Either an audio file or an audio URL must be specified\"\n return Data(data={\"error\": \"Error: Either an audio file or an audio URL must be specified\"})\n\n try:\n transcript = aai.Transcriber().submit(audio, config=config)\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error submitting transcription job\", exc_info=True)\n self.status = f\"An error occurred: {e}\"\n return Data(data={\"error\": f\"An error occurred: {e}\"})\n\n if transcript.error:\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n result = Data(data={\"transcript_id\": transcript.id})\n self.status = result\n return result\n" | ||
| "value": "import os\nimport stat\nfrom pathlib import Path\nfrom typing import BinaryIO\n\nimport assemblyai as aai\n\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, DropdownInput, FileInput, MessageTextInput, Output, SecretStrInput\nfrom lfx.log.logger import logger\nfrom lfx.schema.data import Data\nfrom lfx.services.deps import get_storage_service\n\n\nclass AssemblyAITranscriptionJobCreator(Component):\n display_name = \"AssemblyAI Start Transcript\"\n description = \"Create a transcription job for an audio file using AssemblyAI with advanced options\"\n documentation = \"https://www.assemblyai.com/docs\"\n icon = \"AssemblyAI\"\n\n inputs = [\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Assembly API Key\",\n info=\"Your AssemblyAI API key. You can get one from https://www.assemblyai.com/\",\n required=True,\n ),\n FileInput(\n name=\"audio_file\",\n display_name=\"Audio File\",\n file_types=[\n \"3ga\",\n \"8svx\",\n \"aac\",\n \"ac3\",\n \"aif\",\n \"aiff\",\n \"alac\",\n \"amr\",\n \"ape\",\n \"au\",\n \"dss\",\n \"flac\",\n \"flv\",\n \"m4a\",\n \"m4b\",\n \"m4p\",\n \"m4r\",\n \"mp3\",\n \"mpga\",\n \"ogg\",\n \"oga\",\n \"mogg\",\n \"opus\",\n \"qcp\",\n \"tta\",\n \"voc\",\n \"wav\",\n \"wma\",\n \"wv\",\n \"webm\",\n \"mts\",\n \"m2ts\",\n \"ts\",\n \"mov\",\n \"mp2\",\n \"mp4\",\n \"m4p\",\n \"m4v\",\n \"mxf\",\n ],\n info=\"The audio file to transcribe\",\n required=True,\n ),\n MessageTextInput(\n name=\"audio_file_url\",\n display_name=\"Audio File URL\",\n info=\"The URL of the audio file to transcribe (Can be used instead of a File)\",\n advanced=True,\n ),\n DropdownInput(\n name=\"speech_model\",\n display_name=\"Speech Model\",\n options=[\n \"best\",\n \"nano\",\n ],\n value=\"best\",\n info=\"The speech model to use for the transcription\",\n advanced=True,\n ),\n BoolInput(\n name=\"language_detection\",\n display_name=\"Automatic Language Detection\",\n info=\"Enable automatic language detection\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"language_code\",\n display_name=\"Language\",\n info=(\n \"\"\"\n The language of the audio file. Can be set manually if automatic language detection is disabled.\n See https://www.assemblyai.com/docs/getting-started/supported-languages \"\"\"\n \"for a list of supported language codes.\"\n ),\n advanced=True,\n ),\n BoolInput(\n name=\"speaker_labels\",\n display_name=\"Enable Speaker Labels\",\n info=\"Enable speaker diarization\",\n ),\n MessageTextInput(\n name=\"speakers_expected\",\n display_name=\"Expected Number of Speakers\",\n info=\"Set the expected number of speakers (optional, enter a number)\",\n advanced=True,\n ),\n BoolInput(\n name=\"punctuate\",\n display_name=\"Punctuate\",\n info=\"Enable automatic punctuation\",\n advanced=True,\n value=True,\n ),\n BoolInput(\n name=\"format_text\",\n display_name=\"Format Text\",\n info=\"Enable text formatting\",\n advanced=True,\n value=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Transcript ID\", name=\"transcript_id\", method=\"create_transcription_job\"),\n ]\n\n @staticmethod\n def _is_safe_namespace_id(namespace_id: str) -> bool:\n \"\"\"Return whether a storage namespace is a single safe path segment.\"\"\"\n return (\n bool(namespace_id)\n and namespace_id not in {\".\", \"..\"}\n and not any(token in namespace_id for token in (\"/\", \"\\\\\", \"..\", \"\\x00\", \":\"))\n )\n\n def _open_uploaded_audio_file(self) -> BinaryIO | None:\n \"\"\"Open audio only when its file descriptor resolves inside a trusted storage namespace.\"\"\"\n storage_service = get_storage_service()\n namespace_ids = set()\n for namespace_id in (self.flow_id, self.user_id):\n normalized_id = str(namespace_id) if namespace_id else \"\"\n if self._is_safe_namespace_id(normalized_id):\n namespace_ids.add(normalized_id)\n if storage_service is None or not namespace_ids:\n return None\n\n try:\n trusted_roots = []\n for namespace_id in namespace_ids:\n namespace_path = Path(storage_service.build_full_path(namespace_id, \"\"))\n storage_root = namespace_path.parent.resolve()\n trusted_root = namespace_path.resolve()\n if trusted_root.is_relative_to(storage_root):\n trusted_roots.append(trusted_root)\n\n requested_path = Path(self.audio_file)\n audio_path = requested_path.resolve(strict=True)\n except (OSError, RuntimeError, TypeError, ValueError):\n return None\n\n if not any(audio_path.is_relative_to(root) for root in trusted_roots):\n return None\n\n audio_file = None\n try:\n audio_file = requested_path.open(\"rb\")\n opened_stat = os.fstat(audio_file.fileno())\n current_path = requested_path.resolve(strict=True)\n current_stat = current_path.stat()\n except (OSError, RuntimeError, TypeError, ValueError):\n if audio_file is not None:\n audio_file.close()\n return None\n\n if (\n not stat.S_ISREG(opened_stat.st_mode)\n or not os.path.samestat(opened_stat, current_stat)\n or not any(current_path.is_relative_to(root) for root in trusted_roots)\n ):\n audio_file.close()\n return None\n\n return audio_file\n\n def create_transcription_job(self) -> Data:\n aai.settings.api_key = self.api_key\n\n # Convert speakers_expected to int if it's not empty\n speakers_expected = None\n if self.speakers_expected and self.speakers_expected.strip():\n try:\n speakers_expected = int(self.speakers_expected)\n except ValueError:\n self.status = \"Error: Expected Number of Speakers must be a valid integer\"\n return Data(data={\"error\": \"Error: Expected Number of Speakers must be a valid integer\"})\n\n language_code = self.language_code or None\n\n config = aai.TranscriptionConfig(\n speech_model=self.speech_model,\n language_detection=self.language_detection,\n language_code=language_code,\n speaker_labels=self.speaker_labels,\n speakers_expected=speakers_expected,\n punctuate=self.punctuate,\n format_text=self.format_text,\n )\n\n audio = None\n uploaded_audio = None\n if self.audio_file:\n if self.audio_file_url:\n logger.warning(\"Both an audio file an audio URL were specified. The audio URL was ignored.\")\n\n uploaded_audio = self._open_uploaded_audio_file()\n if uploaded_audio is None:\n self.status = \"Error: Audio file not found\"\n return Data(data={\"error\": \"Error: Audio file not found\"})\n elif self.audio_file_url:\n audio = self.audio_file_url\n else:\n self.status = \"Error: Either an audio file or an audio URL must be specified\"\n return Data(data={\"error\": \"Error: Either an audio file or an audio URL must be specified\"})\n\n try:\n if uploaded_audio is not None:\n with uploaded_audio:\n transcript = aai.Transcriber().submit(uploaded_audio, config=config)\n else:\n transcript = aai.Transcriber().submit(audio, config=config)\n except Exception as e: # noqa: BLE001\n logger.debug(\"Error submitting transcription job\", exc_info=True)\n self.status = f\"An error occurred: {e}\"\n return Data(data={\"error\": f\"An error occurred: {e}\"})\n\n if transcript.error:\n self.status = transcript.error\n return Data(data={\"error\": transcript.error})\n result = Data(data={\"transcript_id\": transcript.id})\n self.status = result\n return result\n" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the warning message grammar.
Change “Both an audio file an audio URL were specified” to “Both an audio file and an audio URL were specified.” Regenerate the affected metadata hash after updating the embedded source.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lfx/src/lfx/_assets/component_index.json` at line 7137, In
AssemblyAITranscriptionJobCreator.create_transcription_job, correct the warning
text to use “Both an audio file and an audio URL were specified.” After updating
the embedded source, regenerate the affected metadata hash.
* fix(kb): enforce per-user path containment * fix(security): protect Docling Serve outbound requests (#14033) * fix(security): protect Docling Serve requests * fix(docling): support Self on Python 3.10 * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fix(auth): verify current password on password changes (#14034) * fix(security): harden MCP stdio configuration (#14036) * fix(security): restrict MCP stdio package sources * fix(security): block MCP Docker host access * fix(security): validate embedded MCP stdio configs * fix: require executable-only MCP commands * test: use allowed MCP commands in timeout tests * fix(security): harden component code module access (#14032) * fix(security): harden component code module access * fix(security): block native FFI imports in generated code * fix: track module assignment aliases in code scanner * fix(security): address alias review findings * fix: correct deprecated model settings behavior (#14047) * fix: prevent deprecated model enablement * test: use supported model in credential cleanup * fix(voice): enforce flow authorization on websocket (#14043) * fix(security): block native FFI imports in generated code (#14040) * fix(security): block native FFI imports in generated code * fix: track module assignment aliases in code scanner * chore: resolve code security conflicts * fix(security): confine AssemblyAI audio file access (#14037) * fix(security): confine AssemblyAI audio file access * [autofix.ci] apply automated fixes * test: stub optional AssemblyAI dependency * fix(security): harden AssemblyAI file submission --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> * fix: track assignment aliases in component code scanner (#14041) fix: bind loop and comprehension aliases --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix(security): protect Docling Serve outbound requests (#14033) * fix(security): protect Docling Serve requests * fix(docling): support Self on Python 3.10 (cherry picked from commit 32bef8c) * fix(auth): verify current password on password changes (#14034) (cherry picked from commit 16725f0) * fix(security): harden component code module access (#14032) * fix(security): harden component code module access * fix(security): block native FFI imports in generated code * fix: track module assignment aliases in code scanner * fix(security): address alias review findings (cherry picked from commit 2016e4b) * fix: bind loop and comprehension aliases (cherry picked from commit 375ce63) * fix(voice): enforce flow authorization on websocket (#14043) (cherry picked from commit 229f86d) * fix(kb): enforce folder connector security settings (cherry picked from commit 917e390) * fix(kb): enforce per-user path containment (cherry picked from commit ec81655) * fix(security): confine AssemblyAI audio file access (#14037) Adapt the 1.10.3 fix to the lfx-bundles component path used by release-1.11.0. * fix(security): harden published image connector defaults * fix(lfx): register code execution aliases (cherry picked from commit cf13188) * fix(security): complete release-1.11 forward port * [autofix.ci] apply automated fixes * fix(security): protect SQL runtime configuration * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fix(security): address review gaps * test: align MCP command security fixtures * fix(voice): preserve public flow websocket access * chore: regenerate component metadata * chore: align generated component metadata --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Summary
Validation
Summary by CodeRabbit