From c45e93b7c22b919274e934aeeb7e244ca9516a89 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Thu, 28 May 2026 23:11:27 +0200 Subject: [PATCH] PY-89964: Do not execute anything in untrusted projects. There are 0 or more opened projects in the Platform. Each of which might be trusrted or untrusted. For untrusted project, its basePath (whatever it means) and roots of all its modules are called "prohibited". We shouldn't start anything if it sits in on the aforementioned "prohibited" paths. GitOrigin-RevId: 9a7c2da916bd95eaf07a72a6cfa83593cf1ec205 --- python/python-exec-service/BUILD.bazel | 4 ++ .../intellij.python.community.execService.iml | 2 + .../messages/PyExecBundle.properties | 1 + .../execService/impl/processLaunchers/eel.kt | 40 ++++++++++++++++++- 4 files changed, 46 insertions(+), 1 deletion(-) 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