T09 · Insecure Skill Coding Practices
- Location
src/approval-engine.js:136- Finding
Parallel approval workflow completes after a single non-rejecting decision
- Content
View full analysis
d.level === approval.current_level); if (levelDecisions.length === 0) { return null; } // Parallel approval: any rejection rejects; otherwise approve if (rule.approval_type === 'parallel') { if (levelDecisions.some(d => d.decision === 'reject')) { return 'rejected'; } return 'approved'; } ``` The result is subsequently applied without checking whether all required parallel approvers have submitted decisions: ```javascript store.submitDecision(approvalId, approverId, approval.current_level, decision, comment); // Retrieve the updated approval record const updatedApproval = store.getApproval(approvalId); // Check the level result const levelResult = getLevelResult(updatedApproval, rule); if (levelResult === 'approved') { const nextLevel = getNextLevel(rule, approval.current_level); if (nextLevel) { store.updateApproval(approvalId, { current_level: nextLevel }); } else { store.updateApproval(approvalId, { status: 'approved' }); } } ``` ### Technical Analysis The module contains an `isLevelComplete()` function that verifies whether all required approvers have acted, but `submitApproval()` never invokes it before applying `getLevelResult()`. For a parallel rule, `getLevelResult()` returns `approved` whenever at least one decision exists and none of the submitted decisions equals `reject`. It does not compare the set of submitting approvers with the configured set of required approvers. The `decision` argument is also not restricted to an exact allowlist. Therefore, any decision other than the exact string `reject`, including an arbitrary value, is treated as approval by the parallel branch. ### Attack Path 1. A para ...[truncated 1051 chars]- Remediation
View remediation
