Medium severity6.5NVD Advisory· Published Apr 2, 2026· Updated Apr 10, 2026
CVE-2026-34825
CVE-2026-34825
Description
NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Prior to version 2.0.30, NocoBase plugin-workflow-sql substitutes template variables directly into raw SQL strings via getParsedValue() without parameterization or escaping. Any user who triggers a workflow containing a SQL node with template variables from user-controlled data can inject arbitrary SQL. This issue has been patched in version 2.0.30.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@nocobase/plugin-workflow-sqlnpm | < 2.0.30 | 2.0.30 |
Affected products
1Patches
175da3dddc4abfix(plugin-workflow-sql): fix security issue (#8989)
23 files changed · +750 −44
packages/plugins/@nocobase/plugin-workflow-sql/src/client/SQLInstruction.tsx+144 −5 modified@@ -8,14 +8,86 @@ */ import { DEFAULT_DATA_SOURCE_KEY, css } from '@nocobase/client'; +import { ArrayItems } from '@formily/antd-v5'; +import { useForm } from '@formily/react'; +import { Space, Input, Alert, Button } from 'antd'; +import { parse } from '@nocobase/utils/client'; +import { set } from 'lodash'; -import { Instruction, WorkflowVariableRawTextArea, defaultFieldNames } from '@nocobase/plugin-workflow/client'; +import { + Instruction, + WorkflowVariableInput, + WorkflowVariableRawTextArea, + defaultFieldNames, +} from '@nocobase/plugin-workflow/client'; import React from 'react'; import { ConsoleSqlOutlined } from '@ant-design/icons'; -import { Trans } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { NAMESPACE } from '../locale'; +function SQLTextArea(props) { + const { values } = useForm(); + + return values.unsafeInjection ? <WorkflowVariableRawTextArea {...props} /> : <Input.TextArea {...props} />; +} + +function UnsafeInjectionWarning() { + const { t } = useTranslation(NAMESPACE); + const form = useForm(); + const { values } = form; + + if (!values.unsafeInjection || form.disabled) { + return null; + } + + const onMigrate = () => { + const sql = values.sql || ''; + const template = parse(sql); + const parameters = template.parameters || []; + + // Deduplicate and assign new names + const uniqueKeys: string[] = [ + ...new Set( + parameters.map((p: { key: string }) => p.key).filter((key) => key && typeof key === 'string') as string[], + ), + ]; + const context = {}; + uniqueKeys.forEach((key, index) => { + set(context, key, `:var${index}`); + }); + + // Build variables config + const variables = uniqueKeys.map((key, index) => ({ + name: `var${index}`, + value: `{{${key}}}`, + })); + + // Replace {{...}} in SQL with :name placeholders + const newSql = template(context); + + form.setValues({ + sql: newSql, + variables, + unsafeInjection: false, + }); + }; + + return ( + <Alert + type="error" + showIcon + message={t('Current node is using unsafe injection mode (legacy), which has SQL injection risks.')} + action={ + <Button size="small" type="primary" onClick={onMigrate}> + {t('Migrate to safe mode')} + </Button> + } + style={{ marginBottom: 16 }} + /> + ); +} + export default class extends Instruction { title = `{{t("SQL action", { ns: "${NAMESPACE}" })}}`; type = 'sql'; @@ -38,13 +110,17 @@ export default class extends Instruction { }, default: 'main', }, + unsafeInjection: { + type: 'void', + 'x-component': 'UnsafeInjectionWarning', + }, sql: { type: 'string', required: true, title: 'SQL', description: '{{sqlDescription()}}', 'x-decorator': 'FormItem', - 'x-component': 'WorkflowVariableRawTextArea', + 'x-component': 'SQLTextArea', 'x-component-props': { rows: 20, className: css` @@ -53,6 +129,65 @@ export default class extends Instruction { `, }, }, + variables: { + type: 'array', + title: `{{t("Parameters", { ns: "${NAMESPACE}" })}}`, + description: `{{t("SQL parameters. Use :name as placeholders in SQL and provide values here.", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems', + 'x-reactions': [ + { + dependencies: ['unsafeInjection'], + fulfill: { + state: { + visible: '{{!$deps[0]}}', + }, + }, + }, + ], + items: { + type: 'object', + properties: { + space1: { + type: 'void', + 'x-component': 'Space', + properties: { + name: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: `{{t("Name", { ns: "${NAMESPACE}" })}}`, + }, + required: true, + }, + value: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'WorkflowVariableInput', + 'x-component-props': { + rows: 1, + placeholder: `{{t("Value", { ns: "${NAMESPACE}" })}}`, + }, + required: true, + }, + remove: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.Remove', + }, + }, + }, + }, + }, + properties: { + add: { + type: 'void', + 'x-component': 'ArrayItems.Addition', + title: `{{t("Add parameter", { ns: "${NAMESPACE}" })}}`, + }, + }, + }, withMeta: { type: 'boolean', 'x-decorator': 'FormItem', @@ -68,13 +203,17 @@ export default class extends Instruction { <a href="https://docs-cn.nocobase.com/handbook/workflow-json-query" target="_blank" rel="noreferrer"> {'JSON query node'} </a> - {' (Commercial plugin).'} + {'.'} </Trans> ); }, }; components = { - WorkflowVariableRawTextArea, + SQLTextArea, + UnsafeInjectionWarning, + WorkflowVariableInput, + ArrayItems, + Space, }; useVariables({ key, title }, { types, fieldNames = defaultFieldNames }) { return {
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/de-DE.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Führt eine SQL-Anweisung in der Datenbank aus.", "Include meta information of this query in result": "Metainformationen dieser Abfrage im Ergebnis einschließen", "SQL action": "SQL-Aktion", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL-Abfrageergebnisse können über <1>JSON-Abfrageknoten</1> verwendet werden (kommerzielles Plugin).", + "SQL query result could be used through <1>JSON query node</1>.": "SQL-Abfrageergebnisse können über <1>JSON-Abfrageknoten</1> verwendet werden.", "Select a data source to execute SQL.": "Wählen Sie eine Datenquelle zur Ausführung von SQL aus", - "Usage of SQL query result is not supported yet.": "Die Verwendung von SQL-Abfrageergebnissen wird noch nicht unterstützt." + "Usage of SQL query result is not supported yet.": "Die Verwendung von SQL-Abfrageergebnissen wird noch nicht unterstützt.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Der aktuelle Knoten verwendet den unsicheren Injektionsmodus (Legacy), der SQL-Injection-Risiken birgt.", + "Migrate to safe mode": "In sicheren Modus migrieren", + "Parameters": "Parameter", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL-Parameter. Verwenden Sie :name als Platzhalter in SQL und geben Sie hier die Werte an.", + "Name": "Name", + "Value": "Wert", + "Add parameter": "Parameter hinzufügen" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/en-US.json+9 −1 modified@@ -3,7 +3,15 @@ "Execute a SQL statement in database.": "Execute a SQL statement in database.", "Include meta information of this query in result": "Include meta information of this query in result", "SQL action": "SQL action", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).", + "Parameters": "Parameters", + "SQL parameters. Use $1, $2, etc. as placeholders in SQL and provide values here in order.": "SQL parameters. Use $1, $2, etc. as placeholders in SQL and provide values here in order.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Current node is using unsafe injection mode (legacy), which has SQL injection risks.", + "Migrate to safe mode": "Migrate to safe mode", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL parameters. Use :name as placeholders in SQL and provide values here.", + "Name": "Name", + "Value": "Value", + "Add parameter": "Add parameter", + "SQL query result could be used through <1>JSON query node</1>.": "SQL query result could be used through <1>JSON query node</1>.", "Select a data source to execute SQL.": "Select a data source to execute SQL.", "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/es-ES.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Execute a SQL statement in database.", "Include meta information of this query in result": "Include meta information of this query in result", "SQL action": "SQL action", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).", + "SQL query result could be used through <1>JSON query node</1>.": "SQL query result could be used through <1>JSON query node</1>.", "Select a data source to execute SQL.": "Select a data source to execute SQL.", - "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." + "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Current node is using unsafe injection mode (legacy), which has SQL injection risks.", + "Migrate to safe mode": "Migrate to safe mode", + "Parameters": "Parameters", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL parameters. Use :name as placeholders in SQL and provide values here.", + "Name": "Name", + "Value": "Value", + "Add parameter": "Add parameter" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/fr-FR.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Execute a SQL statement in database.", "Include meta information of this query in result": "Include meta information of this query in result", "SQL action": "SQL action", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).", + "SQL query result could be used through <1>JSON query node</1>.": "SQL query result could be used through <1>JSON query node</1>.", "Select a data source to execute SQL.": "Select a data source to execute SQL.", - "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." + "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Current node is using unsafe injection mode (legacy), which has SQL injection risks.", + "Migrate to safe mode": "Migrate to safe mode", + "Parameters": "Parameters", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL parameters. Use :name as placeholders in SQL and provide values here.", + "Name": "Name", + "Value": "Value", + "Add parameter": "Add parameter" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/hu-HU.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "SQL utasítás végrehajtása az adatbázisban.", "Include meta information of this query in result": "A lekérdezés metainformációinak belefoglalása az eredménybe", "SQL action": "SQL művelet", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "Az SQL lekérdezés eredménye a <1>JSON lekérdezési csomóponton</1> keresztül használható (Kereskedelmi bővítmény).", + "SQL query result could be used through <1>JSON query node</1>.": "Az SQL lekérdezés eredménye a <1>JSON lekérdezési csomóponton</1> keresztül használható.", "Select a data source to execute SQL.": "Válasszon adatforrást az SQL végrehajtásához.", - "Usage of SQL query result is not supported yet.": "Az SQL lekérdezési eredmény használata még nem támogatott." + "Usage of SQL query result is not supported yet.": "Az SQL lekérdezési eredmény használata még nem támogatott.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "A jelenlegi csomópont nem biztonságos injektálási módot (örökölt) használ, amely SQL injektálási kockázatot jelent.", + "Migrate to safe mode": "Migrálás biztonságos módba", + "Parameters": "Paraméterek", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL paraméterek. Használja a :name-t helyőrzőként az SQL-ben, és adja meg itt az értékeket.", + "Name": "Név", + "Value": "Érték", + "Add parameter": "Paraméter hozzáadása" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/id-ID.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Jalankan pernyataan SQL dalam database.", "Include meta information of this query in result": "Sertakan informasi meta kueri ini dalam hasil", "SQL action": "Tindakan SQL", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "Hasil kueri SQL dapat digunakan melalui <1>node kueri JSON</1> (Plugin Komersial).", + "SQL query result could be used through <1>JSON query node</1>.": "Hasil kueri SQL dapat digunakan melalui <1>node kueri JSON</1>.", "Select a data source to execute SQL.": "Pilih sumber data untuk menjalankan SQL.", - "Usage of SQL query result is not supported yet.": "Penggunaan hasil kueri SQL belum didukung." + "Usage of SQL query result is not supported yet.": "Penggunaan hasil kueri SQL belum didukung.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Node saat ini menggunakan mode injeksi tidak aman (legacy), yang memiliki risiko injeksi SQL.", + "Migrate to safe mode": "Migrasi ke mode aman", + "Parameters": "Parameter", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "Parameter SQL. Gunakan :name sebagai placeholder di SQL dan berikan nilainya di sini.", + "Name": "Nama", + "Value": "Nilai", + "Add parameter": "Tambah parameter" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/it-IT.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Esegui un'istruzione SQL nel database.", "Include meta information of this query in result": "Includi meta informazioni di questa query nel risultato", "SQL action": "Azione SQL", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "Il risultato della query SQL potrebbe essere utilizzato tramite il <1>nodo JSON query</1>(plugin commerciale).", + "SQL query result could be used through <1>JSON query node</1>.": "Il risultato della query SQL potrebbe essere utilizzato tramite il <1>nodo JSON query</1>.", "Select a data source to execute SQL.": "Seleziona un origine dati per eseguire SQL.", - "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." + "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Il nodo corrente utilizza la modalità di iniezione non sicura (legacy), che comporta rischi di SQL injection.", + "Migrate to safe mode": "Migra alla modalità sicura", + "Parameters": "Parametri", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "Parametri SQL. Usa :name come segnaposto in SQL e fornisci i valori qui.", + "Name": "Nome", + "Value": "Valore", + "Add parameter": "Aggiungi parametro" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/ja-JP.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "データベース内でSQL文を実行します。", "Include meta information of this query in result": "結果にこのクエリのメタ情報を含める", "SQL action": "SQL操作", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQLクエリ結果は<1>JSONクエリノード</1>を介して使用できます(商用プラグイン)。", + "SQL query result could be used through <1>JSON query node</1>.": "SQLクエリ結果は<1>JSONクエリノード</1>を介して使用できます。", "Select a data source to execute SQL.": "SQLを実行するためのデータソースを選択します。", - "Usage of SQL query result is not supported yet.": "SQLクエリ結果はまだサポートされていません" + "Usage of SQL query result is not supported yet.": "SQLクエリ結果はまだサポートされていません", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "現在のノードは安全でないインジェクションモード(レガシー)を使用しており、SQLインジェクションのリスクがあります。", + "Migrate to safe mode": "安全モードに移行", + "Parameters": "パラメータ", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQLパラメータ。SQLで :name をプレースホルダとして使用し、ここで値を指定します。", + "Name": "名前", + "Value": "値", + "Add parameter": "パラメータを追加" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/ko-KR.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "데이터베이스에서 SQL 문을 실행합니다.", "Include meta information of this query in result": "결과에 쿼리 메타 정보 포함", "SQL action": "SQL 작업", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL 쿼리 결과는 <1>JSON 쿼리 노드</1>(상용 플러그인)로 사용할 수 있습니다.", + "SQL query result could be used through <1>JSON query node</1>.": "SQL 쿼리 결과는 <1>JSON 쿼리 노드</1>를 통해 사용할 수 있습니다.", "Select a data source to execute SQL.": "SQL을 실행할 데이터 소스 선택", - "Usage of SQL query result is not supported yet.": "SQL 쿼리 결과 사용은 아직 지원되지 않습니다." + "Usage of SQL query result is not supported yet.": "SQL 쿼리 결과 사용은 아직 지원되지 않습니다.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "현재 노드는 안전하지 않은 주입 모드(레거시)를 사용하고 있으며 SQL 주입 위험이 있습니다.", + "Migrate to safe mode": "안전 모드로 마이그레이션", + "Parameters": "매개변수", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL 매개변수. SQL에서 :name을 자리 표시자로 사용하고 여기에 값을 제공하세요.", + "Name": "이름", + "Value": "값", + "Add parameter": "매개변수 추가" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/nl-NL.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Execute a SQL statement in database.", "Include meta information of this query in result": "Include meta information of this query in result", "SQL action": "SQL action", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).", + "SQL query result could be used through <1>JSON query node</1>.": "SQL query result could be used through <1>JSON query node</1>.", "Select a data source to execute SQL.": "Select a data source to execute SQL.", - "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." + "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Current node is using unsafe injection mode (legacy), which has SQL injection risks.", + "Migrate to safe mode": "Migrate to safe mode", + "Parameters": "Parameters", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL parameters. Use :name as placeholders in SQL and provide values here.", + "Name": "Name", + "Value": "Value", + "Add parameter": "Add parameter" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/pt-BR.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Execute a SQL statement in database.", "Include meta information of this query in result": "Include meta information of this query in result", "SQL action": "SQL action", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).", + "SQL query result could be used through <1>JSON query node</1>.": "SQL query result could be used through <1>JSON query node</1>.", "Select a data source to execute SQL.": "Select a data source to execute SQL.", - "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." + "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Current node is using unsafe injection mode (legacy), which has SQL injection risks.", + "Migrate to safe mode": "Migrate to safe mode", + "Parameters": "Parameters", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL parameters. Use :name as placeholders in SQL and provide values here.", + "Name": "Name", + "Value": "Value", + "Add parameter": "Add parameter" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/ru-RU.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Выполнить SQL-запрос в базе данных.", "Include meta information of this query in result": "Включить метаинформацию этого запроса в результат", "SQL action": "Действие SQL", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "Результат SQL-запроса может быть использован через узел <1>JSON-запроса</1> (коммерческий плагин).", + "SQL query result could be used through <1>JSON query node</1>.": "Результат SQL-запроса может быть использован через узел <1>JSON-запроса</1>.", "Select a data source to execute SQL.": "Выберите источник данных для выполнения SQL.", - "Usage of SQL query result is not supported yet.": "Использование результата SQL-запроса пока не поддерживается." + "Usage of SQL query result is not supported yet.": "Использование результата SQL-запроса пока не поддерживается.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Текущий узел использует небезопасный режим инъекции (устаревший), который имеет риски SQL-инъекции.", + "Migrate to safe mode": "Перейти в безопасный режим", + "Parameters": "Параметры", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "Параметры SQL. Используйте :name в качестве заполнителей в SQL и укажите значения здесь.", + "Name": "Имя", + "Value": "Значение", + "Add parameter": "Добавить параметр" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/tr-TR.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Execute a SQL statement in database.", "Include meta information of this query in result": "Include meta information of this query in result", "SQL action": "SQL action", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).", + "SQL query result could be used through <1>JSON query node</1>.": "SQL query result could be used through <1>JSON query node</1>.", "Select a data source to execute SQL.": "Select a data source to execute SQL.", - "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." + "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Current node is using unsafe injection mode (legacy), which has SQL injection risks.", + "Migrate to safe mode": "Migrate to safe mode", + "Parameters": "Parameters", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL parameters. Use :name as placeholders in SQL and provide values here.", + "Name": "Name", + "Value": "Value", + "Add parameter": "Add parameter" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/uk-UA.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Execute a SQL statement in database.", "Include meta information of this query in result": "Include meta information of this query in result", "SQL action": "SQL action", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).", + "SQL query result could be used through <1>JSON query node</1>.": "SQL query result could be used through <1>JSON query node</1>.", "Select a data source to execute SQL.": "Select a data source to execute SQL.", - "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." + "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Current node is using unsafe injection mode (legacy), which has SQL injection risks.", + "Migrate to safe mode": "Migrate to safe mode", + "Parameters": "Parameters", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL parameters. Use :name as placeholders in SQL and provide values here.", + "Name": "Name", + "Value": "Value", + "Add parameter": "Add parameter" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/vi-VN.json+9 −2 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Execute a SQL statement in database.", "Include meta information of this query in result": "Include meta information of this query in result", "SQL action": "SQL action", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).", + "SQL query result could be used through <1>JSON query node</1>.": "SQL query result could be used through <1>JSON query node</1>.", "Select a data source to execute SQL.": "Select a data source to execute SQL.", - "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." + "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "Current node is using unsafe injection mode (legacy), which has SQL injection risks.", + "Migrate to safe mode": "Migrate to safe mode", + "Parameters": "Parameters", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL parameters. Use :name as placeholders in SQL and provide values here.", + "Name": "Name", + "Value": "Value", + "Add parameter": "Add parameter" } \ No newline at end of file
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/zh-CN.json+10 −2 modified@@ -3,7 +3,15 @@ "Execute a SQL statement in database.": "在数据库中执行一个 SQL 语句", "Include meta information of this query in result": "在结果中包含此查询的元信息", "SQL action": "SQL 操作", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL 执行的结果可在 <1>JSON 解析节点</1> 中使用(商业插件)。", + "Parameters": "参数", + "SQL parameters. Use $1, $2, etc. as placeholders in SQL and provide values here in order.": "SQL 参数。在 SQL 中使用 $1, $2 等作为占位符,并按顺序在此提供对应的值。", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "当前节点使用了不安全注入模式(遗留),存在 SQL 注入风险。", + "Migrate to safe mode": "迁移至安全模式", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL 参数。在 SQL 中使用 :name 作为占位符,并在此提供对应的值。", + "Name": "名称", + "Value": "值", + "Add parameter": "添加参数", + "SQL query result could be used through <1>JSON query node</1>.": "SQL 执行的结果可在 <1>JSON 解析节点</1> 中使用。", "Select a data source to execute SQL.": "选择一个数据源来执行 SQL", "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." -} \ No newline at end of file +}
packages/plugins/@nocobase/plugin-workflow-sql/src/locale/zh-TW.json+10 −3 modified@@ -3,7 +3,14 @@ "Execute a SQL statement in database.": "Execute a SQL statement in database.", "Include meta information of this query in result": "Include meta information of this query in result", "SQL action": "SQL action", - "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).", + "SQL query result could be used through <1>JSON query node</1>.": "SQL query result could be used through <1>JSON query node</1>.", "Select a data source to execute SQL.": "Select a data source to execute SQL.", - "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." -} \ No newline at end of file + "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet.", + "Current node is using unsafe injection mode (legacy), which has SQL injection risks.": "當前節點使用了不安全注入模式(遺留),存在 SQL 注入風險。", + "Migrate to safe mode": "遷移至安全模式", + "SQL parameters. Use :name as placeholders in SQL and provide values here.": "SQL 參數。在 SQL 中使用 :name 作為佔位符,並在此提供對應的值。", + "Parameters": "參數", + "Name": "名稱", + "Value": "值", + "Add parameter": "添加參數" +}
packages/plugins/@nocobase/plugin-workflow-sql/src/server/migrations/20260327120000-add-unsafe-injection-flag.ts+46 −0 added@@ -0,0 +1,46 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Migration } from '@nocobase/server'; +import { parse } from '@nocobase/utils'; + +export default class extends Migration { + appVersion = '<2.0.30'; + async up() { + const { db } = this.context; + + const NodeRepo = db.getRepository('flow_nodes'); + await db.sequelize.transaction(async (transaction) => { + const nodes = await NodeRepo.find({ + filter: { + type: 'sql', + }, + transaction, + }); + + await nodes.reduce( + (promise, node) => + promise.then(() => { + const sql = node.config?.sql || ''; + const template = parse(sql); + if (!template.parameters?.length) { + return; + } + node.set('config', { ...node.config, unsafeInjection: true }); + node.changed('config', true); + return node.save({ + silent: true, + transaction, + }); + }), + Promise.resolve(), + ); + }); + } +}
packages/plugins/@nocobase/plugin-workflow-sql/src/server/SQLInstruction.ts+43 −4 modified@@ -14,6 +14,8 @@ export type SQLInstructionConfig = { dataSource?: string; sql?: string; withMeta?: boolean; + unsafeInjection?: boolean; + variables?: Array<{ name: string; value: any }>; }; export default class extends Instruction { @@ -23,7 +25,23 @@ export default class extends Instruction { if (!(collectionManager instanceof SequelizeCollectionManager)) { throw new Error(`type of data source "${node.config.dataSource}" is not database`); } - const sql = processor.getParsedValue(node.config.sql || '', node.id).trim(); + + const { unsafeInjection = false, variables: variablesConfig = [] } = node.config; + + let sql = ''; + let replacements = null; + if (unsafeInjection) { + sql = processor.getParsedValue(node.config.sql || '', node.id).trim(); + } else { + sql = (node.config.sql || '').trim(); + replacements = {}; + for (const { name, value } of variablesConfig) { + if (name) { + replacements[name] = processor.getParsedValue(value, node.id); + } + } + } + if (!sql) { return { status: JOB_STATUS.RESOLVED, @@ -33,6 +51,7 @@ export default class extends Instruction { const [result = null, meta = null] = (await collectionManager.db.sequelize.query(sql, { transaction: this.workflow.useDataSourceTransaction(dataSourceName, processor.transaction), + replacements, // plain: true, // model: db.getCollection(node.config.collection).model })) ?? []; @@ -43,8 +62,14 @@ export default class extends Instruction { }; } - async test({ dataSource, sql, withMeta }: SQLInstructionConfig = {}) { - if (!sql) { + async test({ + dataSource, + sql: sqlConfig, + withMeta, + unsafeInjection = false, + variables: variablesConfig = [], + }: SQLInstructionConfig = {}) { + if (!sqlConfig) { return { result: null, status: JOB_STATUS.RESOLVED, @@ -58,7 +83,21 @@ export default class extends Instruction { } try { - const [result = null, meta = null] = (await collectionManager.db.sequelize.query(sql)) ?? []; + let sql = ''; + let replacements = null; + if (unsafeInjection) { + sql = sqlConfig.trim(); + } else { + sql = sqlConfig.trim(); + replacements = {}; + for (const { name, value } of variablesConfig) { + if (name) { + replacements[name] = value; + } + } + } + + const [result = null, meta = null] = (await collectionManager.db.sequelize.query(sql, { replacements })) ?? []; return { result: withMeta ? [result, meta] : result,
packages/plugins/@nocobase/plugin-workflow-sql/src/server/__tests__/instruction.test.ts+46 −1 modified@@ -111,6 +111,7 @@ describe('workflow > instructions > sql', () => { type: 'sql', config: { sql: `select '{{$system.now}}' as a`, + unsafeInjection: true, }, }); @@ -123,14 +124,57 @@ describe('workflow > instructions > sql', () => { expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED); }); - it('update', async () => { + it('update with unsafeInjection: true', async () => { const queryInterface = db.sequelize.getQueryInterface(); const n1 = await workflow.createNode({ type: 'sql', config: { sql: `update ${PostCollection.quotedTableName()} set ${queryInterface.quoteIdentifier( 'read', )}={{$context.data.id}} where ${queryInterface.quoteIdentifier('id')}={{$context.data.id}}`, + unsafeInjection: true, + }, + }); + + const n2 = await workflow.createNode({ + type: 'query', + config: { + collection: 'posts', + params: { + filter: { + id: '{{ $context.data.id }}', + }, + }, + }, + upstreamId: n1.id, + }); + + await n1.setDownstream(n2); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [execution] = await workflow.getExecutions(); + const [sqlJob, queryJob] = await execution.getJobs({ order: [['id', 'ASC']] }); + expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED); + expect(queryJob.status).toBe(JOB_STATUS.RESOLVED); + expect(queryJob.result.read).toBe(post.id); + }); + + it('update with replacements (unsafeInjection: false)', async () => { + const queryInterface = db.sequelize.getQueryInterface(); + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `update ${PostCollection.quotedTableName()} set ${queryInterface.quoteIdentifier( + 'read', + )}=:val1 where ${queryInterface.quoteIdentifier('id')}=:val2`, + unsafeInjection: false, + variables: [ + { name: 'val1', value: '{{$context.data.id}}' }, + { name: 'val2', value: '{{$context.data.id}}' }, + ], }, }); @@ -168,6 +212,7 @@ describe('workflow > instructions > sql', () => { sql: `delete from ${PostCollection.quotedTableName()} where ${queryInterface.quoteIdentifier( 'id', )}={{$context.data.id}};`, + unsafeInjection: true, }, });
packages/plugins/@nocobase/plugin-workflow-sql/src/server/__tests__/migrations/20260327120000-add-unsafe-injection-flag.test.ts+141 −0 added@@ -0,0 +1,141 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import Database from '@nocobase/database'; +import { Application } from '@nocobase/server'; +import { getApp } from '@nocobase/plugin-workflow-test'; + +import Plugin from '../..'; +import Migration from '../../migrations/20260327120000-add-unsafe-injection-flag'; + +describe('migration - add unsafe injection flag', () => { + let app: Application; + let db: Database; + let WorkflowModel; + let workflow; + + beforeEach(async () => { + app = await getApp({ + plugins: [Plugin], + }); + db = app.db; + WorkflowModel = db.getCollection('workflows').model; + + workflow = await WorkflowModel.create({ + enabled: true, + type: 'collection', + config: { + mode: 1, + collection: 'posts', + }, + }); + }); + + afterEach(() => app.destroy()); + + it('should mark node with variables as unsafeInjection', async () => { + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select * from posts where id = {{$context.data.id}}`, + }, + }); + + const migration = new Migration({ db: app.db, app } as any); + await migration.up(); + + await n1.reload(); + expect(n1.config.unsafeInjection).toBe(true); + }); + + it('should not mark node without variables', async () => { + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select 1 as a`, + }, + }); + + const migration = new Migration({ db: app.db, app } as any); + await migration.up(); + + await n1.reload(); + expect(n1.config.unsafeInjection).toBeUndefined(); + }); + + it('should not mark node with empty sql', async () => { + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: '', + }, + }); + + const migration = new Migration({ db: app.db, app } as any); + await migration.up(); + + await n1.reload(); + expect(n1.config.unsafeInjection).toBeUndefined(); + }); + + it('should handle node without sql config', async () => { + const n1 = await workflow.createNode({ + type: 'sql', + config: {}, + }); + + const migration = new Migration({ db: app.db, app } as any); + await migration.up(); + + await n1.reload(); + expect(n1.config.unsafeInjection).toBeUndefined(); + }); + + it('should mark node with multiple variables', async () => { + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `update posts set read = {{$context.data.id}} where id = {{$context.data.id}}`, + }, + }); + + const migration = new Migration({ db: app.db, app } as any); + await migration.up(); + + await n1.reload(); + expect(n1.config.unsafeInjection).toBe(true); + }); + + it('should only mark nodes with variables among mixed nodes', async () => { + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select 1 as a`, + }, + }); + + const n2 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select * from posts where id = {{$context.data.id}}`, + }, + upstreamId: n1.id, + }); + + await n1.setDownstream(n2); + + const migration = new Migration({ db: app.db, app } as any); + await migration.up(); + + await n1.reload(); + await n2.reload(); + expect(n1.config.unsafeInjection).toBeUndefined(); + expect(n2.config.unsafeInjection).toBe(true); + }); +});
packages/plugins/@nocobase/plugin-workflow-sql/src/server/__tests__/security.test.ts+175 −0 added@@ -0,0 +1,175 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import Database from '@nocobase/database'; +import { Application } from '@nocobase/server'; +import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow'; +import { getApp, sleep } from '@nocobase/plugin-workflow-test'; + +import Plugin from '..'; + +describe('workflow > instructions > sql > security', () => { + let app: Application; + let db: Database; + let PostRepo; + let PostCollection; + let WorkflowModel; + let workflow; + + beforeEach(async () => { + app = await getApp({ + plugins: [Plugin], + }); + db = app.db; + WorkflowModel = db.getCollection('workflows').model; + PostCollection = db.getCollection('posts'); + PostRepo = PostCollection.repository; + + workflow = await WorkflowModel.create({ + enabled: true, + type: 'collection', + config: { + mode: 1, + collection: 'posts', + }, + }); + }); + + afterEach(() => app.destroy()); + + describe('sql injection prevention (safe mode)', () => { + it('tautology in WHERE clause (1 OR 1=1)', async () => { + const queryInterface = db.sequelize.getQueryInterface(); + await PostRepo.create({ values: { title: 't1' }, hooks: false }); + await PostRepo.create({ values: { title: 't2' }, hooks: false }); + + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select * from ${PostCollection.quotedTableName()} where ${queryInterface.quoteIdentifier( + 'title', + )} = :a`, + unsafeInjection: false, + variables: [{ name: 'a', value: '1 OR 1=1' }], + }, + }); + + const post = await PostRepo.create({ values: { title: 't3' } }); + + await sleep(500); + + const [execution] = await workflow.getExecutions(); + const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] }); + expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED); + // "1 OR 1=1" is treated as a literal string, not as SQL expression + expect(sqlJob.result.length).toBe(0); + }); + + it('UNION SELECT injection', async () => { + const queryInterface = db.sequelize.getQueryInterface(); + await PostRepo.create({ values: { title: 'normal' }, hooks: false }); + + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select ${queryInterface.quoteIdentifier( + 'title', + )} from ${PostCollection.quotedTableName()} where ${queryInterface.quoteIdentifier('title')} = :a`, + unsafeInjection: false, + variables: [{ name: 'a', value: "' UNION SELECT usename FROM pg_user --" }], + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [execution] = await workflow.getExecutions(); + const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] }); + expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED); + expect(sqlJob.result.length).toBe(0); + }); + + it('semicolon with DROP TABLE', async () => { + const queryInterface = db.sequelize.getQueryInterface(); + await PostRepo.create({ values: { title: 'keep' }, hooks: false }); + + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select * from ${PostCollection.quotedTableName()} where ${queryInterface.quoteIdentifier( + 'title', + )} = :a`, + unsafeInjection: false, + variables: [{ name: 'a', value: `'; DROP TABLE ${PostCollection.quotedTableName()}; --` }], + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [execution] = await workflow.getExecutions(); + const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] }); + expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED); + + // Table should still exist and data should be intact + const remaining = await PostRepo.find(); + expect(remaining.length).toBeGreaterThanOrEqual(2); + }); + + it('comment bypass (-- )', async () => { + const queryInterface = db.sequelize.getQueryInterface(); + await PostRepo.create({ values: { title: 'secret' }, hooks: false }); + + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select * from ${PostCollection.quotedTableName()} where ${queryInterface.quoteIdentifier( + 'title', + )} = :a AND ${queryInterface.quoteIdentifier('published')} = true`, + unsafeInjection: false, + variables: [{ name: 'a', value: "secret' --" }], + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [execution] = await workflow.getExecutions(); + const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] }); + expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED); + expect(sqlJob.result.length).toBe(0); + }); + + it('numeric parameter with dynamic variable', async () => { + const queryInterface = db.sequelize.getQueryInterface(); + + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select * from ${PostCollection.quotedTableName()} where ${queryInterface.quoteIdentifier('id')} = :a`, + unsafeInjection: false, + variables: [{ name: 'a', value: '{{$context.data.id}}' }], + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [execution] = await workflow.getExecutions(); + const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] }); + expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED); + expect(sqlJob.result.length).toBe(1); + expect(sqlJob.result[0].id).toBe(post.id); + }); + }); +});
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/nocobase/nocobase/commit/75da3dddc4aba739c398f7072725dcf7f5487f5cnvdPatchWEB
- github.com/nocobase/nocobase/security/advisories/GHSA-vx58-fwwq-5g8jnvdExploitMitigationVendor AdvisoryWEB
- github.com/advisories/GHSA-vx58-fwwq-5g8jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-34825ghsaADVISORY
- github.com/nocobase/nocobase/releases/tag/v2.0.30nvdProductRelease NotesWEB
News mentions
0No linked articles in our index yet.