diff --git a/python/python-exec-service/BUILD.bazel b/python/python-exec-service/BUILD.bazel
index 0129af03bb53..30bcaa3df969 100644
--- a/python/python-exec-service/BUILD.bazel
+++ b/python/python-exec-service/BUILD.bazel
@@ -36,6 +36,8 @@ jvm_library(
"//platform/execution-impl",
"//platform/eel-impl",
"//platform/eel-impl-base",
+ "//platform/platform-impl:ide-impl",
+ "//platform/projectModel-impl",
]
)
@@ -79,6 +81,8 @@ jvm_library(
"//platform/execution-impl:execution-impl_test_lib",
"//platform/eel-impl:eel-impl_test_lib",
"//platform/eel-impl-base:eel-impl-base_test_lib",
+ "//platform/platform-impl:ide-impl_test_lib",
+ "//platform/projectModel-impl:projectModel-impl_test_lib",
]
)
### auto-generated section `build intellij.python.community.execService` end
diff --git a/python/python-exec-service/intellij.python.community.execService.iml b/python/python-exec-service/intellij.python.community.execService.iml
index 9dcd93137c37..b1c3cb14267c 100644
--- a/python/python-exec-service/intellij.python.community.execService.iml
+++ b/python/python-exec-service/intellij.python.community.execService.iml
@@ -56,5 +56,7 @@
+
+
\ No newline at end of file
diff --git a/python/python-exec-service/resources/messages/PyExecBundle.properties b/python/python-exec-service/resources/messages/PyExecBundle.properties
index 423864eaa03a..d860a4351573 100644
--- a/python/python-exec-service/resources/messages/PyExecBundle.properties
+++ b/python/python-exec-service/resources/messages/PyExecBundle.properties
@@ -7,5 +7,6 @@ py.exec.fileNotFound=File {0} not found on {1}
py.exec.error.not.zero=Exit code is not zero
py.exec.error.unexpected.output=Unexpected output {0}
py.exec.target.name.default="Local"
+py.exec.error.not.trusted=Could not start {0}: project is not trusted
py.exec.target.binaries.are.not.supported=Target binaries are not supported
diff --git a/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/eel.kt b/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/eel.kt
index b68621ffa0fd..425110edf749 100644
--- a/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/eel.kt
+++ b/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/eel.kt
@@ -1,24 +1,37 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.execService.impl.processLaunchers
+import com.intellij.ide.trustedProjects.TrustedProjects
+import com.intellij.openapi.diagnostic.fileLogger
+import com.intellij.openapi.diagnostic.trace
+import com.intellij.openapi.project.ProjectManager
import com.intellij.platform.eel.EelExecApi
import com.intellij.platform.eel.EelProcess
import com.intellij.platform.eel.ExecuteProcessException
import com.intellij.platform.eel.impl.base.ProcessFunctions
import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.provider.asEelPath
+import com.intellij.platform.eel.provider.asNioPath
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.platform.eel.provider.toEelApi
import com.intellij.platform.eel.provider.utils.EelPathUtils
import com.intellij.platform.eel.spawnProcess
+import com.intellij.project.stateStore
import com.intellij.python.community.execService.BinOnEel
import com.intellij.python.community.execService.TtySize
+import com.intellij.python.community.execService.impl.PyExecBundle
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.Exe
import com.jetbrains.python.errorProcessing.ExecErrorReason
+import com.jetbrains.python.sdk.getModuleRoots
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.nio.file.Path
import kotlin.io.path.pathString
+private val log = fileLogger()
+
internal suspend fun createProcessLauncherOnEel(binOnEel: BinOnEel, launchRequest: LaunchRequest): ProcessLauncher {
val exePath: EelPath = with(binOnEel) {
(if (path.isAbsolute) path else workDir?.resolve(binOnEel.path) ?: path.toAbsolutePath()).asEelPath()
@@ -61,8 +74,23 @@ private class EelProcessCommands(
override suspend fun start(): Result {
var workDir = binOnEel.workDir
- workDir = if (workDir != null && !workDir.isAbsolute) workDir.toRealPath() else workDir
+ workDir = withContext(Dispatchers.IO) { if (workDir != null && !workDir.isAbsolute) workDir.toRealPath() else workDir }
+
+ // If project is untrusted we should not execute anything there
+ val nioPathToExec = withContext(Dispatchers.IO) {
+ path.asNioPath().toAbsolutePath()
+ }
+ val pathIsProhibited = getProhibitedPaths().any { prohibitedParent ->
+ nioPathToExec.startsWith(prohibitedParent) ||
+ (workDir != null && workDir.startsWith(prohibitedParent))
+ }
+ if (pathIsProhibited) {
+ log.trace { "Prohibited exec $nioPathToExec" }
+ return Result.failure(ExecErrorReason.CantStart(null, PyExecBundle.message("py.exec.error.not.trusted", nioPathToExec)))
+ }
+
try {
+ log.trace { "Spawning $nioPathToExec" }
val eelProcess = path.descriptor.toEelApi().exec.spawnProcess(path)
.scope(scopeToBind)
.args(args)
@@ -78,3 +106,13 @@ private class EelProcessCommands(
}
}
}
+
+/**
+ * List of roots of all untrusted projects
+ */
+private suspend fun getProhibitedPaths(): List = withContext(Dispatchers.Default) {
+ val untrustedProjects = ProjectManager.getInstance().openProjects.filter { !TrustedProjects.isProjectTrusted(it) }
+ return@withContext untrustedProjects.flatMap { project ->
+ setOf(project.stateStore.projectBasePath) + project.getModuleRoots().map { it.toNioPath() }
+ }.map { it.toAbsolutePath() }
+}
\ No newline at end of file