From 71e2ff4a7e4778d5e934d1b0031d9c140bdc1374 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Fri, 18 Sep 2026 12:15:08 +0300 Subject: [PATCH] Fix medium-severity CodeQL alerts in openig-core - Script: create the inline Groovy script cache directory with Files.createTempDirectory() and the cached script files with Files.createTempFile(), so both are accessible by the owner only (java/local-temp-file-or-directory-information-disclosure) - MQ_IBM: strip line breaks from the consumed JMS message before writing it to the trace log (java/log-injection), and mask the XMSC_PASSWORD value in the debug dump of the connection factory settings --- .../org/forgerock/openig/script/Script.java | 17 +++-- .../openig/mq/MQ_IBM.java | 21 ++++- .../forgerock/openig/script/ScriptTest.java | 76 +++++++++++++++++++ 3 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 openig-core/src/test/java/org/forgerock/openig/script/ScriptTest.java diff --git a/openig-core/src/main/java/org/forgerock/openig/script/Script.java b/openig-core/src/main/java/org/forgerock/openig/script/Script.java index e7e405c5d..44cc3760c 100644 --- a/openig-core/src/main/java/org/forgerock/openig/script/Script.java +++ b/openig-core/src/main/java/org/forgerock/openig/script/Script.java @@ -12,6 +12,7 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2014-2016 ForgeRock AS. + * Portions Copyright 2026 3A Systems, LLC. */ package org.forgerock.openig.script; @@ -21,6 +22,7 @@ import java.io.FileWriter; import java.io.IOException; import java.net.URL; +import java.nio.file.Files; import java.util.Map; import javax.script.ScriptException; @@ -147,12 +149,13 @@ public static Script fromSource(final Environment environment, final GroovyScriptEngine engine = getGroovyScriptEngine(environment); final File groovyScriptCacheDir = getGroovyScriptCacheDir(); try { + // Files.createTempFile() creates the file readable by the owner only final File cachedScript = - File.createTempFile("script-", ".groovy", groovyScriptCacheDir); + Files.createTempFile(groovyScriptCacheDir.toPath(), "script-", ".groovy").toFile(); cachedScript.deleteOnExit(); - final FileWriter writer = new FileWriter(cachedScript); - writer.write(source); - writer.close(); + try (FileWriter writer = new FileWriter(cachedScript)) { + writer.write(source); + } final Impl impl = new GroovyImpl(engine, cachedScript.toURI().toURL().toString()); return new Script(impl); } catch (final IOException e) { @@ -177,9 +180,9 @@ private static File getGroovyScriptCacheDir() throws ScriptException { } try { - cacheDir = File.createTempFile("openig-groovy-script-cache-", null); - cacheDir.delete(); - cacheDir.mkdir(); + // Files.createTempDirectory() creates the directory atomically and accessible by + // the owner only: the cached scripts may embed credentials from the route config + cacheDir = Files.createTempDirectory("openig-groovy-script-cache-").toFile(); cacheDir.deleteOnExit(); } catch (final IOException e) { throw new ScriptException(e); diff --git a/openig-core/src/main/java/org/openidentityplatform/openig/mq/MQ_IBM.java b/openig-core/src/main/java/org/openidentityplatform/openig/mq/MQ_IBM.java index 1d6f2b333..bd6affcd8 100644 --- a/openig-core/src/main/java/org/openidentityplatform/openig/mq/MQ_IBM.java +++ b/openig-core/src/main/java/org/openidentityplatform/openig/mq/MQ_IBM.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2021-2026 3A Systems, LLC. + */ + package org.openidentityplatform.openig.mq; import java.util.Map.Entry; @@ -136,7 +152,7 @@ public void start() throws HeapException { } handler.cf.forEach((key,value)-> { - logger.debug("settings {}: {}={}",name,key,value); + logger.debug("settings {}: {}={}",name,key,WMQConstants.PASSWORD.equals(key) ? "***" : value); } ); final int core=evaluated.get("core").defaultTo(Runtime.getRuntime().availableProcessors()*32).asInteger(); @@ -153,7 +169,8 @@ public void run() { while (true){ final Message message=consumer.receive(); if (logger.isTraceEnabled() ) { - logger.trace("consume {}",message); + // strip line breaks: the message body comes from the queue and must not forge log lines + logger.trace("consume {}",String.valueOf(message).replaceAll("[\\r\\n]", " ")); } try { final Request request=new Request(); diff --git a/openig-core/src/test/java/org/forgerock/openig/script/ScriptTest.java b/openig-core/src/test/java/org/forgerock/openig/script/ScriptTest.java new file mode 100644 index 000000000..0086fd106 --- /dev/null +++ b/openig-core/src/test/java/org/forgerock/openig/script/ScriptTest.java @@ -0,0 +1,76 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems, LLC. + */ + +package org.forgerock.openig.script; + +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.forgerock.openig.script.Script.GROOVY_MIME_TYPE; + +import java.io.File; +import java.lang.reflect.Field; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.forgerock.openig.config.Environment; +import org.forgerock.openig.config.env.DefaultEnvironment; +import org.testng.SkipException; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("javadoc") +public class ScriptTest { + + private Environment environment; + + @BeforeMethod + public void setUp() throws Exception { + if (!FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) { + throw new SkipException("File permissions can only be checked on a POSIX file system"); + } + environment = new DefaultEnvironment(Files.createTempDirectory("openig-script-test").toFile()); + } + + @Test + public void shouldCreateInlineScriptCacheDirectoryReadableByOwnerOnly() throws Exception { + Script.fromSource(environment, GROOVY_MIME_TYPE, "return 42"); + + Path cacheDir = groovyScriptCacheDir().toPath(); + assertThat(Files.getPosixFilePermissions(cacheDir)) + .containsExactlyInAnyOrder(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE); + } + + @Test + public void shouldCreateInlineScriptFileReadableByOwnerOnly() throws Exception { + Script.fromSource(environment, GROOVY_MIME_TYPE, "return 42"); + + Path cacheDir = groovyScriptCacheDir().toPath(); + try (var scripts = Files.list(cacheDir)) { + assertThat(scripts).isNotEmpty().allSatisfy(script -> + assertThat(Files.getPosixFilePermissions(script)) + .containsExactlyInAnyOrder(OWNER_READ, OWNER_WRITE)); + } + } + + private static File groovyScriptCacheDir() throws Exception { + Field field = Script.class.getDeclaredField("groovyScriptCacheDir"); + field.setAccessible(true); + return (File) field.get(null); + } +}