From 81525cb77a125b528b3413d42dd43ac9c9f334ce Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 22 Jun 2026 11:53:03 -0700 Subject: [PATCH 1/5] Warn about http remote connection --- .../labkey/remoteapi/RemoteConnections.java | 22 ++++++++++++++++++- .../query/view/createRemoteConnection.jsp | 9 +++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/query/api-src/org/labkey/remoteapi/RemoteConnections.java b/query/api-src/org/labkey/remoteapi/RemoteConnections.java index e47f8f0bb15..71f0f28b24e 100644 --- a/query/api-src/org/labkey/remoteapi/RemoteConnections.java +++ b/query/api-src/org/labkey/remoteapi/RemoteConnections.java @@ -92,9 +92,10 @@ public static boolean createOrEditRemoteConnection(RemoteConnectionForm remoteCo } // validate the url string and connection + URL urlObj; try { - URL urlObj = new URL(url); + urlObj = new URL(url); URLConnection conn = urlObj.openConnection(); conn.connect(); } @@ -116,6 +117,11 @@ public static boolean createOrEditRemoteConnection(RemoteConnectionForm remoteCo return false; } + if (isCleartextHttpUrl(urlObj)) + { + LOG.warn("Remote connection '{}' is configured with a cleartext http:// URL ({}). Credentials will be sent unencrypted. Use https:// instead.", newName, url); + } + // validate the user try { @@ -164,6 +170,11 @@ public static String getBriefMessage(Throwable t) return t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage(); } + public static boolean isCleartextHttpUrl(@NotNull URL url) + { + return "http".equalsIgnoreCase(url.getProtocol()); + } + public static boolean deleteRemoteConnection(RemoteConnectionForm remoteConnectionForm, Container container) { String name = remoteConnectionForm.getConnectionName(); @@ -346,5 +357,14 @@ public void testGetBriefMessage() assertEquals("boom", getBriefMessage(new IOException("boom"))); assertEquals("IOException", getBriefMessage(new IOException())); } + + @Test + public void testIsCleartextHttpUrl() throws MalformedURLException + { + assertTrue("http:// must be detected as cleartext", isCleartextHttpUrl(new URL("http://example.com/labkey"))); + assertTrue("scheme match is case-insensitive", isCleartextHttpUrl(new URL("HTTP://example.com/labkey"))); + assertFalse("https:// is encrypted", isCleartextHttpUrl(new URL("https://example.com/labkey"))); + assertFalse("HTTPS:// is encrypted", isCleartextHttpUrl(new URL("HTTPS://example.com/labkey"))); + } } } diff --git a/query/src/org/labkey/query/view/createRemoteConnection.jsp b/query/src/org/labkey/query/view/createRemoteConnection.jsp index b7a1cb289c3..a211c74bfbb 100644 --- a/query/src/org/labkey/query/view/createRemoteConnection.jsp +++ b/query/src/org/labkey/query/view/createRemoteConnection.jsp @@ -35,14 +35,21 @@ String connectionKind = remoteConnectionForm.getConnectionKind(); boolean editConnection = StringUtils.isNotEmpty(name); String nameToShow = editConnection ? name : remoteConnectionForm.getNewConnectionName(); + boolean usingCleartextHttp = url != null && url.toLowerCase().startsWith("http://"); %>

<%=h(RemoteConnections.MANAGEMENT_PAGE_INSTRUCTIONS)%>

+<% if (usingCleartextHttp) { %> +

+ Warning: this connection uses an http:// URL. The configured user and password will be sent to the remote + server in cleartext on every ETL run. Use https:// so credentials are encrypted in transit. +

+<% } %>
From f574ddd5f849082a9573d936a08c1545bc1b63b0 Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 22 Jun 2026 11:57:10 -0700 Subject: [PATCH 2/5] Warn about http remote connection --- query/src/org/labkey/query/view/createRemoteConnection.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query/src/org/labkey/query/view/createRemoteConnection.jsp b/query/src/org/labkey/query/view/createRemoteConnection.jsp index a211c74bfbb..b7ef2eb49dd 100644 --- a/query/src/org/labkey/query/view/createRemoteConnection.jsp +++ b/query/src/org/labkey/query/view/createRemoteConnection.jsp @@ -49,7 +49,7 @@ From 77e1058c036d37229affb7437e0fc1efa3f77ea2 Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 22 Jun 2026 12:34:19 -0700 Subject: [PATCH 3/5] CC review --- .../labkey/remoteapi/RemoteConnections.java | 11 ++++++----- .../query/view/createRemoteConnection.jsp | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/query/api-src/org/labkey/remoteapi/RemoteConnections.java b/query/api-src/org/labkey/remoteapi/RemoteConnections.java index 71f0f28b24e..3bf56421615 100644 --- a/query/api-src/org/labkey/remoteapi/RemoteConnections.java +++ b/query/api-src/org/labkey/remoteapi/RemoteConnections.java @@ -117,11 +117,6 @@ public static boolean createOrEditRemoteConnection(RemoteConnectionForm remoteCo return false; } - if (isCleartextHttpUrl(urlObj)) - { - LOG.warn("Remote connection '{}' is configured with a cleartext http:// URL ({}). Credentials will be sent unencrypted. Use https:// instead.", newName, url); - } - // validate the user try { @@ -161,6 +156,12 @@ public static boolean createOrEditRemoteConnection(RemoteConnectionForm remoteCo if (CONNECTION_KIND_QUERY.equals(connectionKind)) singleConnectionMap.put(RemoteConnections.FIELD_CONTAINER, folderPath); singleConnectionMap.save(); + + if (isCleartextHttpUrl(urlObj)) + { + LOG.warn("Remote connection '{}' is configured with a cleartext http:// URL ({}). Credentials will be sent unencrypted. Use https:// instead.", newName, url); + } + return true; } diff --git a/query/src/org/labkey/query/view/createRemoteConnection.jsp b/query/src/org/labkey/query/view/createRemoteConnection.jsp index b7ef2eb49dd..3a981219020 100644 --- a/query/src/org/labkey/query/view/createRemoteConnection.jsp +++ b/query/src/org/labkey/query/view/createRemoteConnection.jsp @@ -22,6 +22,8 @@ <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.query.controllers.QueryController" %> <%@ page import="org.labkey.remoteapi.RemoteConnections" %> +<%@ page import="java.net.MalformedURLException" %> +<%@ page import="java.net.URL" %> <%@ page extends="org.labkey.api.jsp.FormPage" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <% @@ -35,12 +37,22 @@ String connectionKind = remoteConnectionForm.getConnectionKind(); boolean editConnection = StringUtils.isNotEmpty(name); String nameToShow = editConnection ? name : remoteConnectionForm.getNewConnectionName(); - boolean usingCleartextHttp = url != null && url.toLowerCase().startsWith("http://"); + boolean usingCleartextHttp = false; + if (url != null) + { + try + { + usingCleartextHttp = RemoteConnections.isCleartextHttpUrl(new URL(url)); + } + catch (MalformedURLException ignored) + { + } + } %>

<%=h(RemoteConnections.MANAGEMENT_PAGE_INSTRUCTIONS)%>

<% if (usingCleartextHttp) { %> -

+

Warning: this connection uses an http:// URL. The configured user and password will be sent to the remote server in cleartext on every ETL run. Use https:// so credentials are encrypted in transit.

@@ -49,7 +61,7 @@ From 5998e2a8550608d22ac485e57eb0b5be46501cba Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 22 Jun 2026 12:56:29 -0700 Subject: [PATCH 4/5] CC review --- query/api-src/org/labkey/remoteapi/RemoteConnections.java | 3 ++- query/src/org/labkey/query/view/createRemoteConnection.jsp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/query/api-src/org/labkey/remoteapi/RemoteConnections.java b/query/api-src/org/labkey/remoteapi/RemoteConnections.java index 3bf56421615..8099413f035 100644 --- a/query/api-src/org/labkey/remoteapi/RemoteConnections.java +++ b/query/api-src/org/labkey/remoteapi/RemoteConnections.java @@ -159,7 +159,8 @@ public static boolean createOrEditRemoteConnection(RemoteConnectionForm remoteCo if (isCleartextHttpUrl(urlObj)) { - LOG.warn("Remote connection '{}' is configured with a cleartext http:// URL ({}). Credentials will be sent unencrypted. Use https:// instead.", newName, url); + // Log the host only — interpolating the full URL could leak credentials embedded in userinfo (e.g., http://user:pass@host/). + LOG.warn("Remote connection '{}' is configured with a cleartext http:// URL (host: {}). Credentials will be sent unencrypted. Use https:// instead.", newName, urlObj.getHost()); } return true; diff --git a/query/src/org/labkey/query/view/createRemoteConnection.jsp b/query/src/org/labkey/query/view/createRemoteConnection.jsp index 3a981219020..065046f78df 100644 --- a/query/src/org/labkey/query/view/createRemoteConnection.jsp +++ b/query/src/org/labkey/query/view/createRemoteConnection.jsp @@ -46,6 +46,7 @@ } catch (MalformedURLException ignored) { + // Malformed URLs are surfaced by server-side validation in createOrEditRemoteConnection; suppress here. } } %> From 32895e30fc8b2534d9e1d15c6a9efd54f2680742 Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 22 Jun 2026 13:15:21 -0700 Subject: [PATCH 5/5] CC review --- .../query/view/manageRemoteConnections.jsp | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/query/src/org/labkey/query/view/manageRemoteConnections.jsp b/query/src/org/labkey/query/view/manageRemoteConnections.jsp index e5947d2a6df..2a76fcb8158 100644 --- a/query/src/org/labkey/query/view/manageRemoteConnections.jsp +++ b/query/src/org/labkey/query/view/manageRemoteConnections.jsp @@ -15,6 +15,7 @@ * limitations under the License. */ %> +<%@ page import="org.apache.commons.lang3.StringUtils" %> <%@ page import="org.labkey.api.data.Container" %> <%@ page import="org.labkey.api.security.permissions.AdminOperationsPermission" %> <%@ page import="org.labkey.api.settings.AppProps" %> @@ -22,6 +23,10 @@ <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.query.controllers.QueryController.RemoteQueryConnectionUrls" %> <%@ page import="org.labkey.remoteapi.RemoteConnections" %> +<%@ page import="java.net.MalformedURLException" %> +<%@ page import="java.net.URL" %> +<%@ page import="java.util.ArrayList" %> +<%@ page import="java.util.List" %> <%@ page import="java.util.Map" %> <%@ page extends="org.labkey.api.jsp.FormPage" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> @@ -29,11 +34,41 @@ <% Container c = getContainer(); boolean hasAdminOpsPerm = c.hasPermission(getUser(), AdminOperationsPermission.class); + + Map connectionMap = ((JspView>) HttpView.currentView()).getModelBean(); + + List cleartextConnections = new ArrayList<>(); + if (connectionMap != null) + { + for (String connectionName : connectionMap.values()) + { + Map props = RemoteConnections.getRemoteConnection( + RemoteConnections.REMOTE_QUERY_CONNECTIONS_CATEGORY, connectionName, c); + String connUrl = props.get(RemoteConnections.FIELD_URL); + if (connUrl != null) + { + try + { + if (RemoteConnections.isCleartextHttpUrl(new URL(connUrl))) + cleartextConnections.add(connectionName); + } + catch (MalformedURLException ignored) + { + // Malformed URLs are surfaced by server-side validation in createOrEditRemoteConnection; suppress here. + } + } + } + } %> +<% if (!cleartextConnections.isEmpty()) { %> +

+ Warning: one or more remote connections use a cleartext http:// URL: <%=h(StringUtils.join(cleartextConnections, ", "))%>. Credentials are sent unencrypted on every ETL run. +

+<% } %> +
<% - Map connectionMap = ((JspView>) HttpView.currentView()).getModelBean(); if (connectionMap == null) { %>

EncryptionKey has not been specified in <%= h(AppProps.getInstance().getWebappConfigurationFilename()) %>, or its value no longer matches key previously in use.