All vulnerabilities
GHSA-c3f4-2mrj-jfqr

heym-backend: Collaborator credential theft via error_workflow_id: field missing from OWNER_ONLY_EXECUTION_FIELDS

heym-backend
CVSS
7.1High
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed inNone listedNo source names a fixed version yet
Published30 Sep 2026Updated 30 Sep 2026GitHub

What fixes it

No source in this snapshot names a fixed version.

Description

Summary

PUT /api/workflows/{id} permits a shared collaborator to overwrite error_workflow_id with a workflow they own. When the target workflow later errors, the error runner loads and injects the owner's credentials into the attacker-controlled error workflow, allowing exfiltration of every credential the owner holds.

Root Cause

backend/app/api/workflows.py defines OWNER_ONLY_EXECUTION_FIELDS = ("auth_type","auth_header_key","webhook_body_mode","http_method","cache_ttl_seconds","rate_limit_requests","rate_limit_window_seconds","sse_enabled","sse_node_config") (lines 1481-1491). error_workflow_id is NOT in the list. _reject_non_owner_auth_change therefore does not block a collaborator's write to that field. The only validation on the write path is get_workflow_for_user(db, workflow_data.error_workflow_id, current_user.id) — which is satisfied because the attacker owns the target error workflow.

Impact

When the parent workflow errors, backend/app/services/error_workflow_runner.py:157 falls back to actor_user_id=actor_user_id or workflow.owner_id and get_credentials_context(db, actor_user_id) (line 96) loads the owner's decrypted credential set. The attacker's error workflow (arbitrary HTTP node) exfiltrates all owner credentials — same class of escalation as GHSA-5939-m9jm-7gf5 that motivated OWNER_ONLY_EXECUTION_FIELDS in the first place.

Attack Chain

  1. Attacker becomes a workflow collaborator (direct or team share).
  2. Attacker creates their own error workflow E with an HTTP node pointing at their exfil URL, referencing {{credentials.*}}.
  3. PUT /api/workflows/{victim_id} body {"error_workflow_id": "<E>"} — Guard: _reject_non_owner_auth_change — Bypass: error_workflow_id absent from OWNER_ONLY_EXECUTION_FIELDS.
  4. Guard 2: get_workflow_for_user(db, E, attacker.id) — Bypass: attacker owns E.
  5. Trigger: main workflow errors (attacker can force by editing an accessible node, or wait).
  6. maybe_run_error_workflow(..., actor_user_id=None) → actor_user_id or workflow.owner_id (line 157) → owner ID.
  7. get_credentials_context(db, owner_id) (line 96) hydrates owner credentials into the attacker's error workflow context. HTTP node exfiltrates.

Bypass Evidence

Direct read of v0.0.117 workflows.py lines 1481-1491 and 1634-1652 shows: (a) error_workflow_id missing from list; (b) only check is accessibility of the target error workflow to current_user. Cross-check error_workflow_runner.py line 157 confirms owner-fallback.

Affected Versions

<= v0.0.117 (code present on tag).

Suggested Fix

Add error_workflow_id to OWNER_ONLY_EXECUTION_FIELDS, OR pass the collaborator's user id as the actor for error-workflow runs of shared workflows so credentials load under the caller's authority, not the owner's.

--- Reported by zx (Jace) — GitHub: @manus-use

Text from GitHub Advisory Database

Affected versions 1

  • heym-backend, pipv0.0.55 - v0.0.120GitHub