Skip to content

Fix medium-severity CodeQL alerts in openig-core - #171

Merged
vharseko merged 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:codeql-medium-java
Oct 22, 2026
Merged

vharseko merged 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:codeql-medium-java

Conversation

@vharseko

@vharseko vharseko commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Closes the two open medium Java code scanning alerts (#8, #9) and, on the way, note #54.

Alert Rule Location Change
#8 java/local-temp-file-or-directory-information-disclosure Script.java The inline Groovy script cache directory was created with File.createTempFile() → delete() → mkdir(): umask permissions (typically 0755, readable by other local users) plus a TOCTOU window between delete and mkdir. The cached scripts are the source of route configs, which may embed credentials. Now created with Files.createTempDirectory() (atomic, 0700); the script files with Files.createTempFile() (0600); FileWriter in try-with-resources. Also closes note #54 (java/ignored-error-status-of-call on the removed mkdir()).
#9 java/log-injection MQ_IBM.java, MQ_Kafka.java The consumed message is written to the log as-is; a body containing line breaks could forge log lines. Besides the flagged trace line, the body also reaches the INFO log (the shipped root level) when the root handler is not set yet — the consumers start with the heap, so messages already queued at startup take that branch. MQ_Kafka has the same two sinks; its trace line prints the consumed record with its String key. All four now go through MQLog.forLog(), which replaces line breaks.
— not flagged MQ_IBM.java The debug dump of the connection factory settings printed every property, including XMSC_PASSWORD. The password value is now masked (MQ_IBM.settingForLog()).

Tests added:

  • ScriptTest: shouldCreateInlineScriptCacheDirectoryReadableByOwnerOnly and shouldCreateInlineScriptFileReadableByOwnerOnly — both failed before the fix with GROUP_READ, GROUP_EXECUTE, OTHERS_READ, OTHERS_EXECUTE present. They tell the fix from umask-derived permissions only under a group/other-readable umask such as the default 022, and are skipped on non-POSIX file systems (the Windows runners in the build matrix).
  • MQLogTest: pins MQLog.forLog() and MQ_IBM.settingForLog() — with either helper body reverted to returning its input, its test fails. The handlers themselves are not exercised: MQ_IBMTest runs in the suite, but without a live IBM MQ broker (see the docker run comment in it) it only goes through the connection-failure paths and asserts nothing.

MQ_IBM.java and MQ_Kafka.java had no license header; both were created in 2021 by 3A Systems, so a CDDL header with Copyright 2021-2026 3A Systems, LLC. is added.

@vharseko vharseko added java Pull requests that update Java code security Security fixes and CVE / vulnerability updates tests Adds or updates tests labels Sep 18, 2026

@maximthomas maximthomas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: The temp-file fix sits exactly where alert #8 points and closes the TOCTOU window along with it.

  • Script.getGroovyScriptCacheDir() now uses Files.createTempDirectory, which replaces the createTempFile/delete/mkdir race and the ignored mkdir() result with one atomic, owner-only call.
  • Cached inline scripts are created with Files.createTempFile and written in try-with-resources, so a failed write no longer leaks the FileWriter.
  • ScriptTest asserts the actual POSIX permissions and skips with SkipException on non-POSIX file systems, so the Windows legs stay green.

issue (non-blocking): The consumed message body is still logged raw at INFO when no root handler is set.

openig-core/src/main/java/org/openidentityplatform/openig/mq/MQ_IBM.java:201-203

The consumer threads start during heap init, before HttpFrameworkServlet sets its root handler, so messages already queued at startup take the getRootHandler()==null branch. There, request.getEntity().toString() returns the body set at line 178 unchanged, so a body like x\n12:00:00:000 | INFO | ... forges a line at INFO, the shipped root level, while the fixed line 173 only logs at TRACE. The line predates this PR, but it logs the same source that the comment at line 172 says "must not forge log lines". MQ_Kafka.java:178 has the same sink.

logger.info("process done: {}", request.getEntity().toString().replaceAll("[\\r\\n]", " "));

Or: forLog(request.getEntity()), if the helper from the next comment is extracted.


suggestion (non-blocking): Neither MQ_IBM change is pinned by a test.

openig-core/src/main/java/org/openidentityplatform/openig/mq/MQ_IBM.java:155, :173

If line 155 goes back to value or line 173 goes back to message, the suite stays green. MQ_IBMTest is the only test that references MQ_IBM, and it neither asserts nor captures log output. The description explains why MQ_IBMTest cannot cover these changes (it needs a live broker). Both changes are pure string logic, though, so they can be pinned without a broker.

// MQ_IBM.java
static String forLog(Object message) {
    return String.valueOf(message).replaceAll("[\\r\\n]", " ");
}

static Object settingForLog(String key, Object value) {
    return WMQConstants.PASSWORD.equals(key) ? "***" : value;
}

// MQ_IBMLogTest.java (TestNG)
@Test
public void shouldStripLineBreaksFromConsumedMessage() {
    assertThat(MQ_IBM.forLog("x\r\n12:00:00:000 | INFO | forged")).doesNotContain("\r", "\n");
}

@Test
public void shouldMaskPasswordInSettingsDump() {
    assertThat(MQ_IBM.settingForLog(WMQConstants.PASSWORD, "passw0rd")).isEqualTo("***");
    assertThat(MQ_IBM.settingForLog(WMQConstants.USERID, "app")).isEqualTo("app");
}

Pin: once lines 155 and 173 call these helpers, reverting either helper's body turns its test red.


suggestion (non-blocking): The new permission tests catch a regression only under a group/other-readable umask.

openig-core/src/test/java/org/forgerock/openig/script/ScriptTest.java:51-67

The old File.createTempFile + mkdir code takes its mode from the process umask. Under 022 it yields rwxr-xr-x/rw-r--r-- and both tests fail against it; under 077 it already yields owner-only modes and both tests pass. The ubuntu and macOS legs of build.yml run with the default 022, so CI does pin the fix. A comment stops the next reader from trusting a green run under a restrictive umask.

// These tests tell the fix from the old File.createTempFile()/mkdir() code only when the JVM
// runs with a group/other-readable umask (the CI default 022); under umask 077 both pass anyway.

- 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
Address the review of OpenIdentityPlatform#171:
- MQ_IBM, MQ_Kafka: the consumed message body was still written to the
  INFO log as-is when the root handler is not set yet (messages already
  queued at startup), so a body with line breaks could forge log lines
  at the shipped root level. Route it, and the Kafka trace of the
  consumed record (its String key), through the new MQLog.forLog()
- MQ_IBM: move the password masking of the settings dump into
  settingForLog(); MQLogTest pins both helpers
- ScriptTest: note that the permission tests tell owner-only from
  umask-derived permissions only under a group/other-readable umask
- MQ_Kafka: add the missing CDDL license header
@vharseko

Copy link
Copy Markdown
Member Author

Addressed in 5a1bc43:

  • INFO sink — MQ_IBM and MQ_Kafka now log the body in the no-root-handler branch through MQLog.forLog(). The Kafka trace line (logger.trace("consume {}", record)) goes through it as well: ConsumerRecord.toString() prints the String key.
  • Tests — the helpers are MQLog.forLog(), shared by both handlers, and MQ_IBM.settingForLog(); MQLogTest pins them. With each helper body reverted to returning its input, shouldStripLineBreaksFromConsumedMessage and shouldMaskPasswordInSettingsDump fail. I also checked against com.ibm.mq.allclient 9.3.1.0, without a broker, that the connection factory keeps the password under XMSC_PASSWORD, so the mask matches the key the dump iterates over.
  • umask — comment added above the permission tests in ScriptTest.

One correction to the PR description, which said MQ_IBMTest is not executed: it does run in the TestNG suite, it just asserts nothing without a broker. The description is updated.

@vharseko
vharseko requested a review from maximthomas October 22, 2026 11:51

@maximthomas maximthomas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: The fix sits on every sink the alerts are about, and nowhere else.

  • Script.java:185 Files.createTempDirectory creates the cache directory atomically with 0700, which removes the createTempFile → delete → mkdir window and the umask dependence; the scripts get 0600 from Files.createTempFile (:154).
  • One helper, MQLog.forLog, covers all four consumed-data sinks (MQ_IBM.java:179, :208, MQ_Kafka.java:164, :196), including the INFO process done branch taken before the root handler is set.
  • MQ_IBM.settingForLog masks WMQConstants.PASSWORD in the settings dump.

@vharseko
vharseko merged commit 27b3888 into OpenIdentityPlatform:master Oct 22, 2026
14 checks passed
@vharseko
vharseko deleted the codeql-medium-java branch October 22, 2026 16:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

java Pull requests that update Java code security Security fixes and CVE / vulnerability updates tests Adds or updates tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants