{"record":{"id":"33d1a95575f2e711","repo":"jamiepine/voicebox","slug":"audio-path-is-only-available-to-loopback-callers","errorCode":null,"errorMessage":"`audio_path` is only available to loopback callers — remote callers must use `audio_base64`.","messagePattern":"`audio_path` is only available to loopback callers — remote callers must use `audio_base64`\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/mcp_server/tools.py","lineNumber":139,"sourceCode":"        ),\n    )\n    async def voicebox_transcribe(\n        audio_base64: str | None = None,\n        audio_path: str | None = None,\n        language: str | None = None,\n        model: str | None = None,\n    ) -> dict[str, Any]:\n        if bool(audio_base64) == bool(audio_path):\n            raise ValueError(\n                \"Pass exactly one of `audio_base64` or `audio_path`.\"\n            )\n\n        # Absolute-path mode: validate and transcribe in place. Restricted\n        # to loopback callers so a Voicebox bound on 0.0.0.0 doesn't double\n        # as an unauthenticated arbitrary-local-file read primitive.\n        if audio_path is not None:\n            if not request_is_loopback():\n                raise ValueError(\n                    \"`audio_path` is only available to loopback callers — \"\n                    \"remote callers must use `audio_base64`.\"\n                )\n            path = Path(audio_path)\n            if not path.is_absolute():\n                raise ValueError(\"`audio_path` must be absolute.\")\n            if not path.is_file():\n                raise ValueError(f\"File not found: {audio_path}\")\n            if path.stat().st_size > MAX_TRANSCRIBE_BYTES:\n                raise ValueError(\n                    f\"File exceeds {MAX_TRANSCRIBE_BYTES // (1024 * 1024)} MB limit.\"\n                )\n            return await _transcribe_file(path, language, model)\n\n        # Base64 mode: decode into a temp file, transcribe, clean up.\n        try:\n            raw = b64.b64decode(audio_base64, validate=True)\n        except Exception as exc:","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/mcp_server/tools.py#L121-L157","documentation":"Raised by voicebox_transcribe when audio_path is supplied and request_is_loopback() is False. The audio_path mode reads arbitrary local files, so it is deliberately restricted to loopback (same-host) callers to prevent a Voicebox bound on 0.0.0.0 from becoming an unauthenticated arbitrary-local-file read primitive.","triggerScenarios":"A remote MCP client (request arriving over a non-loopback interface) calling voicebox_transcribe(audio_path=...); running Voicebox on 0.0.0.0 or behind a reverse proxy that does not preserve the loopback origin.","commonSituations":"Exposing Voicebox to LAN/public while still trying to use file-path mode; proxy/TLS terminator rewriting the source address so the request no longer looks loopback; mixing local and remote clients against one instance.","solutions":["Switch the remote caller to audio_base64 (works from anywhere).","Run the file-path caller on the same host as Voicebox and connect via 127.0.0.1/localhost.","Do NOT widen this guard — it is intentional; instead encode the file as base64 remotely."],"exampleFix":"// before (remote caller)\nvoicebox_transcribe(audio_path=\"/var/data/clip.wav\")\n// after\nvoicebox_transcribe(audio_base64=base64.b64encode(open(\"/var/data/clip.wav\",\"rb\").read()).decode())","handlingStrategy":"validation","validationCode":"from backend.mcp_server.context import request_is_loopback\n\nif audio_path and not request_is_loopback():\n    # encode and send as base64 instead\n    import base64\n    audio_base64 = base64.b64encode(open(audio_path, \"rb\").read()).decode()\n    audio_path = None\nawait voicebox_transcribe(audio_base64=audio_base64, audio_path=audio_path)","typeGuard":"def can_use_audio_path() -> bool:\n    from backend.mcp_server.context import request_is_loopback\n    return request_is_loopback()","tryCatchPattern":"try:\n    await voicebox_transcribe(audio_path=audio_path)\nexcept ValueError as exc:\n    if \"loopback callers\" in str(exc):\n        import base64\n        await voicebox_transcribe(\n            audio_base64=base64.b64encode(open(audio_path, \"rb\").read()).decode()\n        )\n    else:\n        raise","preventionTips":["Default remote clients to audio_base64; never send audio_path from off-host.","Bind Voicebox to 127.0.0.1 if file-path mode is required and only local clients exist.","Do not attempt to bypass request_is_loopback — it is a deliberate security boundary."],"tags":["mcp","security","loopback","transcription","filesystem"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}