Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion docs/docs/Develop/api-keys-and-authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,43 @@ SSRF protection prevents requests to internal or private network resources, such

| Variable | Format | Default | Description |
|----------|--------|---------|-------------|
| `LANGFLOW_SSRF_PROTECTION_ENABLED` | Boolean | `False` | Enable SSRF protection for the **API Request** component. When enabled, the component blocks requests to private IP addresses. When disabled, requests are not blocked. |
| `LANGFLOW_SSRF_PROTECTION_ENABLED` | Boolean | `True` | Enable SSRF protection for the **API Request** component. When enabled, the component blocks requests to private IP addresses. When disabled, requests are not blocked. |
| `LANGFLOW_SSRF_ALLOWED_HOSTS` | List[String] | Not set | A comma-separated list of allowed hosts, IP addresses, or CIDR ranges that can bypass SSRF protection checks. For example: `192.168.1.0/24,10.0.0.5,*.internal.company.local`.|
| `LANGFLOW_CONNECTOR_SSRF_VALIDATION_ENABLED` | Boolean | `False` | Opt-in: also apply SSRF host validation to connector components that take a tenant-controlled host or URL — vector stores (Chroma, Qdrant, Elasticsearch, OpenSearch, Milvus, Weaviate, Supabase, Upstash, ClickHouse), the SQL Database components, the Glean and AstraDB-CQL tools, model-provider model discovery (LiteLLM, HuggingFace, xAI, DeepSeek, Groq, watsonx), and the Ollama / LM Studio / Home Assistant base-URL fields. Disabled by default because these connectors commonly point at `localhost` or a private network. When enabled, it defers to `LANGFLOW_SSRF_PROTECTION_ENABLED` and `LANGFLOW_SSRF_ALLOWED_HOSTS` for the host policy. Recommended for multi-tenant deployments where untrusted users build flows. |

:::note Multi-tenant recommendation
In a multi-tenant deployment where mutually-untrusted users build flows, set `LANGFLOW_CONNECTOR_SSRF_VALIDATION_ENABLED=true` (and keep `LANGFLOW_SSRF_PROTECTION_ENABLED=true`) so a tenant cannot point a vector store, SQL database, model-provider proxy, Ollama/LM Studio/Home Assistant URL, or Glean/AstraDB tool at an internal service or the cloud-metadata endpoint. Allowlist your own internal hosts with `LANGFLOW_SSRF_ALLOWED_HOSTS`.
:::

### Multi-tenant component hardening {#multi-tenant-component-hardening}

The following environment variables close code-execution and local-file-read surfaces that remain reachable through *built-in* components even when user-authored custom components are disabled.
They are disabled by default to preserve single-tenant behavior, and are meant to be set together with [`LANGFLOW_ALLOW_CUSTOM_COMPONENTS=false`](/deployment-block-custom-components) in deployments where mutually-untrusted users build flows.

| Variable | Format | Default | Description |
|----------|--------|---------|-------------|
| `LANGFLOW_BLOCK_CODE_INTERPRETER_COMPONENTS` | Boolean | `False` | When `true`, blocks execution of any flow containing a built-in arbitrary-code-execution component (Python Interpreter, Python REPL/Code tools, the Smart Transform / lambda evaluator, and the code-running agents). These components are official, so their class-code hash is valid and they pass the `LANGFLOW_ALLOW_CUSTOM_COMPONENTS=false` policy — yet they execute arbitrary Python supplied through their input fields, which is equivalent to letting users author custom code. |
| `LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS` | Boolean | `False` | When `true`, built-in file-reading components (File, Directory, JSON/CSV-to-Data, and the CSV/JSON/OpenAPI agents) may only read paths that resolve *inside* the storage data directory where uploaded files live, and `save_file` writes are confined there too. With the default (`false`) a tenant can set a component's path field to an absolute server path (`/etc/passwd`, the SQLite DB, secrets), a traversal string, or a symlink and read arbitrary server files — or another tenant's uploads. This setting also blocks local-file database dialects (`sqlite`, `duckdb`) in the SQL Database components and local-filesystem Git clones. |

:::note Multi-tenant recommendation
For a deployment where mutually-untrusted users build flows, set `LANGFLOW_ALLOW_CUSTOM_COMPONENTS=false`, `LANGFLOW_BLOCK_CODE_INTERPRETER_COMPONENTS=true`, and `LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS=true` together. The first blocks user-authored component code; the second blocks the built-in code-execution components that would otherwise be an equivalent escape hatch; the third confines built-in file access to the upload sandbox. Pair these with the SSRF settings above.
:::

:::warning Tracing in multi-tenant deployments
External tracing integrations (LangSmith, Langfuse, Phoenix, Arize, Opik, and similar) are configured **process-wide** from environment variables, not per user. When you enable one in a multi-tenant deployment, **every tenant's** flow inputs, outputs, and prompts are sent to that single external project, where anyone with access to the tracing account can read them — and a tenant's secret echoed into a component output may not be redacted. Do not enable a shared SaaS tracing backend in a deployment with mutually-untrusted tenants; rely on the built-in local tracing instead.
:::

### Session cookie hardening {#session-cookie-hardening}

For a multi-tenant deployment served over HTTPS, harden the access-token cookie. These default to permissive values for local/HTTP development and for the current frontend, which reads the access token in JavaScript.

| Variable | Format | Default | Description |
|----------|--------|---------|-------------|
| `LANGFLOW_ACCESS_SECURE` | Boolean | `False` | When `true`, the `access_token_lf` cookie is sent only over HTTPS. Recommended `true` for any HTTPS deployment. Leave `false` for plain-HTTP/localhost development, where a `Secure` cookie would not be sent. |
| `LANGFLOW_ACCESS_HTTPONLY` | Boolean | `False` | When `true`, the `access_token_lf` cookie is not readable by JavaScript (mitigates token theft via XSS). The default is `false` because the bundled frontend currently reads this cookie in JavaScript; enabling `HttpOnly` requires a frontend that does not read the token directly. |
| `LANGFLOW_ACCESS_SAME_SITE` | String | `lax` | The `SameSite` attribute of the access-token cookie (`lax`, `strict`, or `none`). |

The refresh-token cookie is already `HttpOnly` + `Secure` + `SameSite` by default.

### Login rate limiting {#login-rate-limiting}

Expand Down
23 changes: 23 additions & 0 deletions src/backend/base/langflow/agentic/api/deps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Shared dependencies for the agentic API.

Kept in a leaf module (only fastapi + lfx settings) so both the route definitions
(langflow.agentic.api.router) and the router-include site (langflow.api.router) can import it
without a circular import.
"""

from fastapi import HTTPException, status
from lfx.services.deps import get_settings_service


def require_agentic_experience() -> None:
"""Backend gate for the agentic assistant's code-generating/executing endpoints.

SECURITY: the assistant generates and EXECUTES component code in-process
(langflow.agentic.helpers.validation.validate_component_runtime and the user-components
overlay). ``agentic_experience`` was only a frontend/UX + MCP-provisioning flag, so the codegen
endpoints were live by default. Gate them here (404 when off), matching the per-endpoint
precedent in api/v1/endpoints.py. The read-only ``/agentic/check-config`` probe is intentionally
NOT gated so non-agentic deployments can still query provider configuration.
"""
if not get_settings_service().settings.agentic_experience:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="This endpoint is not available")
9 changes: 5 additions & 4 deletions src/backend/base/langflow/agentic/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from dataclasses import dataclass
from uuid import UUID

from fastapi import APIRouter, HTTPException, Request
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import StreamingResponse
from lfx.base.models.unified_models import (
get_all_variables_for_provider,
Expand All @@ -19,6 +19,7 @@
from lfx.log.logger import logger
from sqlalchemy.ext.asyncio import AsyncSession

from langflow.agentic.api.deps import require_agentic_experience
from langflow.agentic.api.schemas import AssistantRequest
from langflow.agentic.services.assistant_service import (
execute_flow_with_validation,
Expand Down Expand Up @@ -153,7 +154,7 @@ async def _validate_flow_access(flow_id: str | None, user_id: UUID, session: Asy
raise HTTPException(status_code=404, detail="Flow not found.")


@router.post("/execute/{flow_name}")
@router.post("/execute/{flow_name}", dependencies=[Depends(require_agentic_experience)])
async def execute_named_flow(
flow_name: str,
request: AssistantRequest,
Expand Down Expand Up @@ -271,7 +272,7 @@ async def check_assistant_config(
}


@router.post("/assist")
@router.post("/assist", dependencies=[Depends(require_agentic_experience)])
async def assist(
request: AssistantRequest,
current_user: CurrentActiveUser,
Expand All @@ -296,7 +297,7 @@ async def assist(
)


@router.post("/assist/stream")
@router.post("/assist/stream", dependencies=[Depends(require_agentic_experience)])
async def assist_stream(
request: AssistantRequest,
http_request: Request,
Expand Down
13 changes: 13 additions & 0 deletions src/backend/base/langflow/agentic/helpers/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,20 @@ async def validate_component_runtime(code: str, user_id: str | None = None) -> s
reasons. Only pydantic-schema errors — which are almost always LLM-coding
mistakes — are surfaced so the retry loop can recover before the component
is handed to the user.

SECURITY: this "sandbox" only swallows exceptions; it does not constrain what the code can do
(it compiles+execs the module/class body and runs output methods in-process). When the operator
has disabled custom components (``allow_custom_components=false``), we must NOT execute
tenant-influenced generated code — otherwise the assistant becomes a code-execution path that
bypasses the platform-wide policy. Refuse before any instantiation in that case.
"""
from lfx.services.deps import get_settings_service

if not get_settings_service().settings.allow_custom_components:
return (
"Custom component execution is disabled on this server "
"(allow_custom_components=false); generated components cannot be validated or run."
)
try:
from lfx.custom.custom_component.component import Component as ComponentClass
from lfx.custom.utils import build_custom_component_template
Expand Down
Loading
Loading