Skip to content

fix(security): harden dashboard API against unauthenticated access#9800

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-e12e9172
Apr 14, 2026
Merged

fix(security): harden dashboard API against unauthenticated access#9800
teknium1 merged 1 commit into
mainfrom
hermes/hermes-e12e9172

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Addresses responsible disclosure from FuzzMind Security Lab (Callum @0xca1x, @migraine-sudo).

The web dashboard API server had 36 endpoints, of which only 5 checked the session token. The token itself was served from an unauthenticated GET /api/auth/session-token endpoint, rendering the protection circular. When bound to 0.0.0.0 (--host flag), all API keys, config, and cron management were accessible to any machine on the network.

Changes

  • Auth middleware — requires session token on ALL /api/ routes except a small public whitelist (/api/status, /api/config/defaults, /api/config/schema, /api/model/info)
  • Remove token endpointGET /api/auth/session-token deleted; token is now injected into index.html via a <script> tag at serve time so only the actual SPA has it
  • Timing-safe comparison — all token checks now use hmac.compare_digest() instead of !=
  • Block public binding — non-localhost --host now requires --insecure flag (hard error without it)
  • FrontendfetchJSON() sends Authorization header on all requests using window.__HERMES_SESSION_TOKEN__

Test plan

  • test_web_server.py: 67 tests pass (includes new test_unauthenticated_api_blocked + test_session_token_endpoint_removed)
  • test_api_server*.py: 188 tests pass
  • Total: 255 tests, 0 failures

Note

This is a stopgap hardening. Robust authentication (user accounts, API keys) is planned as a separate feature. The primary defense here is preventing public serving without --insecure.

Addresses responsible disclosure from FuzzMind Security Lab (CVE pending).

The web dashboard API server had 36 endpoints, of which only 5 checked
the session token. The token itself was served from an unauthenticated
GET /api/auth/session-token endpoint, rendering the protection circular.
When bound to 0.0.0.0 (--host flag), all API keys, config, and cron
management were accessible to any machine on the network.

Changes:
- Add auth middleware requiring session token on ALL /api/ routes except
  a small public whitelist (status, config/defaults, config/schema,
  model/info)
- Remove GET /api/auth/session-token endpoint entirely; inject the token
  into index.html via a <script> tag at serve time instead
- Replace all inline token comparisons (!=) with hmac.compare_digest()
  to prevent timing side-channel attacks
- Block non-localhost binding by default; require --insecure flag to
  override (with warning log)
- Update frontend fetchJSON() to send Authorization header on all
  requests using the injected window.__HERMES_SESSION_TOKEN__

Credit: Callum (@0xca1x) and @migraine-sudo at FuzzMind Security Lab
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Supply Chain Risk Detected

This PR contains patterns commonly associated with supply chain attacks. This does not mean the PR is malicious — but these patterns require careful human review before merging.

⚠️ WARNING: Install hook files modified

These files can execute code during package installation or interpreter startup.

Files:

hermes_cli/memory_setup.py

Automated scan triggered by supply-chain-audit. If this is a false positive, a maintainer can approve after manual review.

@teknium1 teknium1 merged commit 99bcc2d into main Apr 14, 2026
6 of 7 checks passed
@teknium1 teknium1 deleted the hermes/hermes-e12e9172 branch April 14, 2026 17:57
kingsleydon added a commit to Clawdi-AI/hermes-agent that referenced this pull request Apr 16, 2026
Four minimal patches for Clawdi's multi-tenant dashboard integration:

1. web_server.py: HERMES_SESSION_TOKEN env override — reverse-proxied
   deployments set a stable token so the SPA can authenticate across
   restarts (mirrors upstream PR NousResearch#9800 pattern).

2. web_server.py: load_hermes_dotenv() at module init — uvicorn launches
   web_server.py directly, bypassing hermes_cli/main.py where the dotenv
   loader normally runs. Without this, the HERMES_SESSION_TOKEN written
   to ~/.hermes/.env is never loaded into os.environ.

3. web_server.py: expose plain `value` field for non-password env vars
   in GET /api/env — lets the dashboard round-trip comma-separated
   allow-lists (TELEGRAM_ALLOWED_USERS, FEISHU_ALLOWED_USERS, etc.)
   without forcing users to retype them.

4. config.py: register FEISHU_ALLOWED_USERS in OPTIONAL_ENV_VARS — the
   Feishu gateway adapter reads this from os.getenv() but it was missing
   from the registration list, so GET /api/env never surfaced it.
LucaDeLeo added a commit to LucaDeLeo/hermes-agent that referenced this pull request Apr 16, 2026
Upstream's dashboard auth middleware (NousResearch#9800) gates all /api/* routes
except a public allowlist. The chat fetch was a raw fetch() that
didn't include the bearer token, so every dashboard chat request
returned 401 Unauthorized.

Other dashboard pages go through fetchJSON() which reads
window.__HERMES_SESSION_TOKEN__ and auto-injects the Authorization
header; the chat fetch needs SSE (res.body streaming) so can't reuse
fetchJSON — instead it reads the token directly and sets the header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ulasbilgen pushed a commit to ulasbilgen/hermes-adhd-agent that referenced this pull request May 1, 2026
…ousResearch#9800)

Addresses responsible disclosure from FuzzMind Security Lab (CVE pending).

The web dashboard API server had 36 endpoints, of which only 5 checked
the session token. The token itself was served from an unauthenticated
GET /api/auth/session-token endpoint, rendering the protection circular.
When bound to 0.0.0.0 (--host flag), all API keys, config, and cron
management were accessible to any machine on the network.

Changes:
- Add auth middleware requiring session token on ALL /api/ routes except
  a small public whitelist (status, config/defaults, config/schema,
  model/info)
- Remove GET /api/auth/session-token endpoint entirely; inject the token
  into index.html via a <script> tag at serve time instead
- Replace all inline token comparisons (!=) with hmac.compare_digest()
  to prevent timing side-channel attacks
- Block non-localhost binding by default; require --insecure flag to
  override (with warning log)
- Update frontend fetchJSON() to send Authorization header on all
  requests using the injected window.__HERMES_SESSION_TOKEN__

Credit: Callum (@0xca1x) and @migraine-sudo at FuzzMind Security Lab
aj-nt pushed a commit to aj-nt/hermes-agent that referenced this pull request May 1, 2026
…ousResearch#9800)

Addresses responsible disclosure from FuzzMind Security Lab (CVE pending).

The web dashboard API server had 36 endpoints, of which only 5 checked
the session token. The token itself was served from an unauthenticated
GET /api/auth/session-token endpoint, rendering the protection circular.
When bound to 0.0.0.0 (--host flag), all API keys, config, and cron
management were accessible to any machine on the network.

Changes:
- Add auth middleware requiring session token on ALL /api/ routes except
  a small public whitelist (status, config/defaults, config/schema,
  model/info)
- Remove GET /api/auth/session-token endpoint entirely; inject the token
  into index.html via a <script> tag at serve time instead
- Replace all inline token comparisons (!=) with hmac.compare_digest()
  to prevent timing side-channel attacks
- Block non-localhost binding by default; require --insecure flag to
  override (with warning log)
- Update frontend fetchJSON() to send Authorization header on all
  requests using the injected window.__HERMES_SESSION_TOKEN__

Credit: Callum (@0xca1x) and @migraine-sudo at FuzzMind Security Lab
kingsleydon added a commit to Clawdi-AI/hermes-agent that referenced this pull request May 9, 2026
Four minimal patches for Clawdi's multi-tenant dashboard integration:

1. web_server.py: HERMES_SESSION_TOKEN env override — reverse-proxied
   deployments set a stable token so the SPA can authenticate across
   restarts (mirrors upstream PR NousResearch#9800 pattern).

2. web_server.py: load_hermes_dotenv() at module init — uvicorn launches
   web_server.py directly, bypassing hermes_cli/main.py where the dotenv
   loader normally runs. Without this, the HERMES_SESSION_TOKEN written
   to ~/.hermes/.env is never loaded into os.environ.

3. web_server.py: expose plain `value` field for non-password env vars
   in GET /api/env — lets the dashboard round-trip comma-separated
   allow-lists (TELEGRAM_ALLOWED_USERS, FEISHU_ALLOWED_USERS, etc.)
   without forcing users to retype them.

4. config.py: register FEISHU_ALLOWED_USERS in OPTIONAL_ENV_VARS — the
   Feishu gateway adapter reads this from os.getenv() but it was missing
   from the registration list, so GET /api/env never surfaced it.
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…ousResearch#9800)

Addresses responsible disclosure from FuzzMind Security Lab (CVE pending).

The web dashboard API server had 36 endpoints, of which only 5 checked
the session token. The token itself was served from an unauthenticated
GET /api/auth/session-token endpoint, rendering the protection circular.
When bound to 0.0.0.0 (--host flag), all API keys, config, and cron
management were accessible to any machine on the network.

Changes:
- Add auth middleware requiring session token on ALL /api/ routes except
  a small public whitelist (status, config/defaults, config/schema,
  model/info)
- Remove GET /api/auth/session-token endpoint entirely; inject the token
  into index.html via a <script> tag at serve time instead
- Replace all inline token comparisons (!=) with hmac.compare_digest()
  to prevent timing side-channel attacks
- Block non-localhost binding by default; require --insecure flag to
  override (with warning log)
- Update frontend fetchJSON() to send Authorization header on all
  requests using the injected window.__HERMES_SESSION_TOKEN__

Credit: Callum (@0xca1x) and @migraine-sudo at FuzzMind Security Lab
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…ousResearch#9800)

Addresses responsible disclosure from FuzzMind Security Lab (CVE pending).

The web dashboard API server had 36 endpoints, of which only 5 checked
the session token. The token itself was served from an unauthenticated
GET /api/auth/session-token endpoint, rendering the protection circular.
When bound to 0.0.0.0 (--host flag), all API keys, config, and cron
management were accessible to any machine on the network.

Changes:
- Add auth middleware requiring session token on ALL /api/ routes except
  a small public whitelist (status, config/defaults, config/schema,
  model/info)
- Remove GET /api/auth/session-token endpoint entirely; inject the token
  into index.html via a <script> tag at serve time instead
- Replace all inline token comparisons (!=) with hmac.compare_digest()
  to prevent timing side-channel attacks
- Block non-localhost binding by default; require --insecure flag to
  override (with warning log)
- Update frontend fetchJSON() to send Authorization header on all
  requests using the injected window.__HERMES_SESSION_TOKEN__

Credit: Callum (@0xca1x) and @migraine-sudo at FuzzMind Security Lab
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
…ousResearch#9800)

Addresses responsible disclosure from FuzzMind Security Lab (CVE pending).

The web dashboard API server had 36 endpoints, of which only 5 checked
the session token. The token itself was served from an unauthenticated
GET /api/auth/session-token endpoint, rendering the protection circular.
When bound to 0.0.0.0 (--host flag), all API keys, config, and cron
management were accessible to any machine on the network.

Changes:
- Add auth middleware requiring session token on ALL /api/ routes except
  a small public whitelist (status, config/defaults, config/schema,
  model/info)
- Remove GET /api/auth/session-token endpoint entirely; inject the token
  into index.html via a <script> tag at serve time instead
- Replace all inline token comparisons (!=) with hmac.compare_digest()
  to prevent timing side-channel attacks
- Block non-localhost binding by default; require --insecure flag to
  override (with warning log)
- Update frontend fetchJSON() to send Authorization header on all
  requests using the injected window.__HERMES_SESSION_TOKEN__

Credit: Callum (@0xca1x) and @migraine-sudo at FuzzMind Security Lab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant