diff --git a/README.md b/README.md
index 30cd5de..5cb60ac 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ For adding a library only:
com.instancify.scriptifycore
- 1.3.0-SNAPSHOT
+ 1.3.1-SNAPSHOT
```
@@ -26,12 +26,12 @@ For adding a library with JS for Rhino or GraalVM:
com.instancify.scriptifyscript-js-rhino
- 1.3.0-SNAPSHOT
+ 1.3.1-SNAPSHOTcom.instancify.scriptifyscript-js-graalvm
- 1.3.0-SNAPSHOT
+ 1.3.1-SNAPSHOT
```
## Gradle
@@ -45,11 +45,11 @@ maven {
For adding a library only:
```groovy
-implementation "com.instancify.scriptify:core:1.3.0-SNAPSHOT"
+implementation "com.instancify.scriptify:core:1.3.1-SNAPSHOT"
```
For adding a library with JS for Rhino or GraalVM:
```groovy
-implementation "com.instancify.scriptify:script-js-rhino:1.3.0-SNAPSHOT"
-implementation "com.instancify.scriptify:script-js-graalvm:1.3.0-SNAPSHOT"
+implementation "com.instancify.scriptify:script-js-rhino:1.3.1-SNAPSHOT"
+implementation "com.instancify.scriptify:script-js-graalvm:1.3.1-SNAPSHOT"
```
\ No newline at end of file
diff --git a/api/src/main/java/com/instancify/scriptify/api/script/Script.java b/api/src/main/java/com/instancify/scriptify/api/script/Script.java
index 0240a0b..fb38b95 100644
--- a/api/src/main/java/com/instancify/scriptify/api/script/Script.java
+++ b/api/src/main/java/com/instancify/scriptify/api/script/Script.java
@@ -4,6 +4,7 @@
import com.instancify.scriptify.api.exception.ScriptFunctionException;
import com.instancify.scriptify.api.script.constant.ScriptConstantManager;
import com.instancify.scriptify.api.script.function.ScriptFunctionManager;
+import com.instancify.scriptify.api.script.security.ScriptSecurityManager;
/**
* Defines the structure of a script that can be executed.
@@ -44,6 +45,14 @@ public interface Script {
*/
void setConstantManager(ScriptConstantManager constantManager);
+ /**
+ * Retrieves the security manager associated with this script.
+ *
+ * @return The ScriptSecurityManager for this script
+ * @see ScriptSecurityManager
+ */
+ ScriptSecurityManager getSecurityManager();
+
/**
* Evaluates and executes this script.
*
diff --git a/api/src/main/java/com/instancify/scriptify/api/script/security/ScriptSecurityManager.java b/api/src/main/java/com/instancify/scriptify/api/script/security/ScriptSecurityManager.java
new file mode 100644
index 0000000..ed6c3bd
--- /dev/null
+++ b/api/src/main/java/com/instancify/scriptify/api/script/security/ScriptSecurityManager.java
@@ -0,0 +1,52 @@
+package com.instancify.scriptify.api.script.security;
+
+import com.instancify.scriptify.api.script.constant.ScriptConstantManager;
+import com.instancify.scriptify.api.script.security.exclude.SecurityExclude;
+
+import java.util.Set;
+
+public interface ScriptSecurityManager {
+
+ /**
+ * Gets a current security mode.
+ *
+ * @return The boolean associated with security mode state
+ */
+ boolean getSecurityMode();
+
+ /**
+ * Sets the current security mode.
+ *
+ * @param securityMode The boolean associated with security mode state
+ * @see ScriptConstantManager
+ */
+ void setSecurityMode(boolean securityMode);
+
+ /**
+ * Receives security path accessor.
+ *
+ * @return Security path accessor
+ */
+ SecurityPathAccessor getPathAccessor();
+
+ /**
+ * Retrieves all existing exclusions for this script.
+ *
+ * @return Set with exclusions
+ */
+ Set getExcludes();
+
+ /**
+ * Adds an exclusion for a path or package.
+ *
+ * @param exclude The exclusion to be added
+ */
+ void addExclude(SecurityExclude exclude);
+
+ /**
+ * Removes an exclusion for a path or package.
+ *
+ * @param exclude The exclusion to remove
+ */
+ void removeExclude(SecurityExclude exclude);
+}
diff --git a/api/src/main/java/com/instancify/scriptify/api/script/security/SecurityClassAccessor.java b/api/src/main/java/com/instancify/scriptify/api/script/security/SecurityClassAccessor.java
new file mode 100644
index 0000000..a8f4a26
--- /dev/null
+++ b/api/src/main/java/com/instancify/scriptify/api/script/security/SecurityClassAccessor.java
@@ -0,0 +1,23 @@
+package com.instancify.scriptify.api.script.security;
+
+import java.util.Set;
+
+/**
+ * Manages class access based on security constraints, ensuring only allowed classes can be used.
+ */
+public interface SecurityClassAccessor {
+
+ /**
+ * Retrieves the set of class names that are allowed to be accessed or used.
+ *
+ * @return A set of strings representing the names of allowed classes
+ */
+ Set getAllowedClasses();
+
+ /**
+ * Adds a class to the list of allowed classes, which can then be used or accessed.
+ *
+ * @param allowedClass The name of the class to be added to the allowed list
+ */
+ void addAllowedClass(String allowedClass);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/instancify/scriptify/api/script/security/SecurityPathAccessor.java b/api/src/main/java/com/instancify/scriptify/api/script/security/SecurityPathAccessor.java
new file mode 100644
index 0000000..1a11b16
--- /dev/null
+++ b/api/src/main/java/com/instancify/scriptify/api/script/security/SecurityPathAccessor.java
@@ -0,0 +1,40 @@
+package com.instancify.scriptify.api.script.security;
+
+import java.nio.file.Path;
+
+/**
+ * Manages path access based on security constraints, ensuring only safe paths are accessible.
+ */
+public interface SecurityPathAccessor {
+
+ /**
+ * Gets a base path for this accessor, which will be used for relative path calculations.
+ *
+ * @return The base path to set
+ */
+ Path getBasePath();
+
+ /**
+ * Sets a new base path for this accessor, which will be used for relative path calculations.
+ *
+ * @param basePath The new base path to set
+ */
+ void setBasePath(Path basePath);
+
+ /**
+ * Returns a path that is safe to access according to security rules. If the path is not accessible,
+ * it returns a path relative to the base path with ':' characters removed to prevent potential path traversal attacks.
+ *
+ * @param path The path string to be checked and possibly modified
+ * @return A Path object representing the accessible path or a sanitized version if not accessible
+ */
+ Path getAccessiblePath(String path);
+
+ /**
+ * Checks if the given path is accessible based on the current security settings.
+ *
+ * @param path The path to check for access permission
+ * @return true if the path is accessible, false otherwise
+ */
+ boolean isAccessible(String path);
+}
diff --git a/api/src/main/java/com/instancify/scriptify/api/script/security/exclude/ClassSecurityExclude.java b/api/src/main/java/com/instancify/scriptify/api/script/security/exclude/ClassSecurityExclude.java
new file mode 100644
index 0000000..118c770
--- /dev/null
+++ b/api/src/main/java/com/instancify/scriptify/api/script/security/exclude/ClassSecurityExclude.java
@@ -0,0 +1,15 @@
+package com.instancify.scriptify.api.script.security.exclude;
+
+public class ClassSecurityExclude implements SecurityExclude {
+
+ private final Class> value;
+
+ public ClassSecurityExclude(Class> value) {
+ this.value = value;
+ }
+
+ @Override
+ public String getValue() {
+ return value.getName();
+ }
+}
diff --git a/api/src/main/java/com/instancify/scriptify/api/script/security/exclude/PathSecurityExclude.java b/api/src/main/java/com/instancify/scriptify/api/script/security/exclude/PathSecurityExclude.java
new file mode 100644
index 0000000..0c5e516
--- /dev/null
+++ b/api/src/main/java/com/instancify/scriptify/api/script/security/exclude/PathSecurityExclude.java
@@ -0,0 +1,15 @@
+package com.instancify.scriptify.api.script.security.exclude;
+
+public class PathSecurityExclude implements SecurityExclude {
+
+ private final String value;
+
+ public PathSecurityExclude(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/api/src/main/java/com/instancify/scriptify/api/script/security/exclude/SecurityExclude.java b/api/src/main/java/com/instancify/scriptify/api/script/security/exclude/SecurityExclude.java
new file mode 100644
index 0000000..1b14e79
--- /dev/null
+++ b/api/src/main/java/com/instancify/scriptify/api/script/security/exclude/SecurityExclude.java
@@ -0,0 +1,42 @@
+package com.instancify.scriptify.api.script.security.exclude;
+
+public interface SecurityExclude {
+
+ /**
+ * Value to be added to the exclusion.
+ *
+ * @return The exclusion value
+ */
+ String getValue();
+
+ /**
+ * Checks that the value of path or packet is excluded.
+ *
+ * @param value Path or package
+ * @return True if excluded, otherwise false
+ */
+ default boolean isExcluded(String value) {
+ // Check that the path starts from the path specified in the exclusion
+ return value.startsWith(this.getValue());
+ }
+
+ /**
+ * Creates a new exclusion instance for the class.
+ *
+ * @param value A class that will be excluded
+ * @return A new exclusion instance for the class
+ */
+ static ClassSecurityExclude ofClass(Class> value) {
+ return new ClassSecurityExclude(value);
+ }
+
+ /**
+ * Creates a new exclusion instance for the path.
+ *
+ * @param value A path that will be excluded
+ * @return A new exclusion instance for the path
+ */
+ static PathSecurityExclude ofPath(String value) {
+ return new PathSecurityExclude(value);
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 260ca2a..e7a022c 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -12,7 +12,7 @@ java {
allprojects {
group = "com.instancify.scriptify"
- version = "1.3.1-SNAPSHOT"
+ version = "1.3.2-SNAPSHOT"
}
subprojects {
diff --git a/core/src/main/java/com/instancify/scriptify/core/script/function/impl/file/ScriptFunctionWriteFile.java b/core/src/main/java/com/instancify/scriptify/core/script/function/impl/file/ScriptFunctionWriteFile.java
index 69c881d..8b84678 100644
--- a/core/src/main/java/com/instancify/scriptify/core/script/function/impl/file/ScriptFunctionWriteFile.java
+++ b/core/src/main/java/com/instancify/scriptify/core/script/function/impl/file/ScriptFunctionWriteFile.java
@@ -10,7 +10,6 @@
import java.io.IOException;
import java.nio.file.Files;
-import java.nio.file.Path;
/**
* Represents a function to write the contents of a file
@@ -27,7 +26,7 @@ public Object invoke(Script> script, ScriptFunctionArgument[] args) throws Scr
if (args.length == 2) {
if (args[0].getValue() instanceof String filePath && args[1].getValue() instanceof String fileContent) {
try {
- return Files.writeString(Path.of(filePath), fileContent);
+ return Files.writeString(script.getSecurityManager().getPathAccessor().getAccessiblePath(filePath), fileContent);
} catch (IOException e) {
throw new ScriptFunctionException(e);
}
diff --git a/core/src/main/java/com/instancify/scriptify/core/script/function/impl/os/ScriptFunctionExecCommand.java b/core/src/main/java/com/instancify/scriptify/core/script/function/impl/os/ScriptFunctionExecCommand.java
index 1a8c884..bb9cef3 100644
--- a/core/src/main/java/com/instancify/scriptify/core/script/function/impl/os/ScriptFunctionExecCommand.java
+++ b/core/src/main/java/com/instancify/scriptify/core/script/function/impl/os/ScriptFunctionExecCommand.java
@@ -31,23 +31,23 @@ public Object invoke(Script> script, ScriptFunctionArgument[] args) throws Scr
if (!(args[0].getValue() instanceof String input)) {
throw new ScriptFunctionArgTypeException(String.class, args[0].getType());
}
+
try {
Process process = Runtime.getRuntime().exec(input);
- BufferedReader stdInput = new BufferedReader(new
- InputStreamReader(process.getInputStream()));
- BufferedReader stdError = new BufferedReader(new
- InputStreamReader(process.getErrorStream()));
-
- String message = "";
- String buff = null;
- while ((buff = stdInput.readLine()) != null) {
- message += buff + "\n";
- }
- while ((buff = stdError.readLine()) != null) {
- message += buff + "\n";
- }
- return message;
+ StringBuilder message = new StringBuilder();
+ try (BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
+
+ String line;
+ while ((line = stdInput.readLine()) != null) {
+ message.append(line).append("\n");
+ }
+ while ((line = stdError.readLine()) != null) {
+ message.append(line).append("\n");
+ }
+ }
+ return message.toString();
} catch (IOException e) {
throw new ScriptFunctionException(e);
}
diff --git a/core/src/main/java/com/instancify/scriptify/core/script/security/SecurityPathAccessorImpl.java b/core/src/main/java/com/instancify/scriptify/core/script/security/SecurityPathAccessorImpl.java
new file mode 100644
index 0000000..3fd1bfd
--- /dev/null
+++ b/core/src/main/java/com/instancify/scriptify/core/script/security/SecurityPathAccessorImpl.java
@@ -0,0 +1,97 @@
+package com.instancify.scriptify.core.script.security;
+
+import com.instancify.scriptify.api.script.security.ScriptSecurityManager;
+import com.instancify.scriptify.api.script.security.SecurityPathAccessor;
+import com.instancify.scriptify.api.script.security.exclude.PathSecurityExclude;
+import com.instancify.scriptify.api.script.security.exclude.SecurityExclude;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Manages path access based on security constraints, ensuring only safe paths are accessible.
+ */
+public class SecurityPathAccessorImpl implements SecurityPathAccessor {
+
+ private final ScriptSecurityManager securityManager;
+ private Path basePath;
+
+ /**
+ * Constructs a SecurityPathAccessor with the default base path set to the current working directory.
+ *
+ * @param securityManager The security manager to check access permissions
+ */
+ public SecurityPathAccessorImpl(ScriptSecurityManager securityManager) {
+ this(securityManager, Paths.get("").toAbsolutePath());
+ }
+
+ /**
+ * Constructs a SecurityPathAccessor with a specified base path for relative path calculations.
+ *
+ * @param securityManager The security manager to check access permissions
+ * @param basePath The base path from which relative paths are calculated
+ */
+ public SecurityPathAccessorImpl(ScriptSecurityManager securityManager, Path basePath) {
+ this.securityManager = securityManager;
+ this.basePath = basePath;
+ }
+
+ /**
+ * Gets a base path for this accessor, which will be used for relative path calculations.
+ *
+ * @return The base path to set
+ */
+ @Override
+ public Path getBasePath() {
+ return basePath;
+ }
+
+ /**
+ * Sets a new base path for this accessor, which will be used for relative path calculations.
+ *
+ * @param basePath The new base path to set
+ */
+ @Override
+ public void setBasePath(Path basePath) {
+ this.basePath = basePath;
+ }
+
+ /**
+ * Returns a path that is safe to access according to security rules. If the path is not accessible,
+ * it returns a path relative to the base path with ':' characters removed to prevent potential path traversal attacks.
+ *
+ * @param path The path string to be checked and possibly modified
+ * @return A Path object representing the accessible path or a sanitized version if not accessible
+ */
+ @Override
+ public Path getAccessiblePath(String path) {
+ if (this.isAccessible(path)) {
+ return Path.of(path);
+ }
+ return Path.of(basePath.toString(), path.replaceAll(":", ""));
+ }
+
+ /**
+ * Checks if the given path is accessible based on the current security settings.
+ *
+ * @param path The path to check for access permission
+ * @return true if the path is accessible, false otherwise
+ */
+ @Override
+ public boolean isAccessible(String path) {
+ if (!securityManager.getSecurityMode()) {
+ return true;
+ }
+
+ // Search all exclusions and check that the path is excluded
+ for (SecurityExclude exclude : securityManager.getExcludes()) {
+ if (exclude instanceof PathSecurityExclude) {
+ if (exclude.isExcluded(path)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/core/src/main/java/com/instancify/scriptify/core/script/security/StandardSecurityManager.java b/core/src/main/java/com/instancify/scriptify/core/script/security/StandardSecurityManager.java
new file mode 100644
index 0000000..20dbb66
--- /dev/null
+++ b/core/src/main/java/com/instancify/scriptify/core/script/security/StandardSecurityManager.java
@@ -0,0 +1,45 @@
+package com.instancify.scriptify.core.script.security;
+
+import com.instancify.scriptify.api.script.security.ScriptSecurityManager;
+import com.instancify.scriptify.api.script.security.SecurityPathAccessor;
+import com.instancify.scriptify.api.script.security.exclude.SecurityExclude;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class StandardSecurityManager implements ScriptSecurityManager {
+
+ private boolean securityMode;
+ private final Set excludes = new HashSet<>();
+ private final SecurityPathAccessor pathAccessor = new SecurityPathAccessorImpl(this);
+
+ @Override
+ public boolean getSecurityMode() {
+ return securityMode;
+ }
+
+ @Override
+ public void setSecurityMode(boolean securityMode) {
+ this.securityMode = securityMode;
+ }
+
+ @Override
+ public SecurityPathAccessor getPathAccessor() {
+ return pathAccessor;
+ }
+
+ @Override
+ public Set getExcludes() {
+ return excludes;
+ }
+
+ @Override
+ public void addExclude(SecurityExclude exclude) {
+ excludes.add(exclude);
+ }
+
+ @Override
+ public void removeExclude(SecurityExclude exclude) {
+ excludes.remove(exclude);
+ }
+}
diff --git a/script-js-graalvm/src/main/java/com/instancify/scriptify/script/JsScript.java b/script-js-graalvm/src/main/java/com/instancify/scriptify/script/JsScript.java
index 8801278..041ce37 100644
--- a/script-js-graalvm/src/main/java/com/instancify/scriptify/script/JsScript.java
+++ b/script-js-graalvm/src/main/java/com/instancify/scriptify/script/JsScript.java
@@ -6,14 +6,22 @@
import com.instancify.scriptify.api.script.constant.ScriptConstantManager;
import com.instancify.scriptify.api.script.function.ScriptFunction;
import com.instancify.scriptify.api.script.function.ScriptFunctionManager;
+import com.instancify.scriptify.api.script.security.ScriptSecurityManager;
+import com.instancify.scriptify.core.script.security.StandardSecurityManager;
import org.graalvm.polyglot.*;
public class JsScript implements Script {
private final Context context = Context.create();
+ private final ScriptSecurityManager securityManager = new StandardSecurityManager();
private ScriptFunctionManager functionManager;
private ScriptConstantManager constantManager;
+ @Override
+ public ScriptSecurityManager getSecurityManager() {
+ return securityManager;
+ }
+
@Override
public ScriptFunctionManager getFunctionManager() {
return functionManager;
diff --git a/script-js-rhino/src/main/java/com/instancify/scriptify/script/JsScript.java b/script-js-rhino/src/main/java/com/instancify/scriptify/script/JsScript.java
index 5175383..031b80f 100644
--- a/script-js-rhino/src/main/java/com/instancify/scriptify/script/JsScript.java
+++ b/script-js-rhino/src/main/java/com/instancify/scriptify/script/JsScript.java
@@ -6,16 +6,25 @@
import com.instancify.scriptify.api.script.constant.ScriptConstantManager;
import com.instancify.scriptify.api.script.function.ScriptFunction;
import com.instancify.scriptify.api.script.function.ScriptFunctionManager;
+import com.instancify.scriptify.api.script.security.ScriptSecurityManager;
+import com.instancify.scriptify.api.script.security.exclude.ClassSecurityExclude;
+import com.instancify.scriptify.api.script.security.exclude.SecurityExclude;
+import com.instancify.scriptify.core.script.security.StandardSecurityManager;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptableObject;
public class JsScript implements Script