Summary
The kagent UI is completely unusable when served over a non-secure context (plain HTTP on a raw IP / non-localhost host — e.g. a LoadBalancer or NodePort Service reached at http://<IP>:8080). The entire SPA renders a blank page, so the chat input never appears and no message can ever be sent. This reproduces on both 0.9.7 and 0.9.8.
Root cause: the client bundle calls crypto.subtle.digest(...) unguarded. crypto.subtle (SubtleCrypto) is a secure-context-only API — it is undefined outside HTTPS/localhost — so the call throws TypeError during client hydration, crashing the whole React tree.
This is distinct from the already-fixed crypto.randomUUID issue (#1868 / the generateId() feature-detect fallback is present and works correctly). This is a second, separate secure-context dependency.
Environment
- kagent UI image
cr.kagent.dev/kagent-dev/kagent/ui:0.9.7 and :0.9.8 (both reproduce)
- Served over plain HTTP via a LoadBalancer at
http://<LB-IP>:8080 (no TLS), Chrome
window.isSecureContext === false
Symptom / evidence
In the browser console at the chat URL over plain HTTP:
JSON.stringify({ s: isSecureContext, uuid: typeof crypto.randomUUID, subtle: typeof (crypto.subtle) })
// => {"s":false,"uuid":"undefined","subtle":"undefined"}
document.body.innerText.length // => 0 (entire app blank)
document.querySelectorAll('textarea,input,button').length // => 0
The offending code is in a client chunk (e.g. static/chunks/0pvx_*.js). De-minified shape:
async function c(e) {
const t = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", u.encode(e))).subarray(0, 12);
let r = "";
for (let i = 0; i < t.length; i++) r += String.fromCharCode(t[i]);
return btoa(r).replace(/\+/g, "-").replace(/\//g, "_") /* … base64url */;
}
It looks like a SHA-256 → base64url short-key/id helper (hashing joined args). It is not in kagent's own ui/src (I grepped the v0.9.8 tree) — it appears to come from a bundled dependency, which is why the 0.9.8 npm bump (#2016) didn't change it.
Server-side rendering succeeds (Node has crypto.subtle), so the backend logs show the UI server happily listing agents/sessions — only the browser crashes. Consequently a chat session is created but stays at 0 tasks, and the target agent's logs show only GET /.well-known/agent-card.json polls (no invoke ever dispatched). It looks like "chat hangs / does nothing", but the real cause is the blank/hydration crash upstream.
Steps to reproduce
- Deploy kagent and expose the UI over plain HTTP on a non-localhost host (e.g.
Service type: LoadBalancer, access http://<EXTERNAL-IP>:8080).
- Open any agent chat page.
- Page is blank; no input renders; no message can be sent.
(Accessing the same deployment via https:// or http://localhost:8080 works, because those are secure contexts where crypto.subtle is defined.)
Impact
Any non-TLS, non-localhost deployment of the kagent UI is fully broken — a common setup for quick LoadBalancer/NodePort/IP access. The failure is silent (blank page, no console-visible error toast for users).
Suggested fix
- Feature-detect
crypto.subtle (like generateId() already does for crypto.randomUUID) and fall back to a pure-JS SHA-256 (or getRandomValues-based) implementation when it's unavailable; or
- Identify the bundled dependency performing this
crypto.subtle.digest and pin/replace it with a secure-context-safe one; and/or
- At minimum, document that the UI requires a secure context (HTTPS or localhost) and surface a clear error instead of a blank page.
Happy to help bisect the dependency or test a fix.
Summary
The kagent UI is completely unusable when served over a non-secure context (plain HTTP on a raw IP / non-
localhosthost — e.g. aLoadBalancerorNodePortService reached athttp://<IP>:8080). The entire SPA renders a blank page, so the chat input never appears and no message can ever be sent. This reproduces on both 0.9.7 and 0.9.8.Root cause: the client bundle calls
crypto.subtle.digest(...)unguarded.crypto.subtle(SubtleCrypto) is a secure-context-only API — it isundefinedoutside HTTPS/localhost — so the call throwsTypeErrorduring client hydration, crashing the whole React tree.This is distinct from the already-fixed
crypto.randomUUIDissue (#1868 / thegenerateId()feature-detect fallback is present and works correctly). This is a second, separate secure-context dependency.Environment
cr.kagent.dev/kagent-dev/kagent/ui:0.9.7and:0.9.8(both reproduce)http://<LB-IP>:8080(no TLS), Chromewindow.isSecureContext === falseSymptom / evidence
In the browser console at the chat URL over plain HTTP:
The offending code is in a client chunk (e.g.
static/chunks/0pvx_*.js). De-minified shape:It looks like a SHA-256 → base64url short-key/id helper (hashing joined args). It is not in kagent's own
ui/src(I grepped the v0.9.8 tree) — it appears to come from a bundled dependency, which is why the 0.9.8 npm bump (#2016) didn't change it.Server-side rendering succeeds (Node has
crypto.subtle), so the backend logs show the UI server happily listing agents/sessions — only the browser crashes. Consequently a chat session is created but stays at 0 tasks, and the target agent's logs show onlyGET /.well-known/agent-card.jsonpolls (no invoke ever dispatched). It looks like "chat hangs / does nothing", but the real cause is the blank/hydration crash upstream.Steps to reproduce
Service type: LoadBalancer, accesshttp://<EXTERNAL-IP>:8080).(Accessing the same deployment via
https://orhttp://localhost:8080works, because those are secure contexts wherecrypto.subtleis defined.)Impact
Any non-TLS, non-localhost deployment of the kagent UI is fully broken — a common setup for quick LoadBalancer/NodePort/IP access. The failure is silent (blank page, no console-visible error toast for users).
Suggested fix
crypto.subtle(likegenerateId()already does forcrypto.randomUUID) and fall back to a pure-JS SHA-256 (orgetRandomValues-based) implementation when it's unavailable; orcrypto.subtle.digestand pin/replace it with a secure-context-safe one; and/orHappy to help bisect the dependency or test a fix.