CVE-2026-35582
Description
Emissary is a P2P based data-driven workflow engine. In versions 8.42.0 and below, Executrix.getCommand() is vulnerable to OS command injection because it interpolates temporary file paths into a /bin/sh -c shell command string without any escaping or input validation. The IN_FILE_ENDING and OUT_FILE_ENDING configuration keys flow directly into these paths, allowing a place author who can write or modify a .cfg file to inject arbitrary shell metacharacters that execute OS commands in the JVM process's security context. The framework already sanitizes placeName via an allowlist before embedding it in the same shell string, but applies no equivalent sanitization to file ending values. No runtime privileges beyond place configuration authorship, and no API or network access, are required to exploit this vulnerability. This is a framework-level defect with no safe mitigation available to downstream implementors, as Executrix provides neither escaping nor documented preconditions against metacharacters in file ending inputs. This issue has been fixed in version 8.43.0.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
gov.nsa.emissary:emissaryMaven | < 8.43.0 | 8.43.0 |
Affected products
1Patches
11faf33f2494cMerge commit from fork
2 files changed · +117 −4
src/main/java/emissary/util/shell/Executrix.java+20 −4 modified@@ -77,6 +77,12 @@ public enum OUTPUT_TYPE { protected static final Pattern INVALID_PLACE_NAME_CHARS = Pattern.compile("[^a-zA-Z0-9_-]"); + /** + * Allowlist for file ending values. A leading dot is permitted so that normal extensions like {@code .out} are accepted + * unchanged; every other character must be alphanumeric, underscore, or hyphen. + */ + protected static final Pattern INVALID_FILE_ENDING_CHARS = Pattern.compile("[^a-zA-Z0-9_.\\-]"); + /** * Create using all defaults */ @@ -118,8 +124,8 @@ protected void configure(@Nullable final Configurator configGArg) { final Configurator configG = (configGArg != null) ? configGArg : new ServiceConfigGuide(); this.command = configG.findStringEntry("EXEC_COMMAND", "echo 'YouForGotToSetEXEC_COMMAND' | tee bla.txt"); - this.inFileEnding = configG.findStringEntry("IN_FILE_ENDING", ""); - this.outFileEnding = configG.findStringEntry("OUT_FILE_ENDING", this.inFileEnding.isEmpty() ? ".out" : ""); + this.inFileEnding = cleanFileEnding(configG.findStringEntry("IN_FILE_ENDING", "")); + this.outFileEnding = cleanFileEnding(configG.findStringEntry("OUT_FILE_ENDING", this.inFileEnding.isEmpty() ? ".out" : "")); this.output = configG.findStringEntry("OUTPUT_TYPE", "STD"); this.order = configG.findStringEntry("ORDER", "NORMAL"); this.numArgs = configG.findStringEntry("NUM_ARGS", ""); @@ -149,6 +155,16 @@ protected static String cleanPlaceName(final String placeName) { return INVALID_PLACE_NAME_CHARS.matcher(placeName).replaceAll("_"); } + /** + * Remove invalid characters from a file ending value, see {@link Executrix#INVALID_FILE_ENDING_CHARS} + * + * @param fileEnding the raw file ending (e.g. {@code .out}) + * @return a cleaned string with shell-unsafe characters replaced by {@code _} + */ + protected static String cleanFileEnding(final String fileEnding) { + return INVALID_FILE_ENDING_CHARS.matcher(fileEnding).replaceAll("_"); + } + /** * Creates a set of temp file names (does not do any disk activity) * @@ -1174,7 +1190,7 @@ public String getInFileEnding() { * @param argInFileEnding Value to assign to this.inFileEnding */ public void setInFileEnding(final String argInFileEnding) { - this.inFileEnding = argInFileEnding; + this.inFileEnding = cleanFileEnding(argInFileEnding); } /** @@ -1192,7 +1208,7 @@ public String getOutFileEnding() { * @param argOutFileEnding Value to assign to this.outFileEnding */ public void setOutFileEnding(final String argOutFileEnding) { - this.outFileEnding = argOutFileEnding; + this.outFileEnding = cleanFileEnding(argOutFileEnding); } /**
src/test/java/emissary/util/shell/ExecutrixTest.java+97 −0 modified@@ -1,5 +1,7 @@ package emissary.util.shell; +import emissary.config.Configurator; +import emissary.config.ServiceConfigGuide; import emissary.test.core.junit5.UnitTest; import jakarta.annotation.Nullable; @@ -603,6 +605,101 @@ void testCleanPlaceName() { assertEquals("PLACE-NAME-1", Executrix.cleanPlaceName("PLACE-NAME-1")); } + // ----------------------------------------------------------------------- + // cleanFileEnding – covers INVALID_FILE_ENDING_CHARS and cleanFileEnding() + // ----------------------------------------------------------------------- + + @Test + void testCleanFileEndingAllowedCharsPassThrough() { + // Normal extensions must survive unchanged + assertEquals(".out", Executrix.cleanFileEnding(".out")); + assertEquals(".txt", Executrix.cleanFileEnding(".txt")); + assertEquals("", Executrix.cleanFileEnding("")); + assertEquals(".out-1_a", Executrix.cleanFileEnding(".out-1_a")); + assertEquals("ABC123._-", Executrix.cleanFileEnding("ABC123._-")); + } + + @Test + void testCleanFileEndingShellMetacharacters() { + // Every shell-special character must be individually replaced with '_' + assertEquals("_", Executrix.cleanFileEnding("`")); + assertEquals("_", Executrix.cleanFileEnding("$")); + assertEquals("_", Executrix.cleanFileEnding(";")); + assertEquals("_", Executrix.cleanFileEnding("&")); + assertEquals("_", Executrix.cleanFileEnding("|")); + assertEquals("_", Executrix.cleanFileEnding(">")); + assertEquals("_", Executrix.cleanFileEnding("<")); + assertEquals("_", Executrix.cleanFileEnding("!")); + assertEquals("_", Executrix.cleanFileEnding(" ")); + assertEquals("_", Executrix.cleanFileEnding("(")); + assertEquals("_", Executrix.cleanFileEnding(")")); + assertEquals("_", Executrix.cleanFileEnding("'")); + assertEquals("_", Executrix.cleanFileEnding("\"")); + assertEquals("_", Executrix.cleanFileEnding("\\")); + assertEquals("_", Executrix.cleanFileEnding("/")); + assertEquals("_", Executrix.cleanFileEnding("\n")); + assertEquals("_", Executrix.cleanFileEnding("\t")); + } + + @Test + void testCleanFileEndingDoesNotStripHyphenOrUnderscore() { + // Hyphen and underscore are safe in file extensions and must not be replaced + assertEquals(".tar.gz", Executrix.cleanFileEnding(".tar.gz")); + assertEquals(".out-v2", Executrix.cleanFileEnding(".out-v2")); + assertEquals(".out_v2", Executrix.cleanFileEnding(".out_v2")); + } + + // ----------------------------------------------------------------------- + // setInFileEnding / setOutFileEnding – sanitization enforced by setters + // ----------------------------------------------------------------------- + + @Test + void testSetFileEndingsNormalValuesUnchanged() { + // Legitimate extensions must not be altered by the setters + e.setInFileEnding(".in"); + assertEquals(".in", e.getInFileEnding()); + + e.setOutFileEnding(".out"); + assertEquals(".out", e.getOutFileEnding()); + } + + @Test + void testSetFileEndingsSanitizeInjectionPayloads() { + e.setInFileEnding("`id`"); + assertEquals("_id_", e.getInFileEnding()); + + e.setOutFileEnding(";rm -rf /"); + assertEquals("_rm_-rf__", e.getOutFileEnding()); + } + + // ----------------------------------------------------------------------- + // configure() path – sanitization applied when reading from Configurator + // ----------------------------------------------------------------------- + + @Test + void testConfigureFileEndingsSanitizedFromConfig() { + // Injection payloads in config must be neutralized at construction time + final Configurator cfg = new ServiceConfigGuide(); + cfg.addEntry("IN_FILE_ENDING", "`id > /tmp/pwned.txt`"); + cfg.addEntry("OUT_FILE_ENDING", "$(whoami)"); + + final Executrix ex = new Executrix(cfg); + assertEquals("_id____tmp_pwned.txt_", ex.getInFileEnding()); + assertEquals("__whoami_", ex.getOutFileEnding()); + } + + @Test + void testConfigureNormalFileEndingsUnchanged() { + // Normal extensions must pass through configure() without modification + final Configurator cfg = new ServiceConfigGuide(); + cfg.addEntry("IN_FILE_ENDING", ".xml"); + cfg.addEntry("OUT_FILE_ENDING", ".json"); + + final Executrix ex = new Executrix(cfg); + assertEquals(".xml", ex.getInFileEnding()); + assertEquals(".json", ex.getOutFileEnding()); + } + private static void readAndNuke(final String name) throws IOException { final File f = new File(name); assertTrue(f.exists(), "File " + name + " must exist");
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/NationalSecurityAgency/emissary/commit/1faf33f2494c0128f250d7d2e8f2da99bbd32ae8nvdPatchWEB
- github.com/NationalSecurityAgency/emissary/security/advisories/GHSA-3p24-9x7v-7789nvdExploitMitigationVendor AdvisoryWEB
- github.com/advisories/GHSA-3p24-9x7v-7789ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-35582ghsaADVISORY
News mentions
0No linked articles in our index yet.