Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).

## [5.4.21] 2026-04-21

### Added
- Added redaction of sensitive HTTP header values in debug logging by default,
plus the `com.oracle.nosql.sdk.nosqldriver.log-sensitive-headers` system
property to allow full header values when needed for debugging.

### Changed
- Updated netty version to 4.1.132.Final

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package oracle.nosql.driver.httpclient;

import static oracle.nosql.driver.util.LogUtil.logFine;
import static oracle.nosql.driver.util.LogUtil.logHeaders;
import static oracle.nosql.driver.util.HttpConstants.REQUEST_ID_HEADER;

import java.io.Closeable;
Expand Down Expand Up @@ -203,8 +204,8 @@ void receive(RequestState requestState) {
", but got response for request " + resReqId +
": discarding response");
if (resReqId == null) {
logFine(logger, "Headers for discarded response: " +
requestState.getHeaders());
logHeaders(logger, "Headers for discarded response",
requestState.getHeaders());
if (this.allowRetry) {
this.cause = new ProtocolException(
"Received invalid response with no requestId");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import static oracle.nosql.driver.iam.Utils.*;
import static oracle.nosql.driver.util.HttpConstants.*;
import static oracle.nosql.driver.util.LogUtil.logHeaderValue;

import java.io.IOException;
import java.io.StringWriter;
Expand Down Expand Up @@ -173,8 +174,10 @@ static HttpHeaders setHeaders(URI uri,
SINGATURE_VERSION);


logTrace(logger, "Resource Principal Token request" +
" authorization header " + authHeader);
logHeaderValue(logger,
"Resource Principal Token request authorization header",
AUTHORIZATION,
authHeader);
HttpHeaders headers = new DefaultHttpHeaders();
return headers
.set(DATE, date)
Expand Down Expand Up @@ -217,8 +220,10 @@ static HttpHeaders setHeaders(URI uri,
signature,
SINGATURE_VERSION);

logTrace(logger, "Federation request authorization header " +
authHeader);
logHeaderValue(logger,
"Federation request authorization header",
AUTHORIZATION,
authHeader);
HttpHeaders headers = new DefaultHttpHeaders();
return headers
.set(DATE, date)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import static io.netty.handler.codec.http.HttpMethod.POST;
import static io.netty.handler.codec.http.HttpMethod.PUT;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static oracle.nosql.driver.util.LogUtil.formatHeadersForLog;
import static oracle.nosql.driver.util.LogUtil.logHeaders;
import static oracle.nosql.driver.util.LogUtil.logFine;
import static oracle.nosql.driver.util.LogUtil.logInfo;
import static oracle.nosql.driver.util.HttpConstants.CONTENT_LENGTH;
Expand Down Expand Up @@ -231,7 +233,7 @@ private static HttpResponse doRequest(HttpClient httpClient,
uri, headers, method, payload, channel);
}
addRequiredHeaders(request);
logFine(logger, request.headers().toString());
logHeaders(logger, "Request headers", request.headers());
httpClient.runRequest(request, responseHandler, channel);
if (responseHandler.await(timeoutMs)) {
throw new TimeoutException("Request timed out after " +
Expand Down Expand Up @@ -415,7 +417,7 @@ public HttpHeaders getHeaders() {
public String toString() {
return "HttpResponse [statusCode=" + statusCode + "," +
"output=" + output + "," + "headers=" +
(headers == null ? "null" : headers.toString()) + "]";
formatHeadersForLog(headers) + "]";
}
}
}
112 changes: 112 additions & 0 deletions driver/src/main/java/oracle/nosql/driver/util/LogUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,33 @@

package oracle.nosql.driver.util;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaders;

/**
* Utility methods to facilitate Logging.
*/
public class LogUtil {

private static final String REDACTED = "<redacted>";
private static final String LOG_SENSITIVE_HEADERS_PROPERTY =
"com.oracle.nosql.sdk.nosqldriver.log-sensitive-headers";
private static final Set<String> SENSITIVE_HEADERS = new HashSet<>(
Arrays.asList(HttpConstants.AUTHORIZATION.toLowerCase(Locale.ROOT),
"proxy-authorization",
HttpConstants.COOKIE.toLowerCase(Locale.ROOT),
"set-cookie",
"opc-obo-token",
"security-context"));

public static boolean isFineEnabled(Logger logger) {
return logger != null && logger.isLoggable(Level.FINE);
}
Expand Down Expand Up @@ -67,4 +86,97 @@ public static void logTrace(Logger logger, String msg) {
public static boolean isLoggable(Logger logger, Level level) {
return (logger != null && logger.isLoggable(level));
}

public static boolean isSensitiveHeaderLoggingEnabled(Logger logger) {
if (!isFineEnabled(logger)) {
return false;
}
try {
return Boolean.getBoolean(LOG_SENSITIVE_HEADERS_PROPERTY);
} catch (SecurityException se) {
return false;
}
}

public static void logHeaders(Logger logger,
String label,
HttpHeaders headers) {
if (isFineEnabled(logger)) {
logger.log(Level.FINE,
label + ": " +
formatHeadersForLog(
headers,
!isSensitiveHeaderLoggingEnabled(logger)));
}
}

public static void logHeaderValue(Logger logger,
String label,
String headerName,
String value) {
if (isFineEnabled(logger)) {
logger.log(Level.FINE,
label + ": " +
formatHeaderValueForLog(
headerName,
value,
!isSensitiveHeaderLoggingEnabled(logger)));
}
}

public static String formatHeadersForLog(HttpHeaders headers) {
return formatHeadersForLog(headers, true);
}

public static String formatHeadersForLog(HttpHeaders headers,
boolean redactSensitive) {
if (headers == null) {
return "null";
}

final HttpHeaders formatted = new DefaultHttpHeaders(false);
for (Map.Entry<String, String> entry : headers) {
formatted.add(entry.getKey(),
formatHeaderValueForLog(entry.getKey(),
entry.getValue(),
redactSensitive));
}
return formatted.toString();
}

public static String formatHeaderValueForLog(String headerName,
String value) {
return formatHeaderValueForLog(headerName, value, true);
}

public static String formatHeaderValueForLog(String headerName,
String value,
boolean redactSensitive) {
if (!redactSensitive || !isSensitiveHeader(headerName)) {
return value;
}
return redactHeaderValue(headerName, value);
}

public static String redactHeaderValue(String headerName, String value) {
if (value == null) {
return REDACTED;
}
final String lowerName = headerName.toLowerCase(Locale.ROOT);
if (HttpConstants.AUTHORIZATION.toLowerCase(Locale.ROOT)
.equals(lowerName) ||
"proxy-authorization".equals(lowerName)) {
final int space = value.indexOf(' ');
if (space > 0) {
return value.substring(0, space + 1) + REDACTED;
}
}
return REDACTED;
}

private static boolean isSensitiveHeader(String headerName) {
return headerName != null &&
SENSITIVE_HEADERS.contains(
headerName.toLowerCase(Locale.ROOT));
}
}
92 changes: 92 additions & 0 deletions driver/src/test/java/oracle/nosql/driver/util/LogUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*-
* Copyright (c) 2011, 2026 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/

package oracle.nosql.driver.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaders;

public class LogUtilTest {

@Test
public void testRedactHeaderValue() {
assertEquals("Bearer <redacted>",
LogUtil.redactHeaderValue(HttpConstants.AUTHORIZATION,
"Bearer secret-token"));
assertEquals("Basic <redacted>",
LogUtil.redactHeaderValue("Proxy-Authorization",
"Basic dXNlcjpwYXNz"));
assertEquals("<redacted>",
LogUtil.redactHeaderValue("opc-obo-token",
"delegation-token"));
assertEquals("visible",
LogUtil.formatHeaderValueForLog("X-Custom", "visible"));
}

@Test
public void testFormatHeadersForLogRedactsSensitiveHeaders() {
final HttpHeaders headers = new DefaultHttpHeaders();
headers.add(HttpConstants.AUTHORIZATION, "Bearer secret-token");
headers.add(HttpConstants.COOKIE, "session=abc123");
headers.add("opc-obo-token", "delegation-token");
headers.add("security-context", "opaque-security-context");
headers.add("X-Custom", "visible");

final String formatted = LogUtil.formatHeadersForLog(headers);

assertTrue(formatted.contains("Authorization: Bearer <redacted>"));
assertTrue(formatted.contains("Cookie: <redacted>"));
assertTrue(formatted.contains("opc-obo-token: <redacted>"));
assertTrue(formatted.contains("security-context: <redacted>"));
assertTrue(formatted.contains("X-Custom: visible"));
assertFalse(formatted.contains("secret-token"));
assertFalse(formatted.contains("abc123"));
assertFalse(formatted.contains("delegation-token"));
assertFalse(formatted.contains("opaque-security-context"));
}

@Test
public void testFormatHeadersForLogCanKeepSensitiveHeaders() {
final HttpHeaders headers = new DefaultHttpHeaders();
headers.add(HttpConstants.AUTHORIZATION, "Bearer secret-token");
headers.add(HttpConstants.COOKIE, "session=abc123");

final String formatted = LogUtil.formatHeadersForLog(headers, false);

assertTrue(formatted.contains("Authorization: Bearer secret-token"));
assertTrue(formatted.contains("Cookie: session=abc123"));
}

@Test
public void testFormatHeadersForLogPreservesNonValidatingBehavior() {
final HttpHeaders headers = new DefaultHttpHeaders(false);
headers.add("Bad Header", "visible");
headers.add(HttpConstants.AUTHORIZATION, "Bearer secret-token");

final String formatted = LogUtil.formatHeadersForLog(headers);

assertTrue(formatted.contains("Bad Header: visible"));
assertTrue(formatted.contains("Authorization: Bearer <redacted>"));
}

@Test
public void testFormatHeaderValueForLogCanSkipRedaction() {
assertEquals("<redacted>",
LogUtil.formatHeaderValueForLog(HttpConstants.COOKIE,
"session=abc123"));
assertEquals("session=abc123",
LogUtil.formatHeaderValueForLog(HttpConstants.COOKIE,
"session=abc123",
false));
}
}