Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bundles/com.espressif.idf.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Export-Package: com.espressif.idf.core,
com.espressif.idf.core.configparser.vo,
com.espressif.idf.core.logging,
com.espressif.idf.core.resources,
com.espressif.idf.core.telemetry,
com.espressif.idf.core.toolchain,
com.espressif.idf.core.tools,
com.espressif.idf.core.tools.eimjson,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class IDFCorePreferenceConstants
public static final String HIDE_ERRORS_IDF_COMPONENTS = "hideErrorsOnIdfDerivedFiles"; //$NON-NLS-1$
public static final String AUTOMATE_CLANGD_FORMAT_FILE = "automateClangFormatFileCreation"; //$NON-NLS-1$
public static final String EIM_IDF_JSON_PATH = "eimIdfJsonPath"; //$NON-NLS-1$
public static final String TELEMETRY_ENABLED = "telemetryEnabled"; //$NON-NLS-1$
public static final boolean TELEMETRY_ENABLED_DEFAULT = true;
public static final boolean AUTOMATE_CLANGD_FORMAT_FILE_DEFAULT = true;
public static final boolean CMAKE_CCACHE_DEFAULT_STATUS = true;
public static final boolean AUTOMATE_BUILD_HINTS_DEFAULT_STATUS = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*******************************************************************************
* Copyright 2026 Espressif Systems (Shanghai) PTE LTD. All rights reserved.
* Use is subject to license terms.
*******************************************************************************/
package com.espressif.idf.core.telemetry;

import java.net.URI;
import java.util.Locale;
import java.util.Optional;

/**
* Azure Application Insights connection string, holding the instrumentation key and the regional ingestion endpoint.
*
* @author Kondal Kolipaka <kondal.kolipaka@espressif.com>
*
*/
public final class TelemetryConnection
{
private static final String INSTRUMENTATION_KEY = "instrumentationkey"; //$NON-NLS-1$
private static final String INGESTION_ENDPOINT = "ingestionendpoint"; //$NON-NLS-1$
private static final String DEFAULT_INGESTION_ENDPOINT = "https://dc.services.visualstudio.com/"; //$NON-NLS-1$
private static final String TRACK_PATH = "v2/track"; //$NON-NLS-1$

private final String instrumentationKey;
private final String ingestionEndpoint;

private TelemetryConnection(String instrumentationKey, String ingestionEndpoint)
{
this.instrumentationKey = instrumentationKey;
this.ingestionEndpoint = ingestionEndpoint;
}

/**
* Parses an Application Insights connection string of the form
* <code>InstrumentationKey=&lt;key&gt;;IngestionEndpoint=&lt;url&gt;</code>. Unknown fields are ignored and the
* ingestion endpoint falls back to the global one when absent.
*
* @param connectionString connection string to parse, may be <code>null</code>
* @return the parsed connection or an empty optional when no instrumentation key is present
*/
public static Optional<TelemetryConnection> parse(String connectionString)
{
if (connectionString == null || connectionString.isBlank())
{
return Optional.empty();
}

String key = null;
String endpoint = DEFAULT_INGESTION_ENDPOINT;
for (String field : connectionString.split(";")) //$NON-NLS-1$
{
int separator = field.indexOf('=');
if (separator <= 0)
{
continue;
}
String name = field.substring(0, separator).trim().toLowerCase(Locale.ENGLISH);
String value = field.substring(separator + 1).trim();
if (value.isEmpty())
{
continue;
}
if (INSTRUMENTATION_KEY.equals(name))
{
key = value;
}
else if (INGESTION_ENDPOINT.equals(name))
{
endpoint = value;
}
}

if (key == null)
{
return Optional.empty();
}
if (!endpoint.endsWith("/")) //$NON-NLS-1$
{
endpoint = endpoint + "/"; //$NON-NLS-1$
}
return Optional.of(new TelemetryConnection(key, endpoint));
}

public String getInstrumentationKey()
{
return instrumentationKey;
}

public String getIngestionEndpoint()
{
return ingestionEndpoint;
}

/**
* @return endpoint accepting the telemetry envelopes
*/
public URI getTrackUri()
{
return URI.create(ingestionEndpoint + TRACK_PATH);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright 2026 Espressif Systems (Shanghai) PTE LTD. All rights reserved.
* Use is subject to license terms.
*******************************************************************************/
package com.espressif.idf.core.telemetry;

import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

/**
* Builds the JSON payload accepted by the Application Insights ingestion endpoint.
*
* @author Kondal Kolipaka <kondal.kolipaka@espressif.com>
*
*/
public final class TelemetryEnvelope
{
private static final String ENVELOPE_NAME = "Microsoft.ApplicationInsights.Event"; //$NON-NLS-1$
private static final String EVENT_BASE_TYPE = "EventData"; //$NON-NLS-1$
private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); //$NON-NLS-1$

private static final Gson GSON = new Gson();

private TelemetryEnvelope()
{
}

/**
* @param instrumentationKey Application Insights instrumentation key
* @param eventName name of the custom event, for instance <code>espressif-ide/session</code>
* @param tags Application Insights context tags, such as <code>ai.user.id</code>
* @param properties custom string properties reported along with the event
* @param timestamp time the event occurred
* @return the serialized envelope
*/
public static String build(String instrumentationKey, String eventName, Map<String, String> tags,
Map<String, String> properties, Instant timestamp)
{
JsonObject baseData = new JsonObject();
baseData.addProperty("ver", 2); //$NON-NLS-1$
baseData.addProperty("name", eventName); //$NON-NLS-1$
baseData.add("properties", toJsonObject(properties)); //$NON-NLS-1$

JsonObject data = new JsonObject();
data.addProperty("baseType", EVENT_BASE_TYPE); //$NON-NLS-1$
data.add("baseData", baseData); //$NON-NLS-1$

JsonObject envelope = new JsonObject();
envelope.addProperty("name", ENVELOPE_NAME); //$NON-NLS-1$
envelope.addProperty("time", TIMESTAMP_FORMAT.format(timestamp)); //$NON-NLS-1$
envelope.addProperty("iKey", instrumentationKey); //$NON-NLS-1$
envelope.add("tags", toJsonObject(tags)); //$NON-NLS-1$
envelope.add("data", data); //$NON-NLS-1$

return GSON.toJson(envelope);
}

private static JsonObject toJsonObject(Map<String, String> values)
{
JsonObject object = new JsonObject();
if (values != null)
{
values.forEach((key, value) -> {
if (key != null && value != null)
{
object.addProperty(key, value);
}
});
}
return object;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*******************************************************************************
* Copyright 2026 Espressif Systems (Shanghai) PTE LTD. All rights reserved.
* Use is subject to license terms.
*******************************************************************************/
package com.espressif.idf.core.telemetry;

import java.util.UUID;

import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.osgi.service.prefs.BackingStoreException;

import com.espressif.idf.core.IDFCorePlugin;
import com.espressif.idf.core.IDFCorePreferenceConstants;
import com.espressif.idf.core.logging.Logger;

/**
* Stores the opt-out flag and the anonymous state needed to count installations and updates.
* <p>
* The anonymous identifier and the report timestamps live in the configuration scope so that all workspaces of one
* installation are counted as a single user.
*
* @author Kondal Kolipaka <kondal.kolipaka@espressif.com>
*
*/
public final class TelemetryPreferences
{
/** System property to disable telemetry, for instance <code>-Didf.telemetry=false</code>. */
public static final String TELEMETRY_SYSTEM_PROPERTY = "idf.telemetry"; //$NON-NLS-1$

/** Environment variable to disable telemetry, for instance <code>IDF_TELEMETRY=0</code>. */
public static final String TELEMETRY_ENV_VARIABLE = "IDF_TELEMETRY"; //$NON-NLS-1$

private static final String INSTALL_ID = "telemetryInstallId"; //$NON-NLS-1$
private static final String LAST_SESSION_REPORT = "telemetryLastSessionReport"; //$NON-NLS-1$
private static final String LAST_REPORTED_VERSION = "telemetryLastReportedVersion"; //$NON-NLS-1$
private static final String NOTICE_SHOWN = "telemetryNoticeShown"; //$NON-NLS-1$

private TelemetryPreferences()
{
}

/**
* @return <code>false</code> when the user opted out through the preference page, the system property or the
* environment variable
*/
public static boolean isEnabled()
{
return !isDisabledByOverride() && isEnabledByPreference();
}

/**
* Tells whether reporting is switched off outside of the preference page, which is how shared and automated
* installations opt out. The override can only disable reporting, so that it can never overrule a user who opted
* out through the preference page.
*
* @return <code>true</code> when the system property or the environment variable disables reporting
*/
public static boolean isDisabledByOverride()
{
String property = System.getProperty(TELEMETRY_SYSTEM_PROPERTY);
if (property == null || property.isBlank())
{
property = System.getenv(TELEMETRY_ENV_VARIABLE);
}
return property != null && isDisabledValue(property.trim());
Comment on lines +63 to +68

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="bundles/com.espressif.idf.core/src/com/espressif/idf/core/telemetry/TelemetryPreferences.java"
echo "== candidate file lines =="
sed -n '1,220p' "$file" | nl -ba

echo "== telemetry constants/usages =="
rg -n "TELEMETRY_SYSTEM_PROPERTY|TELEMETRY_ENV_VARIABLE|isDisabledByOverride|IDF_TELEMETRY|idf\.telemetry|Enabled\(|isEnabled\(" -S .

echo "== behavioral probe: current isDisabledByOverride logic =="
python3 - <<'PY'
def current(property_value, env_value):
    property = property_value
    if property == None or property.strip() == "":
        property = env_value
    value = property.strip() if property else None
    return value is not None and value.lower() in ("0", "false", "no")

cases = [
    ("true", "0"),
    ("", "0"),
    (None, "0"),
    ("1", "0"),
    ("0", "1"),
    ("true", None),
]
for prop, env in cases:
    print(repr(prop), repr(env), "=>", current(prop, env))
PY

Repository: espressif/idf-eclipse-plugin

Length of output: 233


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="bundles/com.espressif.idf.core/src/com/espressif/idf/core/telemetry/TelemetryPreferences.java"

echo "== candidate file lines =="
awk '{printf "%s\t%s\n", NR, $0}' "$file" | sed -n '1,220p'

echo
echo "== telemetry constants/usages =="
rg -n "TELEMETRY_SYSTEM_PROPERTY|TELEMETRY_ENV_VARIABLE|isDisabledByOverride|IDF_TELEMETRY|idf\.telemetry|Enabled\(|isEnabled\(" -S . 2>/dev/null || true

echo
echo "== behavioral probe: current isDisabledByOverride logic =="
python3 - <<'PY'
def current(property_value, env_value):
    property = property_value
    if property == None or property.strip() == "":
        property = env_value
    value = property.strip() if property else None
    return value is not None and value.lower() in ("0", "false", "no")

cases = [
    ("true", "0"),
    ("", "0"),
    (None, "0"),
    ("1", "0"),
    ("0", "1"),
    ("true", None),
]
for prop, env in cases:
    print(repr(prop), repr(env), "=>", current(prop, env))
PY

Repository: espressif/idf-eclipse-plugin

Length of output: 29806


Sensitive Data Exposure (CWE-359)

Reachability path
● Entry
  bundles/com.espressif.idf.ui/src/com/espressif/idf/ui/preferences/EspresssifPreferencesPage.java:98
  addPrivacySettings
│
▼
● Sink
  bundles/com.espressif.idf.core/src/com/espressif/idf/core/telemetry/TelemetryPreferences.java

Evaluate both opt-out sources.

A nonblank enabling system property currently prevents checking IDF_TELEMETRY=0, so isDisabledByOverride() returns false and telemetry can run despite the environment variable opt-out. When the system property is not disabling, continue and apply the environment-variable opt-out.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@bundles/com.espressif.idf.core/src/com/espressif/idf/core/telemetry/TelemetryPreferences.java`
around lines 63 - 68, Update isDisabledByOverride() to evaluate both opt-out
sources: check the system property first, but only return true when it contains
a disabling value; otherwise continue checking TELEMETRY_ENV_VARIABLE and return
true when it is disabled. Preserve the existing blank-value fallback and
trimmed-value handling.

}

/**
* @return the stored opt-out, which is off as soon as any scope switched reporting off
*/
public static boolean isEnabledByPreference()
{
boolean defaultValue = DefaultScope.INSTANCE.getNode(IDFCorePlugin.PLUGIN_ID).getBoolean(
IDFCorePreferenceConstants.TELEMETRY_ENABLED,
IDFCorePreferenceConstants.TELEMETRY_ENABLED_DEFAULT);
return ConfigurationScope.INSTANCE.getNode(IDFCorePlugin.PLUGIN_ID)
.getBoolean(IDFCorePreferenceConstants.TELEMETRY_ENABLED, defaultValue)
&& InstanceScope.INSTANCE.getNode(IDFCorePlugin.PLUGIN_ID)
.getBoolean(IDFCorePreferenceConstants.TELEMETRY_ENABLED, defaultValue);
}

/**
* Stores the opt-out for the whole installation, so that opting out in one workspace also applies to the others.
*
* @param enabled <code>true</code> to report usage statistics
*/
public static void setEnabled(boolean enabled)
{
for (IEclipsePreferences node : new IEclipsePreferences[] {
ConfigurationScope.INSTANCE.getNode(IDFCorePlugin.PLUGIN_ID),
InstanceScope.INSTANCE.getNode(IDFCorePlugin.PLUGIN_ID) })
{
node.putBoolean(IDFCorePreferenceConstants.TELEMETRY_ENABLED, enabled);
flush(node);
}
}

private static boolean isDisabledValue(String value)
{
return "false".equalsIgnoreCase(value) || "0".equals(value) || "no".equalsIgnoreCase(value) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|| "off".equalsIgnoreCase(value) || "disabled".equalsIgnoreCase(value); //$NON-NLS-1$ //$NON-NLS-2$
}

/**
* @return <code>true</code> once the installation told the user that usage statistics are reported
*/
public static boolean isNoticeShown()
{
return getStateNode().getBoolean(NOTICE_SHOWN, false);
}

public static void setNoticeShown()
{
IEclipsePreferences node = getStateNode();
node.putBoolean(NOTICE_SHOWN, true);
flush(node);
}

/**
* @return a random identifier created on first use, which is not derived from any machine or user attribute
*/
public static String getInstallId()
{
IEclipsePreferences node = getStateNode();
String installId = node.get(INSTALL_ID, null);
if (installId == null || installId.isBlank())
{
installId = UUID.randomUUID().toString();
node.put(INSTALL_ID, installId);
flush(node);
}
return installId;
}

public static long getLastSessionReport()
{
return getStateNode().getLong(LAST_SESSION_REPORT, 0L);
}

public static void setLastSessionReport(long timestamp)
{
IEclipsePreferences node = getStateNode();
node.putLong(LAST_SESSION_REPORT, timestamp);
flush(node);
}

public static String getLastReportedVersion()
{
return getStateNode().get(LAST_REPORTED_VERSION, ""); //$NON-NLS-1$
}

public static void setLastReportedVersion(String version)
{
IEclipsePreferences node = getStateNode();
node.put(LAST_REPORTED_VERSION, version);
flush(node);
}

private static IEclipsePreferences getStateNode()
{
IEclipsePreferences node = ConfigurationScope.INSTANCE.getNode(IDFCorePlugin.PLUGIN_ID);
if (node == null)
{
node = InstanceScope.INSTANCE.getNode(IDFCorePlugin.PLUGIN_ID);
}
return node;
}

private static void flush(IEclipsePreferences node)
{
try
{
node.flush();
}
catch (BackingStoreException e)
{
// A read-only configuration area only means the state is recomputed on the next start
Logger.log(e, true);
Comment on lines +162 to +181

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate file and relevant symbols"
if [ -f bundles/com.espressif.idf.core/src/com/espressif/idf/core/telemetry/TelemetryPreferences.java ]; then
  wc -l bundles/com.espressif.idf.core/src/com/espressif/idf/core/telemetry/TelemetryPreferences.java
  sed -n '1,260p' bundles/com.espressif.idf.core/src/com/espressif/idf/core/telemetry/TelemetryPreferences.java
else
  echo "File not found"
  fd -i 'TelemetryPreferences.java' .
fi

echo
echo "Search telemetry persistence usages"
rg -n "TelemetryPreferences|getStateNode|flush\\(|ConfigurationScope|InstanceScope|noticeFlag|installationId|reportedVersion|sessionTimestamp" bundles/com.espressif.idf.core/src/com/espressif/idf/core/telemetry -S

Repository: espressif/idf-eclipse-plugin

Length of output: 11313


Persist installation telemetry in InstanceScope when configuration scope is read-only.

getStateNode() selects ConfigurationScope whenever getNode() succeeds, and flush() only logs BackingStoreException. If that write fails, NOTICE_SHOWN, telemetryInstallId, telemetryLastReportedVersion, and telemetryLastSessionReport are not saved and can reset on restart. Add a read/read-fallback plus write fallback to InstanceScope after configuration-scope flush failure, and cover the read-only configuration area.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@bundles/com.espressif.idf.core/src/com/espressif/idf/core/telemetry/TelemetryPreferences.java`
around lines 162 - 181, Update TelemetryPreferences.getStateNode and flush so
configuration-scope state remains the primary read/write location, but a
BackingStoreException during configuration flush retries persistence in the
InstanceScope node. Add the corresponding read fallback so values written to
InstanceScope are used when ConfigurationScope is unavailable or read-only,
preserving NOTICE_SHOWN, telemetryInstallId, telemetryLastReportedVersion, and
telemetryLastSessionReport across restarts; cover the read-only configuration
case.

}
}
}
Loading
Loading