Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions flows/flow-04-agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -218,25 +218,33 @@ else
fail "Hermes gateway health check failed — ${oc_health:0:100}"
fi

step "Hermes native dashboard UI via deeplink"
step "Hermes native dashboard UI reachable + auth-gated"
HERMES_DASHBOARD_HOST="obol-agent.obol.stack"
if [ "$ingress_port" = "80" ]; then
HERMES_DASHBOARD_URL="http://${HERMES_DASHBOARD_HOST}"
else
HERMES_DASHBOARD_URL="http://${HERMES_DASHBOARD_HOST}:${ingress_port}"
fi
dashboard_html=""
# hermes-agent v2026.7.x hardening: a non-loopback (0.0.0.0) dashboard bind now
# requires an auth provider, and the loopback-only inline __HERMES_SESSION_TOKEN__
# is deliberately disabled on a gated bind (closes the hermes-0day hole). The
# stack configures basic-auth. Assert the dashboard container is UP (public
# /api/status → 200) and ENFORCES auth (protected /api/sessions → 401 for an
# unauthenticated caller; /api routes gate on the session token, not basic-auth).
dash_code() { curl --resolve "${HERMES_DASHBOARD_HOST}:${ingress_port}:127.0.0.1" \
-s -o /dev/null -w "%{http_code}" --max-time 10 "$@" 2>/dev/null; }
dash_status="" dash_protected=""
for i in $(seq 1 15); do
dashboard_html=$(curl --resolve "${HERMES_DASHBOARD_HOST}:${ingress_port}:127.0.0.1" \
-sf --max-time 10 "$HERMES_DASHBOARD_URL/" 2>&1) || true
if echo "$dashboard_html" | grep -q "__HERMES_SESSION_TOKEN__"; then
pass "Hermes dashboard UI loaded: $HERMES_DASHBOARD_URL"
dash_status=$(dash_code "$HERMES_DASHBOARD_URL/api/status")
dash_protected=$(dash_code "$HERMES_DASHBOARD_URL/api/sessions")
if [ "$dash_status" = "200" ] && [ "$dash_protected" = "401" ]; then
pass "Hermes dashboard up + auth-gated (status=$dash_status protected=$dash_protected)"
break
fi
sleep 2
done
if ! echo "$dashboard_html" | grep -q "__HERMES_SESSION_TOKEN__"; then
fail "Hermes dashboard UI deeplink failed — ${dashboard_html:0:100}"
if [ "$dash_status" != "200" ] || [ "$dash_protected" != "401" ]; then
fail "Hermes dashboard check failed — status=$dash_status protected=$dash_protected"
fi

# §4: Verify Hermes config still has the expected model/provider wiring.
Expand Down
14 changes: 12 additions & 2 deletions internal/hermes/hermes.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const (
rawChartVersion = "2.0.2"

// renovate: datasource=docker depName=nousresearch/hermes-agent
defaultImage = "nousresearch/hermes-agent:v2026.6.19"
defaultImage = "nousresearch/hermes-agent:v2026.7.1"
// Use the upstream image venv instead of cloning Hermes into the PVC on
// every cold start. The init container below validates the required extras
// are present so image regressions fail before the gateway starts.
Expand Down Expand Up @@ -977,6 +977,16 @@ func generateValues(namespace, hostname, dashboardHostname, agentBaseURL, token,
# is safe here. Production deployments must override this via a values overlay.
- name: GATEWAY_ALLOW_ALL_USERS
value: "true"
# v2026.7.x hardening: a non-loopback (0.0.0.0) dashboard bind now
# requires an auth provider; the legacy --insecure flag no longer
# bypasses it (closes the hermes-0day unauthenticated-dashboard
# hole). Register the bundled basic-auth provider using the agent's
# existing API token as the password (already surfaced to the
# operator). Probe path /api/status stays auth-exempt.
- name: HERMES_DASHBOARD_BASIC_AUTH_USERNAME
value: obol
- name: HERMES_DASHBOARD_BASIC_AUTH_PASSWORD
value: %s
readinessProbe:
httpGet:
path: /api/status
Expand Down Expand Up @@ -1056,7 +1066,7 @@ func generateValues(namespace, hostname, dashboardHostname, agentBaseURL, token,
- name: %s
port: %d
`, desc.DefaultPort, desc.DefaultPort, desc.DefaultPort,
quoteYAML(image()), quoteYAML(hermesBinary), dashboardPort, dashboardPort, desc.DefaultPort, dashboardPort, dashboardPort, dashboardPort,
quoteYAML(image()), quoteYAML(hermesBinary), dashboardPort, dashboardPort, desc.DefaultPort, quoteYAML(token), dashboardPort, dashboardPort, dashboardPort,
desc.DataPVCName,
desc.ServiceName, namespace, desc.ServiceName, desc.ServiceName, desc.DefaultPort, dashboardPort,
desc.ServiceName, namespace, quoteYAML(hostname), desc.ServiceName, desc.DefaultPort,
Expand Down
2 changes: 1 addition & 1 deletion internal/serviceoffercontroller/agent_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const (
hermesDataPVC = "hermes-data"
hermesAPIPath = "/health"
// renovate: datasource=docker depName=nousresearch/hermes-agent
defaultHermesImage = "nousresearch/hermes-agent:v2026.6.19"
defaultHermesImage = "nousresearch/hermes-agent:v2026.7.1"
)

// agentLabels returns the standard label set we attach to every primitive
Expand Down
Loading