# New subcontractor can be set for a SCConfirmed task without current subcontractor consent

Rigor Protocol's inviteSC lacks an SCConfirmed check, letting a contractor reassign a funded, in-progress task to their own subcontractor and steal the task budget on completion.

- Payout: Contest reward
- Severity: medium
- Program: Rigor Protocol
- Credited: hyh
- Disclosed: 2022-09-12
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-08-rigor-findings/issues/378
- Library page: https://bugbountylibrary.com/r/c4%3A2022-08-rigor%3AM-16
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:chained technique:source-review vuln:access-control vuln:business-logic

**Target:** Project.sol / Tasks.sol smart contracts (inviteSC, inviteSubcontractor, changeOrder)
**Root cause:** inviteSubcontractor only enforces onlyInactive; changeOrder's insufficient-funding branch calls unApprove() which resets state to Inactive and clears SCConfirmed, so a task already agreed and in progress can be re-invited to a new subcontractor without the current SC's consent.
**Steps:**
1. Contractor calls changeOrder with a small cost increase that exceeds available lent funds (`totalLent - _totalAllocated < _newCost - _taskCost`), triggering the else branch.
2. That branch calls `tasks[_taskID].unApprove()` setting state=Inactive and SCConfirmed=false, and unAllocateFunds().
3. Contractor then calls `inviteSC([taskID],[own_subcontractor])` — passes onlyInactive since state is Inactive.
4. Own subcontractor calls acceptInvitation, then contractor calls setComplete(); `currency.safeTransfer(tasks[_taskID].subcontractor, tasks[_taskID].cost)` pays the attacker's SC the full task cost.
**How found:** Code review of inviteSC/_inviteSC/inviteSubcontractor noting only onlyInactive guards reassignment; then tracing changeOrder's insufficient-funding branch which calls unApprove() to reset an already SCConfirmed task to Inactive, defeating that guard.
**Exploitation:** Contractor front-runs a legitimate setComplete() by first invoking changeOrder to force the task into Inactive (via the insufficient-funds branch), then inviteSC to their own subcontractor, accept, and setComplete to redirect the entire task budget to themselves; the real subcontractor cannot raiseDispute since the task record is already changed.
**Escalation:** Chains changeOrder's unApprove reset with inviteSC to bypass the onlyInactive guard, converting a state-reset bug into full theft of the task budget.
**Insight:** When a state guard (onlyInactive) is the sole protection for a value-bearing assignment, audit every path that can flip that state back — here changeOrder's underfunded branch silently un-confirms an agreed task, reopening reassignment.
