Skip to content

[DSIP-105][API][UI] Add Property.sensitive with API/UI masking - #18585

Draft
det101 wants to merge 36 commits into
apache:devfrom
det101:feature-17937-sensitive-property-pr1
Draft

[DSIP-105][API][UI] Add Property.sensitive with API/UI masking#18585
det101 wants to merge 36 commits into
apache:devfrom
det101:feature-17937-sensitive-property-pr1

Conversation

@det101

@det101 det101 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements #18586 (subtask of DSIP-105 / #17937): add Property.sensitive and mask sensitive values as ****** on API/UI, with keep-original merge on write/start.

In scope (#18586)

  • Property.sensitive (default false)
  • HTTP outbound mask: SensitivePropertyResponseAdvice copy-then-masks Result.data that carries workflow/task/instance Property (query, version lists, and create/update replies). Persistence / in-process stay plaintext.
  • Write-path merge: only ****** means keep original; empty string is a real empty value
  • Reject creating/enabling/disabling sensitive with placeholder-only ******
  • UI: Sensitive checkbox on global params and task localParams
  • Start/command path skips ****** startParams so they do not overwrite definition secrets

Out of scope (follow-up subtasks)

Related

Test plan

  • Unit: PropertySensitiveUtilsTest, SensitivePropertyUtilsTest
  • Local standalone: create workflow with sensitive global + local params
  • API query returns masked ****** (globalParamList / localParams / view-variables)
  • Create/update Result is masked without mutating the persisted object
  • UI save modal shows Sensitive checked and value ****** after reload
  • CI

Verification screenshots

UI — reload workflow: sensitive global param masked as ******

masked-global-params

UI — save modal: Sensitive checkbox available

sensitive-checkbox

API — same workflow masked on query / view-variables

=== Workflow Definition Query (masked) ===
globalParamList: [{"prop": "db_pwd", "direct": "IN", "type": "VARCHAR", "value": "******", "sensitive": true}]
task localParams: [{"prop": "token", "direct": "IN", "type": "VARCHAR", "value": "******", "sensitive": true}]

=== view-variables (masked) ===
{
  "globalParams": [
    {
      "prop": "db_pwd",
      "direct": "IN",
      "type": "VARCHAR",
      "value": "******",
      "sensitive": true
    }
  ],
  "localParams": {
    "shell_node_1": {
      "localParamsList": [
        {
          "prop": "token",
          "direct": "IN",
          "type": "VARCHAR",
          "value": "******",
          "sensitive": true
        }
      ],
      "taskType": "SHELL"
    }
  }
}

Screenshot branch (fork only, not part of review diff): det101:pr1-17937-verification-screenshots

@det101

det101 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

UI / API verification screenshots (PR1)

1) Reload existing sensitive workflow — global params masked

Save modal shows:

  • db_pwd value = ******
  • Sensitive checkbox checked

masked-global-params

2) Create / save modal — Sensitive checkbox present

sensitive-checkbox

3) API mask proof (same workflow)

=== Workflow Definition Query (masked) ===
globalParamList: [{"prop": "db_pwd", "direct": "IN", "type": "VARCHAR", "value": "******", "sensitive": true}]
task localParams: [{"prop": "token", "direct": "IN", "type": "VARCHAR", "value": "******", "sensitive": true}]

=== view-variables (masked) ===
{
  "globalParams": [
    {
      "prop": "db_pwd",
      "direct": "IN",
      "type": "VARCHAR",
      "value": "******",
      "sensitive": true
    }
  ],
  "localParams": {
    "shell_node_1": {
      "localParamsList": [
        {
          "prop": "token",
          "direct": "IN",
          "type": "VARCHAR",
          "value": "******",
          "sensitive": true
        }
      ],
      "taskType": "SHELL"
    }
  }
}

@det101

det101 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Verification screenshots (hosted on fork evidence branch)

Reload existing sensitive workflow — value masked + Sensitive checked

masked

Save modal — Sensitive checkbox present

checkbox

API mask proof

=== Workflow Definition Query (masked) ===
globalParamList: [{"prop": "db_pwd", "direct": "IN", "type": "VARCHAR", "value": "******", "sensitive": true}]
task localParams: [{"prop": "token", "direct": "IN", "type": "VARCHAR", "value": "******", "sensitive": true}]

=== view-variables (masked) ===
{
  "globalParams": [
    {
      "prop": "db_pwd",
      "direct": "IN",
      "type": "VARCHAR",
      "value": "******",
      "sensitive": true
    }
  ],
  "localParams": {
    "shell_node_1": {
      "localParamsList": [
        {
          "prop": "token",
          "direct": "IN",
          "type": "VARCHAR",
          "value": "******",
          "sensitive": true
        }
      ],
      "taskType": "SHELL"
    }
  }
}

@det101 det101 changed the title [Feature-17937][API][UI] Add Property.sensitive with API/UI masking (PR1) [DSIP-105][API][UI] Add Property.sensitive with API/UI masking (PR1) Aug 24, 2026
@det101
det101 force-pushed the feature-17937-sensitive-property-pr1 branch from 2793cc3 to b1d4895 Compare August 24, 2026 08:46
…PR1)

Introduce sensitive flag on Property, deep-copy mask on read paths, and
****** keep-original merge on write/start. UI adds Sensitive checkbox.
Encryption and worker log redaction are deferred to follow-up PRs.

Co-authored-by: Cursor <cursoragent@cursor.com>
…onflict

Reconcile with apache#18569 removal of obsolete update-with-upstream API while
keeping Property.sensitive masking on task definition read paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
@det101 det101 changed the title [DSIP-105][API][UI] Add Property.sensitive with API/UI masking (PR1) [Feature-18586][API][UI] Add Property.sensitive with API/UI masking Aug 25, 2026
det101 and others added 7 commits August 25, 2026 10:00
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep both SensitivePropertyUtils (PR masking) and WorkflowInstanceSummaryVO
(upstream list-query optimization) imports in WorkflowInstanceServiceImpl.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@SbloodyS SbloodyS added feature new feature DSIP labels Aug 28, 2026
@SbloodyS SbloodyS added this to the 3.5.0 milestone Aug 28, 2026
@SbloodyS SbloodyS changed the title [Feature-18586][API][UI] Add Property.sensitive with API/UI masking [DSIP-105][API][UI] Add Property.sensitive with API/UI masking Aug 28, 2026

@SbloodyS SbloodyS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found two blocking issues:

  1. [P1] Sensitive values are still exposed by the definition-version APIs

The new masking is applied to the current workflow/task definition query paths, but the version-list endpoints still return the persisted entities directly:

  • GET /projects/{projectCode}/task-definition/{code}/versions returns TaskDefinitionLog.taskParams.
  • GET /projects/{projectCode}/workflow-definition/{code}/versions returns WorkflowDefinitionLog.globalParams.

Both SQL projections include these fields, and the service methods put the records into the response without masking them. Therefore, a parameter with sensitive=true can still be read in plaintext by querying its version history, which violates the acceptance criterion that sensitive values must never be returned through external APIs.

Please either mask the returned version entities using the same deep-copy strategy or use dedicated summary DTOs that omit these fields. Regression tests should cover both version endpoints.

  1. [P1] Updating an old workflow instance can restore the secret from the wrong task version

WorkflowInstanceServiceImpl.mergeSensitiveLocalParams() resolves ****** using taskDefinitionDao.queryByCodes(), which loads the current task definitions. However, the workflow-instance detail shown to the user may have been generated from an older workflow/task version.

If the task's sensitive value changed after that instance was created, editing and saving the old instance will silently replace ****** with the latest task definition's value instead of preserving the value belonging to the displayed instance version.

Please resolve each original value using the matching task definition code and version associated with the instance/submitted task definition, rather than querying only the current definition by code. A regression test should cover updating an old instance after its task secret has changed in a newer version.

- Reject true → false with ****** so the mask cannot overwrite the secret
- Skip startParam ****** overlay, including map-format that loses sensitive
- Leave ****** in the UI on uncheck so save fails instead of wiping with empty

Co-authored-by: Cursor <cursoragent@cursor.com>
@det101

det101 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@SbloodyS Thank you for the careful review — this is a real data-loss path, and your analysis is correct.

Unchecking Sensitive while leaving ****** must not persist the mask. Restoring the original value with sensitive = false would also be unsafe, as you noted, because the next read would return the secret as non-sensitive. We followed that guidance.

The backend now rejects this for both workflow global params and task localParams. findInvalidSensitivePlaceholderProp treats true → false with value ****** as invalid and returns REQUEST_PARAMS_NOT_VALID_ERROR, asking the user to enter an explicit replacement. Toggle sequences are judged only by the final (sensitive, value) pair, so uncheck → check again → save with true + ****** still keeps the original secret.

On the UI we chose not to auto-clear ****** when the checkbox is unchecked. Clearing it would make it too easy to save an empty string and wipe the secret by accident. Leaving the mask in the field means save fails until the user types a real new value, or checks Sensitive again (keep-original). Empty string remains a real empty value; only ****** means keep-original.

Unit tests cover the reject path and the toggle-back keep-original path.

As a related hardening, start/backfill also skips a startParam whose value is ****** instead of overlaying it onto workflow globals. That includes map-format {"pwd":"******"}, which loses sensitive=true. Please let us know if you would prefer a different approach here.

Thanks again for the detailed write-up.

@SbloodyS SbloodyS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sensitive global parameters are exposed by create/update responses

The read/query endpoints now mask sensitive parameters, but the write endpoints still return the in-memory WorkflowDefinition containing the merged plaintext globalParams:

  • WorkflowDefinitionServiceImpl#createWorkflowDefinition() returns the object created from the submitted plaintext parameters.
  • WorkflowDefinitionServiceImpl#updateWorkflowDefinition() returns the object after mergeGlobalParams() has restored the real value.
  • WorkflowInstanceServiceImpl#updateWorkflowInstance() also returns a WorkflowDefinition containing the merged plaintext value.

The corresponding controllers serialize these objects directly in their Result<WorkflowDefinition> responses. Therefore, creating a sensitive global parameter or updating it with a new value returns the real value through an external API, which violates the acceptance criterion that sensitive parameters must only be returned as ******.

Please return a masked deep copy from these external write paths, for example by applying copyAndMaskWorkflowDefinition() after persistence. Do not mask the object before it has finished being persisted or reuse a masked object for execution.

Please add regression coverage for:

  1. Creating a workflow with a sensitive global parameter.
  2. Updating a workflow with a new sensitive value.
  3. Updating a workflow instance containing a sensitive global parameter.

Each response should contain ******, while the persisted/internal value must remain unchanged and usable for execution.

det101 and others added 2 commits September 1, 2026 15:30
- Copy-then-mask Result.data in ResponseBodyAdvice so create/update replies are not plaintext
- Remove per-query masking from definition/instance services; persistence stays plaintext

Co-authored-by: Cursor <cursoragent@cursor.com>
@det101

det101 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

@SbloodyS Thank you — you are right that create/update responses were still serializing the merged plaintext WorkflowDefinition.

We did not only wrap those three service returns. Query-time masking was too easy to miss on write endpoints (and on any new Result that carries Property), so masking now happens at the HTTP outbound boundary:

  • SensitivePropertyResponseAdvice copy-then-masks Result.data when it contains workflow/task/instance Property payloads. That includes createWorkflowDefinition, updateWorkflowDefinition, updateWorkflowInstance, version lists, and the existing query/view-variables paths.
  • Query/write services keep returning plaintext. Persistence and in-process objects are not mutated; only a JSON copy is masked before Jackson writes the body.
  • Unrelated Result payloads (login, cluster, user, …) are left unchanged.

Acceptance is unchanged: API/UI still show ******; the metadata DB is still plaintext in this PR (encryption remains #18587).

det101 and others added 4 commits September 2, 2026 09:46
Keep-original ****** must restore the relation-pinned TaskDefinitionLog, not the main-table latest task.

Co-authored-by: Cursor <cursoragent@cursor.com>
Cover create, definition update, and instance update: HTTP Result is ****** while the persisted WorkflowDefinition stays plaintext.

Co-authored-by: Cursor <cursoragent@cursor.com>
@det101

det101 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

@SbloodyS Thank you — the create/update plaintext leak is addressed at the HTTP outbound boundary (SensitivePropertyResponseAdvice copy-then-masks Result.data after persistence). Service / persisted objects stay plaintext for execution.

We also added the three regressions you asked for:

  1. Creating a workflow with a sensitive global parameter
  2. Updating a workflow with a new sensitive value
  3. Updating a workflow instance that already has a sensitive global parameter

Each asserts the HTTP Result contains ****** while the object passed to saveWorkflowDefine remains the real value.

@SbloodyS SbloodyS modified the milestones: 3.4.3, 3.5.0 Sep 7, 2026
@det101
det101 requested a review from SbloodyS September 9, 2026 01:10
- Remove SensitivePropertyResponseAdvice and mask copies at Property HTTP exits
- Cover task-instance list/paging taskParams and varPool
- Add controller tests that fail if a create/update/query path skips masking

Co-authored-by: Cursor <cursoragent@cursor.com>
@det101

det101 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

@ruanwenjun Thanks — I dropped SensitivePropertyResponseAdvice and now mask copies explicitly in the controllers that return Property payloads (create/update/query/versions/view-variables, plus task-instance list/paging). Service and persistence stay plaintext. SensitivePropertyUtils is reduced to merge/validate plus typed copyAndMask* helpers.

I also walked the remaining controller exits that can return Property (globalParams / localParams / taskParams / varPool) and did not find another unmasked HTTP path. Please take another look in case I missed something.

det101 and others added 3 commits September 10, 2026 15:36
- Remove leftover copyAndMask checks from Service tests
- Masking stays covered by Controller tests

Co-authored-by: Cursor <cursoragent@cursor.com>
…t queries

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@det101
det101 marked this pull request as draft September 10, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend DSIP feature new feature test UI ui and front end related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DSIP-105][Feature][API] Add Property.sensitive and mask values in API/UI

3 participants