heym-backend: Collaborator credential theft via error_workflow_id: field missing from OWNER_ONLY_EXECUTION_FIELDS
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
- Attacker becomes a workflow collaborator (direct or team share).
- Attacker creates their own error workflow
Ewith an HTTP node pointing at their exfil URL, referencing{{credentials.*}}. PUT /api/workflows/{victim_id}body{"error_workflow_id": "<E>"}— Guard:_reject_non_owner_auth_change— Bypass:error_workflow_idabsent fromOWNER_ONLY_EXECUTION_FIELDS.- Guard 2:
get_workflow_for_user(db, E, attacker.id)— Bypass: attacker ownsE. - Trigger: main workflow errors (attacker can force by editing an accessible node, or wait).
maybe_run_error_workflow(..., actor_user_id=None)→actor_user_id or workflow.owner_id(line 157) → owner ID.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