From e61edb2073a36899cfe69ea6735fa50ff6d610f3 Mon Sep 17 00:00:00 2001 From: Eleazar Resendez Date: Fri, 26 Sep 2025 09:05:18 -0600 Subject: [PATCH 1/5] Add handlerSecurityProperty to button event properties --- .../button/handler-event-property.js | 24 ++++++++++++++----- src/form-builder-controls.js | 4 +++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/components/inspector/button/handler-event-property.js b/src/components/inspector/button/handler-event-property.js index 17a4885cc..baa732e30 100644 --- a/src/components/inspector/button/handler-event-property.js +++ b/src/components/inspector/button/handler-event-property.js @@ -1,9 +1,21 @@ export const handlerEventProperty = { - type: 'CodeEditor', - field: 'handler', + type: "CodeEditor", + field: "handler", config: { - label: 'Click Handler', - helper: 'The handler is a JavaScript function that will be executed when the button is clicked.', - dataFeature: 'i1177', - }, + label: "Click Handler", + helper: + "The handler is a JavaScript function that will be executed when the button is clicked.", + dataFeature: "i1177" + } +}; + +export const handlerSecurityProperty = { + type: "FormCheckbox", + field: "handlerSecurityEnabled", + config: { + label: "Secure Handler Execution", + toggle: true, + helper: + "When enabled, the handler runs inside a sandboxed worker. Disable to allow full JavaScript access." + } }; diff --git a/src/form-builder-controls.js b/src/form-builder-controls.js index 0dd899444..5bf4f9151 100755 --- a/src/form-builder-controls.js +++ b/src/form-builder-controls.js @@ -14,7 +14,7 @@ import FormListTable from './components/renderer/form-list-table'; import FormAnalyticsChart from "./components/renderer/form-analytics-chart"; import FormCollectionRecordControl from './components/renderer/form-collection-record-control.vue'; import FormCollectionViewControl from './components/renderer/form-collection-view-control.vue'; -import { handlerEventProperty } from './components/inspector/button/handler-event-property'; +import { handlerEventProperty, handlerSecurityProperty } from './components/inspector/button/handler-event-property'; import {DataTypeProperty, DataFormatProperty, DataTypeDateTimeProperty} from './VariableDataTypeProperties'; import { FormInput, @@ -763,6 +763,7 @@ export default [ fieldValue: null, tooltip: {}, handler: '', + handlerSecurityEnabled: true, }, inspector: [ { @@ -786,6 +787,7 @@ export default [ }, buttonTypeEvent, handlerEventProperty, + handlerSecurityProperty, LoadingSubmitButtonProperty, LabelSubmitButtonProperty, tooltipProperty, From c929a876795dcb19f299a5622a9aa14b27427ea4 Mon Sep 17 00:00:00 2001 From: Eleazar Resendez Date: Fri, 26 Sep 2025 09:07:38 -0600 Subject: [PATCH 2/5] Apply eslint format for FormTextArea and FormInput; add handler security property for FormButton --- src/mixins/extensions/LoadFieldComponents.js | 29 ++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/mixins/extensions/LoadFieldComponents.js b/src/mixins/extensions/LoadFieldComponents.js index 52632aecb..2166fd91b 100644 --- a/src/mixins/extensions/LoadFieldComponents.js +++ b/src/mixins/extensions/LoadFieldComponents.js @@ -55,11 +55,19 @@ export default { componentName === "FormTextArea" || componentName === "FormInput" ) { - properties["@input"] = `updateScreenData('${safeDotName}', '${element.config.name}')`; - properties["@change"] = `updateScreenDataNow('${safeDotName}', '${element.config.name}')`; + properties[ + "@input" + ] = `updateScreenData('${safeDotName}', '${element.config.name}')`; + properties[ + "@change" + ] = `updateScreenDataNow('${safeDotName}', '${element.config.name}')`; } else { - properties["@input"] = `updateScreenDataNow('${safeDotName}', '${element.config.name}')`; - properties["@change"] = `updateScreenDataNow('${safeDotName}', '${element.config.name}')`; + properties[ + "@input" + ] = `updateScreenDataNow('${safeDotName}', '${element.config.name}')`; + properties[ + "@change" + ] = `updateScreenDataNow('${safeDotName}', '${element.config.name}')`; } // Process the FormSelectList@reset event properties[ @@ -100,12 +108,17 @@ export default { properties[":readonly"] = isCalcProp || element.config.readonly; properties[":disabled"] = isCalcProp || element.config.disabled; // Events - properties['@submit'] = 'submitForm'; + properties["@submit"] = "submitForm"; // Add handler event if Button - if(componentName === 'FormButton') { - properties[':handler'] = this.byRef(element.config.handler); + if (componentName === "FormButton") { + properties[":handler"] = this.byRef(element.config.handler); + const handlerSecurity = + element.config.handlerSecurityEnabled === undefined + ? true + : element.config.handlerSecurityEnabled; + properties[":handler-security-enabled"] = this.byRef(handlerSecurity); } - }, + } }, mounted() { this.extensions.push({ From 90bfc32b73d0dbb8adeff3be1d9cfa97c185de13 Mon Sep 17 00:00:00 2001 From: Eleazar Resendez Date: Fri, 26 Sep 2025 09:15:47 -0600 Subject: [PATCH 3/5] Enhance handler execution with security checks and improve code readability --- src/components/renderer/form-button.vue | 207 +++++++++++++++++------- 1 file changed, 146 insertions(+), 61 deletions(-) diff --git a/src/components/renderer/form-button.vue b/src/components/renderer/form-button.vue index 9d09e717c..ca8a6a86a 100644 --- a/src/components/renderer/form-button.vue +++ b/src/components/renderer/form-button.vue @@ -2,12 +2,12 @@
+ + + From a8cb2dde56d1f8cfaf99322ef75d4b33aa3e32d1 Mon Sep 17 00:00:00 2001 From: Eleazar Resendez Date: Thu, 9 Oct 2025 13:42:30 -0600 Subject: [PATCH 4/5] Add secureHandlerToggleVisible to screen properties and update related logic --- src/bootstrap.js | 8 +++++++- src/components/vue-form-builder.vue | 14 ++++++++++++++ src/main.js | 9 ++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/bootstrap.js b/src/bootstrap.js index f011a25a1..eeacabc9c 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -50,6 +50,9 @@ const cacheEnabled = document.head.querySelector( const cacheTimeout = document.head.querySelector( "meta[name='screen-cache-timeout']" ); +const secureHandlerToggleVisibleMeta = document.head.querySelector( + "meta[name='screen-secure-handler-toggle-visible']" +); // Get the current protocol, hostname, and port const { protocol, hostname, port } = window.location; @@ -73,7 +76,10 @@ window.ProcessMaker = { alert(message, variant) {}, screen: { cacheEnabled: cacheEnabled ? cacheEnabled.content === "true" : false, - cacheTimeout: cacheTimeout ? Number(cacheTimeout.content) : 0 + cacheTimeout: cacheTimeout ? Number(cacheTimeout.content) : 0, + secureHandlerToggleVisible: !!Number( + secureHandlerToggleVisibleMeta?.content + ) } }; window.Echo = { diff --git a/src/components/vue-form-builder.vue b/src/components/vue-form-builder.vue index b45adf4c9..1f616fb13 100644 --- a/src/components/vue-form-builder.vue +++ b/src/components/vue-form-builder.vue @@ -810,6 +810,13 @@ export default { }, showToolbar() { return this.screenType === formTypes.form; + }, + secureHandlerToggleVisible() { + return _.get( + globalObject, + "ProcessMaker.screen.secureHandlerToggleVisible", + false + ); } }, watch: { @@ -1220,6 +1227,13 @@ export default { (control) => control.component === this.inspection.component ) || { inspector: [] }; return control.inspector.filter((input) => { + if ( + !this.secureHandlerToggleVisible && + typeof input === "object" && + input.field === "handlerSecurityEnabled" + ) { + return false; + } if (accordionFields.includes(input.field)) { return true; } diff --git a/src/main.js b/src/main.js index 0d2c5344d..18ba1e940 100644 --- a/src/main.js +++ b/src/main.js @@ -152,6 +152,10 @@ const cacheEnabled = document.head.querySelector( const cacheTimeout = document.head.querySelector( "meta[name='screen-cache-timeout']" ); +const secureHandlerToggleVisibleMeta = document.head.querySelector( + "meta[name='screen-secure-handler-toggle-visible']" +); + // Get the current protocol, hostname, and port const { protocol, hostname, port } = window.location; window.ProcessMaker = { @@ -302,7 +306,10 @@ window.ProcessMaker = { }, screen: { cacheEnabled: cacheEnabled ? cacheEnabled.content === "true" : false, - cacheTimeout: cacheTimeout ? Number(cacheTimeout.content) : 0 + cacheTimeout: cacheTimeout ? Number(cacheTimeout.content) : 0, + secureHandlerToggleVisible: !!Number( + secureHandlerToggleVisibleMeta?.content + ) } }; window.Echo = { From 0ce45765931e11a6282670f27b9aba0e9a8f090b Mon Sep 17 00:00:00 2001 From: Eleazar Resendez Date: Thu, 9 Oct 2025 14:19:31 -0600 Subject: [PATCH 5/5] Add safety comment for dynamic function execution in handler --- src/components/renderer/form-button.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/renderer/form-button.vue b/src/components/renderer/form-button.vue index ca8a6a86a..8f1c06fc0 100644 --- a/src/components/renderer/form-button.vue +++ b/src/components/renderer/form-button.vue @@ -202,8 +202,8 @@ export default { const functionBody = `return (async () => { ${this.handler} })();`; try { - // eslint-disable-next-line no-new-func - const userFunc = new Function("data", "parent", "toRaw", functionBody); + // eslint-disable-next-line no-new-func, max-len + const userFunc = new Function("data", "parent", "toRaw", functionBody); // NOSONAR. This dynamic code execution is safe because it only occurs when the user has explicitly disabled the security worker. const result = userFunc.apply(context, [ dataReference, parentReference,