This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch
UNOMI-972-credentials-profile-binding-privileged-rest
in repository https://gitbox.apache.org/repos/asf/unomi.git
The following commit(s) were added to
refs/heads/UNOMI-972-credentials-profile-binding-privileged-rest by this push:
new 8566175d3 UNOMI-972: correct two factual errors in the docs and harden
the log sanitizer against a bad limit
8566175d3 is described below
commit 8566175d388e08633d05c98c623eb87bdf802d33
Author: Serge Huber <[email protected]>
AuthorDate: Tue Aug 11 22:05:14 2026 +0200
UNOMI-972: correct two factual errors in the docs and harden the log
sanitizer against a bad limit
The tracker guide told readers the Web Crypto API is restricted to secure
contexts and that plain
HTTP can therefore reach the fail-closed branch. That is wrong: only
crypto.randomUUID() and
crypto.subtle are secure-context-only, and the fallback the tracker
actually uses,
crypto.getRandomValues(), is available over plain HTTP. The branch is
reached when Web Crypto is
absent entirely, so the error message now says that instead of prescribing
HTTPS as the fix. HTTPS
is still recommended on its own merits - a session id in clear text is the
larger problem - but the
support matrix no longer claims getRandomValues needs it.
The quickstart's Docker path put literal passwords in the compose example
and then ran curl commands
reading ${UNOMI_ROOT_PASSWORD}, which is only exported in the Karaf path
further down. A reader
following the Docker path pasted a literal and then hit 401s against an
unset variable. Both compose
blocks now read the exported values, matching the shipped compose files,
with the export step given
once before them.
LogSanitizer.forLogging(String, int) clamps a negative limit rather than
letting substring throw.
No caller passes one today, but this helper exists to be safe to call from
inside a log statement,
and a computed limit would be an easy way to turn a security-refusal log
line into an uncaught
exception. Also drops a local repeat() helper in favour of String.repeat,
which the Java 17 baseline
provides.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
.../java/org/apache/unomi/api/utils/LogSanitizer.java | 7 ++++++-
.../org/apache/unomi/api/utils/LogSanitizerTest.java | 16 ++++++++--------
manual/src/main/asciidoc/5-min-quickstart.adoc | 18 ++++++++++++++----
manual/src/main/asciidoc/javascript-tracker-guide.adoc | 15 +++++++++------
4 files changed, 37 insertions(+), 19 deletions(-)
diff --git a/api/src/main/java/org/apache/unomi/api/utils/LogSanitizer.java
b/api/src/main/java/org/apache/unomi/api/utils/LogSanitizer.java
index 0ff89eaf3..7af6cfad1 100644
--- a/api/src/main/java/org/apache/unomi/api/utils/LogSanitizer.java
+++ b/api/src/main/java/org/apache/unomi/api/utils/LogSanitizer.java
@@ -63,7 +63,12 @@ public final class LogSanitizer {
if (input == null) {
return "null";
}
- String value = input.length() > maxLength ? input.substring(0,
maxLength) + "...[truncated]" : input;
+ // Clamped: a negative limit would make substring throw, from inside a
helper whose whole
+ // contract is that it is always safe to call in a log statement. No
caller passes one today,
+ // but a computed limit (a remaining-budget calculation, say) would be
an easy way to turn a
+ // security-refusal log line into an uncaught exception.
+ int limit = Math.max(0, maxLength);
+ String value = input.length() > limit ? input.substring(0, limit) +
"...[truncated]" : input;
StringBuilder sanitized = new StringBuilder(value.length());
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
diff --git a/api/src/test/java/org/apache/unomi/api/utils/LogSanitizerTest.java
b/api/src/test/java/org/apache/unomi/api/utils/LogSanitizerTest.java
index 6eee9ffb1..2a8c7093a 100644
--- a/api/src/test/java/org/apache/unomi/api/utils/LogSanitizerTest.java
+++ b/api/src/test/java/org/apache/unomi/api/utils/LogSanitizerTest.java
@@ -68,7 +68,7 @@ public class LogSanitizerTest {
@Test
public void oversizedValuesAreTruncatedSoTheyCannotFloodTheLog() {
- String sanitized = LogSanitizer.forLogging(repeat("a", 5000));
+ String sanitized = LogSanitizer.forLogging("a".repeat(5000));
assertTrue(sanitized.endsWith("...[truncated]"));
assertTrue("truncated output must stay bounded", sanitized.length() <
300);
@@ -191,7 +191,7 @@ public class LogSanitizerTest {
*/
@Test
public void payloadHiddenBeyondTheTruncationPointIsDropped() {
- String sanitized = LogSanitizer.forLogging(repeat("a", 400) + "\nWARN
forged-record");
+ String sanitized = LogSanitizer.forLogging("a".repeat(400) + "\nWARN
forged-record");
assertFalse(sanitized.contains("forged-record"));
assertFalse(sanitized.contains("\n"));
@@ -217,11 +217,11 @@ public class LogSanitizerTest {
assertEquals(once, LogSanitizer.forLogging(once));
}
- private static String repeat(String s, int times) {
- StringBuilder sb = new StringBuilder(s.length() * times);
- for (int i = 0; i < times; i++) {
- sb.append(s);
- }
- return sb.toString();
+ /** A negative limit must not throw: this helper is called from inside log
statements. */
+ @Test
+ public void negativeLimitIsClampedRatherThanThrowing() {
+ assertEquals("...[truncated]", LogSanitizer.forLogging("abcdef", -1));
+ assertEquals("...[truncated]", LogSanitizer.forLogging("abcdef", 0));
}
+
}
diff --git a/manual/src/main/asciidoc/5-min-quickstart.adoc
b/manual/src/main/asciidoc/5-min-quickstart.adoc
index 002e304c3..9966b275a 100644
--- a/manual/src/main/asciidoc/5-min-quickstart.adoc
+++ b/manual/src/main/asciidoc/5-min-quickstart.adoc
@@ -19,6 +19,16 @@ Begin by creating a `docker-compose.yml` file. You can
choose between Elasticsea
==== Option 1: Using Elasticsearch
+Export the passwords first. The compose files read them from your shell, and
the `curl` commands
+further down use the same variables, so setting them once here keeps both
consistent. Unomi refuses
+to start if they are unset.
+
+[source,bash]
+----
+export UNOMI_ROOT_PASSWORD='choose-a-strong-password'
+export UNOMI_HEALTHCHECK_PASSWORD='choose-a-strong-health-password'
+----
+
[source,yaml]
----
version: '3.8'
@@ -35,8 +45,8 @@ services:
environment:
- UNOMI_ELASTICSEARCH_ADDRESSES=elasticsearch:9200
- UNOMI_THIRDPARTY_PROVIDER1_IPADDRESSES=0.0.0.0/0,::1,127.0.0.1
- - UNOMI_ROOT_PASSWORD=choose-a-strong-password
- - UNOMI_HEALTHCHECK_PASSWORD=choose-a-strong-health-password
+ - UNOMI_ROOT_PASSWORD=${UNOMI_ROOT_PASSWORD}
+ - UNOMI_HEALTHCHECK_PASSWORD=${UNOMI_HEALTHCHECK_PASSWORD}
ports:
- 8181:8181
- 9443:9443
@@ -86,8 +96,8 @@ services:
- UNOMI_OPENSEARCH_SSL_ENABLE=true
- UNOMI_OPENSEARCH_SSL_TRUST_ALL_CERTIFICATES=true
- UNOMI_HEALTHCHECK_PROVIDERS=cluster,opensearch,unomi,persistence
- - UNOMI_ROOT_PASSWORD=choose-a-strong-password
- - UNOMI_HEALTHCHECK_PASSWORD=choose-a-strong-health-password
+ - UNOMI_ROOT_PASSWORD=${UNOMI_ROOT_PASSWORD}
+ - UNOMI_HEALTHCHECK_PASSWORD=${UNOMI_HEALTHCHECK_PASSWORD}
ports:
- 8181:8181
- 9443:9443
diff --git a/manual/src/main/asciidoc/javascript-tracker-guide.adoc
b/manual/src/main/asciidoc/javascript-tracker-guide.adoc
index 0da22ba27..b9acf9773 100644
--- a/manual/src/main/asciidoc/javascript-tracker-guide.adoc
+++ b/manual/src/main/asciidoc/javascript-tracker-guide.adoc
@@ -144,8 +144,8 @@ generateUUID: function() {
throw new Error(
'Unomi tracker: no cryptographically secure random source available ' +
'(neither crypto.randomUUID nor crypto.getRandomValues). This browser
is ' +
- 'unsupported. Serve the page over HTTPS (the Web Crypto API is
restricted to ' +
- 'secure contexts) or install a Web Crypto polyfill.'
+ 'unsupported: install a Web Crypto polyfill, or use a browser that
provides ' +
+ 'crypto.getRandomValues.'
);
},
@@ -1058,8 +1058,8 @@ Here's a complete, production-ready tracker
implementation combining all the con
throw new Error(
'Unomi tracker: no cryptographically secure random source
available ' +
'(neither crypto.randomUUID nor crypto.getRandomValues). This
browser is ' +
- 'unsupported. Serve the page over HTTPS (the Web Crypto API is
restricted to ' +
- 'secure contexts) or install a Web Crypto polyfill.'
+ 'unsupported: install a Web Crypto polyfill, or use a browser
that provides ' +
+ 'crypto.getRandomValues.'
);
},
@@ -1244,7 +1244,10 @@ Session IDs are security-relevant: a session ID is a
bearer identifier, so anyon
**Recommendations:**
* For production applications, ensure your minimum browser support includes
browsers with `crypto.getRandomValues()` support (essentially all browsers from
2013+)
-* Serve your pages over HTTPS: the Web Crypto API is only exposed in secure
contexts, so a page served over plain HTTP can hit the fail-closed branch even
on a current browser
+* Serve your pages over HTTPS. `crypto.randomUUID()` is restricted to secure
contexts, so over plain HTTP the tracker
+ falls back to `crypto.getRandomValues()`, which is available in non-secure
contexts too. HTTPS is worth doing on its
+ own merits - a session identifier travelling in clear text is the larger
problem - but plain HTTP alone does not reach
+ the fail-closed branch on a current browser
* If you must support browsers without the Web Crypto API (IE 10 and below),
install a Web Crypto polyfill — do not reintroduce a `Math.random()` fallback
* Never use `Math.random()` for security-sensitive identifiers like session
IDs or authentication tokens
* Handle the thrown error in your integration (for example, disable tracking
and log a warning) rather than letting it break unrelated page scripts
@@ -1252,7 +1255,7 @@ Session IDs are security-relevant: a session ID is a
bearer identifier, so anyon
**Browser Compatibility:**
* `crypto.randomUUID()`: Chrome 92+, Firefox 95+, Safari 15.4+, Edge 92+
(2021+), secure contexts only
-* `crypto.getRandomValues()`: All modern browsers (2013+), secure contexts only
+* `crypto.getRandomValues()`: All modern browsers (2013+), available in secure
and non-secure contexts
* Anything older, or any non-secure context: unsupported — the tracker throws
rather than generating a weak session ID
The implementation automatically uses the best available secure method, and
refuses to generate a session ID when there is none.