From d086f0d3583162420b785976ff830c8d4a22ba50 Mon Sep 17 00:00:00 2001 From: r3352 Date: Sun, 5 Apr 2026 20:48:52 -0400 Subject: [PATCH 1/2] Add permission check to ExecuteProcess and ExecuteProcessReturnOutput API functions --- java/sage/Permissions.java | 3 ++- java/sage/api/Utility.java | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/java/sage/Permissions.java b/java/sage/Permissions.java index de5737a09..244e1ed33 100644 --- a/java/sage/Permissions.java +++ b/java/sage/Permissions.java @@ -40,12 +40,13 @@ public class Permissions public static final String PERMISSION_STUDIO = "Studio"; public static final String PERMISSION_LIVETV = "LiveTV"; public static final String PERMISSION_PICTUREROTATION = "PictureRotation"; + public static final String PERMISSION_EXECUTEPROCESS = "ExecuteProcess"; public static final String[] PREDEFINED_PERMISSIONS = new String[] { PERMISSION_WATCHEDTRACKING, PERMISSION_RECORDINGSCHEDULE, PERMISSION_DELETE, PERMISSION_SYSTEMMESSAGE, PERMISSION_EDITMETADATA, PERMISSION_ARCHIVE, PERMISSION_PLAYLIST, PERMISSION_CONVERSION, PERMISSION_SAVEONLINEVIDEO, PERMISSION_UICONFIGURATION, PERMISSION_SECURITY, PERMISSION_GENERALSETUP, PERMISSION_FILESYSTEM, - PERMISSION_STUDIO, PERMISSION_LIVETV, PERMISSION_PICTUREROTATION + PERMISSION_STUDIO, PERMISSION_LIVETV, PERMISSION_PICTUREROTATION, PERMISSION_EXECUTEPROCESS }; private Permissions() diff --git a/java/sage/api/Utility.java b/java/sage/api/Utility.java index a06e258b8..4689892cf 100644 --- a/java/sage/api/Utility.java +++ b/java/sage/api/Utility.java @@ -1282,6 +1282,8 @@ else if (v1 instanceof Long || v2 instanceof Long) * @declaration public Process ExecuteProcess(String CommandString, Object Arguments, java.io.File WorkingDirectory, boolean ConsoleApp); */ public Object runSafely(Catbert.FastStack stack) throws Exception{ + if (!Permissions.hasPermission(Permissions.PERMISSION_EXECUTEPROCESS, stack.getUIMgr())) + return null; boolean consumeIO = evalBool(stack.pop()); java.io.File wd = getFile(stack); Object args = stack.pop(); @@ -1369,6 +1371,8 @@ public void run() * @declaration public String ExecuteProcessReturnOutput(String CommandString, Object Arguments, java.io.File WorkingDirectory, boolean ReturnStdout, boolean ReturnStderr); */ public Object runSafely(Catbert.FastStack stack) throws Exception{ + if (!Permissions.hasPermission(Permissions.PERMISSION_EXECUTEPROCESS, stack.getUIMgr())) + return null; final boolean retStderr = evalBool(stack.pop()); final boolean retStdout = evalBool(stack.pop()); java.io.File wd = getFile(stack); From 84dd07fcde45efcece09eacafc2a583565f1f477 Mon Sep 17 00:00:00 2001 From: e54323 <144442042+r3352@users.noreply.github.com> Date: Fri, 17 Apr 2026 18:43:31 -0400 Subject: [PATCH 2/2] Rework: gate ExecuteProcess via property in recvAction instead of Permissions Replaces the Permissions-based approach with a property check (security/block_remote_process_execution) in SageTVConnection.recvAction(). Defaults to false so existing behavior is unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) --- java/sage/Permissions.java | 3 +-- java/sage/SageTVConnection.java | 9 +++++++++ java/sage/api/Utility.java | 4 ---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/java/sage/Permissions.java b/java/sage/Permissions.java index 244e1ed33..de5737a09 100644 --- a/java/sage/Permissions.java +++ b/java/sage/Permissions.java @@ -40,13 +40,12 @@ public class Permissions public static final String PERMISSION_STUDIO = "Studio"; public static final String PERMISSION_LIVETV = "LiveTV"; public static final String PERMISSION_PICTUREROTATION = "PictureRotation"; - public static final String PERMISSION_EXECUTEPROCESS = "ExecuteProcess"; public static final String[] PREDEFINED_PERMISSIONS = new String[] { PERMISSION_WATCHEDTRACKING, PERMISSION_RECORDINGSCHEDULE, PERMISSION_DELETE, PERMISSION_SYSTEMMESSAGE, PERMISSION_EDITMETADATA, PERMISSION_ARCHIVE, PERMISSION_PLAYLIST, PERMISSION_CONVERSION, PERMISSION_SAVEONLINEVIDEO, PERMISSION_UICONFIGURATION, PERMISSION_SECURITY, PERMISSION_GENERALSETUP, PERMISSION_FILESYSTEM, - PERMISSION_STUDIO, PERMISSION_LIVETV, PERMISSION_PICTUREROTATION, PERMISSION_EXECUTEPROCESS + PERMISSION_STUDIO, PERMISSION_LIVETV, PERMISSION_PICTUREROTATION }; private Permissions() diff --git a/java/sage/SageTVConnection.java b/java/sage/SageTVConnection.java index a36f70b48..660f9ed5c 100644 --- a/java/sage/SageTVConnection.java +++ b/java/sage/SageTVConnection.java @@ -1122,6 +1122,15 @@ private Msg recvAction(Msg myMsg) throws java.io.IOException MyDataOutput dos = new MyDataOutput(baos); try { + // Optional hardening: block remote process execution when the property is set + if (Sage.getBoolean("security/block_remote_process_execution", false) && + ("ExecuteProcess".equals(methodName) || "ExecuteProcessReturnOutput".equals(methodName))) + { + if (Sage.DBG) System.out.println("Blocked remote " + methodName + " call (security/block_remote_process_execution=true)"); + dos.writeBoolean(false); + writeObjectToStream(null, dos); + return new Msg(RESPONSE_MSG, myMsg.type, baos.toByteArray(), myMsg.id); + } String uiContext = null; if (Sage.client) { diff --git a/java/sage/api/Utility.java b/java/sage/api/Utility.java index 4689892cf..a06e258b8 100644 --- a/java/sage/api/Utility.java +++ b/java/sage/api/Utility.java @@ -1282,8 +1282,6 @@ else if (v1 instanceof Long || v2 instanceof Long) * @declaration public Process ExecuteProcess(String CommandString, Object Arguments, java.io.File WorkingDirectory, boolean ConsoleApp); */ public Object runSafely(Catbert.FastStack stack) throws Exception{ - if (!Permissions.hasPermission(Permissions.PERMISSION_EXECUTEPROCESS, stack.getUIMgr())) - return null; boolean consumeIO = evalBool(stack.pop()); java.io.File wd = getFile(stack); Object args = stack.pop(); @@ -1371,8 +1369,6 @@ public void run() * @declaration public String ExecuteProcessReturnOutput(String CommandString, Object Arguments, java.io.File WorkingDirectory, boolean ReturnStdout, boolean ReturnStderr); */ public Object runSafely(Catbert.FastStack stack) throws Exception{ - if (!Permissions.hasPermission(Permissions.PERMISSION_EXECUTEPROCESS, stack.getUIMgr())) - return null; final boolean retStderr = evalBool(stack.pop()); final boolean retStdout = evalBool(stack.pop()); java.io.File wd = getFile(stack);