Rework terminal launches

This commit is contained in:
crschnick
2024-09-28 15:09:47 +00:00
parent 5b9649d0b6
commit 37b1e627b7
23 changed files with 214 additions and 128 deletions
@@ -28,7 +28,7 @@ public class SshLaunchExchangeImpl extends SshLaunchExchange {
// There are sometimes multiple requests by a terminal client (e.g. Termius)
// This might fail sometimes, but it is expected
var r = TerminalLauncherManager.waitForNextLaunch();
var r = TerminalLauncherManager.sshLaunchExchange();
var c = ProcessControlProvider.get()
.getEffectiveLocalDialect()
.getOpenScriptCommand(r.toString())
@@ -9,7 +9,7 @@ import com.sun.net.httpserver.HttpExchange;
public class TerminalLaunchExchangeImpl extends TerminalLaunchExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException {
var r = TerminalLauncherManager.performLaunch(msg.getRequest());
var r = TerminalLauncherManager.launchExchange(msg.getRequest());
return Response.builder().targetFile(r).build();
}
@@ -10,7 +10,7 @@ import com.sun.net.httpserver.HttpExchange;
public class TerminalWaitExchangeImpl extends TerminalWaitExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException, BeaconServerException {
TerminalLauncherManager.waitForCompletion(msg.getRequest());
TerminalLauncherManager.waitExchange(msg.getRequest());
return Response.builder().build();
}
@@ -130,7 +130,7 @@ public class AppLayoutModel {
var now = Instant.now();
var zone = ZoneId.of(ZoneId.SHORT_IDS.get("PST"));
var phStart = ZonedDateTime.of(2024, 10, 22, 0, 1, 0, 0, zone).toInstant();
var phEnd = ZonedDateTime.of(2024, 10, 22, 23, 59, 0, 0, zone).toInstant();
var phEnd = ZonedDateTime.of(2024, 10, 29, 23, 59, 0, 0, zone).toInstant();
var phShow = now.isAfter(phStart) && now.isBefore(phEnd);
if (phShow) {
l.add(new Entry(
@@ -36,6 +36,8 @@ public class AppPrefs {
private static AppPrefs INSTANCE;
private final List<Mapping<?>> mapping = new ArrayList<>();
final BooleanProperty dontAllowTerminalRestart =
mapVaultSpecific(new SimpleBooleanProperty(false), "dontAllowTerminalRestart", Boolean.class);
final BooleanProperty enableHttpApi =
mapVaultSpecific(new SimpleBooleanProperty(false), "enableHttpApi", Boolean.class);
final BooleanProperty dontAutomaticallyStartVmSshServer =
@@ -153,6 +155,10 @@ public class AppPrefs {
return enableHttpApi;
}
public ObservableBooleanValue dontAllowTerminalRestart() {
return dontAllowTerminalRestart;
}
private final IntegerProperty editorReloadTimeout =
map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class);
private final BooleanProperty confirmDeletions =
@@ -28,7 +28,10 @@ public class SecurityCategory extends AppPrefsCategory {
.nameAndDescription("dontAutomaticallyStartVmSshServer")
.addToggle(prefs.dontAutomaticallyStartVmSshServer)
.nameAndDescription("disableTerminalRemotePasswordPreparation")
.addToggle(prefs.disableTerminalRemotePasswordPreparation));
.addToggle(prefs.disableTerminalRemotePasswordPreparation)
.nameAndDescription("dontAllowTerminalRestart")
.addToggle(prefs.dontAllowTerminalRestart)
);
return builder.buildComp();
}
}
@@ -114,6 +114,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
}
@Override
public String getWebsite() {
return "https://www.netsarang.com/en/xshell/";
}
@Override
public boolean supportsTabs() {
return true;
@@ -213,6 +218,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return false;
}
@Override
public String getWebsite() {
return "https://www.vandyke.com/products/securecrt/";
}
@Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
try (var sc = LocalShell.getShell()) {
@@ -261,6 +271,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return true;
}
@Override
public String getWebsite() {
return "https://mobaxterm.mobatek.net/";
}
@Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
try (var sc = LocalShell.getShell()) {
@@ -316,6 +331,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
}
@Override
public String getWebsite() {
return "https://termius.com/";
}
@Override
public boolean supportsTabs() {
return true;
@@ -15,6 +15,7 @@ import lombok.Setter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Pattern;
@Getter
public class SshLocalBridge {
@@ -109,8 +110,7 @@ public class SshLocalBridge {
}
var config = INSTANCE.getConfig();
var command = "\"" + XPipeInstallation.getLocalDefaultCliExecutable() + "\" ssh-launch "
+ sc.getShellDialect().environmentVariable("SSH_ORIGINAL_COMMAND");
var command = get().getRemoteCommand(sc);
var pidFile = bridgeDir.resolve("sshd.pid");
var content =
"""
@@ -148,6 +148,18 @@ public class SshLocalBridge {
}
}
private String getRemoteCommand(ShellControl sc) {
var command = "\"" + XPipeInstallation.getLocalDefaultCliExecutable() + "\" ssh-launch "
+ sc.getShellDialect().environmentVariable("SSH_ORIGINAL_COMMAND");
var p = Pattern.compile("\".+?\\\\Users\\\\([^\\\\]+)\\\\(.+)\"");
var matcher = p.matcher(command);
if (matcher.find() && matcher.group(1).contains(" ")) {
return matcher.replaceFirst("\"$2\"");
} else {
return command;
}
}
private void updateConfig() throws IOException {
var file = Path.of(System.getProperty("user.home"), ".ssh", "config");
if (!Files.exists(file)) {
@@ -0,0 +1,87 @@
package io.xpipe.app.util;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.core.process.*;
import io.xpipe.core.store.FilePath;
import lombok.Setter;
import lombok.Value;
import lombok.experimental.NonFinal;
import java.nio.file.Path;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
@Value
public class TerminalLaunchRequest {
UUID request;
ProcessControl processControl;
TerminalInitScriptConfig config;
String workingDirectory;
@Setter
@NonFinal
TerminalLaunchResult result;
@Setter
@NonFinal
boolean setupCompleted;
public Path waitForCompletion() throws BeaconServerException {
while (true) {
if (getResult() == null) {
ThreadHelper.sleep(10);
continue;
}
var r = getResult();
if (r instanceof TerminalLaunchResult.ResultFailure failure) {
var t = failure.getThrowable();
throw new BeaconServerException(t);
}
return ((TerminalLaunchResult.ResultSuccess) r).getTargetScript();
}
}
public CountDownLatch setupRequestAsync() {
var latch = new CountDownLatch(1);
ThreadHelper.runAsync(() -> {
setupRequest();
latch.countDown();
});
return latch;
}
public void setupRequest() {
var wd = new WorkingDirectoryFunction() {
@Override
public boolean isFixed() {
return true;
}
@Override
public boolean isSpecified() {
return workingDirectory != null;
}
@Override
public FilePath apply(ShellControl shellControl) {
if (workingDirectory == null) {
return null;
}
return new FilePath(workingDirectory);
}
};
try {
var file = ScriptHelper.createLocalExecScript(processControl.prepareTerminalOpen(config, wd));
setResult(new TerminalLaunchResult.ResultSuccess(Path.of(file.toString())));
} catch (Exception e) {
setResult(new TerminalLaunchResult.ResultFailure(e));
}
}
}
@@ -0,0 +1,18 @@
package io.xpipe.app.util;
import lombok.Value;
import java.nio.file.Path;
public interface TerminalLaunchResult {
@Value
public static class ResultSuccess implements TerminalLaunchResult {
Path targetScript;
}
@Value
public static class ResultFailure implements TerminalLaunchResult {
Throwable throwable;
}
}
@@ -1,126 +1,71 @@
package io.xpipe.app.util;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
import io.xpipe.core.process.ProcessControl;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.TerminalInitScriptConfig;
import io.xpipe.core.process.WorkingDirectoryFunction;
import io.xpipe.core.store.FilePath;
import lombok.Setter;
import lombok.Value;
import lombok.experimental.NonFinal;
import java.nio.file.Path;
import java.util.*;
import java.util.LinkedHashMap;
import java.util.SequencedMap;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
public class TerminalLauncherManager {
private static final SequencedMap<UUID, Entry> entries = new LinkedHashMap<>();
private static void prepare(
ProcessControl processControl, TerminalInitScriptConfig config, String directory, Entry entry) {
var workingDirectory = new WorkingDirectoryFunction() {
@Override
public boolean isFixed() {
return true;
}
@Override
public boolean isSpecified() {
return directory != null;
}
@Override
public FilePath apply(ShellControl shellControl) {
if (directory == null) {
return null;
}
return new FilePath(directory);
}
};
try {
var file = ScriptHelper.createLocalExecScript(processControl.prepareTerminalOpen(config, workingDirectory));
entry.setResult(new ResultSuccess(Path.of(file.toString())));
} catch (Exception e) {
entry.setResult(new ResultFailure(e));
}
}
private static final SequencedMap<UUID, TerminalLaunchRequest> entries = new LinkedHashMap<>();
public static CountDownLatch submitAsync(
UUID request, ProcessControl processControl, TerminalInitScriptConfig config, String directory) {
UUID request, ProcessControl processControl, TerminalInitScriptConfig config, String directory) throws
BeaconClientException {
synchronized (entries) {
var entry = entries.get(request);
if (entry == null) {
entry = new Entry(request, processControl, config, directory, null, false);
entries.put(request, entry);
var req = entries.get(request);
if (req == null) {
req = new TerminalLaunchRequest(request, processControl, config, directory, null, false);
entries.put(request, req);
} else {
entry.setResult(null);
req.setResult(null);
}
var latch = new CountDownLatch(1);
Entry finalEntry = entry;
ThreadHelper.runAsync(() -> {
prepare(processControl, config, directory, finalEntry);
latch.countDown();
});
return latch;
return req.setupRequestAsync();
}
}
public static Path waitForNextLaunch() throws BeaconClientException, BeaconServerException {
Entry first;
public static Path sshLaunchExchange() throws BeaconClientException, BeaconServerException {
TerminalLaunchRequest last;
synchronized (entries) {
first = entries.values().stream()
.filter(entry -> !entry.isLaunched())
.findFirst()
.orElse(null);
if (first == null) {
var all = entries.values().stream().toList();
last = !all.isEmpty() ? all.getLast() : null;
if (last == null) {
throw new BeaconClientException("Unknown launch request");
}
}
return waitForCompletion(first);
return last.waitForCompletion();
}
public static Path waitForCompletion(UUID request) throws BeaconClientException, BeaconServerException {
Entry e;
public static Path waitExchange(UUID request) throws BeaconClientException, BeaconServerException {
TerminalLaunchRequest req;
synchronized (entries) {
e = entries.get(request);
req = entries.get(request);
}
if (e == null) {
if (req == null) {
throw new BeaconClientException("Unknown launch request " + request);
}
if (e.isLaunched()) {
submitAsync(e.getRequest(), e.getProcessControl(), e.getConfig(), e.getWorkingDirectory());
if (req.isSetupCompleted() && AppPrefs.get().dontAllowTerminalRestart().get()) {
throw new BeaconClientException("Terminal session restarts have been disabled in the security settings");
}
return waitForCompletion(e);
}
public static Path waitForCompletion(Entry e) throws BeaconServerException {
while (true) {
if (e.result == null) {
ThreadHelper.sleep(10);
continue;
}
synchronized (entries) {
var r = e.getResult();
e.setLaunched(true);
if (r instanceof ResultFailure failure) {
var t = failure.getThrowable();
throw new BeaconServerException(t);
}
return ((ResultSuccess) r).getTargetScript();
}
if (req.isSetupCompleted()) {
submitAsync(req.getRequest(), req.getProcessControl(), req.getConfig(), req.getWorkingDirectory());
}
try {
return req.waitForCompletion();
} finally {
req.setSetupCompleted(true);
}
}
public static Path performLaunch(UUID request) throws BeaconClientException {
public static Path launchExchange(UUID request) throws BeaconClientException {
synchronized (entries) {
var e = entries.values().stream()
.filter(entry -> entry.getRequest().equals(request))
@@ -130,40 +75,11 @@ public class TerminalLauncherManager {
throw new BeaconClientException("Unknown launch request " + request);
}
if (!(e.result instanceof ResultSuccess)) {
if (!(e.getResult() instanceof TerminalLaunchResult.ResultSuccess)) {
throw new BeaconClientException("Invalid launch request state " + request);
}
return ((ResultSuccess) e.getResult()).getTargetScript();
return ((TerminalLaunchResult.ResultSuccess) e.getResult()).getTargetScript();
}
}
public interface Result {}
@Value
public static class Entry {
UUID request;
ProcessControl processControl;
TerminalInitScriptConfig config;
String workingDirectory;
@Setter
@NonFinal
Result result;
@Setter
@NonFinal
boolean launched;
}
@Value
public static class ResultSuccess implements Result {
Path targetScript;
}
@Value
public static class ResultFailure implements Result {
Throwable throwable;
}
}
@@ -526,3 +526,5 @@ asktextAlertTitle=Spørg
fileWriteSudoTitle=Sudo filskrivning
fileWriteSudoHeader=Den fil, du forsøger at skrive, kræver root-rettigheder. Vil du skrive denne fil med sudo?
fileWriteSudoContent=Dette vil automatisk hæve sig til root med enten de angivne legitimationsoplysninger eller via en prompt.
dontAllowTerminalRestart=Tillad ikke genstart af terminal
dontAllowTerminalRestartDescription=Som standard kan terminalsessioner genstartes, når de er afsluttet inde fra terminalen. For at tillade dette accepterer XPipe disse eksterne anmodninger fra terminalen om at starte sessionen igen\n\nXPipe har ingen kontrol over terminalen, og hvor dette opkald kommer fra, så ondsindede lokale programmer kan også bruge denne funktion til at starte forbindelser gennem XPipe. Ved at deaktivere denne funktion forhindres dette scenarie.
@@ -520,3 +520,5 @@ asktextAlertTitle=Eingabeaufforderung
fileWriteSudoTitle=Sudo-Datei schreiben
fileWriteSudoHeader=Die Datei, die du zu schreiben versuchst, erfordert Root-Rechte. Willst du diese Datei mit sudo schreiben?
fileWriteSudoContent=Dadurch wird automatisch ein Root-Zugang eingerichtet, entweder mit den angegebenen Anmeldedaten oder über eine Eingabeaufforderung.
dontAllowTerminalRestart=Terminal-Neustart nicht zulassen
dontAllowTerminalRestartDescription=Standardmäßig können Terminalsitzungen neu gestartet werden, nachdem sie vom Terminal aus beendet wurden. Um dies zu ermöglichen, akzeptiert XPipe diese externen Anfragen vom Terminal, um die Sitzung erneut zu starten\n\nXPipe hat keine Kontrolle über das Terminal und darüber, woher dieser Aufruf kommt. Daher können böswillige lokale Anwendungen diese Funktion ebenfalls nutzen, um Verbindungen über XPipe zu starten. Die Deaktivierung dieser Funktion verhindert dieses Szenario.
+3 -1
View File
@@ -525,4 +525,6 @@ red=Red
asktextAlertTitle=Prompt
fileWriteSudoTitle=Sudo file write
fileWriteSudoHeader=The file you are trying to write requires root privileges. Do you want to write this file with sudo?
fileWriteSudoContent=This will automatically elevate to root with either the provided credentials or via a prompt.
fileWriteSudoContent=This will automatically elevate to root with either the provided credentials or via a prompt.
dontAllowTerminalRestart=Don't allow terminal restart
dontAllowTerminalRestartDescription=By default, terminal sessions can be restarted after they ended from within the terminal. To allow this, XPipe will accept these external requests from the terminal to launch the session again\n\nXPipe doesn't have any control over the terminal and where this call comes from, so malicious local applications can use this functionality as well to launch connections through XPipe. Disabling this functionality prevents this scenario.
@@ -507,3 +507,5 @@ asktextAlertTitle=Pregunta
fileWriteSudoTitle=Escritura de archivos Sudo
fileWriteSudoHeader=El archivo que intentas escribir requiere privilegios de root. ¿Quieres escribir este archivo con sudo?
fileWriteSudoContent=Esto elevará automáticamente a root con las credenciales proporcionadas o a través de un prompt.
dontAllowTerminalRestart=No permitir el reinicio del terminal
dontAllowTerminalRestartDescription=Por defecto, las sesiones de terminal pueden reiniciarse una vez finalizadas desde dentro del terminal. Para permitirlo, XPipe aceptará estas peticiones externas del terminal para iniciar de nuevo la sesión\n\nXPipe no tiene ningún control sobre el terminal y de dónde procede esta llamada, por lo que las aplicaciones locales maliciosas también pueden utilizar esta funcionalidad para lanzar conexiones a través de XPipe. Deshabilitar esta funcionalidad evita este escenario.
@@ -507,3 +507,5 @@ asktextAlertTitle=Invite
fileWriteSudoTitle=Sudo file write
fileWriteSudoHeader=Le fichier que tu essaies d'écrire nécessite les privilèges de root. Veux-tu écrire ce fichier avec sudo ?
fileWriteSudoContent=Cette opération permet d'accéder automatiquement à la fonction de super-utilisateur à l'aide des informations d'identification fournies ou d'un message d'invite.
dontAllowTerminalRestart=Ne pas autoriser le redémarrage du terminal
dontAllowTerminalRestartDescription=Par défaut, les sessions de terminal peuvent être relancées après s'être terminées depuis le terminal. Pour permettre cela, XPipe acceptera ces demandes externes du terminal pour relancer la session\n\nXPipe n'a aucun contrôle sur le terminal et sur la provenance de cet appel, de sorte que des applications locales malveillantes peuvent également utiliser cette fonctionnalité pour lancer des connexions par l'intermédiaire de XPipe. La désactivation de cette fonctionnalité permet d'éviter ce scénario.
@@ -507,3 +507,5 @@ asktextAlertTitle=Prompt
fileWriteSudoTitle=Scrittura di file Sudo
fileWriteSudoHeader=Il file che stai cercando di scrivere richiede i privilegi di root. Vuoi scrivere questo file con sudo?
fileWriteSudoContent=In questo modo si eleva automaticamente a root con le credenziali fornite o tramite un prompt.
dontAllowTerminalRestart=Non consentire il riavvio del terminale
dontAllowTerminalRestartDescription=Per impostazione predefinita, le sessioni del terminale possono essere riavviate dopo la loro conclusione dall'interno del terminale stesso. Per consentire ciò, XPipe accetterà le seguenti richieste esterne dal terminale per avviare nuovamente la sessione\n\nXPipe non ha alcun controllo sul terminale e sulla provenienza di questa chiamata, quindi anche le applicazioni locali malintenzionate possono utilizzare questa funzionalità per avviare connessioni attraverso XPipe. Disabilitando questa funzionalità si evita questo scenario.
@@ -507,3 +507,5 @@ asktextAlertTitle=プロンプト
fileWriteSudoTitle=須藤ファイル書き込み
fileWriteSudoHeader=書き込もうとしているファイルにはroot権限が必要だ。このファイルをsudoで書き込むか?
fileWriteSudoContent=これは、提供された認証情報またはプロンプト経由で自動的にrootに昇格する。
dontAllowTerminalRestart=端末の再起動を許可しない
dontAllowTerminalRestartDescription=デフォルトでは、ターミナル・セッションはターミナル内から終了後に再開することができる。これを可能にするため、XPipeはターミナルからセッションを再び起動するための以下の外部リクエストを受け付ける。\n\nXPipeはターミナルとこの呼び出しの発信元を制御できないため、悪意のあるローカルアプリケーションはこの機能を使用してXPipe経由で接続を開始することができる。この機能を無効にすることで、このシナリオを防ぐことができる。
@@ -507,3 +507,5 @@ asktextAlertTitle=Prompt
fileWriteSudoTitle=Sudo bestand schrijven
fileWriteSudoHeader=Het bestand dat je probeert te schrijven vereist rootrechten. Wil je dit bestand schrijven met sudo?
fileWriteSudoContent=Dit zal automatisch verheffen naar root met de verstrekte inloggegevens of via een prompt.
dontAllowTerminalRestart=Terminal opnieuw opstarten niet toestaan
dontAllowTerminalRestartDescription=Standaard kunnen terminalsessies opnieuw worden gestart nadat ze vanuit de terminal zijn beëindigd. Om dit mogelijk te maken, accepteert XPipe deze externe verzoeken van de terminal om de sessie opnieuw te starten\n\nXPipe heeft geen controle over de terminal en waar deze oproep vandaan komt, dus kwaadwillende lokale applicaties kunnen deze functionaliteit ook gebruiken om verbindingen via XPipe te starten. Het uitschakelen van deze functionaliteit voorkomt dit scenario.
@@ -507,3 +507,5 @@ asktextAlertTitle=Prompt
fileWriteSudoTitle=Escreve um ficheiro Sudo
fileWriteSudoHeader=O ficheiro que estás a tentar escrever requer privilégios de root. Queres escrever este ficheiro com sudo?
fileWriteSudoContent=Isto irá elevar automaticamente para a raiz com as credenciais fornecidas ou através de um prompt.
dontAllowTerminalRestart=Não permitir o reinício do terminal
dontAllowTerminalRestartDescription=Por defeito, as sessões de terminal podem ser reiniciadas depois de terminarem a partir do terminal. Para permitir isso, o XPipe aceitará essas solicitações externas do terminal para iniciar a sessão novamente\n\nO XPipe não tem qualquer controlo sobre o terminal e sobre a origem desta chamada, pelo que as aplicações locais maliciosas também podem utilizar esta funcionalidade para iniciar ligações através do XPipe. Desativar esta funcionalidade evita este cenário.
@@ -507,3 +507,5 @@ asktextAlertTitle=Prompt
fileWriteSudoTitle=Запись файла Sudo
fileWriteSudoHeader=Файл, который ты пытаешься записать, требует привилегий root. Хочешь ли ты записать этот файл с помощью sudo?
fileWriteSudoContent=Это автоматически повысит статус до root либо с помощью предоставленных учетных данных, либо через приглашение.
dontAllowTerminalRestart=Не разрешайте перезагрузку терминала
dontAllowTerminalRestartDescription=По умолчанию терминальные сессии могут быть перезапущены после их завершения изнутри терминала. Чтобы разрешить это, XPipe будет принимать такие внешние запросы от терминала, чтобы снова запустить сессию\n\nXPipe не имеет никакого контроля над терминалом и тем, откуда поступает этот вызов, поэтому вредоносные локальные приложения могут использовать эту функциональность и для запуска соединений через XPipe. Отключение этой функциональности предотвращает подобный сценарий.
@@ -508,3 +508,5 @@ asktextAlertTitle=İstem
fileWriteSudoTitle=Sudo dosya yazma
fileWriteSudoHeader=Yazmaya çalıştığınız dosya root ayrıcalıkları gerektiriyor. Bu dosyayı sudo ile mi yazmak istiyorsunuz?
fileWriteSudoContent=Bu, sağlanan kimlik bilgileriyle veya bir komut istemi aracılığıyla otomatik olarak kök dizine yükseltme yapacaktır.
dontAllowTerminalRestart=Terminalin yeniden başlatılmasına izin verme
dontAllowTerminalRestartDescription=Varsayılan olarak, terminal oturumları terminal içinden sonlandırıldıktan sonra yeniden başlatılabilir. Buna izin vermek için XPipe, oturumu tekrar başlatmak üzere terminalden gelen şu harici istekleri kabul edecektir\n\nXPipe terminal ve bu çağrının nereden geldiği üzerinde herhangi bir kontrole sahip değildir, bu nedenle kötü niyetli yerel uygulamalar XPipe üzerinden bağlantı başlatmak için bu işlevi de kullanabilir. Bu işlevselliğin devre dışı bırakılması bu senaryoyu önler.
@@ -507,3 +507,5 @@ asktextAlertTitle=提示
fileWriteSudoTitle=Sudo 文件写入
fileWriteSudoHeader=您要写入的文件需要 root 权限。你想用 sudo 来写这个文件吗?
fileWriteSudoContent=这将使用提供的凭据或通过提示自动提升为根用户。
dontAllowTerminalRestart=不允许终端重启
dontAllowTerminalRestartDescription=默认情况下,终端会话可以在终端内部结束后重新启动。为了做到这一点,XPipe 将接受来自终端的这些外部请求,以再次启动会话\n\nXPipe无法控制终端以及该调用的来源,因此恶意本地应用程序也可以使用该功能通过XPipe启动连接。禁用该功能可防止出现这种情况。