From dcea97340a92f8f4eb3212b0d36bc7effac564fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 01:34:40 +0000 Subject: [PATCH 1/2] chore(deps): update nousresearch/hermes-agent docker tag to v2026.7.1 --- internal/hermes/hermes.go | 2 +- internal/serviceoffercontroller/agent_render.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/hermes/hermes.go b/internal/hermes/hermes.go index 479ca5a5..df489607 100644 --- a/internal/hermes/hermes.go +++ b/internal/hermes/hermes.go @@ -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. diff --git a/internal/serviceoffercontroller/agent_render.go b/internal/serviceoffercontroller/agent_render.go index 91a7e276..4e030929 100644 --- a/internal/serviceoffercontroller/agent_render.go +++ b/internal/serviceoffercontroller/agent_render.go @@ -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 From d89df1726c8a5ea21ee6fa001c834e360ec3db54 Mon Sep 17 00:00:00 2001 From: bussyjd Date: Tue, 7 Jul 2026 11:11:03 +0400 Subject: [PATCH 2/2] fix(hermes): configure dashboard basic-auth required by hermes-agent v2026.7.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v2026.7.1 bump in this PR hardened the dashboard: a non-loopback (0.0.0.0) bind now REQUIRES an auth provider and the legacy --insecure flag no longer bypasses it (the hermes-0day fix). The master agent binds the dashboard on 0.0.0.0 for the k8s Service, so without this it crashloops (hermes-dashboard CrashLoopBackOff → deployment never Ready → flow-04 fail). Register the bundled basic-auth provider via env (user 'obol', password = the agent's existing API token); probe path /api/status stays auth-exempt. Child agents run 'gateway run' with no dashboard sidecar, so only the master agent (internal/hermes) needs it. Also update flow-04's dashboard check: the loopback-only inline __HERMES_SESSION_TOKEN__ is disabled on a gated bind, so assert the dashboard is up (public /api/status=200) and auth-gated (/api/sessions=401) instead. Validated on arm64 QA (spark): hermes pod 2/2 Running (was 1/2 CrashLoopBackOff); paid buy flow green. --- flows/flow-04-agent.sh | 24 ++++++++++++++++-------- internal/hermes/hermes.go | 12 +++++++++++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/flows/flow-04-agent.sh b/flows/flow-04-agent.sh index ad4f6f81..aa72067b 100755 --- a/flows/flow-04-agent.sh +++ b/flows/flow-04-agent.sh @@ -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. diff --git a/internal/hermes/hermes.go b/internal/hermes/hermes.go index df489607..b4e26a0e 100644 --- a/internal/hermes/hermes.go +++ b/internal/hermes/hermes.go @@ -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 @@ -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,