Is SAP CPI Helper safe?

Low risk

SAP CPI Helper is low risk. SAP CPI Helper's GroovyDebugX action confirms, then opens a groovyide.com URL: a raw-DEFLATE, base64url JSON package built from trace data, which can include message body, runtime headers, exchange properties, and Groovy script source.

dbeckbauerv3.26.0Chrome Web Store
20Risk
Who publishes it

dbeckbauer - no other listings under this identity, 2 shared hostnames

What this publisher told the store about itself, and the other listings that told it the same thing.

Shared hosts - 2 hostnames

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.

blogs.sap.com
Also called by 1 other listing: SAP API Tester
groovyide.com
Also called by 2 other listings, including CPI Lens: SAP Integration Suite Inspector

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SAP CPI trace data is packed into a groovyide.com share URL

MediumunwantedCWE-200Dynamic sandbox

SAP CPI Helper's GroovyDebugX action confirms, then opens a groovyide.com URL: a raw-DEFLATE, base64url JSON package built from trace data, which can include message body, runtime headers, exchange properties, and Groovy script source.

What actually happens

You did this

You click the GroovyDebugX option to debug a traced SAP CPI Groovy step in an external IDE.

The confirmation dialog lets you choose message body, script, properties, and headers before continuing.

The extension did this

The extension creates a compressed share link and opens groovyide.com in a new tab.

The share link carries the selected trace fields inside the URL path rather than a separate request body.

Concrete JSON fields placed into the share package

  • Message body
    input.body = payload from TraceMessages(traceId)/$value

    Can include the business payload from the SAP CPI trace you are debugging.

  • Runtime headers
    input.headers = {"Content-Type":"application/json"}

    Can reveal routing, integration, and message-handling context for the traced exchange.

  • Exchange properties
    input.properties = {"CamelSplitIndex":"0"}

    Can include configuration and runtime values attached to the SAP CPI exchange.

  • Groovy script source
    script.code = "import com.sap.gateway.ip.core.customdev.util.Message\nMessage processData(Message message) { return message }"

    Can disclose the integration logic you are trying to debug.

  • Function name
    script.function = "processData"

    Identifies which Groovy entry point the external IDE should run for the debug session.

Captured request

GEThttps://groovyide.com/cpi/share/v1/

The extension opens this default share endpoint with the encoded trace package appended to the path; no request body was recorded for this claim.

The code that does this

The confirmation dialog and sender that build the groovyide.com URL

Readable version

Readable confirmation workflow from the deobfuscated source

plugins/groovyDebugger.js
window.groovyDebugSendToIDE = async function () {  const settings = window.groovyDebuggerData?.settings || {};  const ideUrl = settings["groovyDebugger---externalIdeUrl"]  || "https://groovyide.com/cpi/share/v1/";  const domain = new URL(ideUrl).hostname;  // Load saved user preferences for checkbox states using plugin settings  const loadTransferPreferences = async () => {    const body = (await getStorageValue("groovyDebugger", "transferBody", "browser")) ?? true;    const script = (await getStorageValue("groovyDebugger", "transferScript", "browser")) ?? true;    const properties = (await getStorageValue("groovyDebugger", "transferProperties", "browser")) ?? false;    const headers = (await getStorageValue("groovyDebugger", "transferHeaders", "browser")) ?? false;    return {      body: body,      script: script,      properties: properties,      headers: headers,    };  };  // Load saved user preferences for checkbox states  const userPreferences = await loadTransferPreferences();  // Create custom confirmation popup  const popupContent = `    <div class="ui warning message">      <div class="header">        <i class="exclamation triangle icon"></i>        Data Transfer Confirmation      </div>      <div class="ui info message">        <i class="info circle icon"></i>        <strong>Privacy Notice:</strong> The selected data may contain sensitive business information. Proceed with caution.      </div>      <p><strong>Destination:</strong> ${domain}</p>      <p>Select which data you want to transfer to the external Groovy WebIDE:</p>      <div class="ui form">        <div class="grouped fields">          <div class="field">            <div class="ui checkbox" id="transfer-body">              <input type="checkbox" name="transfer-body" ${userPreferences.body ? "checked" : ""}>              <label>Message Body <em>(may contain sensitive data)</em></label>            </div>          </div>          <div class="field">            <div class="ui checkbox" id="transfer-script">              <input type="checkbox" name="transfer-script" ${userPreferences.script ? "checked" : ""}>              <label>Groovy Script <em>(source code)</em></label>            </div>          </div>          <div class="field">            <div class="ui checkbox" id="transfer-properties">              <input type="checkbox" name="transfer-properties" ${userPreferences.properties ? "checked" : ""}>              <label>Properties <em>(may contain configuration data)</em></label>            </div>          </div>          <div class="field">            <div class="ui checkbox" id="transfer-headers">              <input type="checkbox" name="transfer-headers" ${userPreferences.headers ? "checked" : ""}>              <label>Headers <em>(may contain security & metadata)</em></label>            </div>          </div>        </div>      </div>    </div>  `;  showBigPopup(popupContent, "Confirm Data Transfer", {    fullscreen: false,    large: false,    callback: () => {      // Add custom buttons to the actions div      let actionsDiv = $("#cpiHelper_semanticui_modal .actions");      actionsDiv.empty(); // Remove default close button      // Cancel button      let cancelBtn = $('<div class="ui button">Cancel</div>');      cancelBtn.on("click", () => {        $("#cpiHelper_semanticui_modal").modal("hide");      });      actionsDiv.append(cancelBtn);      // Continue button      let continueBtn = $('<div class="ui positive button"><i class="rocket icon"></i>Continue</div>');      // Function to check if any checkbox is selected and enable/disable continue button      const updateContinueButton = () => {        const anyChecked = $("#transfer-body input").is(":checked") || $("#transfer-properties input").is(":checked") || $("#transfer-headers input").is(":checked") || $("#transfer-script input").is(":checked");        if (anyChecked) {          continueBtn.removeClass("disabled");          continueBtn.prop("disabled", false);        } else {          continueBtn.addClass("disabled");          continueBtn.prop("disabled", true);        }      };      // Add event listeners to all checkboxes to update continue button state      $("#transfer-body input, #transfer-properties input, #transfer-headers input, #transfer-script input").on("change", updateContinueButton);      // Initial check      updateContinueButton();      continueBtn.on("click", async () => {        // Get selected data types        const transferOptions = {          body: $("#transfer-body input").is(":checked"),          properties: $("#transfer-properties input").is(":checked"),          headers: $("#transfer-headers input").is(":checked"),          script: $("#transfer-script input").is(":checked"),        };        // Save user preferences for next time using plugin settings        await syncChromeStoragePromise(getStoragePath("groovyDebugger", "transferBody", "browser"), transferOptions.body);        await syncChromeStoragePromise(getStoragePath("groovyDebugger", "transferScript", "browser"), transferOptions.script);        await syncChromeStoragePromise(getStoragePath("groovyDebugger", "transferProperties", "browser"), transferOptions.properties);        await syncChromeStoragePromise(getStoragePath("groovyDebugger", "transferHeaders", "browser"), transferOptions.headers);        $("#cpiHelper_semanticui_modal").modal("hide");        const debugData = window.currentGroovyDebugData;        await sendToExternalIDE(settings, debugData, transferOptions);        showToast(`Debug data sent to IDE`, "Success");        // Close the main debug popup after sending        $("#cpiHelper_semanticui_modal").modal("hide");      });      actionsDiv.append(continueBtn);    },  });}

Readable sender and encoder from the deobfuscated source

plugins/groovyDebugger.js
async function sendToExternalIDE(settings, debugData, transferOptions = { body: true, properties: true, headers: true, script: true }) {  var ideUrl = settings["groovyDebugger---externalIdeUrl"]  || "https://groovyide.com/cpi/share/v1/";  // Use actual debug data based on transfer options  let groovyScript = "";  if (transferOptions.script) {    groovyScript = debugData.groovyScript;    // If script not fetched yet (lazy loading), fetch it now    if (groovyScript === "// Script content not available" && debugData.scriptInfo) {      try {        if (debugData.scriptInfo.scriptPath) {          let scriptPath = debugData.scriptInfo.scriptPath;          if (scriptPath.startsWith("/script/")) {            scriptPath = scriptPath.replace("/script/", "//");          }          const scriptUrl = "https://" + debugData.scriptInfo.tenant + "/api/1.0/iflows/" + debugData.scriptInfo.artifactId + "/script/" + scriptPath;          const scriptResponse = await fetch(scriptUrl);          const scriptData = await scriptResponse.json();          groovyScript = scriptData.content || "// Script content not available";        }      } catch (error) {        log.error("Error fetching script for IDE:", error);        groovyScript = "// Script content not available";      }    }  }  let payload = "";  if (transferOptions.body) {    payload = debugData.payload;    // If payload not fetched yet (lazy loading), fetch it now    if (!payload && debugData.traceId) {      try {        payload = await makeCallPromise("GET", "/" + cpiData.urlExtension + "odata/api/v1/TraceMessages(" + debugData.traceId + ")/$value", true);      } catch (error) {        log.error("Error fetching payload for IDE:", error);        payload = "";      }    }  }  let headers = {};  if (transferOptions.headers) {    headers = debugData.headers || {};    // If headers not fetched yet (lazy loading), fetch them now    if ((!headers || Object.keys(headers).length === 0) && debugData.traceId) {      try {        let headersData = JSON.parse(await makeCallPromise("GET", "/" + cpiData.urlExtension + "odata/api/v1/TraceMessages(" + debugData.traceId + ")/Properties?$format=json", true)).d.results;        headers = {};        headersData.forEach((header) => {          headers[header.Name] = header.Value;        });      } catch (error) {        log.error("Error fetching headers for IDE:", error);        headers = {};      }    }  }  let properties = {};  if (transferOptions.properties) {    properties = debugData.properties || {};  }  // Build the JSON structure from actual debug data  let dataObject = {    input: {      body: payload,      headers: headers,      properties: properties,    },    script: {      code: groovyScript,      function: debugData.scriptFunction || "processData",    },  };  let dataString = JSON.stringify(dataObject);  // Compress and encode  let encoded;  encoded = await compressToBase64(dataString);  // Make URL-safe and remove padding  encoded = encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");  var fullUrl = ideUrl + encoded;  // Open in new tab/window  window.open(fullUrl, "_blank");}function compressToBase64(dataString) {  // Step A: Convert the JSON string into a Uint8Array (binary data)  const dataBytes = new TextEncoder().encode(dataString);  // Step B: Compress using pako.deflateRaw()  // This creates the raw Deflate stream without Zlib/Gzip headers.  const compressedBytes = pako.deflateRaw(dataBytes, { level: 9 }); // level 9 is max compression  // Step C: Base64URL Encode the compressed binary data  const encodedString = uint8ArrayToBase64Url(compressedBytes);  return encodedString;}function uint8ArrayToBase64Url(bytes) {  // Convert Uint8Array to a binary string  let binaryString = "";  bytes.forEach((byte) => {    binaryString += String.fromCharCode(byte);  });  // Standard Base64 encoding using the built-in browser function  let base64 = btoa(binaryString);  // Convert to URL-safe format and remove padding  let base64Url = base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");  return base64Url;}

Remote destination used by the external IDE workflow

    • groovyide.com

    Default Groovy WebIDE share destination that receives the encoded SAP CPI trace package in the URL path.

What it can do

Permissions this extension asks for, as declared in version 3.26.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 hana.ondemand.com

    https://*.hana.ondemand.com/itspaces and 1 more

  • Read and change your data on platform.sapcloud.cn

    https://*.platform.sapcloud.cn/itspaces and 1 more

  • Store data in your browser

    storage

Updated 15 October 2026epoggeaemnkacpinjfgccbjakglngkpb