[DSIP-105][API][UI] Add Property.sensitive with API/UI masking - #18585
[DSIP-105][API][UI] Add Property.sensitive with API/UI masking#18585det101 wants to merge 23 commits into
Conversation
UI / API verification screenshots (PR1)1) Reload existing sensitive workflow — global params maskedSave modal shows:
2) Create / save modal — Sensitive checkbox present3) API mask proof (same workflow) |
Verification screenshots (hosted on fork evidence branch)Reload existing sensitive workflow — value masked + Sensitive checkedSave modal — Sensitive checkbox presentAPI mask proof |
2793cc3 to
b1d4895
Compare
…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>
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
left a comment
There was a problem hiding this comment.
I found two blocking issues:
- [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}/versionsreturnsTaskDefinitionLog.taskParams.GET /projects/{projectCode}/workflow-definition/{code}/versionsreturnsWorkflowDefinitionLog.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.
- [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.
… version Mask sensitive values on task/workflow definition version list responses, and restore ****** from the matching TaskDefinitionLog code+version when updating an old workflow instance. Co-authored-by: Cursor <cursoragent@cursor.com>
Avoid N+1 on instance secret merge via queryByTaskDefinitions, and mask version-list responses on JSON copies so mapper entities stay unmasked. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@SbloodyS Thanks for the review. Both P1s are addressed, with a few extra hardening changes.
|
Co-authored-by: Cursor <cursoragent@cursor.com>
SbloodyS
left a comment
There was a problem hiding this comment.
Disabling sensitive can silently overwrite the real value with ******
There is still a data-loss path when an existing sensitive parameter is changed to non-sensitive:
- The API returns the parameter as
value = "******", sensitive = true. - The user unchecks Sensitive without editing the value.
- The UI submits
value = "******", sensitive = false. findInvalidSensitivePlaceholderProp()skips the parameter because it is no longer sensitive.mergeSensitiveValuePlaceholder()also skips it for the same reason.- The update therefore persists the literal
******and permanently overwrites the original value.
This affects both workflow global parameters and task localParams. Besides losing the secret, subsequent task executions will receive ****** instead of the configured credential.
Please reject ****** when an existing sensitive parameter is being changed to non-sensitive, requiring the user to enter an explicit replacement value. This validation must be enforced by the backend rather than only by the UI. Restoring the original value while setting sensitive = false would not be safe either, because the next read would expose that value as non-sensitive.
Co-authored-by: Cursor <cursoragent@cursor.com>
- 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>
|
@SbloodyS Thank you for the careful review — this is a real data-loss path, and your analysis is correct. Unchecking Sensitive while leaving The backend now rejects this for both workflow global params and task On the UI we chose not to auto-clear 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 Thanks again for the detailed write-up. |
SbloodyS
left a comment
There was a problem hiding this comment.
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 aftermergeGlobalParams()has restored the real value.WorkflowInstanceServiceImpl#updateWorkflowInstance()also returns aWorkflowDefinitioncontaining 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:
- Creating a workflow with a sensitive global parameter.
- Updating a workflow with a new sensitive value.
- 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.
- 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>
|
@SbloodyS Thank you — you are right that create/update responses were still serializing the merged plaintext We did not only wrap those three service returns. Query-time masking was too easy to miss on write endpoints (and on any new
Acceptance is unchanged: API/UI still show |
Keep-original ****** must restore the relation-pinned TaskDefinitionLog, not the main-table latest task. Co-authored-by: Cursor <cursoragent@cursor.com>


Summary
Implements #18586 (subtask of DSIP-105 / #17937): add
Property.sensitiveand mask sensitive values as******on API/UI, with keep-original merge on write/start.In scope (#18586)
Property.sensitive(defaultfalse)SensitivePropertyResponseAdvicecopy-then-masksResult.datathat carries workflow/task/instanceProperty(query, version lists, and create/update replies). Persistence / in-process stay plaintext.******means keep original; empty string is a real empty value******localParams******startParams so they do not overwrite definition secretsOut of scope (follow-up subtasks)
PasswordUtilsRelated
Test plan
PropertySensitiveUtilsTest,SensitivePropertyUtilsTest******(globalParamList / localParams / view-variables)Resultis masked without mutating the persisted object******after reloadVerification screenshots
UI — reload workflow: sensitive global param masked as
******UI — save modal: Sensitive checkbox available
API — same workflow masked on query / view-variables
Screenshot branch (fork only, not part of review diff):
det101:pr1-17937-verification-screenshots