{"schema":"threatline.record/2","record":{"id":"GHSA-c3f4-2mrj-jfqr","aliases":["GHSA-c3f4-2mrj-jfqr"],"summary":"Collaborator credential theft via error_workflow_id: field missing from OWNER_ONLY_EXECUTION_FIELDS","details":"## Summary\r\n`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.\r\n\r\n## Root Cause\r\n`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.\r\n\r\n## Impact\r\nWhen 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.\r\n\r\n## Attack Chain\r\n1. Attacker becomes a workflow collaborator (direct or team share).\r\n2. Attacker creates their own error workflow `E` with an HTTP node pointing at their exfil URL, referencing `{{credentials.*}}`.\r\n3. `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`.\r\n4. Guard 2: `get_workflow_for_user(db, E, attacker.id)` — Bypass: attacker owns `E`.\r\n5. Trigger: main workflow errors (attacker can force by editing an accessible node, or wait).\r\n6. `maybe_run_error_workflow(..., actor_user_id=None)` → `actor_user_id or workflow.owner_id` (line 157) → owner ID.\r\n7. `get_credentials_context(db, owner_id)` (line 96) hydrates owner credentials into the attacker's error workflow context. HTTP node exfiltrates.\r\n\r\n## Bypass Evidence\r\nDirect 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.\r\n\r\n## Affected Versions\r\n`<= v0.0.117` (code present on tag).\r\n\r\n## Suggested Fix\r\nAdd `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.\r\n\r\n---\r\nReported by **zx (Jace)** — GitHub: @manus-use","categories":["Unclassified"],"product":"heym-backend","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/heymrun/heym/security/advisories/GHSA-c3f4-2mrj-jfqr","source_modified_at":"2026-09-30T19:07:44Z","fetched_at":"2026-10-24T05:41:33.294140Z","package":{"ecosystem":"pip","name":"heym-backend"},"range":"v0.0.55 - v0.0.120"}],"fixed_versions":[],"cvss":7.1,"severity":"High","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-30T18:49:13Z","modified_at":"2026-09-30T19:07:44Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/heymrun/heym/security/advisories/GHSA-c3f4-2mrj-jfqr","fetched_at":"2026-10-24T05:41:33.294140Z","source_modified_at":"2026-09-30T19:07:44Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-09-30T19:07:44Z","content_hash":"7e77f57071f48a1c26640454b87194a146cbece17f1e50d32aa6626f5a88d425"}}
