Is Automation 360 safe?
Automation 360 is high risk. When Automation 360's API recorder starts for a matching tab, it requests cookies for jeckycompay.lightning.force.com and inserts them as a Cookie header in the recorded node, sent to the native agent. The behavioral run skipped this path.…
Who publishes itAutomation Anywhere Inc - no other listings under this identity, 1 shared hostname
Automation Anywhere Inc - no other listings under this identity, 1 shared hostname
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 1 hostname
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Salesforce Cookies Added To API Recordings
When Automation 360's API recorder starts for a matching tab, it requests cookies for jeckycompay.lightning.force.com and inserts them as a Cookie header in the recorded node, sent to the native agent.
The behavioral run skipped this path.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You start API recording for a tab that matches the requested recording URL.
The extension adds cookies from a fixed Salesforce org to the recorded API node and sends that node to the native automation agent.
- Fixed Salesforce originhttps://jeckycompay.lightning.force.com/
This is the Salesforce site whose cookies are requested, even though the recorded request is selected through the API recorder workflow.
- Salesforce cookie headersid=00D5g00000AbCDe!AQEAQEJ2zV8gF9v3kZ7YpR1nM6bH4qT0;inst=APP_005 (illustrative)
This can identify an authenticated Salesforce browser session for that org when the browser has matching cookies.
- Recorded request metadatamethod=POST; url=https://jeckycompay.lightning.force.com/services/data/v59.0/query/ (illustrative)
The API recorder keeps the request ID, method, URL, content type, headers, and post data when present.
- Native message wrapperapiResponse.headers contains name=Cookie and the Salesforce cookie string
The node is wrapped as an API recorder response and sent to the local automation agent connection.
on start
You didYou start API recording for a selected web application tab.
The extension didThe extension searches open windows for a tab whose URL starts with the requested recording URL.
same workflow
You didA matching tab is found and activated.
The extension didThe extension attaches the network debugger and begins receiving request events.
per request
You didA matching network request is observed during recording.
The extension didThe extension requests cookies for the fixed Salesforce origin and appends them as a Cookie header.
after assembly
You didThe recorded node is assembled.
The extension didThe extension posts the API response object to the native automation agent port.
The recorder path that adds Salesforce cookies
API recorder setup
ExtensionRecorderApiAction.jsasync function registerApiRecorderEvent(context, request) { context.apiRecorder.jsonMessage = request; context.apiRecorder.rawMessage = request; var windows = undefined; try { windows = await retrieveAllWindows(); await onAllWindowCallBack(windows, context); } catch (e) { AALogger.warn('script', e); context.sendResponse(getErrorResponse(e.message)); return; }}async function onAllWindowCallBack(windows, context) { for (var windowIndex = 0; windowIndex < windows.length; windowIndex++) { for (var tabIndex = 0; tabIndex < windows[windowIndex].tabs.length; tabIndex++) { var tabAtIndex = windows[windowIndex].tabs[tabIndex]; if (tabAtIndex.url && tabAtIndex.url.indexOf(context.apiRecorder.jsonMessage.startRecording.url) == 0) { AALogger.log('ExtensionAPIAction', 'Url is matched.'); var tabs = await updateTab(tabAtIndex.id, { "active": true }); await onTabActive(tabs, context); return; } } } sendErrorResponse(createErrorMessage("Url does not match."), context);}async function onTabActive(tab, context) { AALogger.log('ExtensionAPIAction', 'tab is active.'); var tab = await getCurrentWindowActiveTab(tab.windowId); await queryCallBack(tab, context);}async function queryCallBack(tab, context) { try { if (tab == undefined) { AALogger.log('ExtensionAPIAction', 'No tab is active.'); var errorResponse = createErrorMessage("No active tab found."); sendErrorResponse(errorResponse, context) return; } context.apiRecorder.tab = tab; var tab = tab; AALogger.log('ExtensionAPIAction', "TabId: " + tab.id); var contextMessage = context.apiRecorder.jsonMessage; if (contextMessage.startRecording) { await attachDebuggerForAPIRecorder(tab, context); sendStartRecorderResponse(tab, context); } AALogger.log('ExtensionAPIAction', 'Response Sent to browser agent'); chrome.tabs.sendMessage(tab.id, context.apiRecorder.rawMessage, function (response) { registerUserActionEvent(context); }); AALogger.log('ExtensionAPIAction', 'message sent to content script'); } catch (e) { AALogger.log('ExtensionAPIAction', e.message); sendErrorResponse(createErrorMessage(e.message), context) }}async function attachDebuggerForAPIRecorder(requestFilter, context) { var tab = context.apiRecorder.tab; var debuggerRequest = { tabId: parseInt(tab.id, 10), debuggerAction: 'NetworkEnable' }; await sendCommandForNetworkDebugger(debuggerRequest, (result) => ({})); context.registerListenerDuringRequest(chrome.debugger.onEvent, onAPIRequestSent); context.apiRecorder.isStarted = true;}Network request handler
ExtensionRecorderApiAction.jsasync function onAPIRequestSent(context, source, method, params) { var defaultContentType = ["application/json", "application/x-www-form-urlencoded", "application/xml", "text/plain", "text/xml", "text/html", "multipart/form-data"]; var contentTypeMapper = { "application/x-www-form-urlencoded": "APPLICATION_FORM", "application/json": "APPLICATION_JSON", "application/xml": "APPLICATION_XML", "text/plain": "PLAIN_TEXT", "text/xml": "TEXT_XML", "text/html": "TEXT_HTML", "multipart/form-data": "MULTIPART_FORM" } var valueDictionary = []; if (method === "Network.requestWillBeSentExtraInfo") { if (params.headers) { var KEY_METHOD = ':method'; var KEY_SCHEMA = ':scheme'; var KEY_AUTHORTY = ':authority'; var KEY_PATH = ':path'; var KEY_CONTENT_TYPE = 'content-type'; var requestMethod = params.headers[KEY_METHOD]; if (requestMethod === "HEAD") return; var schema = params.headers[KEY_SCHEMA]; var authority = params.headers[KEY_AUTHORTY]; var path = params.headers[KEY_PATH]; if (!(schema || authority || path)) return; var requestUrl = schema + "://" + authority + path; if (requestUrl.indexOf(context.apiRecorder.jsonMessage.startRecording.url) === -1) return; var node = { "requestId": params.requestId, "method": requestMethod, "url": requestUrl, "authentication": "NoAuthentication" }; //decide the control type var contentType = params.headers[KEY_CONTENT_TYPE]; if (!contentType) contentType = params.headers['accept']; if (contentType) { var contentTypeSegment = contentType.split(";") var parameterName = ''; if (defaultContentType.indexOf(contentTypeSegment[0]) >= 0) { var contentType = { "type": contentTypeMapper[contentTypeSegment[0]] }; node.contentType = contentType; parameterName = contentTypeSegment[0]; } else { var contentType = { "type": "CUSTOM", "value": contentTypeSegment[0] }; node.contentType = contentType; parameterName = "custom"; } } for (var key in params.headers) { if (key === KEY_METHOD || key === KEY_SCHEMA || key == KEY_AUTHORTY || key === KEY_CONTENT_TYPE || key === KEY_PATH) continue; valueDictionary.push({ "name": key, "value": params.headers[key] }); } node.headers = valueDictionary; var debuggerRequest = { tabId: parseInt(context.apiRecorder.tab.id, 10), debuggerAction: 'NetworkPostData', commandData: { requestId: params.requestId } }; //await executeDebuggerCommand(context, debuggerRequest, (result) => ({node: {body: result}})); await sendCommandForNetworkDebugger(debuggerRequest, function (scriptResponse) { if (scriptResponse && scriptResponse.postData) { node.body = JSON.stringify(scriptResponse); } sendApiResponse(node, context); }); } } else if (method === "Network.requestWillBeSent") { //if(params.request.url.indexOf("aura.RecordUi.createRecord") === -1) return; if (params.redirectHasExtraInfo === true) return; if (params.request.url.indexOf(context.apiRecorder.jsonMessage.startRecording.url) === -1) return; if (params.request && params.request.headers) { var node = { "requestId": params.requestId, "method": params.request.method, "url": params.request.url, "authentication": "NoAuthentication" }; var contentType = params.request.headers['Content-Type']; if (!contentType) contentType = params.request.headers['accept']; if (contentType) { var contentTypeSegment = contentType.split(";") var parameterName = ''; if (defaultContentType.indexOf(contentTypeSegment[0]) >= 0) { var contentType = { "type": contentTypeMapper[contentTypeSegment[0]] }; node.contentType = contentType; parameterName = contentTypeSegment[0]; } else { var contentType = { "type": "CUSTOM", "value": contentTypeSegment[0] }; node.contentType = contentType; parameterName = "custom"; } } for (var key in params.request.headers) { if (key === 'Content-Type') continue; valueDictionary.push({ "name": key, "value": params.request.headers[key] }); } var debuggerRequest = { tabId: parseInt(context.apiRecorder.tab.id, 10), debuggerAction: 'GetCookies', commandData: { "urls": ['https://jeckycompay.lightning.force.com/'] } }; var result = await sendCommandForNetworkDebugger(debuggerRequest); var cookieHeader = "" for (var index = 0; index < result.cookies.length; index++) { if (cookieHeader !== "") cookieHeader = cookieHeader + ";" cookieHeader = cookieHeader + result.cookies[index].name + "=" + result.cookies[index].value; } valueDictionary.push({ "name": "Cookie", "value": cookieHeader }); node.headers = valueDictionary; if (params.request.hasPostData === true) { // var decodedData = decodeURIComponent(params.request.postData); // var urlParams = new URLSearchParams(params.request.postData); // var allParams = urlParams.keys(); // for (const key of allParams) { // var keyValue = urlParams.get(key); // //const messageObj = JSON.parse(keyValue); // console.log(key); // console.log(keyValue); // } node.body = JSON.stringify(params.request.postData); } sendApiResponse(node, context); } }}Debugger command wrapper
ExtensionDebuggerCommand.jsasync function sendCommandForNetworkDebugger(debuggerCommand, resultCallback) { var target = { tabId: debuggerCommand.tabId }; try { var commandResult; if (debuggerCommand.debuggerAction === "NetworkEnable") { await chrome.debugger.attach(target, "1.3"); commandResult = await chrome.debugger.sendCommand(target, "Network.enable"); } else if (debuggerCommand.debuggerAction === "NetworkDisable") { commandResult = await chrome.debugger.sendCommand(target, "Network.disable"); await chrome.debugger.detach(target); } else if (debuggerCommand.debuggerAction === "NetworkPostData") { commandResult = await chrome.debugger.sendCommand(target, "Network.getRequestPostData", debuggerCommand.commandData); } else if (debuggerCommand.debuggerAction === "GetCookies") { commandResult = await chrome.debugger.sendCommand(target, "Network.getCookies", debuggerCommand.commandData); } if (resultCallback) resultCallback(commandResult); else return commandResult; } catch (e) { AALogger.warn('debugger', 'err', e); chrome.debugger.detach(target, debuggerCommand.detachCallback); }}Native-port response path
ExtensionRecorderApiAction.js / BrowserNativeMessaging.jsfunction sendApiResponse(message, context) { var response = { "apiResponse": message }; context._port.postMessage(response);}function BrowserNativePort(extensionId) { var np = this; var port = chrome.runtime.connectNative(extensionId); this.onMessage = new ListenableEvent("onMessage"); this.onDisconnect = new ListenableEvent("onDisconnect"); port.onDisconnect.addListener((event) => { var results = np.onDisconnect.fire(event); if (results && results.length > 0) { return results[0]; } }) port.onMessage.addListener((event) => { var results = np.onMessage.fire(event); if (results && results.length > 0) { return results[0]; } }) this.postMessage = function(message, responseFunc) { return port.postMessage(message, responseFunc); } this.disconnect = function() { return port.disconnect(); }}- jeckycompay.lightning.force.com
Fixed Salesforce org URL used for the debugger cookie lookup.
- automation.edge.browseragent
Native-messaging host name for the local Automation 360 browser agent that receives apiResponse messages.
What it can do
Permissions this extension asks for, as declared in version 4.4.0.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on every site you visit
<all_urls>
Store data in your browser
storage
See the address and title of every tab you have open
tabs
See every page you navigate to, as you navigate to it
webNavigation
Run its own code inside the pages you visit
scripting
Talk to a program installed on your computer, outside the browser's sandbox
nativeMessaging
Attach to pages with the browser's debugger, which can read and rewrite anything on them
debugger
Schedule its own background tasks
alarms