api/manage_workflows.php:82-127 (POST) and :129-176 (PUT) — nothing prevents creating a second status_transitions row for the same (from_status, to_status) pair; only from_status !== to_status is validated (server- and client-side, WorkflowDesignerView.php:228).
WorkflowModel::getAllTransitions() (models/WorkflowModel.php:34-60) queries WHERE is_active = TRUE with no ORDER BY and collapses rows into a PHP array keyed by [from_status][to_status], so when two active rows exist for the same pair, one silently overwrites the other with no deterministic winner (MySQL doesn't guarantee row return order without ORDER BY).
Impact:WorkflowDesignerView.php lists all rows from a separate, un-deduped query, so the admin sees two distinct-looking transition rows for the same pair — possibly with different requires_comment/requires_admin settings — while only one, non-deterministically chosen, actually governs isTransitionAllowed()/transitionRequiresComment() at runtime. An admin editing what looks like "the" transition rule for a pair may be editing the row that isn't actually enforced.
Fix: Add a uniqueness constraint (DB unique index on (from_status, to_status) where is_active = TRUE, or an application-level check in manage_workflows.php before insert) and add ORDER BY to getAllTransitions() for deterministic behavior regardless.
**Severity:** Medium
`api/manage_workflows.php:82-127` (POST) and `:129-176` (PUT) — nothing prevents creating a second `status_transitions` row for the same `(from_status, to_status)` pair; only `from_status !== to_status` is validated (server- and client-side, `WorkflowDesignerView.php:228`).
`WorkflowModel::getAllTransitions()` (models/WorkflowModel.php:34-60) queries `WHERE is_active = TRUE` with **no `ORDER BY`** and collapses rows into a PHP array keyed by `[from_status][to_status]`, so when two active rows exist for the same pair, one silently overwrites the other with no deterministic winner (MySQL doesn't guarantee row return order without `ORDER BY`).
**Impact:** `WorkflowDesignerView.php` lists all rows from a separate, un-deduped query, so the admin sees two distinct-looking transition rows for the same pair — possibly with different `requires_comment`/`requires_admin` settings — while only one, non-deterministically chosen, actually governs `isTransitionAllowed()`/`transitionRequiresComment()` at runtime. An admin editing what looks like "the" transition rule for a pair may be editing the row that isn't actually enforced.
**Fix:** Add a uniqueness constraint (DB unique index on `(from_status, to_status)` where `is_active = TRUE`, or an application-level check in `manage_workflows.php` before insert) and add `ORDER BY` to `getAllTransitions()` for deterministic behavior regardless.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: Medium
api/manage_workflows.php:82-127(POST) and:129-176(PUT) — nothing prevents creating a secondstatus_transitionsrow for the same(from_status, to_status)pair; onlyfrom_status !== to_statusis validated (server- and client-side,WorkflowDesignerView.php:228).WorkflowModel::getAllTransitions()(models/WorkflowModel.php:34-60) queriesWHERE is_active = TRUEwith noORDER BYand collapses rows into a PHP array keyed by[from_status][to_status], so when two active rows exist for the same pair, one silently overwrites the other with no deterministic winner (MySQL doesn't guarantee row return order withoutORDER BY).Impact:
WorkflowDesignerView.phplists all rows from a separate, un-deduped query, so the admin sees two distinct-looking transition rows for the same pair — possibly with differentrequires_comment/requires_adminsettings — while only one, non-deterministically chosen, actually governsisTransitionAllowed()/transitionRequiresComment()at runtime. An admin editing what looks like "the" transition rule for a pair may be editing the row that isn't actually enforced.Fix: Add a uniqueness constraint (DB unique index on
(from_status, to_status)whereis_active = TRUE, or an application-level check inmanage_workflows.phpbefore insert) and addORDER BYtogetAllTransitions()for deterministic behavior regardless.