Repository navigation
[DSIP-105][Feature][Parent] Sensitive Variable Support — Masking in API/UI & Encrypted Storage #17937
Description
Activity
- addedfeaturenew featurenew featureWaiting for replyWaiting for replyWaiting for reply
on Feb 3, 2026 Hi @det101, this looks like a valuable feature, especially from a security standpoint 👍
Before starting implementation, do maintainers have a preferred design for how sensitive variables should behave (e.g. UI masking, logging behavior, export/import handling)?Happy to work on this once the expected approach is clarified.
Please create an DSIP for this. Feature like this belong to DSIP. And you should provide more specific design detail in it. @det101
Thanks for the guidance @SbloodyS
That makes sense — this definitely fits the DSIP process.@det101, could you please create a DSIP for this feature with the proposed design details?
It would be great if the DSIP could cover things like:expected behavior of sensitive variables (UI masking, logging, export/import),
reuse of existing encryption/decryption mechanisms,
backward compatibility and migration (if any).
Once the DSIP is up, we can continue the discussion and move toward implementation.
- changed the title
[-][Feature][Variable Management] Add sensitive variable type to securely handle secrets like passwords[/-][+][DSIP-105][Api-server] Sensitive Variable Type for Secure Secret Handling[/+]on Feb 4, 2026 It's better to add
sensitiveswitch at the currently variable, rather than add a new type, otherwise you need to add a lot of type here.It's better to add
sensitiveswitch at the currently variable, rather than add a new type, otherwise you need to add a lot of type here.@ruanwenjun You're absolutely right that adding a sensitive switch/flag to the existing Property class is a much better design than introducing a new variable type. Creating a dedicated "Sensitive Variable" type would indeed lead to type proliferation and maintenance challenges.
- added 3 commits that reference this issue
on Feb 27, 2026 - changed the title
[-][DSIP-105][Api-server] Sensitive Variable Type for Secure Secret Handling[/-][+][DSIP-105][Api-server] Sensitive Variable Support — Masking in API/UI & Encrypted Storage[/+]on Mar 10, 2026 为了方便大家讨论,附上中文说明
[DSIP-105][Api-server] 敏感变量支持 — API/UI 脱敏与加密存储
动机
当前 DolphinScheduler 的变量不区分敏感与非敏感信息。当用户将密码、API Key 等作为工作流全局参数或任务本地参数存储时,这些值可能以明文出现在:
- 展示定义变量的页面
- API 响应(如
viewVariables、getWorkflowDefinition、queryWorkflowDefinitionByCode等) - 数据库定义态 JSON(
global_params/task_params中的 value)
本变更在现有
Property上增加变量级敏感标记:API/UI 脱敏;定义态敏感 value 落库前做与数据源同级的 at-rest 保护(复用PasswordUtils)。交付拆为 两个 PR(见 Delivery):PR1 覆盖定义存储与 API/UI;PR2 覆盖任务 stdout 动态脱敏。PR1 合入后、PR2 完成前,任务日志仍可能打印敏感值。
Out of scope(相对整个 DSIP 的长期非目标,或明确不在对应 PR)
项 说明 项目参数( ProjectParameter/t_ds_project_parameter)存储模型与 Property不同,暂不实现;UI/文档注明暂不支持工作流定义 Export / Import 功能已下线或不在维护范围,不考虑 KMS / 信封加密 / 密钥轮转 不在范围 实例 global_params密文存储本 DSIP 采用运行态明文物化;若社区后续要求再开 follow-up 任务 stdout 日志脱敏:不在 PR1,在 PR2 实现(仍属本 DSIP 交付,见 Delivery)。
Goals(验收口径)
PR1
- 标记为
sensitive=true的定义参数,在所有对外 API/UI 中不返回真实 value。 - 定义态在
datasource.encryption.enable=true时,敏感 value 不以明文持久化。 - 未标记敏感的参数行为与现网完全一致。
- 保存时对「未修改」敏感值不二次加密。
- 同集群 Copy 定义后,敏感参数仍可正常运行。
PR2
- 任务 stdout(及约定的任务日志路径)中,不出现本任务敏感参数的明文 value。
- 任务结束(成功/失败/kill)后清理动态 mask,不污染后续任务日志。
明确不声称
- 加密开关默认关闭时,定义态仍可能明文。
- 本方案与数据源密码同级(salt + Base64 混淆),不是合规级加密。
- Master/Worker 内存、任务进程内仍可见明文。
- PR1 单独合入时,不声称日志安全。
Delivery(两个 PR)
PR 范围 依赖 PR1 Property.sensitive;定义态加解密;API/UI 脱敏;保存 merge(防二次加密);启动合并;Copy 原样拷贝;单测 + api-test;UI checkbox无 PR2 按本次任务敏感参数值动态注册日志 mask;任务结束必须清理;单测 + 日志断言 依赖 PR1 合入顺序: 先 PR1,再 PR2。
Design Details
1. 在
Property中新增sensitive布尔字段不引入新的敏感变量类型(如
SENSITIVE_VARCHAR),仅在现有Property增加字段:@Builder.Default private boolean sensitive = false;
- 缺省 / JSON 缺失 / null → 视为
false(向后兼容)。 - 随现有 JSON 列序列化,无 schema 变更。
- 适用于工作流全局参数与任务
localParams。
2. 占位符语义
复用已有常量
Constants.XXXXXX(值为"******")。场景 含义 API / UI 展示 敏感 value 一律为 ******更新提交 敏感项 value 为 ******或空串 → 「未修改,保留库中原值」真实密钥 文档约定:用户不得将 ******作为真实密码(与数据源惯例一致)UI:敏感标记使用 checkbox;不以
type=password作为唯一手段。回显固定******;用户修改时整段替换;未改则提交******。3. 存储分层与威胁模型
层 存储内容 说明 定义态(workflow/task definition 及 definition log) sensitive=true的 value 在加密开启时为密文保护静态配置 运行态(workflow instance global_params、任务执行上下文)启动时解密并合并后的明文(物化) 供参数固化与任务下发 对外 API / UI 永远 ******定义与实例查询均脱敏 选择「运行态明文」的原因:固化参数、补数、重跑、上下文下发已假设可读 value;实例再密文会把 decrypt 散落到整条执行链。本 DSIP 用 定义态加密 + API 脱敏(+ PR2 日志脱敏)覆盖主要目标。
4. 加解密(仅定义态写路径,PR1)
- 复用
PasswordUtils.encodePassword/decodePassword。 - 开关与数据源相同:
datasource.encryption.enable(默认false)。 - salt 变更后旧密文无法解密,需用户重填(与数据源一致)。
仅当同时满足时 encode:
sensitive == true- value 非空
- value 不是保留标记(非
******、非空串) - value 来自客户端提交的新明文(见 §5)
5. 写路径(禁止二次加密,PR1)
客户端提交 → 对每个字段: if sensitive && value 是保留标记(****** 或空): 用 DB 中同 prop 的 value **原样**写回 (已是密文则保持密文) **禁止**再 encode else if sensitive && value 为新明文: encode(若加密开启)→ 写入 else: 按非敏感原样写入 → 持久化- 匹配键:参数名
prop。 - 新建
prop不允许只提交保留标记(无旧值可合并 → 校验失败,要求提供明文)。
sensitive开关切换:变更 行为 false → true,提交新明文encode 后存储 false → true,提交保留标记拒绝,要求重填明文 true → false先 decode 得明文,再以 sensitive=false明文落库true → true,保留标记原样保留库中密文,不 encode 入口建议:全局参数在工作流定义保存前;本地参数在任务定义保存前。具体类名以实现为准,行为以本表为准。
6. 读路径(副本;禁止污染实体,PR1)
统一工具(名称示意):
maskSensitiveProperties(...)→ 深拷贝后脱敏,供 API 响应decryptSensitiveProperties(...)→ 深拷贝后解密,供内部执行/合并mergeKeepOriginal(submitted, existingFromDb)→ 仅合并保留标记,不负责加密
规则:
- 从 DB 读取定义后,仅在「启动合并 / 下发任务」等内部路径使用 decrypt 副本。
- 凡返回前端 / OpenAPI / Token 客户端的路径,只返回 mask 副本。
- 禁止在共享的
WorkflowDefinition/TaskDefinition实体上原地改成******后再用于执行。
脱敏应覆盖所有返回
Property/globalParams/localParams/ DagData 的接口(含 list/paging/detail/variables 等),不能假设仅genDagData一处即可盖全。加解密与脱敏顺序:
- 写:merge 保留标记 →(仅新明文)encrypt → 持久化
- 读(对外):DB →(如需)decrypt 到副本 → mask → 响应
- 读(执行):DB → decrypt 到副本 → 合并/固化/下发(明文)
7. 启动与 Command(PR1)
- 加载定义 globalParams → decrypt 副本。
- 与 command / startParams 合并:同名为保留标记 → 用定义解密值;提交新明文 → 允许覆盖。
- 合并结果(明文)写入 workflow instance
global_params(运行态物化)。 - 后续
prepareParamsMap等使用实例/上下文中的明文,不再对实例字段做定义态 encode。
启动 UI:敏感项展示
******;未改则提交保留标记;允许用户输入新值覆盖。8. OUT 参数(PR1 API 脱敏;PR2 覆盖日志)
- 允许
sensitive=true的 OUT。 - 运行写出的值出现在实例/变量类 API 时做脱敏(PR1)。
- stdout 中的敏感 OUT/参数值由 PR2 脱敏。
9. Copy(Import / Export 不考虑)
操作 本 DSIP Export / Import Out of scope(功能下线/不维护) Copy(同集群复制工作流定义,如 batchCopyWorkflowDefinition)服务端直接复制 JSON(含密文与 sensitive),不经前端,不二次加密(PR1)10. 需要脱敏的对外面(PR1 原则)
凡响应中出现全局/本地
Property的接口均需 mask,包括但不限于:viewVariables(定义 / 实例)queryWorkflowDefinitionByCode/getWorkflowDefinition/ list / paging / byNamequeryWorkflowInstanceById等返回 dag/params 的接口getTaskDefinition/queryTaskDefinitionDetail
保存类 API:先 merge 保留标记,再按 §5 加密后持久化。
11. UI(PR1)
- 全局参数、任务本地参数表单增加
sensitivecheckbox。 - 敏感 value 回显
******;未修改提交******。 - 项目参数保持现状,并提示:敏感标记暂仅支持工作流全局参数与任务局部参数。
12. 任务 stdout 日志脱敏(PR2)
目标: 任务日志中不出现本任务敏感参数的明文 value。
要点:
- 输入:本任务
prepareParamsMap(或等价上下文)中sensitive=true的明文 value。 - 行为:日志中出现这些 value → 替换为
******(可基于现有SensitiveDataConverter扩展为按任务动态注册 pattern)。 - 生命周期:任务开始注册;任务结束(成功/失败/kill)必须清理;禁止静态全局状态泄漏到后续无关任务。
- 范围:Physical 任务执行路径上的 stdout/任务日志;细则在 PR2 描述中展开。
- 非目标:不做项目参数;不替代定义态加密;不宣称覆盖所有插件自定义日志文件。
13. Risks & Mitigations
风险 缓解 ******被当作真实密码文档约定禁止;与数据源一致 对保留值二次加密导致密钥损坏 §5:保留标记原样写回,禁止再 encode 原地 mask 污染执行路径 §6:强制深拷贝;测试覆盖 旧数据无 sensitive默认 falsesalt 变更 与数据源相同:需重填 旧数据未加密 decodePassword不匹配则原样返回加密默认关闭 文档诚实说明;与数据源同级预期 PR2 mask 未清理导致串任务 结束路径(含异常/kill)统一 cleanup;并发用例 仅合入 PR1 时用户误以为日志也安全 Motivation / Goals 写明;Release note 提示等 PR2
Compatibility, Deprecation, and Migration Plan
- 向后兼容:
sensitive默认false,既有工作流行为不变。 - 无需 DB migration:字段在现有 JSON 中。
datasource.encryption.enable=false时不加密;旧明文仍可读。- 发布顺序:后端(PR1)→ 前端(PR1)→ 日志(PR2)。
- 回滚:可分别回滚 PR2 / PR1 前端;旧客户端忽略未知字段。
Test Plan
PR1
- 单元:
Property.sensitive序列化;merge 保留且不二次加密;false↔true切换;mask/decrypt 深拷贝不污染原对象;encryption 开关 true/false。 - API 集成:创建含敏感全局/局部参数的定义 → 查询均为
******→ 只改非敏感字段保存后任务仍能读到真密钥 →enable=true时定义态 DB 非明文 → Copy 后任务仍可用。 - 前端:lint / prettier;变量查看 UI 不出现明文。
- 回归:未标记敏感的参数与现网一致。
PR2
- 单元:动态注册/清理;并发或先后两个任务 mask 不串扰。
- 集成或可重复的任务日志用例:敏感值出现在脚本输出中时日志为
******;任务失败/kill 后清理仍生效。
Separate / Follow-up(非本 DSIP 当前两 PR)
- 项目参数敏感标记
- Export / Import 策略
- 实例
global_params密文存储 - 独立于
datasource.encryption的变量加密开关(可选增强)
已定取舍摘要
- 定义态加密 + 运行态明文物化 + API 脱敏
- Import/Export 不考虑;Copy 原样拷贝密文
false→true必须重填明文- 保留标记 =
******或空串;禁止对保留值再 encode - 拆 两个 PR:PR1 变量存储/API/UI;PR2 stdout 动态脱敏
@ruanwenjun @SbloodyS This issue has been fully detailed; please review it. Thanks.
- added a commit that references this issue
on Jun 9, 2026 @ruanwenjun @SbloodyS The DSIP design has been revised based on review feedback. Please take another look when you have time.
Key updates:
- Write path: keep-original (
******/ empty) must not be re-encoded (avoid double encryption) - Storage model: definition ciphertext + runtime plaintext materialization + API/UI masking
- Export/Import: out of scope; Copy keeps ciphertext as-is
- Delivery: two PRs — PR1 storage/API/UI; PR2 stdout dynamic log masking
false → truerequires re-entering plaintext
English body and Chinese comment are both updated on this issue. Thanks.
- Write path: keep-original (
@det101 The design looks good to me, but when implementing it, we should break it down into smaller PRs, such as:
- API module: Changes to property-related interfaces
- API module: Changes to data sources
- Worker module: Changes to log content
Reacted by luxiaolong@ruanwenjun Thanks, I'll split into smaller PRs as you suggested:
- API: Property interfaces + UI masking
- API: reuse
PasswordUtils(no datasource CRUD change) - Worker: log masking
Keep-original is only
******(write back DB value, do not re-encode).
Empty/null is a real empty value and is persisted as""— same as currentPasswordUtils.encodePassword/decodePassword(isEmpty→EMPTY, even when encryption is on).******is write-path only and is never decoded.- changed the title
[-][DSIP-105][Api-server] Sensitive Variable Support — Masking in API/UI & Encrypted Storage[/-][+][DSIP-105][Feature][Parent] Sensitive Variable Support — Masking in API/UI & Encrypted Storage[/+]on Aug 25, 2026 @SbloodyS Split into subtask issues (one issue / one PR), listed on this parent like #6407:
- [DSIP-105][Feature][API] Add Property.sensitive and mask values in API/UI #18586 API/UI masking — PR [DSIP-105][API][UI] Add Property.sensitive with API/UI masking #18585
- [DSIP-105][Feature][API] Encrypt sensitive definition params with PasswordUtils #18587 Definition encryption (
PasswordUtils, no datasource CRUD) - [DSIP-105][Feature][Worker] Mask sensitive parameter values in task logs #18588 Worker log masking
#18585 closes #18586 only (
Fixes #18586,Refs #17937). Parent stays open until all three subtasks are done.
Search before asking
Subtasks
Each subtask is one issue and one PR (same pattern as #6407). Design stays on this parent.
Property.sensitive, mask******on read, keep-original on write — [DSIP-105][Feature][API] Add Property.sensitive and mask values in API/UI #18586 / PR [DSIP-105][API][UI] Add Property.sensitive with API/UI masking #18585PasswordUtils(no datasource CRUD change) — [DSIP-105][Feature][API] Encrypt sensitive definition params with PasswordUtils #18587Motivation
Currently, DolphinScheduler variables do not distinguish between sensitive and non-sensitive information. When users store secrets such as passwords or API keys as workflow global parameters or task local parameters, these values may be exposed in plaintext via:
global_params/task_paramsvalue fields)This change adds a variable-level sensitive flag on the existing
Propertymodel: mask values in API/UI, and apply datasource-level at-rest protection for sensitive definition values before persist (reusePasswordUtils).Delivery is split into three subtasks (see Subtasks): #18586 API/UI masking, #18587 definition encryption, #18588 Worker log masking. Until #18588 lands, task logs may still print sensitive values.
Out of scope
ProjectParameter/t_ds_project_parameter)Property; not in this DSIP. UI/docs should state this is unsupported for nowglobal_paramsTask stdout log masking: not in #18586 / #18587; implemented in #18588 (still part of this DSIP delivery — see Subtasks).
Goals (acceptance)
#18586 — API/UI masking
sensitive=truenever return real values in any external API/UI response.******keeps the stored value (keep-original). Empty string is a real empty value.#18587 — Definition encryption
datasource.encryption.enable=true, sensitive definition values are not stored as plaintext.#18588 — Worker log masking
Explicitly not claimed
Delivery (three subtasks)
Property.sensitive; API/UI masking; save merge (keep-original******only); start-time merge; UI checkbox; unit testsPasswordUtils(no datasource CRUD); no double-encrypt; Copy keeps ciphertext as-isMerge order: #18586 first, then #18587 and #18588 (both can follow #18586 independently).
Design Details
1. Add
sensitiveboolean toPropertyDo not introduce a new data type (e.g.
SENSITIVE_VARCHAR). Only add a field on existingProperty:false(backward compatible).localParams.2. Placeholder semantics
Reuse existing constant
Constants.XXXXXX("******").************or empty string → “unchanged; keep DB value”******as a real password (same convention as datasource)UI: use a checkbox for
sensitive; do not rely ontype=passwordalone. Always echo******; on edit, replace the whole value; if unchanged, submit******.3. Storage layers and threat model
sensitive=truevalues are ciphertext when encryption is enabledglobal_params, task execution context)******Why runtime plaintext: curing, complement, rerun, and context dispatch already assume readable values; encrypting instance params would scatter decrypt across the execution path. This DSIP covers the main goals with definition encryption + API masking (+ PR2 log masking).
4. Encrypt / decrypt (definition write path only, PR1)
PasswordUtils.encodePassword/decodePassword.datasource.encryption.enable(defaultfalse).Encode only when all of the following hold:
sensitive == true******, not empty)5. Write path (forbid double encryption, PR1)
prop.propwith only a keep-original marker is invalid (nothing to merge → reject; require plaintext).sensitiveflag transitions:false → true, new plaintext submittedfalse → true, keep-original submitted on updatesensitive=truetrue → falsesensitive=falsetrue → true, keep-originalSuggested hooks: before workflow definition save for global params; before task definition save for local params. Exact class names are implementation details; behavior above is normative.
6. Read path (copies only; do not mutate shared entities, PR1)
Shared helpers (names illustrative):
maskSensitiveProperties(...)→ deep copy then mask, for API responsesdecryptSensitiveProperties(...)→ deep copy then decrypt, for internal execution/mergemergeKeepOriginal(submitted, existingFromDb)→ merge keep-original only; does not encryptRules:
******on a sharedWorkflowDefinition/TaskDefinitionentity that is later used for execution.Masking must cover every API that returns
Property/globalParams/localParams/ DagData (including list/paging/detail/variables). Do not assumegenDagDataalone is sufficient.Order of operations:
7. Start & Command (PR1)
global_params(runtime materialization).prepareParamsMapuses instance/context plaintext; do not apply definition-style encode on instance fields.Start UI: show
******for sensitive items; submit keep-original if unchanged; allow override with a new value.8. OUT parameters (PR1 API masking; PR2 for logs)
sensitive=trueis allowed on OUT params.9. Copy (Export / Import not considered)
batchCopyWorkflowDefinition)sensitive); no UI round-trip; no double encryption (PR1)10. External surfaces that must be masked (PR1 principles)
Any response containing global/local
Propertymust be masked, including but not limited to:viewVariables(definition / instance)queryWorkflowDefinitionByCode/getWorkflowDefinition/ list / paging / byNamequeryWorkflowInstanceByIdand other APIs returning dag/paramsgetTaskDefinition/queryTaskDefinitionDetailSave APIs: merge keep-original first, then encrypt per §5, then persist.
11. UI (PR1)
sensitivecheckbox on global params and task local params forms.******; submit******when unchanged.12. Task stdout log masking (PR2)
Goal: task logs must not contain plaintext values of this task’s sensitive parameters.
Design points:
sensitive=trueentries in this task’sprepareParamsMap(or equivalent context).******(may extend existingSensitiveDataConverterwith per-task dynamic pattern registration).13. Risks & mitigations
******used as a real passwordsensitivefalsedecodePasswordreturns original when undecodableCompatibility, Deprecation, and Migration Plan
sensitivedefaults tofalse; existing workflows unchanged.datasource.encryption.enable=false, do not encrypt; old plaintext remains readable.Test Plan
PR1
Property.sensitiveserialization; merge keep-original without double encrypt;false↔truetransitions; mask/decrypt deep copies do not mutate originals; encryption enable true/false.******→ update only non-sensitive fields then run task still sees real secret → withenable=true, definition DB value is not plaintext → Copy still runs correctly.PR2
******; cleanup still works after failure/kill.Separate / Follow-up (not in these three subtasks)
global_paramsdatasource.encryptionDesign decisions (summary)
false→true: create rejects******; update allow keep-original (merge existing plaintext, then encode)******or empty string; never re-encode keep-originalCode of Conduct