This is an automated email from the ASF dual-hosted git repository.
bamaer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 4de05ac5b9 Hop Web security follow-ups: mode NONE docs, threat model,
EXTERNAL w… (#8402)
4de05ac5b9 is described below
commit 4de05ac5b9af30ea303af8ff02ac9e557b4be8e2
Author: Bart Maertens <[email protected]>
AuthorDate: Mon Sep 21 10:17:05 2026 +0200
Hop Web security follow-ups: mode NONE docs, threat model, EXTERNAL w…
(#8402)
* Hop Web security follow-ups: mode NONE docs, threat model, EXTERNAL
warning #8391
- hop-web.adoc: correct the mode NONE description (/ui open by design,
/hop/*
default-denied) and document allowUnauthenticatedServerApi /
HOP_WEB_ALLOW_UNAUTHENTICATED_SERVER_API as the execution-server opt-in.
Add an IMPORTANT note that EXTERNAL delegates rather than enforces.
- THREAT_MODEL.md: add Hop Web as a distinct deployment, scope the
enable_auth
assumption to hop-server, and describe the per-mode boundary.
- HopWebEntryPoint: log a warning when a session has no principal in a mode
other than NONE, so EXTERNAL without a container security-constraint no
longer fails open silently.
* Address review feedback: thread Hop Web through the threat model #8391
- THREAT_MODEL.md: name Hop Web as a second HTTP trust boundary in the
in-scope
line, the untrusted-actor sentence, SS7 and SS12; qualify the JSON API
row so
ConstraintSecurityHandler/Basic-JAAS is scoped to standalone hop-server
and
Hop Web's own filters are named for the co-deployed /hop/api/v1/*.
- THREAT_MODEL.md SS11: mode NONE is an unrestricted GUI that runs pipelines
in-process (LocalPipelineEngine), so it is RCE even while /hop/* returns
403;
HOP_WEB_ALLOW_UNAUTHENTICATED_SERVER_API additionally opens the remote
API.
- hop-web.adoc + SS8: EXTERNAL without a container constraint shows the
General
tab on an authenticated mode while the status line still reads
unrestricted /
no AuthN - the tab surfaces the contradiction, it does not claim auth is
on.
- HopWebEntryPoint: trim the block comment; the log message carries the
detail. fixes #8391
---
THREAT_MODEL.md | 106 +++++++++++++++++----
.../modules/ROOT/pages/hop-gui/hop-web.adoc | 27 +++++-
.../org/apache/hop/ui/hopgui/HopWebEntryPoint.java | 31 +++++-
3 files changed, 145 insertions(+), 19 deletions(-)
diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md
index 4696a98d37..f30730655a 100644
--- a/THREAT_MODEL.md
+++ b/THREAT_MODEL.md
@@ -22,8 +22,10 @@ were then code-verified against the source and
confirmed/corrected by the PMC
(2026-06-22). Claims below cite `file:line` evidence where it is load-bearing.
**Revision triggers:** a change to the Hop Server's auth / remote-execution
-model; a new scripting/exec transform or action; a change to how connection
-credentials / variables are stored or resolved; a new metadata source.
+model; a change to the Hop Web authentication modes or to what the Hop Web
+web application deploys; a new scripting/exec transform or action; a change to
+how connection credentials / variables are stored or resolved; a new metadata
+source.
---
@@ -53,17 +55,20 @@ the `hop-run` CLI, or submitted to a **Hop Server** for
local/remote execution.
|---|---|---|
| Execution engine | `engine/`, `engine-beam/` | Runs pipelines/workflows the
operator authored — incl. transforms that touch files, DBs, network, and
**scripting/exec** steps. |
| Hop Server (servlet / HTTP) | `engine/` — package `org.apache.hop.www` +
`HopServerMeta` in `org.apache.hop.server`; launched by the `hop-server`
command (`org.apache.hop.www.HopServer`). *(The `org.apache.hop.server`
connection helpers `HttpUtil`/`ServerConnectionManager` — see §9 — live in
`core/`.)* | The **network trust boundary**: an embedded Jetty server whose
servlets accept pipeline/workflow run requests over HTTP (`/hop/execPipeline`,
`/hop/addPipeline`+`/hop/startExec`, `/hop [...]
-| Hop JSON API | `engine/src/main/java/org/apache/hop/www/api/` | **Part of
Hop Server**, not a separate deployable. Mounted on `/hop/api/v1/`
([`WebServer.java`](engine/src/main/java/org/apache/hop/www/WebServer.java))
and exposes `execute/sync` (run a web service), `metadata` CRUD, `plugins` and
`location` execution info. It sits inside the same `ConstraintSecurityHandler`
as every servlet, so it inherits Hop Server's Basic/JAAS auth (§8). |
-| Arrow Flight server (gRPC) | `plugins/tech/arrow/` — package
`org.apache.hop.arrow.flight`; launched by the `hop arrow` command
(`org.apache.hop.arrow.command.ArrowCommand`) | A **second network listener**,
separate from Hop Server: a gRPC/Arrow Flight endpoint that hands rows to and
from Data Stream metadata elements. Binds `0.0.0.0:33333` by default. TLS
(incl. mutual TLS) and username/password authentication are configurable but
**off unless the operator passes the options** (§8) — [...]
-| GUI (desktop / web) | `ui/` (`hop-ui`, SWT core), `rcp/` (desktop fragment),
`rap/` (`hop-ui-rap`, RAP/RWT **web** GUI) | Authoring surface used by the
trusted operator. `rap/` is the **web GUI**, not a server. |
+| Hop JSON API | `engine/src/main/java/org/apache/hop/www/api/` | **Part of
Hop Server**, not a separate deployable. Mounted on `/hop/api/v1/`
([`WebServer.java`](engine/src/main/java/org/apache/hop/www/WebServer.java))
and exposes `execute/sync` (run a web service), `metadata` CRUD, `plugins` and
`location` execution info. In the standalone `hop-server` deployment it sits
inside the same `ConstraintSecurityHandler` as every servlet, so it inherits
Hop Server's Basic/JAAS auth; when the [...]
+| Arrow Flight server (gRPC) | `plugins/tech/arrow/` — package
`org.apache.hop.arrow.flight`; launched by the `hop arrow` command
(`org.apache.hop.arrow.command.ArrowCommand`) | A **separate network
listener**, distinct from both Hop Server and Hop Web: a gRPC/Arrow Flight
endpoint that hands rows to and from Data Stream metadata elements. Binds
`0.0.0.0:33333` by default. TLS (incl. mutual TLS) and username/password
authentication are configurable but **off unless the operator passes th [...]
+| GUI (desktop / web) | `ui/` (`hop-ui`, SWT core), `rcp/` (desktop fragment),
`rap/` (`hop-ui-rap`, RAP/RWT **web** GUI) | Authoring surface used by the
trusted operator. `rap/` is the **web GUI code**, not a server — but see the
Hop Web row: the shipped web application deploys it together with Hop Server
servlets. |
+| Hop Web (deployed web application) | `assemblies/web/` (WAR +
`apache/hop-web` image), built from `rap/` + `engine/` | A **distinct
network-facing deployment**, separate from both `rap/` and `hop-server`. The
one WAR co-deploys the RAP UI on `/ui` and `/ui-dark`, `HopServerServlet` on
`/hop/*`, and `HopApiApplication` on `/hop/api/v1/*` on the same origin
([`web.xml`](assemblies/web/src/main/resources/WEB-INF/web.xml)). It does
**not** use Hop Server's `enable_auth` / `hop.pwd` Basic a [...]
| Plugins | `plugins/` | The large transform/action set, incl. scripting
(GraalJS/Rhino/Groovy), shell/exec, SQL, and per-DB/per-cloud connectors
(`plugins/tech/*`). |
| Connection / driver layer | `lib-jdbc/` (bundled JDBC drivers), `core/`
(`org.apache.hop.core.database`, `org.apache.hop.metadata`) | DB/file/cloud
credentials + JDBC drivers the operator configures. (There is no standalone
`metadata/` module — connection/metadata code is in `core/`.) |
-**In scope:** engine + Hop Server + plugins + connection layer + GUI glue.
+**In scope:** engine + Hop Server + Hop Web + plugins + connection layer + GUI
glue.
**Intended caller trust:** Hop is operated as a **single trust domain** — the
pipeline/workflow **author, the local operator, and anyone holding Hop Server
-credentials are all trusted** (they direct what Hop does). The **network-facing
-Hop Server** is where an untrusted actor can appear.
+credentials are all trusted** (they direct what Hop does). There are **several
+network-facing surfaces — Hop Server, Hop Web and the optional Arrow Flight
+server** — and any of them is where an untrusted actor can appear; Hop Server
+and Hop Web have **separate auth models** (§8).
## §3 Out of scope (explicit non-goals)
@@ -132,17 +137,22 @@ where strict outbound TLS verification is required (see
§9).
holder of Hop Server credentials** — Hop is a single trust domain, and they
already control execution (a scripting step that runs code is intent, not an
attack).
-- **In scope:** a **remote actor against an exposed Hop Server** (submitting or
- running pipelines, reading results, mutating metadata) —
- bounded by whatever auth fronts the surface; and a **network MITM** between
- Hop and its backends or between client and server.
+- **In scope:** a **remote actor against an exposed Hop Server or Hop Web
+ deployment** — submitting or running pipelines, reading results and mutating
+ metadata over `/hop/*` or `/hop/api/v1/*`, or driving the RAP authoring UI on
+ `/ui` — bounded by whatever auth fronts the surface (`enable_auth` for
+ `hop-server`, the Hop Web auth mode for Hop Web, §8); and a **network MITM**
+ between Hop and its backends or between client and server.
- **Conditional:** an attacker who can get an **untrusted pipeline/metadata
file** loaded/run, or who controls a **variable/parameter** value (incl. an
upstream row value picked up by `Set Variables`) that reaches a sink.
## §8 Security properties the project provides
-- **Hop Server authentication (with a critical caveat).** The servlet Hop
Server
+- **Hop Server authentication (with a critical caveat).** *(This property is
+ scoped to the standalone `hop-server` deployment only — Hop Web has no
+ `enable_auth` and no `hop.pwd`; see the separate Hop Web property below.)*
+ The servlet Hop Server
enables HTTP Basic authentication **by default** (`enable_auth` defaults to
true —
[`HopServerMeta.java:229,251`](engine/src/main/java/org/apache/hop/server/HopServerMeta.java#L229),
[`WebServer.java:197`](engine/src/main/java/org/apache/hop/www/WebServer.java#L197)),
gating every endpoint, so a *fully unauthenticated* client is
rejected. **Caveat:** the only shipped credential is the **publicly-known
@@ -152,6 +162,49 @@ where strict outbound TLS verification is required (see
§9).
Changing the credential and restricting exposure is an operator
responsibility
(§10). — violation symptom: remote code execution using the unchanged default
credential on an exposed deployment; severity: critical.
+- **Hop Web authentication and authorization (separate model from
`hop-server`).**
+ Hop Web does not inherit the `hop-server` `enable_auth` / `cluster:cluster`
+ posture described above; it has its own authentication mode in
+ `security-config.json`
+
([`HopSecurityConfig.java`](core/src/main/java/org/apache/hop/core/security/HopSecurityConfig.java),
+ `HOP_WEB_SECURITY_MODE`), and it **ships in mode `NONE`**. The boundary per
+ mode:
+ - **`NONE` (default).** The RAP UI on `/ui` / `/ui-dark` is **open by
design**
+ — this is the single-user / trusted-network install and the session is
+ treated as *unrestricted*. The co-deployed Hop Server API on `/hop/*` is
+ **default-denied** (`403`) by
+
[`HopServerAuthorizationFilter`](rap/src/main/java/org/apache/hop/ui/hopgui/security/HopServerAuthorizationFilter.java),
+ because mode `NONE` has no identity to authorize against and those
endpoints
+ deploy and execute pipelines/workflows. Opt-in flag
+ `allowUnauthenticatedServerApi` /
+ `HOP_WEB_ALLOW_UNAUTHENTICATED_SERVER_API` reopens it for operators running
+ Hop Web purely as an execution server behind their own network controls —
+ violation symptom: unauthenticated remote code execution via `/hop/*` on an
+ exposed deployment that enabled the flag; severity: critical; mitigation is
+ operator-side network control (§10).
+ - **`BASIC` / `OAUTH2`.** Hop's own servlet filters
+
([`HopBasicAuthFilter`](rap/src/main/java/org/apache/hop/ui/hopgui/security/HopBasicAuthFilter.java),
+
[`HopOidcAuthFilter`](rap/src/main/java/org/apache/hop/ui/hopgui/security/HopOidcAuthFilter.java))
+ are mapped on `/*` — the whole application, UI and server API alike — and
+ establish a principal plus roles. Unlike `hop-server`, `/hop/*` then gets
+ **per-endpoint authorization**: each path maps to a required permission and
+ unmapped paths are **default-denied**, so a *Read-only* user can read
status
+ but not `addPipeline` / `startPipeline` / `execWorkflow`.
+ - **`EXTERNAL`.** Authentication is **delegated to the servlet container or a
+ reverse proxy**; Hop only reads `request.getUserPrincipal()`. Nothing in
+ `rap/` enforces it, and the shipped
+ [`web.xml`](assemblies/web/src/main/resources/WEB-INF/web.xml) contains
**no
+ `<security-constraint>`** — so an operator who selects `EXTERNAL` without
+ adding one gets an unauthenticated `/ui` while the General tab shows an
+ authenticated mode (`EXTERNAL`) — the Security tab's own status line still
+ reads *"Session is unrestricted (no AuthN)"*, so the tab surfaces the
+ contradiction rather than claiming authentication is on. `/hop/*` still
+ returns `401` (no principal) and `HopWebEntryPoint` now logs a warning for
+ every principal-less session in a non-`NONE` mode, but the UI itself is
+ open. — violation symptom: a fail-open UI on a deployment the operator
+ believes is authenticated;
+ severity: high; mitigation: a container `<security-constraint>` over `/*`,
+ or a proxy that rejects unauthenticated requests (§10).
- **Cross-site browser requests to the Hop Server are rejected.** The servlets
answer state-changing operations on `GET`, so a page the operator is visiting
could otherwise drive them with the operator's browser and credentials
@@ -264,6 +317,15 @@ where strict outbound TLS verification is required (see
§9).
protects the XML servlets and the JSON API alike —
it has none of its own. Be aware the Docker image binds `0.0.0.0:8080` with
the default credential.
+- **Lock down Hop Web** (separate from `hop-server` above — `enable_auth` and
+ `hop.pwd` do not apply): it ships in mode `NONE`, where `/ui` is open by
+ design. For any shared deployment set `HOP_WEB_SECURITY_MODE` to `BASIC` or
+ `OAUTH2`, or to `EXTERNAL` **together with** a container
+ `<security-constraint>` over `/*` (or an authenticating reverse proxy) —
+ `EXTERNAL` on its own fails open. Leave
+ `HOP_WEB_ALLOW_UNAUTHENTICATED_SERVER_API` off unless you deliberately run
Hop
+ Web as an execution server behind network controls. Persist
+ `HOP_CONFIG_FOLDER/security/` so users and role mappings survive upgrades.
- **Protect credentials at rest:** enable the AES2 encoder
(`HOP_PASSWORD_ENCODER_PLUGIN=AES2` + `HOP_AES_ENCODER_KEY` or
`HOP_AES_ENCODER_KEY_FILE`) and/or a secrets
@@ -285,6 +347,16 @@ where strict outbound TLS verification is required (see
§9).
pipeline execution = remote code execution.
- Running Hop Server with `enable_auth=false` on an untrusted network →
unauthenticated pipeline execution + metadata mutation over the JSON API.
+- Exposing **Hop Web in mode `NONE`** to an untrusted network → `/ui` is an
+ **unrestricted** GUI session, so anyone who can reach it can author *and* run
+ pipelines/workflows **in-process** (the GUI executes locally through
+ `PipelineEngineFactory` / `LocalPipelineEngine`, it does not go through
+ `/hop/*`) = remote code execution, even though `/hop/*` still answers `403`.
+ Enabling `HOP_WEB_ALLOW_UNAUTHENTICATED_SERVER_API` additionally opens the
+ remote `/hop/*` and `/hop/api/v1/*` execution API.
+- Selecting Hop Web mode **`EXTERNAL` without a container
+ `<security-constraint>`** (or proxy) → an open `/ui` on a deployment the
+ operator believes is authenticated.
- Running an **untrusted pipeline/workflow file** (or one fetched from an
untrusted source).
- Building unparameterized SQL / shell / file paths from **untrusted variable
@@ -332,9 +404,11 @@ vulnerabilities:
## §12 Conditions that would change this model
-A change to the Hop Server auth or remote-exec model; a new
-scripting/exec transform or action; a change to credential/variable storage or
-resolution (e.g. a new default encoder); a new metadata source format/location;
+A change to the Hop Server auth or remote-exec model; a change to the Hop Web
+authentication modes, its servlet filters, or to what the Hop Web web
+application deploys; a new scripting/exec transform or action; a change to
+credential/variable storage or resolution (e.g. a new default encoder); a new
+metadata source format/location;
or a decision to support multi-tenant isolation (which today does not exist).
## §13 Triage dispositions
diff --git a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-web.adoc
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-web.adoc
index 279b492f91..84ca08ce03 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-web.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-web.adoc
@@ -193,7 +193,20 @@ Hop Web co-deploys the RAP UI, RAP service handlers, and
the Hop Server servlet
NOTE: The Hop Server API under `/hop/*` follows the same authorization as the
rest of Hop Web.
In the authenticated modes (`BASIC`, `EXTERNAL`, `OAUTH2`) each endpoint
requires the matching permission from the caller's role — for example a
*Read-only* user can call `status` and the status/image endpoints but not
`addPipeline`, `startPipeline`, `execWorkflow`, or the register/remove
endpoints, and an *Operator* can run and stop but not deploy (`add*`) or remove.
Endpoints that are not recognized are denied by default.
-In mode `NONE` (the default, open install) `/hop/*` is unauthenticated just
like `/ui`; put an authentication layer in front of Hop Web for any shared
deployment.
+In mode `NONE` (the default, open install) the two surfaces behave differently:
+
+* `/ui` stays **open by design** — mode `NONE` is the single-user /
trusted-network install and the UI is the product.
+* `/hop/*` is **closed by default** and returns `403`, because mode `NONE` has
no user identity to authorize against and the Hop Server API can run and deploy
pipelines and workflows.
+
+To use Hop Web as an execution server in mode `NONE`, opt in explicitly:
+
+* *Configuration perspective → Security → General → Expose the Hop Server API
without authentication*, or
+* set `HOP_WEB_ALLOW_UNAUTHENTICATED_SERVER_API=true` (persisted as
`allowUnauthenticatedServerApi` in `security-config.json`).
+
+WARNING: With that option on, anyone who can reach `/hop/*` can deploy and
execute pipelines and workflows without credentials. Only enable it behind your
own network controls (private network, firewall, service mesh, authenticating
reverse proxy).
+The option applies to mode `NONE` only — `BASIC`, `EXTERNAL`, and `OAUTH2`
always enforce the per-endpoint role permission and ignore it.
+
+Put an authentication layer in front of Hop Web for any shared deployment.
=== Built-in Hop roles
@@ -267,6 +280,14 @@ To keep audit data across container restarts, mount a
volume and set the same pa
The default Hop Web docker image picks up `tomcat-users.xml` and `web.xml`
files and moves them to the correct location before Hop Web starts.
+IMPORTANT: Mode `EXTERNAL` is *delegation, not enforcement*.
+Hop itself does not challenge anyone in this mode: it only reads the
`Principal` the servlet container (or authenticating reverse proxy) put on the
request.
+The `web.xml` that ships with Hop Web contains no `<security-constraint>`, so
selecting `EXTERNAL` without supplying your own constraint over `/*` leaves
`/ui` **completely open**.
+The Security perspective makes that contradiction visible rather than hiding
it: the General tab shows an authenticated mode (`EXTERNAL`), while the status
line for the same session still reads `Session is unrestricted (no AuthN).
Config mode: EXTERNAL.`
+If you see that combination, the container is not challenging anyone.
+`/hop/*` still returns `401` (no principal), and Hop logs a warning on every
unauthenticated UI session, but the UI itself is unprotected.
+Always pair `EXTERNAL` with a `<security-constraint>` + `<login-config>`
covering `/*` (see <<hop-web-external-full-webxml,the full `web.xml` sample>>)
or with a proxy that rejects unauthenticated requests before they reach Tomcat.
+
==== Single user (minimal)
A minimal sample `tomcat-users.xml` file (legacy single role `apachehop` → Hop
*User*):
@@ -389,6 +410,9 @@ Environment variables (evaluated at startup):
| `HOP_WEB_SEED_DEMO_USERS`
| `true` to seed admin, developer, operator, viewer (password = username) when
the store is empty
+
+| `HOP_WEB_ALLOW_UNAUTHENTICATED_SERVER_API`
+| `true` to open the embedded Hop Server API (`/hop/*`) in mode `NONE`. Off by
default; ignored in the authenticated modes. Only use behind your own network
controls.
|===
Example Docker run:
@@ -569,6 +593,7 @@ IMPORTANT: These tokens are not revoked when you log off or
when an administrato
Hop creates `jdbc-token.secret` with owner-only permissions (`0600`) on local
filesystems when it can. Anyone who can read that file can mint tokens for any
user.
+[[hop-web-external-full-webxml]]
==== Full web.xml with Tomcat BASIC (single role, EXTERNAL)
The following sample `web.xml` extends Hop Web's default `web.xml` with the
`<security-constraint />` and `<login-config />` elements required for basic
authentication.
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
index ed9833a429..09c143c21b 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
@@ -31,6 +31,7 @@ import org.apache.hop.core.extension.HopExtensionPoint;
import org.apache.hop.core.gui.plugin.GuiRegistry;
import org.apache.hop.core.gui.plugin.key.KeyboardShortcut;
import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.security.HopSecurityConfig;
import org.apache.hop.core.security.HopSecurityContext;
import org.apache.hop.history.AuditManager;
import org.apache.hop.history.AuditState;
@@ -163,8 +164,20 @@ public class HopWebEntryPoint extends AbstractEntryPoint {
"Hop Web security: user ''{0}'' roles={1}",
securityContext.getUsername(), securityContext.getRoleIds());
} else {
- LogChannel.UI.logDebug(
- "Hop Web security: no authenticated principal (mode NONE or
unrestricted)");
+ HopSecurityConfig.AuthMode mode = resolveAuthMode();
+ if (mode == HopSecurityConfig.AuthMode.NONE) {
+ LogChannel.UI.logDebug("Hop Web security: no authenticated principal
(mode NONE)");
+ } else {
+ // A principal-less session in a non-NONE mode is unexpected: log at
error level so
+ // EXTERNAL without a container security-constraint is visible instead
of failing open.
+ LogChannel.UI.logError(
+ "Hop Web security WARNING: authentication mode is ''{0}'' but this
request has no "
+ + "authenticated principal, so the UI is being served
unauthenticated. "
+ + "In EXTERNAL mode Hop relies on the servlet container or
reverse proxy: add a "
+ + "<security-constraint> covering /* (and a <login-config>) to
WEB-INF/web.xml, "
+ + "or switch to BASIC / OAUTH2.",
+ mode.name());
+ }
}
ResourceManager resourceManager = RWT.getResourceManager();
@@ -326,6 +339,20 @@ public class HopWebEntryPoint extends AbstractEntryPoint {
});
}
+ /**
+ * The configured Hop Web authentication mode, or {@code NONE} when the
security configuration
+ * cannot be read. Used to decide whether an unauthenticated request is
expected (mode {@code
+ * NONE}) or a sign that the container security constraint for {@code
EXTERNAL} is missing.
+ */
+ private HopSecurityConfig.AuthMode resolveAuthMode() {
+ try {
+ return HopSecurityConfig.load().getAuthMode();
+ } catch (Exception e) {
+ LogChannel.UI.logDebug("Could not read the Hop Web security
configuration", e);
+ return HopSecurityConfig.AuthMode.NONE;
+ }
+ }
+
/**
* When there is no saved theme preference, run a client script to check
prefers-color-scheme and
* redirect to /ui or /ui-dark so the UI follows system light/dark mode.
Preserves query string