diff --git a/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt b/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt index dde977b..ad1b22c 100644 --- a/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt +++ b/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt @@ -284,9 +284,10 @@ DEFAULT_JVM_OPTS="\"-javaagent:${"$"}APP_HOME/agent-libs/simple-agent.jar\" \"-X assertTrue(applicationDistributionScript.readText().contains(expectedWindowsDefaultJvmOpts)) } else { // The option string rides inside the agent's own backslash-escaped quoted token -- see issue #95. + // Option values are wrapped in single quotes to prevent shell injection (issue #50). val expectedDefaultJavaOpts = """ -DEFAULT_JVM_OPTS="\"-javaagent:${"$"}APP_HOME/agent-libs/simple-agent.jar=12345:config.yaml\" \"-Xmx256m\"" +DEFAULT_JVM_OPTS="\"-javaagent:${"$"}APP_HOME/agent-libs/simple-agent.jar='12345:config.yaml'\" \"-Xmx256m\"" """ assertTrue(applicationDistributionScript.readText().contains(expectedDefaultJavaOpts)) } diff --git a/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt b/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt index 3c7819e..900e842 100644 --- a/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt +++ b/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt @@ -33,6 +33,7 @@ class JavaagentAwareStartScriptGenerator( platform.appHomeVar, platform.agentArgSeparator, optionsByFilePath, + platform, ), ) } @@ -62,6 +63,7 @@ class JavaagentAwareStartScriptGenerator( private val appHomeVar: String, private val agentArgSeparator: String, private val optionsByFilePath: Provider>, + private val platform: Platform, ) : Writer() { override fun close() { inner.close() @@ -79,6 +81,17 @@ class JavaagentAwareStartScriptGenerator( inner.write(cbuf, off, len) } + /** + * Escapes a javaagent option value for safe inclusion in shell scripts. + * - For Unix: wraps in single quotes, escaping internal single quotes with '\'' + * - For Windows: doubles percent signs to prevent variable expansion + */ + private fun escapeOptionValue(value: String): String = + when (platform) { + Platform.UNIX -> "'" + value.replace("'", "'\\''") + "'" + Platform.WINDOWS -> value.replace("%", "%%") + } + /** * Rewrites the javaagent placeholder that the templated `defaultJvmOpts` injects into the rendered start * script. When the configuration resolves to one or more agents, the single placeholder is replaced by one @@ -112,7 +125,7 @@ class JavaagentAwareStartScriptGenerator( files.joinToString( agentArgSeparator, ) { jar -> - val suffix = options[jar.canonicalPath]?.let { "=$it" } ?: "" + val suffix = options[jar.canonicalPath]?.let { "=" + escapeOptionValue(it) } ?: "" "-javaagent:$appHomeVar${pathSeparator}agent-libs$pathSeparator${jar.name}$suffix" }, )