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
This commit is contained in:
Ilya.Kazakevich
2026-06-01 21:59:53 +00:00
committed by intellij-monorepo-bot
parent fb18825b0a
commit c45e93b7c2
4 changed files with 46 additions and 1 deletions
+4
View File
@@ -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
@@ -56,5 +56,7 @@
<orderEntry type="module" module-name="intellij.platform.execution.impl" />
<orderEntry type="module" module-name="intellij.platform.eel.impl" />
<orderEntry type="module" module-name="intellij.platform.eel.impl.base" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.projectModel.impl" />
</component>
</module>
@@ -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
@@ -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<Process, ExecErrorReason.CantStart> {
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<Path> = 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() }
}