Use of Same Invokable Control Element in Multiple Architectural Layers

Description

Use of Same Invokable Control Element in Multiple Architectural Layers occurs when a product implements identical control elements (such as functions, methods, or modules) across multiple architectural layers rather than maintaining proper separation of concerns. This violates the principle of layered architecture where each layer should have distinct responsibilities. When the same functionality is duplicated across layers (e.g., presentation, business logic, data access), it creates maintenance challenges and potential inconsistencies.

Risk

Duplicating control elements across architectural layers has indirect security implications. Inconsistent implementations across layers create security gaps when one copy is patched but others are not. Security fixes must be applied multiple times, increasing the chance of missing one. Code review becomes more difficult as reviewers must check multiple locations. Changes to security logic must be synchronized across all layers. The complexity makes it harder to ensure security controls are applied consistently. Divergent implementations over time can create exploitable inconsistencies.

Solution

Follow proper layered architecture principles with clear separation of concerns. Implement shared functionality in a common service layer accessible to all architectural layers. Use dependency injection to provide shared services to different layers. Define clear interfaces between layers and enforce them. Apply the DRY (Don't Repeat Yourself) principle consistently. Use architectural analysis tools to detect layer violations. Conduct architecture reviews to ensure proper separation. Document layer responsibilities and boundaries clearly.

Common Consequences

ImpactDetails
OtherScope: Other

Reduce Maintainability - Duplicate code across layers is harder to maintain consistently.
OtherScope: Other

Increase Analytical Complexity - Security analysis must cover multiple implementations of the same logic.
IntegrityScope: Integrity

Inconsistent State - Different implementations may behave differently, causing inconsistencies.

Example Code

Vulnerable Code

// Vulnerable: Same validation logic duplicated across layers

// Presentation Layer
@Controller
public class VulnerableUserController {

    @PostMapping("/register")
    public ResponseEntity<?> registerUser(@RequestBody UserDTO user) {
        // Vulnerable: Validation logic in presentation layer
        if (user.getUsername() == null || user.getUsername().length() < 3) {
            return ResponseEntity.badRequest().body("Invalid username");
        }
        if (user.getEmail() == null || !user.getEmail().contains("@")) {
            return ResponseEntity.badRequest().body("Invalid email");
        }
        if (user.getPassword() == null || user.getPassword().length() < 8) {
            return ResponseEntity.badRequest().body("Invalid password");
        }

        return userService.createUser(user);
    }
}

// Business Logic Layer
@Service
public class VulnerableUserService {

    public User createUser(UserDTO userDTO) {
        // Vulnerable: SAME validation logic duplicated here!
        if (userDTO.getUsername() == null || userDTO.getUsername().length() < 3) {
            throw new ValidationException("Invalid username");
        }
        if (userDTO.getEmail() == null || !userDTO.getEmail().contains("@")) {
            throw new ValidationException("Invalid email");
        }
        if (userDTO.getPassword() == null || userDTO.getPassword().length() < 8) {
            throw new ValidationException("Invalid password");
        }

        User user = new User();
        user.setUsername(userDTO.getUsername());
        user.setEmail(userDTO.getEmail());
        user.setPasswordHash(hashPassword(userDTO.getPassword()));

        return userRepository.save(user);
    }
}

// Data Access Layer
@Repository
public class VulnerableUserRepository {

    public User save(User user) {
        // Vulnerable: SAME validation logic duplicated AGAIN!
        if (user.getUsername() == null || user.getUsername().length() < 3) {
            throw new DataIntegrityException("Invalid username");
        }
        if (user.getEmail() == null || !user.getEmail().contains("@")) {
            throw new DataIntegrityException("Invalid email");
        }

        // Save to database
        return jdbcTemplate.insert(user);
    }
}

// Problem: If validation rules change (e.g., minimum password length),
// must update THREE places. Easy to miss one, creating inconsistency.
# Vulnerable: Duplicate price calculation across layers

# API Layer
class VulnerablePriceController:

    def calculate_price(self, request):
        items = request.get('items', [])

        # Vulnerable: Price calculation in API layer
        total = 0
        for item in items:
            price = item['price'] * item['quantity']
            if item.get('discount'):
                price *= (1 - item['discount'] / 100)
            total += price

        # Apply tax
        total *= 1.19  # 19% tax

        return {'total': total}


# Service Layer
class VulnerablePriceService:

    def calculate_order_total(self, order):
        # Vulnerable: SAME calculation duplicated!
        total = 0
        for item in order.items:
            price = item.price * item.quantity
            if item.discount:
                price *= (1 - item.discount / 100)
            total += price

        # Apply tax - but wait, this uses 20%!
        total *= 1.20  # BUG: Different tax rate!

        return total


# Background Job Layer
class VulnerableInvoiceGenerator:

    def generate_invoice(self, order):
        # Vulnerable: SAME calculation duplicated AGAIN!
        total = 0
        for item in order.items:
            price = item.price * item.quantity
            # Oops, forgot discount logic here!
            total += price

        # Tax calculation missing entirely!

        return self.create_invoice_pdf(order, total)


# Problem: Three different implementations with inconsistencies:
# - API: 19% tax, has discount
# - Service: 20% tax (wrong!), has discount
# - Invoice: no tax, no discount (definitely wrong!)
// Vulnerable: Duplicate authorization logic across layers

// Web API Layer
public class VulnerableDocumentController : ApiController
{
    [HttpGet]
    public IHttpActionResult GetDocument(int documentId, int userId)
    {
        // Vulnerable: Authorization in controller
        var document = _documentRepo.GetById(documentId);
        if (document.OwnerId != userId &&
            !IsAdmin(userId) &&
            !document.SharedWith.Contains(userId))
        {
            return Unauthorized();
        }

        return Ok(document);
    }

    [HttpDelete]
    public IHttpActionResult DeleteDocument(int documentId, int userId)
    {
        // Vulnerable: SAME authorization duplicated
        var document = _documentRepo.GetById(documentId);
        if (document.OwnerId != userId &&
            !IsAdmin(userId) &&
            !document.SharedWith.Contains(userId))  // BUG: Shared users shouldn't delete!
        {
            return Unauthorized();
        }

        _documentService.Delete(documentId);
        return Ok();
    }
}

// Service Layer
public class VulnerableDocumentService
{
    public Document GetDocument(int documentId, int userId)
    {
        var document = _repository.GetById(documentId);

        // Vulnerable: SAME check duplicated, but different!
        if (document.OwnerId != userId && !IsAdmin(userId))
            // BUG: Forgot to check SharedWith!
        {
            throw new UnauthorizedAccessException();
        }

        return document;
    }

    public void Delete(int documentId, int userId)
    {
        // Vulnerable: No authorization check at all in this method!
        // Assumes controller already checked...
        _repository.Delete(documentId);
    }
}

// Security holes created by inconsistent implementations:
// 1. Shared users can delete documents (controller bug)
// 2. Service layer doesn't check SharedWith (service bug)
// 3. Delete in service has no authorization (missing check)

Fixed Code

// Fixed: Centralized validation in dedicated component

// Validation Layer - Single source of truth
@Component
public class UserValidator {

    public ValidationResult validate(UserDTO user) {
        List<String> errors = new ArrayList<>();

        if (user.getUsername() == null || user.getUsername().length() < 3) {
            errors.add("Username must be at least 3 characters");
        }

        if (user.getEmail() == null || !isValidEmail(user.getEmail())) {
            errors.add("Valid email address required");
        }

        if (user.getPassword() == null || !isStrongPassword(user.getPassword())) {
            errors.add("Password must be at least 8 characters with mixed case");
        }

        return new ValidationResult(errors);
    }

    private boolean isValidEmail(String email) {
        return email != null && EMAIL_PATTERN.matcher(email).matches();
    }

    private boolean isStrongPassword(String password) {
        return password != null &&
               password.length() >= 8 &&
               password.matches(".*[A-Z].*") &&
               password.matches(".*[a-z].*");
    }
}

// Presentation Layer - Uses validator
@Controller
public class FixedUserController {

    private final UserValidator validator;
    private final UserService userService;

    @PostMapping("/register")
    public ResponseEntity<?> registerUser(@RequestBody UserDTO user) {
        // Fixed: Delegate to validator
        ValidationResult result = validator.validate(user);
        if (!result.isValid()) {
            return ResponseEntity.badRequest().body(result.getErrors());
        }

        return ResponseEntity.ok(userService.createUser(user));
    }
}

// Business Logic Layer - Uses same validator
@Service
public class FixedUserService {

    private final UserValidator validator;
    private final UserRepository userRepository;

    @Transactional
    public User createUser(UserDTO userDTO) {
        // Fixed: Same validator used consistently
        ValidationResult result = validator.validate(userDTO);
        if (!result.isValid()) {
            throw new ValidationException(result.getErrors());
        }

        User user = new User();
        user.setUsername(userDTO.getUsername());
        user.setEmail(userDTO.getEmail());
        user.setPasswordHash(hashPassword(userDTO.getPassword()));

        return userRepository.save(user);
    }
}

// Data Access Layer - NO validation (not its responsibility)
@Repository
public class FixedUserRepository {

    public User save(User user) {
        // Fixed: Repository only handles persistence
        // Validation is done at higher layers
        return jdbcTemplate.insert(user);
    }
}
# Fixed: Centralized pricing service

from dataclasses import dataclass
from decimal import Decimal
from typing import List


@dataclass
class PriceCalculation:
    subtotal: Decimal
    discount_amount: Decimal
    tax_amount: Decimal
    total: Decimal


class PricingService:
    """Single source of truth for all pricing calculations."""

    TAX_RATE = Decimal('0.19')  # 19% VAT

    def calculate_item_price(self, item) -> Decimal:
        """Calculate price for a single item."""
        base_price = Decimal(str(item.price)) * item.quantity

        if item.discount:
            discount_multiplier = 1 - (Decimal(str(item.discount)) / 100)
            base_price *= discount_multiplier

        return base_price

    def calculate_total(self, items: List) -> PriceCalculation:
        """Calculate complete order total."""
        subtotal = sum(
            self.calculate_item_price(item) for item in items
        )

        discount_amount = Decimal('0')  # Could add order-level discounts

        tax_amount = subtotal * self.TAX_RATE
        total = subtotal + tax_amount

        return PriceCalculation(
            subtotal=subtotal,
            discount_amount=discount_amount,
            tax_amount=tax_amount,
            total=total
        )


# API Layer - Uses pricing service
class FixedPriceController:

    def __init__(self, pricing_service: PricingService):
        self._pricing = pricing_service

    def calculate_price(self, request):
        items = self._parse_items(request)
        result = self._pricing.calculate_total(items)

        return {
            'subtotal': str(result.subtotal),
            'tax': str(result.tax_amount),
            'total': str(result.total)
        }


# Service Layer - Uses same pricing service
class FixedOrderService:

    def __init__(self, pricing_service: PricingService):
        self._pricing = pricing_service

    def calculate_order_total(self, order) -> Decimal:
        result = self._pricing.calculate_total(order.items)
        return result.total


# Invoice Generator - Uses same pricing service
class FixedInvoiceGenerator:

    def __init__(self, pricing_service: PricingService):
        self._pricing = pricing_service

    def generate_invoice(self, order):
        result = self._pricing.calculate_total(order.items)

        return self.create_invoice_pdf(
            order,
            subtotal=result.subtotal,
            tax=result.tax_amount,
            total=result.total
        )


# All layers now use the same pricing logic - consistent everywhere!
// Fixed: Centralized authorization service

// Authorization Service - Single point of control
public interface IAuthorizationService
{
    bool CanRead(int userId, Document document);
    bool CanDelete(int userId, Document document);
    void EnsureCanRead(int userId, Document document);
    void EnsureCanDelete(int userId, Document document);
}

public class DocumentAuthorizationService : IAuthorizationService
{
    private readonly IUserService _userService;

    public bool CanRead(int userId, Document document)
    {
        return document.OwnerId == userId ||
               _userService.IsAdmin(userId) ||
               document.SharedWith.Contains(userId);
    }

    public bool CanDelete(int userId, Document document)
    {
        // Only owner or admin can delete - NOT shared users
        return document.OwnerId == userId ||
               _userService.IsAdmin(userId);
    }

    public void EnsureCanRead(int userId, Document document)
    {
        if (!CanRead(userId, document))
        {
            throw new UnauthorizedAccessException(
                $"User {userId} cannot read document {document.Id}");
        }
    }

    public void EnsureCanDelete(int userId, Document document)
    {
        if (!CanDelete(userId, document))
        {
            throw new UnauthorizedAccessException(
                $"User {userId} cannot delete document {document.Id}");
        }
    }
}

// Controller - Delegates to authorization service
public class FixedDocumentController : ApiController
{
    private readonly IDocumentService _documentService;
    private readonly IAuthorizationService _auth;

    [HttpGet]
    public IHttpActionResult GetDocument(int documentId)
    {
        var userId = GetCurrentUserId();
        var document = _documentService.GetDocument(documentId, userId);
        return Ok(document);
    }

    [HttpDelete]
    public IHttpActionResult DeleteDocument(int documentId)
    {
        var userId = GetCurrentUserId();
        _documentService.Delete(documentId, userId);
        return Ok();
    }
}

// Service - Uses same authorization service
public class FixedDocumentService : IDocumentService
{
    private readonly IDocumentRepository _repository;
    private readonly IAuthorizationService _auth;

    public Document GetDocument(int documentId, int userId)
    {
        var document = _repository.GetById(documentId);
        _auth.EnsureCanRead(userId, document);
        return document;
    }

    public void Delete(int documentId, int userId)
    {
        var document = _repository.GetById(documentId);
        _auth.EnsureCanDelete(userId, document);
        _repository.Delete(documentId);
    }
}

// Authorization is now:
// 1. Defined once in AuthorizationService
// 2. Consistent across all layers
// 3. Easy to audit and modify
// 4. Properly separates read vs delete permissions

CVE Examples

This CWE is marked as PROHIBITED for direct CVE mapping as it represents a code quality/maintainability concern rather than a direct security vulnerability.


  • CWE-710: Improper Adherence to Coding Standards (parent)
  • CWE-1006: Bad Coding Practices (category member)
  • CWE-1061: Insufficient Encapsulation (related)

References

  1. MITRE Corporation. "CWE-1092: Use of Same Invokable Control Element in Multiple Architectural Layers." https://cwe.mitre.org/data/definitions/1092.html
  2. Martin, Robert C. "Clean Architecture."
  3. CISQ Quality Measures - Maintainability.