Business Workflow and Approval Process Bypass Through Logic Manipulation

High Risk Business Logic
workflow-bypassapproval-systemsbusiness-logicstate-manipulationauthorization-bypassprocess-circumventionprivilege-escalation

What it is

A high-severity vulnerability where attackers can bypass critical business workflows, approval processes, and authorization gates through manipulation of state transitions, parameter injection, or exploitation of race conditions in multi-step business processes. This can lead to unauthorized transactions, privilege escalation, and circumvention of critical business controls such as financial approvals, document reviews, or access permissions.

// VULNERABLE: Simple approval system with multiple bypass opportunities app.post('/api/requests/:id/approve', async (req, res) => { const { id } = req.params; const { decision } = req.body; try { const request = await Request.findById(id); // PROBLEM: No state validation request.status = decision === 'approve' ? 'approved' : 'rejected'; request.approvedBy = req.user.id; await request.save(); // PROBLEM: No permission validation if (decision === 'approve') { await executeRequestAction(request); } res.json({ success: true }); } catch (error) { res.status(500).json({ error: error.message }); } });
// SECURE: Comprehensive approval workflow with state machine and permissions const { body, param, validationResult } = require('express-validator'); app.post('/api/requests/:id/approve', [ param('id').isUUID().withMessage('Invalid request ID'), body('decision').isIn(['approve', 'reject']).withMessage('Invalid decision'), body('notes').optional().isLength({ max: 1000 }).withMessage('Notes too long'), body('idempotencyKey').optional().isUUID().withMessage('Invalid idempotency key') ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { id } = req.params; const { decision, notes, idempotencyKey } = req.body; // Initialize secure managers const workflowManager = new SecureWorkflowManager(); const permissionManager = new SecurePermissionManager(); const approvalProcessor = new SecureMultiStepApproval(); // Fetch request with current state const request = await Request.findById(id).populate('requestType department'); if (!request) { return res.status(404).json({ error: 'Request not found' }); } // Validate user permissions for this specific request const permissionResult = await permissionManager.validateApprovalPermission( req.user.id, { id: request.id, type: request.type, amount: request.amount, department: request.department.id }, { currentState: request.status, userAgent: req.get('User-Agent'), ipAddress: req.ip } ); if (!permissionResult.hasPermission) { return res.status(403).json({ error: 'Insufficient permissions', reasons: permissionResult.reasons, requiredPermissions: permissionResult.limitations }); } // Process approval through secure state machine let result; if (request.isMultiStep) { // Multi-step approval process result = await approvalProcessor.processApprovalStep( request.processId, request.currentStep, decision, req.user.id, { notes, idempotencyKey, permissionSource: permissionResult.permissionSource } ); } else { // Single-step approval with state validation const targetState = decision === 'approve' ? 'approved' : 'rejected'; result = await workflowManager.updateWorkflowState( request.id, targetState, req.user.id, { decision, notes, permissionSource: permissionResult.permissionSource, validationHash: crypto.createHash('sha256') .update(`${request.id}:${req.user.id}:${decision}:${Date.now()}`) .digest('hex') } ); } // Log approval action await AuditLog.create({ type: 'approval_decision', userId: req.user.id, resourceType: 'request', resourceId: request.id, action: `request_${decision}`, details: { previousState: request.status, newState: result.newState || (decision === 'approve' ? 'approved' : 'rejected'), permissionSource: permissionResult.permissionSource, notes: notes, isMultiStep: request.isMultiStep, stepNumber: request.currentStep }, timestamp: new Date() }); res.json({ success: true, requestId: request.id, decision: decision, newState: result.newState, processComplete: result.processComplete, nextStep: result.nextStep, message: result.stepComplete ? 'Approval recorded successfully' : 'Approval recorded, waiting for additional approvers' }); } catch (error) { console.error('Approval processing error:', { error: error.message, requestId: req.params.id, userId: req.user?.id, timestamp: new Date() }); res.status(500).json({ error: 'Approval processing failed', message: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } } );

💡 Why This Fix Works

The secure implementation uses a state machine to enforce valid workflow transitions, implements comprehensive permission validation that considers user roles, department hierarchy, and delegation rules, uses distributed locking to prevent race conditions, and maintains detailed audit trails for all approval decisions. It also supports both single-step and multi-step approval processes with idempotency protection.

Why it happens

Applications that fail to validate proper state transitions in business workflows allow attackers to skip approval steps, jump directly to final states, or manipulate workflow status parameters to bypass required authorization checkpoints.

Root causes

State Transition Manipulation and Workflow Jumping

Applications that fail to validate proper state transitions in business workflows allow attackers to skip approval steps, jump directly to final states, or manipulate workflow status parameters to bypass required authorization checkpoints.

Preview example – JAVASCRIPT
// VULNERABLE: Document approval workflow with state manipulation flaws
const express = require('express');
const app = express();

// PROBLEM: Workflow states defined as simple strings without validation
const WORKFLOW_STATES = {
    DRAFT: 'draft',
    PENDING_REVIEW: 'pending_review',
    UNDER_REVIEW: 'under_review',
    APPROVED: 'approved',
    REJECTED: 'rejected',
    PUBLISHED: 'published'
};

// VULNERABLE: Document status update without proper validation
app.put('/api/documents/:id/status', async (req, res) => {
    const { id } = req.params;
    const { status, comment } = req.body;
    
    try {
        const document = await Document.findById(id);
        
        if (!document) {
            return res.status(404).json({ error: 'Document not found' });
        }
        
        // PROBLEM 1: No validation of state transition logic
        // Attacker can jump from 'draft' directly to 'published'
        if (Object.values(WORKFLOW_STATES).includes(status)) {
            document.status = status;  // DANGEROUS: Any status allowed
            document.lastModified = new Date();
            
            if (comment) {
                document.comments.push({
                    user: req.user.id,
                    comment,
                    timestamp: new Date()
                });
            }
            
            await document.save();
            
            res.json({ success: true, document });
        } else {
            res.status(400).json({ error: 'Invalid status' });
        }
        
    } catch (error) {
        res.status(500).json({ error: 'Failed to update document' });
    }
});

// PROBLEM 2: Approval endpoint without proper authorization
app.post('/api/documents/:id/approve', async (req, res) => {
    const { id } = req.params;
    const { decision, notes } = req.body; // 'approve' or 'reject'
    
    try {
        const document = await Document.findById(id);
        
        // INSUFFICIENT: Only checking if user exists, not role/permissions
        if (!req.user) {
            return res.status(401).json({ error: 'Authentication required' });
        }
        
        // PROBLEM: No validation of approver authority
        // PROBLEM: No check if document is in correct state for approval
        if (decision === 'approve') {
            document.status = WORKFLOW_STATES.APPROVED;
            document.approvedBy = req.user.id;
            document.approvedAt = new Date();
        } else {
            document.status = WORKFLOW_STATES.REJECTED;
            document.rejectedBy = req.user.id;
            document.rejectedAt = new Date();
        }
        
        if (notes) {
            document.approvalNotes = notes;
        }
        
        await document.save();
        
        res.json({ success: true, document });
        
    } catch (error) {
        res.status(500).json({ error: 'Approval failed' });
    }
});

// PROBLEM 3: Bulk operations without individual validation
app.post('/api/documents/bulk-approve', async (req, res) => {
    const { documentIds, decision } = req.body;
    
    try {
        // VULNERABLE: Bulk approval without checking individual permissions
        const result = await Document.updateMany(
            { _id: { $in: documentIds } },
            {
                status: decision === 'approve' ? WORKFLOW_STATES.APPROVED : WORKFLOW_STATES.REJECTED,
                approvedBy: req.user.id,
                approvedAt: new Date()
            }
        );
        
        res.json({ success: true, modifiedCount: result.modifiedCount });
        
    } catch (error) {
        res.status(500).json({ error: 'Bulk approval failed' });
    }
});

// Attack scenarios:
// 1. POST /api/documents/123/status {"status": "published"} - Skip all approvals
// 2. Approve documents user doesn't have authority over
// 3. Bulk approve sensitive documents
// 4. Change status of already-approved documents
// 5. Manipulate document state during concurrent reviews

Parameter Injection and Authorization Context Manipulation

Vulnerabilities in approval systems where attackers can manipulate request parameters to change authorization context, impersonate approvers, or inject malicious data that bypasses permission checks and approval requirements.

Preview example – JAVASCRIPT
// VULNERABLE: Financial transaction approval with parameter injection flaws
const express = require('express');
const app = express();

// PROBLEM: Approval system trusting client-provided context
app.post('/api/transactions/:id/approve', async (req, res) => {
    const { id } = req.params;
    const {
        approverRole,     // DANGEROUS: Client-provided role
        department,       // DANGEROUS: Client-provided department
        approvalLevel,    // DANGEROUS: Client-provided level
        delegatedBy,      // DANGEROUS: Delegation claims
        emergencyOverride // DANGEROUS: Emergency bypass flag
    } = req.body;
    
    try {
        const transaction = await Transaction.findById(id);
        
        if (!transaction) {
            return res.status(404).json({ error: 'Transaction not found' });
        }
        
        // PROBLEM 1: Trusting client-provided authorization context
        let requiredApprovalLevel = 1;
        
        if (transaction.amount > 10000) {
            requiredApprovalLevel = 2;
        }
        if (transaction.amount > 100000) {
            requiredApprovalLevel = 3;
        }
        
        // VULNERABLE: Using client-provided approval level
        if (approvalLevel >= requiredApprovalLevel) {
            
            // PROBLEM 2: Emergency override without proper validation
            if (emergencyOverride === 'true') {
                transaction.status = 'approved';
                transaction.approvalMethod = 'emergency_override';
                transaction.approvedBy = req.user.id;
                transaction.emergencyReason = req.body.emergencyReason;
                
                await transaction.save();
                
                return res.json({ 
                    success: true, 
                    message: 'Transaction approved via emergency override' 
                });
            }
            
            // PROBLEM 3: Delegation without verification
            if (delegatedBy) {
                // INSUFFICIENT: Not verifying delegation authority
                const delegator = await User.findById(delegatedBy);
                
                if (delegator) {
                    transaction.status = 'approved';
                    transaction.approvedBy = req.user.id;
                    transaction.delegatedBy = delegatedBy;
                    transaction.approvalNotes = req.body.notes;
                    
                    await transaction.save();
                    
                    return res.json({ success: true, transaction });
                }
            }
            
            // PROBLEM 4: Role-based approval without role verification
            const validRoles = ['manager', 'director', 'cfo'];
            
            if (validRoles.includes(approverRole)) {
                transaction.status = 'approved';
                transaction.approvedBy = req.user.id;
                transaction.approverRole = approverRole; // Using client role
                transaction.department = department;     // Using client department
                
                await transaction.save();
                
                res.json({ success: true, transaction });
            } else {
                res.status(403).json({ error: 'Insufficient role for approval' });
            }
            
        } else {
            res.status(403).json({ error: 'Insufficient approval level' });
        }
        
    } catch (error) {
        res.status(500).json({ error: 'Approval processing failed' });
    }
});

// PROBLEM 5: Mass approval with injected parameters
app.post('/api/transactions/approve-batch', async (req, res) => {
    const {
        transactionIds,
        approverContext,  // DANGEROUS: Batch context injection
        overrideChecks   // DANGEROUS: Bypass flag
    } = req.body;
    
    try {
        const results = [];
        
        for (const txId of transactionIds) {
            // VULNERABLE: Using injected context for each transaction
            if (overrideChecks || approverContext.hasFullAuthority) {
                await Transaction.findByIdAndUpdate(txId, {
                    status: 'approved',
                    approvedBy: req.user.id,
                    batchApproval: true,
                    approverContext: approverContext // Storing injected data
                });
                
                results.push({ transactionId: txId, status: 'approved' });
            }
        }
        
        res.json({ success: true, results });
        
    } catch (error) {
        res.status(500).json({ error: 'Batch approval failed' });
    }
});

// Attack scenarios:
// 1. Inject high approvalLevel: {"approvalLevel": 999}
// 2. Claim false delegation: {"delegatedBy": "cfo_user_id"}
// 3. Use emergency override: {"emergencyOverride": "true"}
// 4. Inject admin role: {"approverRole": "cfo"}
// 5. Batch approve with override: {"overrideChecks": true}

Multi-Step Process Race Conditions and Timing Attacks

Approval workflows with multiple sequential steps are vulnerable to race condition attacks where concurrent requests can manipulate process state, skip validation steps, or create inconsistent approval states by exploiting timing windows between process stages.

Preview example – JAVASCRIPT
// VULNERABLE: Multi-step approval process with race condition flaws
const express = require('express');
const app = express();

// PROBLEM: Multi-step process without proper locking
class WorkflowManager {
    constructor() {
        this.activeProcesses = new Map(); // In-memory state (problematic)
    }
    
    // VULNERABLE: Initiate approval without atomic operations
    async initiateApproval(requestId, userId) {
        const request = await ApprovalRequest.findById(requestId);
        
        if (!request) {
            throw new Error('Request not found');
        }
        
        // PROBLEM 1: Check-then-act race condition
        if (request.status !== 'pending') {
            throw new Error('Request is not in pending state');
        }
        
        // VULNERABLE: Delay between check and update
        await this.performBusinessValidation(request);
        
        // RACE CONDITION: Multiple threads can pass validation simultaneously
        request.status = 'under_review';
        request.reviewStartedAt = new Date();
        request.reviewStartedBy = userId;
        
        await request.save();
        
        // PROBLEM 2: Non-atomic multi-step process
        await this.notifyApprovers(request);
        await this.logWorkflowEvent(request, 'review_started');
        
        return request;
    }
    
    // VULNERABLE: Approval step without proper state validation
    async processApprovalStep(requestId, userId, stepNumber, decision) {
        const request = await ApprovalRequest.findById(requestId);
        
        // PROBLEM: No locking mechanism
        if (!this.activeProcesses.has(requestId)) {
            this.activeProcesses.set(requestId, {
                currentStep: stepNumber,
                lockedBy: userId,
                lockTime: Date.now()
            });
        }
        
        const processState = this.activeProcesses.get(requestId);
        
        // VULNERABLE: State checks without atomic updates
        if (processState.currentStep !== stepNumber) {
            throw new Error('Invalid step sequence');
        }
        
        // PROBLEM: Approval steps processed in parallel
        const approvalStep = {
            stepNumber,
            approver: userId,
            decision,
            timestamp: new Date(),
            notes: req.body.notes
        };
        
        // RACE CONDITION: Multiple approvers can approve same step
        request.approvalSteps.push(approvalStep);
        
        // PROBLEM: Non-atomic status updates
        if (decision === 'approve') {
            const requiredApprovals = await this.getRequiredApprovals(request);
            const approvedSteps = request.approvalSteps.filter(s => s.decision === 'approve');
            
            // VULNERABLE: Final approval check without locking
            if (approvedSteps.length >= requiredApprovals) {
                request.status = 'approved';
                request.finallyApprovedAt = new Date();
                
                // PROBLEM: Side effects without transaction
                await this.executeApprovedAction(request);
                await this.notifyRequestor(request);
            }
        } else {
            request.status = 'rejected';
            request.rejectedAt = new Date();
        }
        
        await request.save();
        
        // Clean up in-memory state
        this.activeProcesses.delete(requestId);
        
        return request;
    }
    
    // PROBLEM 3: Side effects without proper validation
    async executeApprovedAction(request) {
        switch (request.type) {
            case 'budget_allocation':
                // VULNERABLE: Executing without re-validation
                await this.allocateBudget(request.details.amount, request.details.department);
                break;
                
            case 'user_privilege_escalation':
                // DANGEROUS: Privilege changes without final checks
                await this.updateUserPrivileges(request.details.userId, request.details.newRole);
                break;
                
            case 'system_configuration':
                // RISKY: System changes without verification
                await this.updateSystemConfig(request.details.configChanges);
                break;
        }
    }
}

// VULNERABLE: API endpoints without proper concurrency control
const workflowManager = new WorkflowManager();

app.post('/api/approval/:id/step/:stepNumber', async (req, res) => {
    const { id, stepNumber } = req.params;
    const { decision, notes } = req.body;
    
    try {
        // PROBLEM: No request deduplication or locking
        const result = await workflowManager.processApprovalStep(
            id,
            req.user.id,
            parseInt(stepNumber),
            decision
        );
        
        res.json({ success: true, request: result });
        
    } catch (error) {
        res.status(400).json({ error: error.message });
    }
});

// PROBLEM 4: Concurrent approval processing
app.post('/api/approval/:id/final-approve', async (req, res) => {
    const { id } = req.params;
    
    try {
        const request = await ApprovalRequest.findById(id);
        
        // RACE CONDITION: Multiple final approvals possible
        if (request.status === 'under_review') {
            request.status = 'approved';
            request.finalApprover = req.user.id;
            request.approvedAt = new Date();
            
            await request.save();
            
            // PROBLEM: Side effects without ensuring single execution
            await workflowManager.executeApprovedAction(request);
            
            res.json({ success: true });
        } else {
            res.status(400).json({ error: 'Request not in reviewable state' });
        }
        
    } catch (error) {
        res.status(500).json({ error: 'Final approval failed' });
    }
});

// Attack scenarios:
// 1. Submit multiple concurrent approval requests
// 2. Race condition in final approval step
// 3. Parallel processing of approval steps
// 4. Double-execution of approved actions
// 5. State manipulation during process transitions

Insufficient Permission Validation and Context Bypass

Approval systems that fail to properly validate user permissions within the specific context of the approval request allow attackers to approve requests outside their authority, bypass departmental restrictions, or exploit delegated permissions inappropriately.

Preview example – JAVASCRIPT
// VULNERABLE: Permission validation with context bypass flaws
const express = require('express');
const app = express();

// PROBLEM: Inadequate permission checking system
class PermissionManager {
    // INSUFFICIENT: Basic role checking without context
    async hasApprovalPermission(userId, requestType) {
        const user = await User.findById(userId).populate('roles');
        
        // PROBLEM: Simple role-based check ignores context
        const approverRoles = ['manager', 'director', 'admin'];
        
        return user.roles.some(role => approverRoles.includes(role.name));
    }
    
    // VULNERABLE: Department-based validation without hierarchy checks
    async canApproveForDepartment(userId, departmentId) {
        const user = await User.findById(userId);
        
        // INSUFFICIENT: Only checking direct department membership
        return user.department.toString() === departmentId.toString();
    }
    
    // PROBLEM: Delegation validation without proper authority checks
    async validateDelegation(userId, delegatedBy, requestType) {
        const delegator = await User.findById(delegatedBy);
        const delegate = await User.findById(userId);
        
        if (!delegator || !delegate) {
            return false;
        }
        
        // VULNERABLE: Accepting any delegation claim
        const delegation = await Delegation.findOne({
            delegator: delegatedBy,
            delegate: userId,
            isActive: true
        });
        
        // PROBLEM: Not checking delegation scope or expiration
        return !!delegation;
    }
}

// VULNERABLE: Expense approval with insufficient permission validation
app.post('/api/expenses/:id/approve', async (req, res) => {
    const { id } = req.params;
    const { notes, delegationClaim } = req.body;
    
    try {
        const expense = await Expense.findById(id).populate('submittedBy');
        
        if (!expense) {
            return res.status(404).json({ error: 'Expense not found' });
        }
        
        const permissionManager = new PermissionManager();
        let hasPermission = false;
        
        // PROBLEM 1: Basic permission check ignores expense amount and type
        hasPermission = await permissionManager.hasApprovalPermission(
            req.user.id,
            'expense_approval'
        );
        
        // PROBLEM 2: Inadequate delegation validation
        if (!hasPermission && delegationClaim) {
            hasPermission = await permissionManager.validateDelegation(
                req.user.id,
                delegationClaim.delegatedBy,
                'expense_approval'
            );
        }
        
        // PROBLEM 3: No validation of approval amount limits
        if (hasPermission) {
            expense.status = 'approved';
            expense.approvedBy = req.user.id;
            expense.approvedAt = new Date();
            expense.approvalNotes = notes;
            
            if (delegationClaim) {
                expense.delegatedBy = delegationClaim.delegatedBy;
            }
            
            await expense.save();
            
            // PROBLEM 4: No audit trail of approval authority used
            res.json({ success: true, expense });
        } else {
            res.status(403).json({ error: 'Insufficient permissions' });
        }
        
    } catch (error) {
        res.status(500).json({ error: 'Approval failed' });
    }
});

// VULNERABLE: Purchase order approval without proper validation
app.post('/api/purchase-orders/:id/approve', async (req, res) => {
    const { id } = req.params;
    const { approvalLevel, departmentOverride } = req.body;
    
    try {
        const purchaseOrder = await PurchaseOrder.findById(id);
        
        if (!purchaseOrder) {
            return res.status(404).json({ error: 'Purchase order not found' });
        }
        
        const user = await User.findById(req.user.id).populate('roles department');
        
        // PROBLEM: Complex approval logic with multiple bypass opportunities
        let canApprove = false;
        
        // INSUFFICIENT: Basic role check
        if (user.roles.some(role => role.name === 'manager')) {
            canApprove = purchaseOrder.amount <= 5000;
        }
        
        if (user.roles.some(role => role.name === 'director')) {
            canApprove = purchaseOrder.amount <= 25000;
        }
        
        // VULNERABLE: Department override without validation
        if (departmentOverride && user.roles.some(role => role.name === 'admin')) {
            canApprove = true; // DANGEROUS: Admin can override any department
        }
        
        // PROBLEM: Cross-department approval without proper checks
        if (user.department.id !== purchaseOrder.requestingDepartment) {
            // INSUFFICIENT: Only checking if user is director or above
            canApprove = canApprove && user.roles.some(role => 
                ['director', 'vp', 'ceo'].includes(role.name)
            );
        }
        
        // VULNERABLE: Client-provided approval level override
        if (approvalLevel && approvalLevel === 'emergency') {
            // DANGEROUS: Emergency approval without proper validation
            const emergencyRoles = ['director', 'vp', 'ceo'];
            if (user.roles.some(role => emergencyRoles.includes(role.name))) {
                canApprove = true;
            }
        }
        
        if (canApprove) {
            purchaseOrder.status = 'approved';
            purchaseOrder.approvedBy = req.user.id;
            purchaseOrder.approvalMethod = approvalLevel || 'standard';
            purchaseOrder.approvedAt = new Date();
            
            await purchaseOrder.save();
            
            res.json({ success: true, purchaseOrder });
        } else {
            res.status(403).json({ error: 'Insufficient approval authority' });
        }
        
    } catch (error) {
        res.status(500).json({ error: 'Approval processing failed' });
    }
});

// Attack scenarios:
// 1. Claim false delegation: {"delegationClaim": {"delegatedBy": "ceo_id"}}
// 2. Department override abuse: {"departmentOverride": true}
// 3. Emergency approval exploitation: {"approvalLevel": "emergency"}
// 4. Cross-department approval without authority
// 5. Approving requests above permission limits

Fixes

1

Implement State Machine with Atomic Transitions

Create a robust state machine that enforces valid workflow transitions, uses atomic database operations for state changes, and maintains comprehensive audit trails for all workflow modifications.

View implementation – JAVASCRIPT
// SECURE: Workflow state machine with atomic transitions
const mongoose = require('mongoose');
const redis = require('redis');
const { v4: uuidv4 } = require('uuid');

const redisClient = redis.createClient(process.env.REDIS_URL);

class SecureWorkflowManager {
    constructor() {
        // Define valid state transitions
        this.validTransitions = {
            'draft': ['pending_review', 'cancelled'],
            'pending_review': ['under_review', 'cancelled'],
            'under_review': ['approved', 'rejected', 'returned_for_revision'],
            'returned_for_revision': ['pending_review', 'cancelled'],
            'approved': ['published', 'archived'],
            'rejected': ['archived'],
            'published': ['archived'],
            'cancelled': [],
            'archived': []
        };
        
        // Define required permissions for transitions
        this.transitionPermissions = {
            'draft->pending_review': ['author', 'editor'],
            'pending_review->under_review': ['reviewer', 'manager'],
            'under_review->approved': ['approver', 'manager'],
            'under_review->rejected': ['approver', 'manager'],
            'approved->published': ['publisher', 'admin'],
            '*->cancelled': ['author', 'manager', 'admin'],
            '*->archived': ['admin']
        };
    }
    
    // Validate state transition with comprehensive checks
    validateTransition(fromState, toState, userRoles, documentContext) {
        // Check if transition is valid
        if (!this.validTransitions[fromState]?.includes(toState)) {
            throw new Error(`Invalid transition from ${fromState} to ${toState}`);
        }
        
        // Check user permissions for this transition
        const transitionKey = `${fromState}->${toState}`;
        const wildcardKey = `*->${toState}`;
        
        const requiredRoles = this.transitionPermissions[transitionKey] || 
                             this.transitionPermissions[wildcardKey];
        
        if (requiredRoles && !requiredRoles.some(role => userRoles.includes(role))) {
            throw new Error(`Insufficient permissions for transition ${transitionKey}`);
        }
        
        // Additional context-specific validations
        this.validateContextualRequirements(fromState, toState, documentContext);
        
        return true;
    }
    
    // Atomic state transition with distributed locking
    async updateWorkflowState(documentId, newState, userId, context = {}) {
        const lockKey = `workflow_lock:${documentId}`;
        const lockValue = uuidv4();
        const lockTimeout = 30; // 30 seconds
        
        try {
            // Acquire distributed lock
            const lockAcquired = await this.acquireDistributedLock(lockKey, lockValue, lockTimeout);
            if (!lockAcquired) {
                throw new Error('Unable to acquire workflow lock, please try again');
            }
            
            // Start database transaction
            const session = await mongoose.startSession();
            await session.withTransaction(async () => {
                // Fetch current document state
                const document = await Document.findById(documentId).session(session);
                if (!document) {
                    throw new Error('Document not found');
                }
                
                // Get user permissions
                const user = await User.findById(userId)
                    .populate('roles')
                    .session(session);
                const userRoles = user.roles.map(role => role.name);
                
                // Validate transition
                this.validateTransition(
                    document.status,
                    newState,
                    userRoles,
                    { document, user, context }
                );
                
                // Update document with atomic operation
                const updateResult = await Document.findOneAndUpdate(
                    {
                        _id: documentId,
                        status: document.status, // Ensure state hasn't changed
                        version: document.version // Optimistic locking
                    },
                    {
                        $set: {
                            status: newState,
                            lastModifiedBy: userId,
                            lastModifiedAt: new Date()
                        },
                        $inc: { version: 1 },
                        $push: {
                            stateHistory: {
                                fromState: document.status,
                                toState: newState,
                                changedBy: userId,
                                changedAt: new Date(),
                                context: context,
                                transactionId: session.id
                            }
                        }
                    },
                    { 
                        new: true,
                        session,
                        runValidators: true
                    }
                );
                
                if (!updateResult) {
                    throw new Error('State update failed - document may have been modified');
                }
                
                // Execute state-specific side effects
                await this.executeStateTransitionEffects(
                    updateResult,
                    document.status,
                    newState,
                    userId,
                    session
                );
                
                return updateResult;
            });
            
        } finally {
            // Always release the lock
            await this.releaseDistributedLock(lockKey, lockValue);
        }
    }
    
    // Execute side effects for state transitions
    async executeStateTransitionEffects(document, fromState, toState, userId, session) {
        const effects = {
            'pending_review': async () => {
                await this.notifyReviewers(document, session);
                await this.createReviewTasks(document, session);
            },
            'approved': async () => {
                await this.notifyStakeholders(document, 'approved', session);
                await this.updateRelatedDocuments(document, session);
            },
            'published': async () => {
                await this.publishToTargetSystems(document, session);
                await this.updateSearchIndex(document, session);
            },
            'rejected': async () => {
                await this.notifyAuthor(document, 'rejected', session);
                await this.cleanupResources(document, session);
            }
        };
        
        const effect = effects[toState];
        if (effect) {
            await effect();
        }
    }
    
    // Distributed locking implementation
    async acquireDistributedLock(key, value, timeoutSeconds) {
        const result = await redisClient.set(
            key,
            value,
            'PX',
            timeoutSeconds * 1000,
            'NX'
        );
        return result === 'OK';
    }
    
    async releaseDistributedLock(key, value) {
        const script = `
            if redis.call("get", KEYS[1]) == ARGV[1] then
                return redis.call("del", KEYS[1])
            else
                return 0
            end
        `;
        return redisClient.eval(script, 1, key, value);
    }
}
2

Implement Comprehensive Permission Validation System

Create a context-aware permission system that validates user authority based on multiple factors including role, department hierarchy, approval limits, and delegation rules with proper audit trails.

View implementation – JAVASCRIPT
// SECURE: Comprehensive permission validation system
class SecurePermissionManager {
    constructor() {
        this.permissionCache = new Map();
        this.cacheTimeout = 5 * 60 * 1000; // 5 minutes
    }
    
    // Comprehensive permission validation with context
    async validateApprovalPermission(userId, request, context = {}) {
        const cacheKey = `perm:${userId}:${request.type}:${request.id}`;
        
        // Check cache first
        const cached = this.permissionCache.get(cacheKey);
        if (cached && (Date.now() - cached.timestamp) < this.cacheTimeout) {
            return cached.result;
        }
        
        const user = await User.findById(userId)
            .populate('roles department')
            .populate({
                path: 'delegations',
                match: { isActive: true, expiresAt: { $gt: new Date() } }
            });
        
        if (!user) {
            throw new Error('User not found');
        }
        
        const validationResult = {
            hasPermission: false,
            reasons: [],
            permissionSource: null,
            limitations: {},
            auditData: {
                userId,
                requestId: request.id,
                requestType: request.type,
                validatedAt: new Date(),
                context
            }
        };
        
        // 1. Direct role-based permission check
        const directPermission = await this.checkDirectPermissions(
            user,
            request,
            validationResult
        );
        
        // 2. Department hierarchy permission check
        const hierarchyPermission = await this.checkHierarchyPermissions(
            user,
            request,
            validationResult
        );
        
        // 3. Delegation-based permission check
        const delegationPermission = await this.checkDelegationPermissions(
            user,
            request,
            validationResult
        );
        
        // 4. Amount/value-based limitations
        const amountLimitations = await this.checkAmountLimitations(
            user,
            request,
            validationResult
        );
        
        // 5. Time-based restrictions
        const timeRestrictions = await this.checkTimeRestrictions(
            user,
            request,
            validationResult
        );
        
        // Determine final permission
        validationResult.hasPermission = directPermission || 
                                       hierarchyPermission || 
                                       delegationPermission;
        
        // Apply limitations
        if (validationResult.hasPermission) {
            validationResult.hasPermission = 
                validationResult.hasPermission && 
                amountLimitations && 
                timeRestrictions;
        }
        
        // Cache result
        this.permissionCache.set(cacheKey, {
            result: validationResult,
            timestamp: Date.now()
        });
        
        // Log permission validation
        await this.logPermissionValidation(validationResult);
        
        return validationResult;
    }
    
    // Check direct role-based permissions
    async checkDirectPermissions(user, request, result) {
        const rolePermissions = {
            'expense_approval': {
                'manager': { maxAmount: 5000, departments: 'own' },
                'director': { maxAmount: 25000, departments: 'division' },
                'vp': { maxAmount: 100000, departments: 'all' },
                'cfo': { maxAmount: Infinity, departments: 'all' }
            },
            'purchase_order_approval': {
                'manager': { maxAmount: 10000, departments: 'own' },
                'director': { maxAmount: 50000, departments: 'division' },
                'vp': { maxAmount: 250000, departments: 'all' }
            }
        };
        
        const requestPermissions = rolePermissions[request.type];
        if (!requestPermissions) {
            result.reasons.push(`No permissions defined for request type: ${request.type}`);
            return false;
        }
        
        for (const role of user.roles) {
            const permission = requestPermissions[role.name];
            if (permission) {
                // Check amount limitation
                if (request.amount && request.amount > permission.maxAmount) {
                    result.reasons.push(
                        `Amount ${request.amount} exceeds ${role.name} limit of ${permission.maxAmount}`
                    );
                    continue;
                }
                
                // Check department access
                const hasDepAccess = await this.validateDepartmentAccess(
                    user.department,
                    request.department,
                    permission.departments
                );
                
                if (!hasDepAccess) {
                    result.reasons.push(
                        `Role ${role.name} cannot approve for department ${request.department}`
                    );
                    continue;
                }
                
                result.permissionSource = {
                    type: 'direct_role',
                    role: role.name,
                    limitations: permission
                };
                result.limitations = permission;
                return true;
            }
        }
        
        result.reasons.push('No sufficient direct role permissions');
        return false;
    }
    
    // Check delegation-based permissions
    async checkDelegationPermissions(user, request, result) {
        for (const delegation of user.delegations) {
            // Validate delegation scope
            if (delegation.scope && !delegation.scope.includes(request.type)) {
                continue;
            }
            
            // Validate delegation amount limits
            if (delegation.maxAmount && request.amount > delegation.maxAmount) {
                result.reasons.push(
                    `Delegation max amount ${delegation.maxAmount} exceeded`
                );
                continue;
            }
            
            // Check if delegator has permission for this request
            const delegatorPermission = await this.validateApprovalPermission(
                delegation.delegator,
                request,
                { isDelegationCheck: true }
            );
            
            if (delegatorPermission.hasPermission) {
                result.permissionSource = {
                    type: 'delegation',
                    delegator: delegation.delegator,
                    delegationId: delegation.id,
                    limitations: {
                        maxAmount: delegation.maxAmount,
                        scope: delegation.scope,
                        expiresAt: delegation.expiresAt
                    }
                };
                result.limitations = delegation;
                return true;
            }
        }
        
        result.reasons.push('No valid delegations found');
        return false;
    }
    
    // Validate department access based on hierarchy
    async validateDepartmentAccess(userDept, requestDept, accessLevel) {
        if (accessLevel === 'all') return true;
        if (accessLevel === 'own') return userDept.id === requestDept;
        
        if (accessLevel === 'division') {
            // Check if departments are in same division
            const userDivision = await Department.findById(userDept.parentDivision);
            const requestDivision = await Department.findById(requestDept.parentDivision);
            return userDivision?.id === requestDivision?.id;
        }
        
        return false;
    }
    
    // Log permission validation for audit
    async logPermissionValidation(validationResult) {
        await AuditLog.create({
            type: 'permission_validation',
            userId: validationResult.auditData.userId,
            resourceType: 'approval_request',
            resourceId: validationResult.auditData.requestId,
            action: 'validate_permission',
            result: validationResult.hasPermission ? 'granted' : 'denied',
            details: {
                permissionSource: validationResult.permissionSource,
                reasons: validationResult.reasons,
                limitations: validationResult.limitations,
                context: validationResult.auditData.context
            },
            timestamp: new Date()
        });
    }
}
3

Implement Multi-Step Process Protection with Idempotency

Create a secure multi-step approval process that uses idempotency keys, distributed locking, and atomic operations to prevent race conditions and ensure process integrity.

View implementation – JAVASCRIPT
// SECURE: Multi-step approval process with race condition protection
const { v4: uuidv4 } = require('uuid');
const crypto = require('crypto');

class SecureMultiStepApproval {
    constructor() {
        this.lockPrefix = 'approval_process:';
        this.lockTimeout = 30; // seconds
    }
    
    // Initialize approval process with idempotency
    async initiateApprovalProcess(requestData, userId, idempotencyKey) {
        if (!idempotencyKey) {
            idempotencyKey = uuidv4();
        }
        
        // Check for existing process with same idempotency key
        const existingProcess = await ApprovalProcess.findOne({
            idempotencyKey,
            status: { $in: ['initiated', 'in_progress'] }
        });
        
        if (existingProcess) {
            return {
                success: true,
                processId: existingProcess.id,
                message: 'Process already initiated',
                isIdempotent: true
            };
        }
        
        const lockKey = `${this.lockPrefix}init:${crypto.createHash('sha256').update(JSON.stringify(requestData)).digest('hex')}`;
        const lockValue = uuidv4();
        
        try {
            // Acquire lock for process initiation
            const lockAcquired = await this.acquireLock(lockKey, lockValue);
            if (!lockAcquired) {
                throw new Error('Unable to initiate process, please try again');
            }
            
            // Start transaction
            const session = await mongoose.startSession();
            const result = await session.withTransaction(async () => {
                // Validate request data
                const validationResult = await this.validateRequestData(requestData, userId);
                if (!validationResult.isValid) {
                    throw new Error(`Invalid request: ${validationResult.errors.join(', ')}`);
                }
                
                // Create approval process
                const approvalProcess = await ApprovalProcess.create([{
                    idempotencyKey,
                    requestType: requestData.type,
                    requestData: requestData,
                    initiatedBy: userId,
                    status: 'initiated',
                    currentStep: 0,
                    totalSteps: validationResult.requiredSteps.length,
                    requiredSteps: validationResult.requiredSteps,
                    createdAt: new Date(),
                    processHash: this.generateProcessHash(requestData, validationResult.requiredSteps)
                }], { session });
                
                // Create first step
                await ApprovalStep.create([{
                    processId: approvalProcess[0].id,
                    stepNumber: 1,
                    stepType: validationResult.requiredSteps[0].type,
                    requiredApprovers: validationResult.requiredSteps[0].approvers,
                    status: 'pending',
                    createdAt: new Date()
                }], { session });
                
                // Notify initial approvers
                await this.notifyApprovers(
                    approvalProcess[0].id,
                    validationResult.requiredSteps[0].approvers,
                    session
                );
                
                return approvalProcess[0];
            });
            
            return {
                success: true,
                processId: result.id,
                currentStep: 1,
                totalSteps: result.totalSteps
            };
            
        } finally {
            await this.releaseLock(lockKey, lockValue);
        }
    }
    
    // Process approval step with race condition protection
    async processApprovalStep(processId, stepNumber, decision, userId, context = {}) {
        const idempotencyKey = context.idempotencyKey || uuidv4();
        
        // Check for duplicate submission
        const existingApproval = await ApprovalDecision.findOne({
            processId,
            stepNumber,
            approverId: userId,
            idempotencyKey
        });
        
        if (existingApproval) {
            return {
                success: true,
                decision: existingApproval.decision,
                message: 'Decision already recorded',
                isIdempotent: true
            };
        }
        
        const lockKey = `${this.lockPrefix}${processId}:step:${stepNumber}`;
        const lockValue = uuidv4();
        
        try {
            // Acquire step-specific lock
            const lockAcquired = await this.acquireLock(lockKey, lockValue);
            if (!lockAcquired) {
                throw new Error('Step is being processed by another user');
            }
            
            const session = await mongoose.startSession();
            const result = await session.withTransaction(async () => {
                // Fetch current process state
                const process = await ApprovalProcess.findById(processId).session(session);
                if (!process) {
                    throw new Error('Approval process not found');
                }
                
                if (process.status === 'completed' || process.status === 'cancelled') {
                    throw new Error('Process is already completed or cancelled');
                }
                
                // Fetch current step
                const step = await ApprovalStep.findOne({
                    processId,
                    stepNumber,
                    status: 'pending'
                }).session(session);
                
                if (!step) {
                    throw new Error('Step not found or already completed');
                }
                
                // Validate approver authority
                const permissionManager = new SecurePermissionManager();
                const permissionResult = await permissionManager.validateApprovalPermission(
                    userId,
                    {
                        id: processId,
                        type: process.requestType,
                        amount: process.requestData.amount,
                        department: process.requestData.department
                    },
                    { stepNumber, processId }
                );
                
                if (!permissionResult.hasPermission) {
                    throw new Error(`Insufficient permissions: ${permissionResult.reasons.join(', ')}`);
                }
                
                // Record decision
                const approvalDecision = await ApprovalDecision.create([{
                    processId,
                    stepNumber,
                    approverId: userId,
                    decision,
                    notes: context.notes,
                    permissionSource: permissionResult.permissionSource,
                    decidedAt: new Date(),
                    idempotencyKey
                }], { session });
                
                // Check if step is complete
                const allDecisions = await ApprovalDecision.find({
                    processId,
                    stepNumber
                }).session(session);
                
                const requiredApprovals = step.requiredApprovers.length;
                const approvals = allDecisions.filter(d => d.decision === 'approve').length;
                const rejections = allDecisions.filter(d => d.decision === 'reject').length;
                
                let stepComplete = false;
                let processComplete = false;
                let nextStep = null;
                
                if (rejections > 0) {
                    // Any rejection completes the step and process
                    await ApprovalStep.findByIdAndUpdate(step.id, {
                        status: 'rejected',
                        completedAt: new Date()
                    }, { session });
                    
                    await ApprovalProcess.findByIdAndUpdate(processId, {
                        status: 'rejected',
                        completedAt: new Date(),
                        finalDecision: 'rejected'
                    }, { session });
                    
                    stepComplete = true;
                    processComplete = true;
                    
                } else if (approvals >= requiredApprovals) {
                    // Step approved
                    await ApprovalStep.findByIdAndUpdate(step.id, {
                        status: 'approved',
                        completedAt: new Date()
                    }, { session });
                    
                    stepComplete = true;
                    
                    // Check if this was the final step
                    if (stepNumber >= process.totalSteps) {
                        await ApprovalProcess.findByIdAndUpdate(processId, {
                            status: 'approved',
                            completedAt: new Date(),
                            finalDecision: 'approved'
                        }, { session });
                        
                        processComplete = true;
                        
                        // Execute approved action
                        await this.executeApprovedAction(process, session);
                        
                    } else {
                        // Create next step
                        const nextStepConfig = process.requiredSteps[stepNumber];
                        
                        nextStep = await ApprovalStep.create([{
                            processId,
                            stepNumber: stepNumber + 1,
                            stepType: nextStepConfig.type,
                            requiredApprovers: nextStepConfig.approvers,
                            status: 'pending',
                            createdAt: new Date()
                        }], { session });
                        
                        await ApprovalProcess.findByIdAndUpdate(processId, {
                            currentStep: stepNumber + 1
                        }, { session });
                        
                        // Notify next approvers
                        await this.notifyApprovers(
                            processId,
                            nextStepConfig.approvers,
                            session
                        );
                    }
                }
                
                return {
                    stepComplete,
                    processComplete,
                    decision: approvalDecision[0],
                    nextStep: nextStep ? nextStep[0] : null
                };
            });
            
            return {
                success: true,
                stepComplete: result.stepComplete,
                processComplete: result.processComplete,
                decision: result.decision.decision,
                nextStep: result.nextStep?.stepNumber
            };
            
        } finally {
            await this.releaseLock(lockKey, lockValue);
        }
    }
    
    // Generate process integrity hash
    generateProcessHash(requestData, requiredSteps) {
        const hashData = {
            requestData: requestData,
            requiredSteps: requiredSteps,
            timestamp: Date.now()
        };
        
        return crypto.createHash('sha256')
            .update(JSON.stringify(hashData, Object.keys(hashData).sort()))
            .digest('hex');
    }
    
    // Execute approved action atomically
    async executeApprovedAction(process, session) {
        const actionHandlers = {
            'budget_allocation': async (data) => {
                await Budget.findByIdAndUpdate(
                    data.budgetId,
                    {
                        $inc: { allocatedAmount: data.amount },
                        $push: {
                            allocations: {
                                processId: process.id,
                                amount: data.amount,
                                allocatedAt: new Date()
                            }
                        }
                    },
                    { session }
                );
            },
            'user_access_grant': async (data) => {
                await User.findByIdAndUpdate(
                    data.userId,
                    {
                        $addToSet: { roles: data.newRole },
                        $push: {
                            accessHistory: {
                                processId: process.id,
                                action: 'role_added',
                                role: data.newRole,
                                grantedAt: new Date()
                            }
                        }
                    },
                    { session }
                );
            }
        };
        
        const handler = actionHandlers[process.requestType];
        if (handler) {
            await handler(process.requestData);
        }
    }
}

Detect This Vulnerability in Your Code

Sourcery automatically identifies business workflow and approval process bypass through logic manipulation and many other security issues in your codebase.