diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 0218f4cb8c83..c2bae49d78cd 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,7 +1,6 @@
-
+
-
-
+
\ No newline at end of file
diff --git a/platform/analysis-impl/resources/META-INF/AnalysisImpl.xml b/platform/analysis-impl/resources/META-INF/AnalysisImpl.xml
index a9a5013ba539..a49c627f3d8d 100644
--- a/platform/analysis-impl/resources/META-INF/AnalysisImpl.xml
+++ b/platform/analysis-impl/resources/META-INF/AnalysisImpl.xml
@@ -37,6 +37,8 @@
serviceImplementation="com.intellij.codeInsight.completion.BaseCompletionService"/>
+
diff --git a/platform/ide-core-impl/src/com/intellij/util/OpenIdePluginUtilImpl.kt b/platform/ide-core-impl/src/com/intellij/util/OpenIdePluginUtilImpl.kt
new file mode 100644
index 000000000000..4c32ac1688c0
--- /dev/null
+++ b/platform/ide-core-impl/src/com/intellij/util/OpenIdePluginUtilImpl.kt
@@ -0,0 +1,20 @@
+// Copyright (c) Haulmont 2025. All Rights Reserved.
+// Use is subject to license terms.
+package com.intellij.util
+
+import com.intellij.ide.plugins.PluginManagerCore
+import com.intellij.ide.plugins.PluginUtil
+import com.intellij.openapi.extensions.PluginId
+import ru.openide.io.OpenIdePluginUtil
+
+class OpenIdePluginUtilImpl : OpenIdePluginUtil {
+ override fun findNonBundledPluginId(t: Throwable): PluginId? {
+ val pluginId = PluginUtil.getInstance().findPluginId(t) ?: return null
+ val idString = pluginId.idString
+ if (idString == "ru.openide.docker" || idString == "com.haulmont.amplicode") return pluginId
+
+ val pluginDescriptor = PluginManagerCore.getPlugin(pluginId) ?: return null
+ if (pluginDescriptor.isBundled) return null
+ return pluginId
+ }
+}
\ No newline at end of file
diff --git a/platform/ide-core/resources/messages/OpenIdeBundle.properties b/platform/ide-core/resources/messages/OpenIdeBundle.properties
new file mode 100644
index 000000000000..407ee845f5dc
--- /dev/null
+++ b/platform/ide-core/resources/messages/OpenIdeBundle.properties
@@ -0,0 +1,4 @@
+# Copyright (c) Haulmont 2025. All Rights Reserved.
+# Use is subject to license terms.
+access.to.untrusted.source=Access to an untrusted source
+allow.access=Allow access
\ No newline at end of file
diff --git a/platform/ide-core/resources/messages/OpenIdeBundle_ru_RU.properties b/platform/ide-core/resources/messages/OpenIdeBundle_ru_RU.properties
new file mode 100644
index 000000000000..a0156964d643
--- /dev/null
+++ b/platform/ide-core/resources/messages/OpenIdeBundle_ru_RU.properties
@@ -0,0 +1,2 @@
+access.to.untrusted.source=Доступ к неразрешенному источнику
+allow.access=Разрешить доступ
\ No newline at end of file
diff --git a/platform/ide-core/src/com/intellij/util/io/HttpRequests.java b/platform/ide-core/src/com/intellij/util/io/HttpRequests.java
index ed95e9cf75d1..76e70fbaa1e0 100644
--- a/platform/ide-core/src/com/intellij/util/io/HttpRequests.java
+++ b/platform/ide-core/src/com/intellij/util/io/HttpRequests.java
@@ -21,6 +21,8 @@ import com.intellij.util.Url;
import com.intellij.util.net.NetUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import ru.openide.io.StubUrlConnection;
+import ru.openide.io.WhiteListUrls;
import javax.net.ssl.*;
import java.io.*;
diff --git a/platform/ide-core/src/ru/openide/io/OpenIdeBundle.kt b/platform/ide-core/src/ru/openide/io/OpenIdeBundle.kt
new file mode 100644
index 000000000000..a217a373553c
--- /dev/null
+++ b/platform/ide-core/src/ru/openide/io/OpenIdeBundle.kt
@@ -0,0 +1,19 @@
+// Copyright (c) Haulmont 2025. All Rights Reserved.
+// Use is subject to license terms.
+package ru.openide.io
+
+import com.intellij.DynamicBundle
+import org.jetbrains.annotations.Nls
+import org.jetbrains.annotations.NonNls
+import org.jetbrains.annotations.PropertyKey
+
+@NonNls
+private const val BUNDLE = "messages.OpenIdeBundle"
+
+object OpenIdeBundle {
+ private val INSTANCE = DynamicBundle(OpenIdeBundle::class.java, BUNDLE)
+
+ fun message(key: @PropertyKey(resourceBundle = BUNDLE) String, vararg params: Any): @Nls String {
+ return INSTANCE.getMessage(key, *params)
+ }
+}
\ No newline at end of file
diff --git a/platform/ide-core/src/ru/openide/io/OpenIdePersistentUrlStorage.kt b/platform/ide-core/src/ru/openide/io/OpenIdePersistentUrlStorage.kt
new file mode 100644
index 000000000000..b1fdab2923db
--- /dev/null
+++ b/platform/ide-core/src/ru/openide/io/OpenIdePersistentUrlStorage.kt
@@ -0,0 +1,27 @@
+// Copyright (c) Haulmont 2025. All Rights Reserved.
+// Use is subject to license terms.
+package ru.openide.io
+
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.components.*
+import com.intellij.util.xmlb.annotations.CollectionBean
+
+@Service(Service.Level.APP)
+@State(name = "OpenIdePersistentUrlStorage", storages = [Storage("openIdeUrlStorage.xml")])
+class OpenIdePersistentUrlStorage: SimplePersistentStateComponent(PersistentUrlStorageState()) {
+
+ companion object {
+ fun getInstance(): OpenIdePersistentUrlStorage {
+ return ApplicationManager.getApplication().getService(OpenIdePersistentUrlStorage::class.java)
+ }
+ }
+
+ fun getUrls(): MutableList {
+ return state.urls
+ }
+}
+
+class PersistentUrlStorageState : BaseState() {
+ @get:CollectionBean
+ val urls: MutableList by list()
+}
\ No newline at end of file
diff --git a/platform/ide-core/src/ru/openide/io/OpenIdePluginUtil.kt b/platform/ide-core/src/ru/openide/io/OpenIdePluginUtil.kt
new file mode 100644
index 000000000000..f4fe5ba51aa3
--- /dev/null
+++ b/platform/ide-core/src/ru/openide/io/OpenIdePluginUtil.kt
@@ -0,0 +1,16 @@
+// Copyright (c) Haulmont 2025. All Rights Reserved.
+// Use is subject to license terms.
+package ru.openide.io
+
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.extensions.PluginId
+
+interface OpenIdePluginUtil {
+ companion object {
+ fun getInstance(): OpenIdePluginUtil {
+ return ApplicationManager.getApplication().getService(OpenIdePluginUtil::class.java)
+ }
+ }
+
+ fun findNonBundledPluginId(t: Throwable): PluginId?
+}
\ No newline at end of file
diff --git a/platform/ide-core/src/com/intellij/util/io/WhiteListUrls.kt b/platform/ide-core/src/ru/openide/io/WhiteListUrls.kt
similarity index 90%
rename from platform/ide-core/src/com/intellij/util/io/WhiteListUrls.kt
rename to platform/ide-core/src/ru/openide/io/WhiteListUrls.kt
index ffbcc7e5d2f2..1829f537cc64 100644
--- a/platform/ide-core/src/com/intellij/util/io/WhiteListUrls.kt
+++ b/platform/ide-core/src/ru/openide/io/WhiteListUrls.kt
@@ -1,7 +1,10 @@
// Copyright (c) Haulmont 2024. All Rights Reserved.
// Use is subject to license terms.
-package com.intellij.util.io
+package ru.openide.io
+import com.intellij.notification.Notification
+import com.intellij.notification.NotificationAction
+import com.intellij.notification.NotificationType
import java.net.URL
import java.net.URLConnection
@@ -171,7 +174,29 @@ object WhiteListUrls {
@JvmStatic
fun isAvailableUrl(url: String): Boolean {
- return urls.startWith(url) || jsonSchemaUrls.startWith(url)
+ if (urls.startWith(url)
+ || jsonSchemaUrls.startWith(url)
+ || OpenIdePersistentUrlStorage.getInstance().getUrls().startWith(url)) {
+ return true
+ }
+
+ val findPluginId = OpenIdePluginUtil.getInstance().findNonBundledPluginId(Throwable())
+ if (findPluginId != null) {
+ return true
+ }
+
+ showAccessRequestNotification(url)
+ return false
+ }
+
+ private fun showAccessRequestNotification(url: String) {
+ val addUrlAction = NotificationAction.createSimpleExpiring(OpenIdeBundle.message("allow.access")) {
+ OpenIdePersistentUrlStorage.getInstance().getUrls().add(url)
+ }
+
+ Notification("Find Problems", OpenIdeBundle.message("access.to.untrusted.source"), url, NotificationType.WARNING)
+ .addAction(addUrlAction)
+ .notify(null)
}
private fun List.startWith(url: String): Boolean {