Skip to content

fix(security): confine AssemblyAI audio file access#14037

Merged
erichare merged 5 commits into
release-1.10.3from
fix/le-1782-assemblyai-file-boundary
Jul 14, 2026
Merged

fix(security): confine AssemblyAI audio file access#14037
erichare merged 5 commits into
release-1.10.3from
fix/le-1782-assemblyai-file-boundary

Conversation

@erichare

@erichare erichare commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • resolve local AssemblyAI audio only within the current flow or user storage namespace
  • canonicalize paths before provider submission
  • preserve flow uploads, file-manager uploads, and existing error behavior

Validation

  • 56 focused LFX tests passed
  • Ruff and pre-commit passed
  • LFX wheel build passed
  • component index integrity tests passed

Summary by CodeRabbit

  • Bug Fixes
    • Improved uploaded audio handling for meeting transcription.
    • Added trusted storage resolution to securely open uploaded audio and reject unsafe or out-of-scope paths before any transcription request starts.
    • Preserved existing audio-file vs audio-URL behavior and error responses when files are missing or invalid.
  • Tests
    • Added unit tests covering rejected untrusted paths and unsafe namespace inputs.
    • Added a test confirming valid uploads are passed to the transcription provider from an opened file handle.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

AssemblyAI audio resolution

Layer / File(s) Summary
Storage-scoped audio resolution
src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py
Uploaded audio is resolved through trusted storage namespaces, verified, opened as a binary handle, and submitted to AssemblyAI.
Embedded component synchronization
src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json, src/lfx/src/lfx/_assets/component_index.json
Embedded component code and metadata hashes are updated to match the storage-aware implementation.
Trusted and untrusted path tests
src/lfx/tests/unit/components/test_assemblyai_start_transcript.py
Tests cover unsafe paths, unsafe namespace IDs, successful submission, and handle closure.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: ogabrielluiz, cristhianzl

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
Loading
🚥 Pre-merge checks | ✅ 8 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely reflects the main security change to restrict AssemblyAI audio file access.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Test Coverage For New Implementations ✅ Passed Added test_assemblyai_start_transcript.py follows naming rules and covers unsafe paths, unsafe namespace IDs, and successful open-handle submission.
Test Quality And Coverage ✅ Passed PASS: 3 focused pytest cases cover unsafe paths, unsafe namespace IDs, and successful uploaded-audio submission with handle closure; behavior is asserted, not just smoke-tested.
Test File Naming And Structure ✅ Passed The added backend test file follows pytest naming/placement, uses clear descriptive test names, and covers both negative and positive scenarios with organized fixtures/parameterization.
Excessive Mock Usage Warning ✅ Passed Mocks are limited to the optional AssemblyAI client; filesystem and LocalStorageService are exercised for real, so the tests don’t look overly mocked.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/le-1782-assemblyai-file-boundary

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Jul 13, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 13, 2026
@erichare erichare marked this pull request as ready for review July 13, 2026 19:47
@github-actions

Copy link
Copy Markdown
Contributor

✅ Test Coverage Advisor

No source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉

Advisory check only — never blocks merge.

@erichare erichare requested a review from Adam-Aghili July 13, 2026 19:49
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/lfx/tests/unit/components/test_assemblyai_start_transcript.py (1)

63-105: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2433e87 and 0196230.

📒 Files selected for processing (4)
  • src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json
  • src/lfx/src/lfx/_assets/component_index.json
  • src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py
  • src/lfx/tests/unit/components/test_assemblyai_start_transcript.py

Comment thread src/lfx/src/lfx/_assets/component_index.json Outdated
Comment thread src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py Outdated
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 13, 2026
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 43%
43.53% (58078/133393) 69.19% (7885/11395) 41.56% (1298/3123)

Unit Test Results

Tests Skipped Failures Errors Time
4979 0 💤 0 ❌ 0 🔥 11m 38s ⏱️

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 13, 2026
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (release-1.10.3@61d6490). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                @@
##             release-1.10.3   #14037   +/-   ##
=================================================
  Coverage                  ?   58.77%           
=================================================
  Files                     ?     2309           
  Lines                     ?   220760           
  Branches                  ?    31302           
=================================================
  Hits                      ?   129751           
  Misses                    ?    89475           
  Partials                  ?     1534           
Flag Coverage Δ
backend 66.00% <ø> (?)
frontend 57.87% <ø> (?)
lfx 54.94% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Adam-Aghili Adam-Aghili left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM other than code rabbit comment. Will likely need o regenerate components after fix

Comment thread src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py Outdated
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 14, 2026
@Adam-Aghili

Copy link
Copy Markdown
Collaborator

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 14, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Solid 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.samestat after open() 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

📥 Commits

Reviewing files that changed from the base of the PR and between 61d6490 and fed31bf.

📒 Files selected for processing (4)
  • src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json
  • src/lfx/src/lfx/_assets/component_index.json
  • src/lfx/src/lfx/components/assemblyai/assemblyai_start_transcript.py
  • src/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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

@erichare erichare requested a review from Adam-Aghili July 14, 2026 18:37
@github-actions github-actions Bot added the lgtm This PR has been approved by a maintainer label Jul 14, 2026
@erichare erichare merged commit f53b2f1 into release-1.10.3 Jul 14, 2026
130 of 131 checks passed
@erichare erichare deleted the fix/le-1782-assemblyai-file-boundary branch July 14, 2026 18:39
erichare added a commit that referenced this pull request Jul 14, 2026
* 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>
erichare added a commit that referenced this pull request Jul 16, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants