Skip to content

Vite dev proxy does not forward Authorization on /api/* → 401 on auth-gated gateways #657

Description

@Josephur

Summary

The Vite dev proxy in vite.config.ts does not forward (or inject) the Authorization header on /api/* requests to the Hermes Agent gateway. Result: every dashboard data fetch (/api/sessions, /api/history, /api/connection-status, /api/skills, /api/files) returns 401 when API_SERVER_KEY / HERMES_API_TOKEN is set on the gateway. This affects any Hermes Workspace deployment where the agent is auth-gated — including every production and Docker setup.

The token cannot be injected client-side (Vite only ships VITE_* env vars to the browser bundle, and shipping HERMES_API_TOKEN to the browser would leak the gateway bearer token to every visitor). It must be injected server-side by the Vite middleware.

Affected setups

  • Workspace running pnpm dev (vite) against a Hermes Agent with API_SERVER_KEY set → broken, no /api/* data loads
  • Docker deployments with hermes-agent exposing API_SERVER_KEY → broken, dashboard renders empty/error
  • Any setup where HERMES_DASHBOARD_URL / HERMES_API_TOKEN is set in workspace .env → broken

Dev-only setups (no API_SERVER_KEY on the agent) are unaffected, which is why this never shows up in the workspace's own development loop.

Root cause

vite.config.ts defines the dev proxy like this:

// line ~525
'/api/claude-proxy': {
  target: proxyTarget,
  changeOrigin: true,
  rewrite: (path) => path.replace(/^\/api\/claude-proxy/, ''),
},

The browser sends GET /api/sessions. Vite proxies it to http://127.0.0.1:8642/sessions. The browser cannot include Authorization because the token lives in HERMES_API_TOKEN (server-side env), not VITE_HERMES_API_TOKEN (client-side). The proxy forwards headers as-is. The gateway responds 401.

For comparison, the same file's connection-status probe (the one that drives the green/red "API connected" indicator) does inject auth correctly — it was added in commit ce4fab30 ("fix(vite): point /api/sessions probe at dashboard URL, add auth header & ignore noise", May 4 2026). The fix only landed for the probe, not the runtime proxy.

Reproduction

# Prereqs: Hermes Agent running on :8642 with API_SERVER_KEY set.
# Hermes Workspace running via `pnpm dev` on :3000.
# HERMES_API_TOKEN set in workspace .env.

# 1. Hit the proxy from the browser's perspective (same as the UI does):
$ curl -s http://127.0.0.1:3000/api/sessions
{"ok":false,"error":"Unauthorized"}

# 2. Confirm the upstream actually rejects unauthenticated requests:
$ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8642/api/sessions
401

# 3. Confirm the upstream accepts auth:
$ curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $HERMES_API_TOKEN" http://127.0.0.1:8642/api/sessions
200

In the browser, this surfaces as a banner: Failed to load sessions. Hermes Agent dashboard /api/sessions?limit=50&offset=0: 401 {"error":{"message":"Invalid API key",...}}

Suggested fix

Server-side token injection in the Vite proxy. Roughly:

// vite.config.ts
const claudeApiToken =
  env.HERMES_API_TOKEN?.trim() || env.CLAUDE_API_TOKEN?.trim() || ''

const apiProxyOptions: ProxyOptions = {
  target: proxyTarget,
  changeOrigin: true,
  rewrite: (path) => path.replace(/^\/api\/claude-proxy/, ''),
  configure: (proxy) => {
    if (claudeApiToken) {
      proxy.on('proxyReq', (proxyReq) => {
        proxyReq.setHeader('Authorization', `Bearer ${claudeApiToken}`)
      })
    }
  },
}

// Then use { ...apiProxyOptions } for both /api/claude-proxy and the
// /api fallback block.

HERMES_API_TOKEN is already loaded into env via loadEnv(mode, process.cwd(), '') at the top of the config file (line ~85) and is used by the existing probe. Same source, same precedence rules, no new env vars.

Related

Environment

  • outsourc-e/hermes-workspace v2.3.0 (tag)
  • Hermes Agent latest
  • Linux native, pnpm dev

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions