@@ -31670,6 +31670,7 @@ define("extensibility/ExtensionManager", function (require, exports, module) {
3167031670 exports.updateExtensions = updateExtensions;
3167131671 exports.getAvailableUpdates = getAvailableUpdates;
3167231672 exports.cleanAvailableUpdates = cleanAvailableUpdates;
31673+ exports.isExtensionTakenDown = ExtensionLoader.isExtensionTakenDown;
3167331674
3167431675 exports.ENABLED = ENABLED;
3167531676 exports.DISABLED = DISABLED;
@@ -32262,6 +32263,11 @@ define("extensibility/ExtensionManagerView", function (require, exports, module)
3226232263 </div>
3226332264 </td>
3226432265 <td class="ext-desc">
32266+ {{#isDeprecatedExtension}}
32267+ {{#isInstalled}}
32268+ <div class="alert warning">{{Strings.EXTENSION_DEPRECATED_NOT_LOADED}}</div>
32269+ {{/isInstalled}}
32270+ {{/isDeprecatedExtension}}
3226532271 {{#showInstallButton}}
3226632272 <!-- Warnings when trying to install extension where latest/all versions not compatible -->
3226732273 {{#defaultFeature}}
@@ -32363,8 +32369,8 @@ define("extensibility/ExtensionManagerView", function (require, exports, module)
3236332369</tr>
3236432370`,
3236532371 PreferencesManager = require("preferences/PreferencesManager"),
32366- warnExtensionIDs = JSON.parse(require("text!extensions/default/DefaultExtensions.json"))
32367- .warnExtensionStoreExtensions.extensionIDs,
32372+ DefaultExtensions = JSON.parse(require("text!extensions/default/DefaultExtensions.json")),
32373+ warnExtensionIDs = new Set(DefaultExtensions .warnExtensionStoreExtensions.extensionIDs) ,
3236832374 Metrics = require("utils/Metrics");
3236932375
3237032376
@@ -32683,7 +32689,8 @@ define("extensibility/ExtensionManagerView", function (require, exports, module)
3268332689 context.isCurrentTheme = entry.installInfo &&
3268432690 (entry.installInfo.metadata.name === ThemeManager.getCurrentTheme().name);
3268532691
32686- context.defaultFeature = warnExtensionIDs.includes(info.metadata.name);
32692+ context.defaultFeature = warnExtensionIDs.has(info.metadata.name);
32693+ context.isDeprecatedExtension = ExtensionManager.isExtensionTakenDown(info.metadata.name);
3268732694
3268832695 context.allowInstall = context.isCompatible && !context.isInstalled;
3268932696
@@ -32739,9 +32746,9 @@ define("extensibility/ExtensionManagerView", function (require, exports, module)
3273932746 var isDefaultOrInstalled = this.model.source === "default" || this.model.source === "installed";
3274032747 var isDefaultAndTheme = this.model.source === "default" && context.metadata.theme;
3274132748 context.disablingAllowed = isDefaultOrInstalled && !isDefaultAndTheme && !context.disabled
32742- && !hasPendingAction && !context.metadata.theme;
32749+ && !hasPendingAction && !context.metadata.theme && !context.isDeprecatedExtension ;
3274332750 context.enablingAllowed = isDefaultOrInstalled && !isDefaultAndTheme && context.disabled
32744- && !hasPendingAction && !context.metadata.theme;
32751+ && !hasPendingAction && !context.metadata.theme && !context.isDeprecatedExtension ;
3274532752
3274632753 // Copy over helper functions that we share with the registry app.
3274732754 ["lastVersionDate", "authorInfo"].forEach(function (helper) {
@@ -32895,11 +32902,11 @@ define("extensibility/ExtensionManagerViewModel", function (require, exports, mo
3289532902
3289632903 var _ = require("thirdparty/lodash");
3289732904
32898- var ExtensionManager = require("extensibility/ExtensionManager"),
32899- registry_utils = require("extensibility/registry_utils"),
32900- EventDispatcher = require("utils/EventDispatcher"),
32901- Strings = require("strings"),
32902- PreferencesManager = require("preferences/PreferencesManager");
32905+ const ExtensionManager = require("extensibility/ExtensionManager"),
32906+ registry_utils = require("extensibility/registry_utils"),
32907+ EventDispatcher = require("utils/EventDispatcher"),
32908+ Strings = require("strings"),
32909+ PreferencesManager = require("preferences/PreferencesManager");
3290332910
3290432911 /**
3290532912 * @private
@@ -33176,6 +33183,9 @@ define("extensibility/ExtensionManagerViewModel", function (require, exports, mo
3317633183 return entry.registryInfo && entry.registryInfo.metadata.theme;
3317733184
3317833185 })
33186+ .filter(function (entry) {
33187+ return !ExtensionManager.isExtensionTakenDown(entry.registryInfo.metadata.name);
33188+ })
3317933189 .map(function (entry) {
3318033190 return entry.registryInfo.metadata.name;
3318133191 });
@@ -33424,6 +33434,9 @@ define("extensibility/ExtensionManagerViewModel", function (require, exports, mo
3342433434 .filter(function (key) {
3342533435 return self.extensions[key].installInfo &&
3342633436 self.extensions[key].installInfo.locationType === ExtensionManager.LOCATION_DEFAULT;
33437+ })
33438+ .filter(function (key) {
33439+ return !ExtensionManager.isExtensionTakenDown(key);
3342733440 });
3342833441 this._sortFullSet();
3342933442 this._setInitialFilter();
@@ -112920,6 +112933,7 @@ define("nls/root/strings", {
112920112933 "EXTENSION_INCOMPATIBLE_NEWER": "This extension requires a newer version of {APP_NAME}.",
112921112934 "EXTENSION_INCOMPATIBLE_OLDER": "This extension currently only works with older versions of {APP_NAME}.",
112922112935 "EXTENSION_DEFAULT_FEATURE_PRESENT": "You may not need this extension. {APP_NAME} already has this feature.",
112936+ "EXTENSION_DEPRECATED_NOT_LOADED": "Extension not loaded. It is either deprecated or insecure.",
112923112937 "EXTENSION_LATEST_INCOMPATIBLE_NEWER": "Version {0} of this extension requires a newer version of {APP_NAME}. But you can install the earlier version {1}.",
112924112938 "EXTENSION_LATEST_INCOMPATIBLE_OLDER": "Version {0} of this extension only works with older versions of {APP_NAME}. But you can install the earlier version {1}.",
112925112939 "EXTENSION_NO_DESCRIPTION": "No description",
@@ -174797,6 +174811,96 @@ define("utils/ExtensionLoader", function (require, exports, module) {
174797174811 PathUtils = require("thirdparty/path-utils/path-utils"),
174798174812 DefaultExtensions = JSON.parse(require("text!extensions/default/DefaultExtensions.json"));
174799174813
174814+ // takedown/dont load extensions that are compromised at app start - start
174815+ const EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY = "PH_EXTENSION_TAKEDOWN_LIST";
174816+
174817+ function _getTakedownListLS() {
174818+ try{
174819+ let list = localStorage.getItem(EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY);
174820+ if(list) {
174821+ list = JSON.parse(list);
174822+ if (Array.isArray(list)) {
174823+ return list;
174824+ }
174825+ }
174826+ } catch (e) {
174827+ console.error(e);
174828+ }
174829+ return [];
174830+ }
174831+
174832+ const loadedExtensionIDs = new Set();
174833+ let takedownExtensionList = new Set(_getTakedownListLS());
174834+
174835+ const EXTENSION_TAKEDOWN_URL = brackets.config.extensionTakedownURL;
174836+
174837+ function _anyTakenDownExtensionLoaded() {
174838+ if (takedownExtensionList.size === 0 || loadedExtensionIDs.size === 0) {
174839+ return [];
174840+ }
174841+ let smaller;
174842+ let larger;
174843+
174844+ if (takedownExtensionList.size < loadedExtensionIDs.size) {
174845+ smaller = takedownExtensionList;
174846+ larger = loadedExtensionIDs;
174847+ } else {
174848+ smaller = loadedExtensionIDs;
174849+ larger = takedownExtensionList;
174850+ }
174851+
174852+ const matches = [];
174853+
174854+ for (const id of smaller) {
174855+ if (larger.has(id)) {
174856+ matches.push(id);
174857+ }
174858+ }
174859+
174860+ return matches;
174861+ }
174862+
174863+ function fetchWithTimeout(url, ms) {
174864+ const c = new AbortController();
174865+ const t = setTimeout(() => c.abort(), ms);
174866+ return fetch(url, { signal: c.signal }).finally(() => clearTimeout(t));
174867+ }
174868+
174869+ // we dont want a restart after user does too much in the app causing data loss. So we wont reload after 20 seconds.
174870+ fetchWithTimeout(EXTENSION_TAKEDOWN_URL, 20000)
174871+ .then(response => {
174872+ if (!response.ok) {
174873+ throw new Error(`HTTP ${response.status} - ${response.statusText}`);
174874+ }
174875+ return response.json();
174876+ })
174877+ .then(data => {
174878+ console.log('Extension takedown data:', data);
174879+ if (!Array.isArray(data) || !data.every(x => typeof x === "string")) {
174880+ console.error("Takedown list must be an array of strings.");
174881+ return;
174882+ }
174883+ const dataToWrite = JSON.stringify(data);
174884+ localStorage.setItem(EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY, dataToWrite);
174885+ takedownExtensionList = new Set(data);
174886+ const compromisedExtensionsLoaded = _anyTakenDownExtensionLoaded();
174887+ if(!compromisedExtensionsLoaded.length){
174888+ return;
174889+ }
174890+ // if we are here, we have already loaded some compromised extensions. we need to reload app as soon as
174891+ // possible. no await after this. all sync js calls to prevent extension from tampering with this list.
174892+ const writtenData = localStorage.getItem(EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY);
174893+ if(writtenData !== dataToWrite) {
174894+ // the write did not succeded. local storage write can fail if storage full, if so we may cause infinite
174895+ // reloads here if we dont do the check.
174896+ console.error("Failed to write taken down extension to localstorage");
174897+ return;
174898+ }
174899+ location.reload();
174900+ })
174901+ .catch(console.error);
174902+ // takedown/dont load extensions that are compromised at app start - end
174903+
174800174904 const desktopOnlyExtensions = DefaultExtensions.desktopOnly;
174801174905 const DefaultExtensionsList = Phoenix.isNativeApp ?
174802174906 [...DefaultExtensions.defaultExtensionsList, ...desktopOnlyExtensions]:
@@ -175157,6 +175261,16 @@ define("utils/ExtensionLoader", function (require, exports, module) {
175157175261
175158175262 return promise
175159175263 .then(function (metadata) {
175264+ if (isExtensionTakenDown(metadata.name)) {
175265+ logger.leaveTrail("skip load taken down extension: " + metadata.name);
175266+ console.warn("skip load taken down extension: " + metadata.name);
175267+ return new $.Deferred().reject("disabled").promise();
175268+ }
175269+
175270+ if(metadata.name) {
175271+ loadedExtensionIDs.add(metadata.name);
175272+ }
175273+
175160175274 // No special handling for themes... Let the promise propagate into the ExtensionManager
175161175275 if (metadata && metadata.theme) {
175162175276 return;
@@ -175664,6 +175778,15 @@ define("utils/ExtensionLoader", function (require, exports, module) {
175664175778 return promise;
175665175779 }
175666175780
175781+ function isExtensionTakenDown(extensionID) {
175782+ if(!extensionID){
175783+ // extensions without id can happen with local development. these are never distributed in store.
175784+ // so safe to return false here.
175785+ return false;
175786+ }
175787+ return takedownExtensionList.has(extensionID);
175788+ }
175789+
175667175790
175668175791 EventDispatcher.makeEventDispatcher(exports);
175669175792
@@ -175687,6 +175810,7 @@ define("utils/ExtensionLoader", function (require, exports, module) {
175687175810 exports.testExtension = testExtension;
175688175811 exports.loadAllExtensionsInNativeDirectory = loadAllExtensionsInNativeDirectory;
175689175812 exports.loadExtensionFromNativeDirectory = loadExtensionFromNativeDirectory;
175813+ exports.isExtensionTakenDown = isExtensionTakenDown;
175690175814 exports.testAllExtensionsInNativeDirectory = testAllExtensionsInNativeDirectory;
175691175815 exports.testAllDefaultExtensions = testAllDefaultExtensions;
175692175816 exports.EVENT_EXTENSION_LOADED = EVENT_EXTENSION_LOADED;
@@ -178384,6 +178508,37 @@ define("utils/NodeUtils", function (require, exports, module) {
178384178508 return false;
178385178509 }
178386178510
178511+ /**
178512+ * Retrieves the operating system username of the current user.
178513+ * This method is only available in native apps.
178514+ *
178515+ * @throws {Error} Throws an error if called in a browser environment.
178516+ * @return {Promise<string>} A promise that resolves to the OS username of the current user.
178517+ */
178518+ async function getOSUserName() {
178519+ if (!Phoenix.isNativeApp) {
178520+ throw new Error("getOSUserName not available in browser");
178521+ }
178522+ return utilsConnector.execPeer("getOSUserName");
178523+ }
178524+
178525+ let _systemSettingsDir;
178526+ /**
178527+ * Retrieves the directory path for system settings. This method is applicable to native apps only.
178528+ *
178529+ * @return {Promise<string>} A promise that resolves to the path of the system settings directory.
178530+ * @throws {Error} If the method is called in browser app.
178531+ */
178532+ async function getSystemSettingsDir() {
178533+ if (!Phoenix.isNativeApp) {
178534+ throw new Error("getSystemSettingsDir is sudo folder is win/linux/mac. not available in browser.");
178535+ }
178536+ if(!_systemSettingsDir){
178537+ _systemSettingsDir = await utilsConnector.execPeer("getSystemSettingsDir");
178538+ }
178539+ return _systemSettingsDir;
178540+ }
178541+
178387178542 if(NodeConnector.isNodeAvailable()) {
178388178543 // todo we need to update the strings if a user extension adds its translations. Since we dont support
178389178544 // node extensions for now, should consider when we support node extensions.
@@ -178426,6 +178581,8 @@ define("utils/NodeUtils", function (require, exports, module) {
178426178581 exports.removeDeviceLicenseSystemWide = removeDeviceLicenseSystemWide;
178427178582 exports.isLicensedDeviceSystemWide = isLicensedDeviceSystemWide;
178428178583 exports.getDeviceID = getDeviceID;
178584+ exports.getOSUserName = getOSUserName;
178585+ exports.getSystemSettingsDir = getSystemSettingsDir;
178429178586
178430178587 /**
178431178588 * checks if Node connector is ready
0 commit comments