Is GFormTasker safe?
GFormTasker redirects Google Forms submissions to gformtasker.xyz with answers and form metadata.
When you submit a Google Form, GFormTasker adds hidden fields containing the original form URL, parsed question metadata, and extension version. It then changes the form action to gformtasker.xyz/submit, causing the browser to send the existing answers plus the added metadata to that endpoint instead of the original Google Forms action.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Google Forms submissions redirect to gformtasker.xyz
On Google Forms pages, the extension changes the form's submission target to gformtasker.xyz/submit when submit is clicked, adding formUrl, formData, and gftVersion; formData is built from question metadata and saved per-question settings.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You submit a Google Form while the extension is active.
The extension changes where the form is posted and adds extra fields before submission.
The added fields include the original form URL, parsed question data, and the extension version.
- Your form answersentry.123456789=alice@example.com (illustrative)
The answers already present in the Google Form are forwarded with the redirected form submission.
- Original form URLhttps://docs.google.com/forms/d/e/1FAIpQLSeA1b2C3d4E5f6G7h8I9J0K/formResponse (illustrative)
This identifies which Google Form was being submitted.
- Question titlesWork email address (illustrative)
The extension includes labels parsed from the form page, which can reveal the subject of each answer.
- Choice valuesEngineering, Sales, Finance (illustrative)
For multiple-choice questions, the submitted metadata can include the available answer choices.
- Randomization settings[123456789,"Department",true,["Engineering","Sales"],[50,50],"",2,["Engineering","Sales"]] (illustrative)
The request can include whether the extension should randomize choices and the per-choice probability values.
- Extension version0.3.5
This lets the receiving service know which installed extension build sent the form.
The shipped content script builds metadata and redirects the form
Readable equivalent of the option/value extraction helper
content/content.jsfunction extractOptionValues(questionEntry, formMetadata) { const configuredValues = questionEntry[4]; const fieldType = formMetadata[3]; const gridValues = formMetadata[8]; if (fieldType !== 7) { if (configuredValues == null) return []; return Array.isArray(configuredValues) ? configuredValues : [configuredValues]; } const rowValues = Array.isArray(configuredValues) && configuredValues.length > 0 ? configuredValues : []; const columnValues = Array.isArray(gridValues) && gridValues.length > 0 ? gridValues : []; if (rowValues.length && columnValues.length) return [...rowValues, ...columnValues]; return columnValues.length ? columnValues : rowValues;}Readable equivalent of the submit-button click handler
content/content.jsconst submitButton = findGoogleFormsSubmitButton();submitButton && submitButton.addEventListener('click', async (event) => { if (document.querySelector('.RHiWt')) { event.preventDefault(); return; } const formUrlInput = document.createElement('input'); formUrlInput.setAttribute('type', 'hidden'); formUrlInput.setAttribute('name', 'formUrl'); formUrlInput.setAttribute('value', form.action); form.appendChild(formUrlInput); document.querySelector('#g-form-tasker-submissionCount').setAttribute('name', 'submissionCount'); document.querySelector('#g-form-tasker-intervalMin').setAttribute('name', 'submissionIntervalMin'); document.querySelector('#g-form-tasker-intervalMax').setAttribute('name', 'submissionIntervalMax'); const entries = {}; const questionTitles = {}; const randomization = {}; const fieldTypes = {}; const parsedValues = {}; for (let index = 0; index < sessionStorage.length; index++) { const key = sessionStorage.key(index); if (key && key.startsWith('entry.')) { const value = sessionStorage.getItem(key); if (value) entries[key] = JSON.parse(value); } if (key && key.startsWith('q-entry.')) { const value = sessionStorage.getItem(key); if (value) questionTitles[key] = value; } if (key && key.startsWith('r-entry.')) { const value = sessionStorage.getItem(key); if (value) randomization[key] = value === 'true'; } if (key && key.startsWith('t-entry.')) { const value = sessionStorage.getItem(key); if (value) fieldTypes[key] = parseInt(value); } if (key && key.startsWith('v-entry.')) { const value = sessionStorage.getItem(key); if (value) { try { parsedValues[key] = JSON.parse(value); } catch (error) { parsedValues[key] = []; } } } } const probabilities = {}; for (let index = 0; index < sessionStorage.length; index++) { const key = sessionStorage.key(index); if (key && key.startsWith('prob.')) { const [, entryId, choiceIndex] = key.split('.'); let normalizedEntryId = entryId; if (entryId.includes('_row')) normalizedEntryId = entryId.replace('_row', ''); probabilities[normalizedEntryId] ||= {}; probabilities[normalizedEntryId][choiceIndex] = sessionStorage.getItem(key); } } const otherText = {}; for (let index = 0; index < sessionStorage.length; index++) { const key = sessionStorage.key(index); if (key && key.startsWith('other-text.')) { const entryId = key.replace('other-text.', ''); const value = sessionStorage.getItem(key); if (value) otherText[entryId] = value; } } const formData = []; for (const key in entries) { if (!Object.prototype.hasOwnProperty.call(entries, key)) continue; const entryIdText = key.replace('entry.', ''); const entryId = parseInt(entryIdText); const randomizationKey = `r-entry.${entryIdText}`; const fieldTypeKey = `t-entry.${entryIdText}`; const parsedValuesKey = `v-entry.${entryIdText}`; const questionTitle = questionTitles[`q-entry.${entryIdText}`] || ''; const randomizeEnabled = randomization[randomizationKey] || false; const fieldType = fieldTypes[fieldTypeKey] || 0; const values = parsedValues[parsedValuesKey] || []; const options = entries[key] || []; const probabilityByChoice = probabilities[entryIdText] || {}; const otherResponse = otherText[entryIdText] || ''; const probabilityList = []; for (let index = 0; index < options.length; index++) { const probability = probabilityByChoice[index.toString()] || '0'; probabilityList.push(parseInt(probability)); } formData.push([entryId, questionTitle, randomizeEnabled, options, probabilityList, otherResponse, fieldType, values]); } const formDataInput = document.createElement('input'); formDataInput.setAttribute('type', 'hidden'); formDataInput.setAttribute('name', 'formData'); formDataInput.setAttribute('value', JSON.stringify(formData)); form.appendChild(formDataInput); const versionInput = document.createElement('input'); versionInput.setAttribute('type', 'hidden'); versionInput.setAttribute('name', 'gftVersion'); versionInput.setAttribute('value', extensionVersion); form.appendChild(versionInput); document.querySelectorAll('.g-form-tasker-switch').forEach((element) => element.remove()); form.action = 'https://gformtasker.xyz/submit'; form.submit();});- gformtasker.xyz
Receives the redirected Google Forms submission at /submit, including original form fields plus formUrl, formData, and gftVersion added by the extension.
Where it sends data
Destinations our analysis observed GFormTasker contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- gformtasker.xyz
GFormTasker sends data to gformtasker.xyz. No other extension we have analysed sends data here.