Skip to content

UNOMI-972: Harden default credentials, public profile binding, and privileged REST endpoints - #846

Closed
sergehuber wants to merge 9 commits into
masterfrom
UNOMI-972-credentials-profile-binding-privileged-rest
Closed

UNOMI-972: Harden default credentials, public profile binding, and privileged REST endpoints#846
sergehuber wants to merge 9 commits into
masterfrom
UNOMI-972-credentials-profile-binding-privileged-rest

Conversation

@sergehuber

@sergehuber sergehuber commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements UNOMI-972: defense-in-depth hardening for Unomi 3.1 around credentials, public context profile binding, and privileged extension/admin actions.

This PR replaces #842 (same change set; branch renamed to include the Jira key).

Changes

1. Explicit admin and health passwords at startup

  • Require UNOMI_ROOT_PASSWORD and UNOMI_HEALTHCHECK_PASSWORD before the runtime starts (setenv / setenv.bat, Docker entrypoint).
  • Stop shipping known default passwords in packaging and sample compose files; compose examples document the required variables.
  • Update getting-started, configuration, Docker docs, and the 5-minute quickstart so operators must set strong credentials deliberately.
  • Dev convenience: setup-opensearch.sh / setup-elasticsearch.sh load both passwords from .env.local; clear scripts unset them.

2. Stronger public context / eventcollector profile binding

For unauthenticated / public callers of context and eventcollector:

  • Treat the profile cookie as the only profile identity bearer.
  • Ignore a client-supplied profileId in the request body (so public clients cannot select an arbitrary profile that way).
  • Refuse session-driven profile switches unless the cookie already matches the target profile.
  • Default the profile cookie HttpOnly flag to true (configurable via existing web settings); keep custom.system.properties and org.apache.unomi.web.cfg defaults aligned.

Authenticated / private-key callers keep the previous ability to bind profiles intentionally for admin and integration use cases.

3. Restrict privileged extension REST endpoints

  • Annotate Groovy Actions REST and Router import/export REST endpoints with @RequiresRole(ADMINISTRATOR) so only the system administrator role can use them (not a tenant administrator).

4. Gate cross-profile merge and system property updates

  • MergeProfilesOnPropertyAction and UpdatePropertiesAction refuse cross-profile merge/update and systemProperties.* writes unless the caller is a trusted identity (system administrator or tenant administrator).

5. Login sample rewrite (trusted server-side pattern)

  • Replace the old browser→Unomi login demo with a DS LoginServlet (/login/authenticate) that validates a demo password then posts a login event to /cxs/context.json with trusted Basic auth (tenantId:privateKey preferred, or karaf + X-Unomi-Tenant-Id).
  • Whiteboard static UI at /login (redirect to /login/index.html), metatype cfg, Jackson JSON.
  • Rewrite login-sample.adoc (including PlantUML sequence) and align recipes / builtin login-event / threat-model wording so the sample no longer teaches an untrusted public merge path.

Docs and threat model

  • Document the new contracts in how-profile-tracking-works, security, recipes, tracker guide, configuration, and What's new.
  • Add a client-facing hardening migration table under Migrate from 3.0 to 3.1 (#_client_facing_hardening_3_1).
  • Align THREAT_MODEL.md with the HttpOnly default and the trusted login-sample pattern.

Test plan

  • ShippedAdminPasswordConfigTest
  • RestServiceUtilsImplProfileBindingTest
  • GroovyActionsEndPointRoleTest, RouterConfigurationEndPointRoleTest
  • MergeProfilesOnPropertyActionTest, UpdatePropertiesActionTest
  • ContextServletIT public vs private binding cases
  • RestEndpointRoleSecurityIT
  • ProfileMergeIT / PropertiesUpdateActionIT trusted vs untrusted
  • Smoke: Docker/compose fails closed without both password env vars
  • Spot-check AsciiDoc: migrate-3.0-to-3.1 hardening section + how-profile-tracking Step 1
  • Login sample: install bundle, create tenant+scope default, login at /login, verify merge after clearing context-profile-id

@sergehuber

Copy link
Copy Markdown
Contributor Author

Follow-up: login sample rewrite

Pushed UNOMI-972: rewrite login sample for trusted server-side merge.

  • Replaces the old browser→Unomi login demo with a DS LoginServlet at /login/authenticate that posts login events with trusted Basic auth (tenantId:privateKey or karaf + X-Unomi-Tenant-Id).
  • Static page under /login (redirect + /login/index.html), metatype cfg, Jackson JSON.
  • Docs: login sample chapter + PlantUML sequence, recipes / builtin login event / threat model notes.
  • Dev scripts: setup-*/clear-* now load/clear UNOMI_ROOT_PASSWORD and UNOMI_HEALTHCHECK_PASSWORD.

Tested locally: create tenant+scope default, login with test1234, merge on email works after clearing context-profile-id.

Copilot AI 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.

Pull request overview

Hardens Unomi’s credential handling, public profile binding, privileged operations, and login integration.

Changes:

  • Requires explicit administrator and health-check credentials across runtime, Docker, tests, and documentation.
  • Restricts public profile/session binding and privileged REST/action behavior.
  • Replaces the browser-trusted login sample with a server-side authenticated flow and expands security tests.

Reviewed changes

Copilot reviewed 92 out of 94 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
SECURITY.md Expands vulnerability-handling guidance.
THREAT_MODEL.md Updates security assumptions and mitigations.
api/src/main/java/org/apache/unomi/api/utils/LogSanitizer.java Adds shared audit-log sanitization.
api/src/test/java/org/apache/unomi/api/utils/LogSanitizerTest.java Tests log sanitization.
clear-elasticsearch.sh Clears added credential variables.
clear-opensearch.sh Clears added credential variables.
docker/README.md Documents required Docker credentials.
docker/src/main/docker/docker-compose-build-es.yml Requires Elasticsearch runtime credentials.
docker/src/main/docker/docker-compose-build-os.yml Requires OpenSearch runtime credentials.
docker/src/main/docker/docker-compose-cluster.yml Requires cluster credentials.
docker/src/main/docker/docker-compose-es.yml Removes default Elasticsearch credentials.
docker/src/main/docker/docker-compose-os.yml Removes default OpenSearch credentials.
docker/src/main/docker/entrypoint.sh Fails startup without required passwords.
extensions/groovy-actions/karaf-kar/src/main/feature/feature.xml Updates Groovy feature dependencies.
extensions/groovy-actions/rest/pom.xml Adds REST security test dependencies.
extensions/groovy-actions/rest/src/main/java/org/apache/unomi/groovy/actions/rest/GroovyActionsEndPoint.java Restricts Groovy endpoints to administrators.
extensions/groovy-actions/rest/src/test/java/org/apache/unomi/groovy/actions/rest/GroovyActionsEndPointRoleTest.java Tests Groovy endpoint roles.
extensions/groovy-actions/services/src/main/java/org/apache/unomi/groovy/actions/services/impl/GroovyActionsServiceImpl.java Hardens script compilation and adds auditing.
extensions/groovy-actions/services/src/test/java/org/apache/unomi/groovy/actions/services/impl/GroovyActionsServiceImplTest.java Tests Groovy upload hardening.
extensions/groovy-actions/services/src/test/resources/META-INF/cxs/actions/fieldInitializerAction.groovy Adds initializer regression fixture.
extensions/groovy-actions/services/src/test/resources/META-INF/cxs/actions/rceProofAction.groovy Adds execution-safety fixture.
extensions/router/router-karaf-feature/src/main/feature/feature.xml Updates Router feature dependencies.
extensions/router/router-rest/pom.xml Adds Router security test dependencies.
extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ExportConfigurationServiceEndPoint.java Restricts configuration export.
extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java Restricts configuration import.
extensions/router/router-rest/src/test/java/org/apache/unomi/router/rest/RouterConfigurationEndPointRoleTest.java Tests Router endpoint roles.
itests/src/test/java/org/apache/unomi/itests/AllITs.java Registers new integration tests.
itests/src/test/java/org/apache/unomi/itests/BaseIT.java Supplies hardened integration-test configuration.
itests/src/test/java/org/apache/unomi/itests/BasicIT.java Updates baseline credential behavior.
itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java Tests public and trusted profile binding.
itests/src/test/java/org/apache/unomi/itests/CorePersistenceITs.java Registers persistence-related tests.
itests/src/test/java/org/apache/unomi/itests/HealthCheckIT.java Tests health-check authentication.
itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java Tests trusted profile merging.
itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java Tests protected property updates.
itests/src/test/java/org/apache/unomi/itests/RestEndpointRoleSecurityIT.java Tests privileged REST roles.
itests/src/test/java/org/apache/unomi/itests/TenantIT.java Updates tenant credential handling.
itests/src/test/java/org/apache/unomi/itests/V2CompatibilityModeIT.java Aligns compatibility tests with hardening.
itests/src/test/java/org/apache/unomi/itests/graphql/BaseGraphQLIT.java Updates GraphQL test authentication.
itests/src/test/resources/etc/users.properties Removes shipped test defaults.
manual/src/main/asciidoc/5-min-quickstart.adoc Updates quickstart credentials.
manual/src/main/asciidoc/builtin-event-types.adoc Documents trusted login events.
manual/src/main/asciidoc/configuration.adoc Documents credential and cookie settings.
manual/src/main/asciidoc/connectors/salesforce-connector.adoc Updates connector credential examples.
manual/src/main/asciidoc/getting-started.adoc Requires explicit startup credentials.
manual/src/main/asciidoc/graphql-examples.adoc Updates GraphQL authentication examples.
manual/src/main/asciidoc/how-profile-tracking-works.adoc Documents cookie-owned profile binding.
manual/src/main/asciidoc/javascript-tracker-guide.adoc Updates secure session-ID guidance.
manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc Updates authenticated API examples.
manual/src/main/asciidoc/migrations/migrate-3.0-to-3.1.adoc Adds client hardening migration guidance.
manual/src/main/asciidoc/migrations/migrations.adoc Links migration changes.
manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc Updates compatibility security guidance.
manual/src/main/asciidoc/migrations/v2-v3-compatibility.adoc Aligns compatibility documentation.
manual/src/main/asciidoc/multitenancy.adoc Clarifies privileged tenant behavior.
manual/src/main/asciidoc/privacy.adoc Documents profile identity protections.
manual/src/main/asciidoc/recipes.adoc Reworks trusted login recipes.
manual/src/main/asciidoc/request-examples.adoc Updates authenticated requests.
manual/src/main/asciidoc/samples/login-sample.adoc Documents server-side login flow.
manual/src/main/asciidoc/scheduler.adoc Updates credential examples.
manual/src/main/asciidoc/security.adoc Documents new security contracts.
manual/src/main/asciidoc/shell-commands.adoc Updates shell authentication guidance.
manual/src/main/asciidoc/tutorial.adoc Updates tutorial credentials.
manual/src/main/asciidoc/whats-new.adoc Announces security hardening.
package/src/main/resources/bin/setenv Requires Unix runtime passwords.
package/src/main/resources/bin/setenv.bat Requires Windows runtime passwords.
package/src/main/resources/etc/custom.system.properties Enables HttpOnly cookies by default.
package/src/main/resources/etc/users.properties Removes known administrator passwords.
plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java Gates cross-profile merging.
plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/UpdatePropertiesAction.java Gates cross-profile and system updates.
plugins/baseplugin/src/main/resources/OSGI-INF/blueprint/blueprint.xml Injects action security services.
plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyActionTest.java Tests merge authorization.
plugins/baseplugin/src/test/java/org/apache/unomi/plugins/baseplugin/actions/UpdatePropertiesActionTest.java Tests property-update authorization.
rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java Rejects blank-password authentication.
rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java Passes caller trust information.
rest/src/main/java/org/apache/unomi/rest/exception/LogSanitizer.java Migrates REST log sanitization.
rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java Enforces profile-cookie ownership.
rest/src/main/java/org/apache/unomi/utils/EventsRequestContext.java Tracks trusted caller state.
rest/src/test/java/org/apache/unomi/rest/authentication/AuthenticationFilterBlankPasswordTest.java Tests blank-password rejection.
rest/src/test/java/org/apache/unomi/rest/config/ShippedAdminPasswordConfigTest.java Detects shipped default credentials.
rest/src/test/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImplProfileBindingTest.java Tests profile-binding rules.
samples/login-integration/pom.xml Converts the sample to an OSGi service bundle.
samples/login-integration/setup.sh Configures the trusted login sample.
samples/login-integration/src/main/java/org/apache/unomi/samples/login/LoginSampleResources.java Registers static sample resources.
samples/login-integration/src/main/java/org/apache/unomi/samples/login/LoginServlet.java Implements trusted server-side login.
samples/login-integration/src/main/resources/static/index.html Adds the replacement login UI.
samples/login-integration/src/main/resources/static/javascript/login-example.js Calls the server-side login endpoint.
samples/login-integration/src/main/webapp/WEB-INF/web.xml Removes the legacy WAR descriptor.
samples/login-integration/src/main/webapp/index.html Removes the insecure browser-driven UI.
samples/login-integration/src/main/webapp/javascript/login-example.js Removes browser-to-Unomi login logic.
samples/login-integration/src/test/java/org/apache/unomi/samples/login/LoginServletTest.java Tests the login servlet boundary.
setup-elasticsearch.sh Loads all required Elasticsearch credentials.
setup-opensearch.sh Loads all required OpenSearch credentials.
setup-utils.sh Adds reusable password loading.
web-servlets/src/main/java/org/apache/unomi/web/servlets/WebConfig.java Defaults profile cookies to HttpOnly.
web-servlets/src/main/resources/org.apache.unomi.web.cfg Aligns the packaged HttpOnly default.
Suppressed comments (4)

manual/src/main/asciidoc/5-min-quickstart.adoc:90

  • The OpenSearch copy-paste compose example still embeds predictable public passwords instead of requiring operator-provided values. This undermines the stated removal of known credentials and leaves the shell variable used by subsequent curl examples unset.
    manual/src/main/asciidoc/javascript-tracker-guide.adoc:1062
  • This duplicate sample also incorrectly says the entire Web Crypto API is secure-context-only. getRandomValues() remains exposed on non-HTTPS pages, so this branch indicates lack of either random API, not merely an insecure context.
    manual/src/main/asciidoc/javascript-tracker-guide.adoc:1247
  • This recommendation conflates randomUUID() with getRandomValues(): the latter is available in insecure contexts, so a current browser on plain HTTP will use the fallback rather than fail closed. Keep HTTPS as a general production recommendation, but do not claim it is required for this fallback.
    manual/src/main/asciidoc/javascript-tracker-guide.adoc:1256
  • The compatibility table is inaccurate: crypto.getRandomValues() does not require a secure context, and a non-secure page is therefore not automatically unsupported. This could lead integrators to diagnose a tracker failure incorrectly.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 215 to 217
if (!eventsRequestContext.getProfile().isAnonymousProfile() &&
!anonymousSessionProfile &&
!eventsRequestContext.getProfile().getItemId().equals(sessionProfile.getItemId())) {
Comment on lines +162 to +165
final boolean trustedExplicitProfileOverride = trustedCaller
&& requestedBodyProfileId != null
&& cookieProfileIdAtRequest != null
&& !requestedBodyProfileId.equals(cookieProfileIdAtRequest);
Comment on lines +167 to +170
if (!trustedCaller && prop.startsWith(SYSTEM_PROPERTIES_PREFIX)) {
LOGGER.warn("Refusing systemProperties update for untrusted caller: {}", LogSanitizer.forLogging(prop));
continue;
}
Comment on lines +38 to +39
- UNOMI_ROOT_PASSWORD=choose-a-strong-password
- UNOMI_HEALTHCHECK_PASSWORD=choose-a-strong-health-password
validateNotEmpty(groovyScript, "Groovy script");

long startTime = System.currentTimeMillis();
auditScriptChange("save", actionName, groovyScript);
validateNotEmpty(actionName, "Action name");
validateNoControlCharacters(actionName, "Action name");

auditScriptChange("remove", actionName, null);
Comment on lines +147 to +148
'unsupported. Serve the page over HTTPS (the Web Crypto API is restricted to ' +
'secure contexts) or install a Web Crypto polyfill.'
sergehuber and others added 8 commits August 10, 2026 09:24
…rtup

Reported issue 1. users.properties resolved the shipped karaf and health accounts
via ${...:-karaf} / ${...:-health}, so a deployment that set nothing authenticated
with a known password. Removing the fallback alone is not enough: an unset property
expands to the empty string, which Karaf's PropertiesLoginModule still accepts, so
the accounts would simply have accepted an empty password instead.

bin/setenv and the Docker entrypoint therefore refuse to start without the passwords,
and AuthenticationFilter rejects a blank Basic credential wherever one is consumed -
the launchers cannot cover every way the JVM is started (notably karaf.bat, whose
inability to halt startup is documented in setenv itself).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported issue 3. /cxs/context.json and /cxs/eventcollector took profileId and
sessionId from the request body and loaded them directly, so a caller holding only
the tenant public key could read any profile whose identifier it knew.

For an untrusted caller the cookie is now the sole profile bearer: the body profileId
is ignored, including when no cookie is present, since otherwise knowing a UUID is
itself sufficient. Adopting a session requires the cookie to already own it, and a
refused session is detached rather than rebound and is not echoed back - a client told
its session was accepted would keep replaying a rejected id. invalidateSession is
closed as a route around the same rule. The profile cookie now defaults to HttpOnly,
which addresses the XSS-to-identifier-theft chain the report describes.

Trusted callers keep explicit binding, including when they present no cookie at all -
a server-side integration has no cookie jar, so an explicit profileId is the only way
it can name the profile it means.

The new refusal paths log request-derived values, so a shared LogSanitizer is
introduced and applied to them: an unsanitised newline in a session id would let a
caller forge audit-shaped log records. The pre-existing REST sanitizer now delegates
to it rather than keeping a second copy; equivalence was verified differentially
across every BMP code unit, the truncation boundaries and 20k fuzz iterations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported issue 4. POST /cxs/groovyActions carried no @RequiresRole, and SecurityFilter
only enforces where that annotation is present, so any authenticated subject reached it -
including a tenant administrator, whose authority is meant to stop at its own tenant's
data. The endpoint now requires the system ADMINISTRATOR role; tenant administrators
are deliberately excluded, unlike the profile-binding trust check.

The report also showed the upload itself executing. GroovyShell#parse returns a Script
*instance*, and constructing it runs the script's field initializers, so a script with a
Groovy @field initializer ran at save time, before any rule dispatched it. Every caller
here only needs the compiled Class - to read the @action annotation or check for
execute() - so compilation now uses parseClass and instantiates nothing. This also closes
a second occurrence: the cache-refresh path re-instantiated persisted scripts on every
refresh cycle.

The regression test carries the reported payload shape and is preceded by a positive
control that runs the same payload through a bare GroovyShell, so a payload that silently
failed to execute could not make the assertion pass vacuously.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nistrators

Reported issue 5. An ImportConfiguration's properties.source is a raw Apache Camel
endpoint URI, guarded only by a scheme allowlist that permits file, ftp, sftp and ftps
with no path canonicalisation or host validation. A tenant administrator could therefore
read arbitrary server files into profile properties, or reach internal hosts - authority
well outside that tenant's data plane. The same allowlist governs the export
destinationEndpoint.

Both endpoints now require the system ADMINISTRATOR role, which closes the reported
reach. Confining the paths and hosts themselves is defence in depth against an actor who
is already a system administrator, and is tracked separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…trusted caller

Reported issue 6. MergeProfilesOnPropertyAction resolved its merge value from
attacker-controlled event input and never checked that the event's profile was entitled
to claim it, and UpdatePropertiesAction took targetId straight from the event and loaded
any profile with it. Wired to a public event type - which the shipped login sample
demonstrated - a credential-less caller could rebind its session to a victim's profile or
write arbitrary properties onto one.

Both actions now refuse a cross-profile merge or update, and any write to the reserved
systemProperties area, unless the caller holds system access. The refusal is logged at
WARN so an operator sees attempts.

The systemProperties gate matches the bare key as well as the dotted prefix: for a flat
name PropertyHelper falls through to BeanUtils.setProperty, which calls
Profile#setSystemProperties(Map) and replaces the entire map - a superset of the per-key
write the gate exists to block, and enough to plant the mergeIdentifier the merge action
keys on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-side pattern

Coupled to reported issue 6. The sample shipped exampleLogin.json, which wired a public
login event to mergeProfilesOnPropertyAction - exactly the pattern the documentation warns
against, and the pattern the report cites. The gate added for issue 6 refuses it, so the
sample would otherwise ship broken as well as misleading.

The browser now posts only to the sample's own /login/authenticate, which validates a demo
password and then calls /cxs/context.json itself with trusted credentials, so the merge is
performed by a caller the server can vouch for. The session id is server-generated and held
on the container session rather than accepted from the request, and a same-origin check
stands in for the per-session CSRF token a real integration would use.

No demo password ships with the sample, for the same reason Unomi no longer ships a default
admin password: a credential baked into published source is a credential everyone has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…reak

Adds the integration coverage for the fixes above, plus a before/after baseline for the two
public client endpoints, which is the part that answers whether existing clients still work.

ContextEndpointBaselineIT is written to compile and run against both master and this branch
and is split into two groups with opposite expectations. Run on both, it gives:

  compat_* (7)                               master pass / branch pass
  hardened_publicBodyProfileIdIsIgnored      master FAIL / branch pass
  hardened_publicCallerCannotAdoptAForeign*  master FAIL / branch pass

On master the first hardened test returns the victim's profile identifier and properties to
a caller holding only the public API key - the reported issue reproduced end to end - and the
branch answers 400. The compat group deliberately covers the client entry points that had no
coverage at all: the GET forms carrying ?payload=, which is how a script tag or image beacon
tracks and which route through the same binding code as POST.

Also pins the areas a future ownership check is most likely to break, none of which was
covered anywhere: all four branches of the anonymous-browsing handling, and persona binding.
Personas short-circuit binding entirely and profileOverrides only apply to a Persona, so both
are structurally isolated from these changes - now asserted rather than assumed.

RestEndpointRoleSecurityIT covers the Groovy and router role gates over real HTTP; ProfileMergeIT
and PropertiesUpdateActionIT cover the action gates. Every new IT is registered in AllITs and
CorePersistenceITs, without which failsafe silently never runs them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hardening changes behaviour that existing clients and operators depend on, so the
documentation has to change with it or it teaches something the server now refuses.

- a client-facing hardening table in the 3.0-to-3.1 migration guide, cross-referenced from
  the pages that describe the affected behaviour
- public context examples switched from a body profileId to the context-profile-id cookie
  in request-examples and multitenancy; the remaining profileId occurrences elsewhere are
  response bodies or already cookie-based
- credentials in examples no longer show karaf:karaf, and the quickstart, getting-started,
  configuration and Docker pages require the operator to choose passwords deliberately
- the threat model records what the Groovy action endpoint is (equivalent to shell access,
  system administrator only, no sandbox planned and why), so a report of "Groovy is not
  sandboxed" triages consistently rather than being re-litigated

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@asf-gitbox-commits
asf-gitbox-commits force-pushed the UNOMI-972-credentials-profile-binding-privileged-rest branch from fae021f to 532d855 Compare August 11, 2026 19:38
…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) <noreply@anthropic.com>
@sergehuber

Copy link
Copy Markdown
Contributor Author

Superseded. This work has been split into one PR per issue so each can be reviewed, merged and backported independently:

Together those five carry the whole of this change: every line this branch removed from master is removed by one of them, and the production code is equivalent. The only content not carried forward is the THREAT_MODEL.md and SECURITY.md edits, which were dropped deliberately, and some Groovy hardening that was never in scope here and will get its own ticket.

The branch is kept, so nothing here is lost.

@sergehuber sergehuber closed this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants