Compliance

Subscription Enforcement Security Analysis and Fixes

Source file: SUBSCRIPTION_ENFORCEMENT_SECURITY.md

Executive Summary

This document details the security analysis of subscription enforcement gaps identified in commit f863fe4 ("sandbox requests no longer require subscription entitlements") and the implemented fixes to prevent production environment bypasses.

Status: ✅ All identified gaps have been addressed Commit Reference: f863fe4 ("Added sandbox requests no longer require subscription entitlements") Date: January 13, 2026


Security Analysis of Commit f863fe4

What Changed

Commit f863fe4 modified service-enrollment.service.ts to exempt SANDBOX environments from subscription entitlement checks:

View full snippet(click to expand)
// Before: All environments required subscription checks
if (isSubscriptionEntitlementsEnforced()) {
  // check entitlements
}

// After: Only production environments require subscription checks
const isSandbox = environment.type === EnvironmentType.SANDBOX;
if (!isSandbox && isSubscriptionEntitlementsEnforced()) {
  // check entitlements
}

Security Concern

Question: Could this change allow users to bypass production subscription requirements by manipulating environment types?

Answer: ✅ NO - The implementation is secure for the following reasons:

  1. Environment Type Immutability: The type field is only set during environment creation and is NOT included in UpdateEnvironmentInput (environment.repository.ts:13-19)

  2. Organization Ownership Verification: The getEnvironmentForOrganization method verifies that the environment belongs to the requesting organization before checking the type (service-enrollment.service.ts:119-120)

  3. Database-Sourced Type: The environment type is read from the database, not from user input during service operations

  4. Multiple Validation Layers:

    • Application-level validation in service-enrollment.service.ts
    • Type validation added (lines 124-126)
    • Database CHECK constraint prevents direct manipulation

Critical Vulnerability Found and Fixed

Vulnerability: Direct Production Environment Creation Bypass

Severity: 🔴 CRITICAL

Description: The POST /environments endpoint allowed direct creation of production environments without requiring KYC verification, bypassing the intended security control.

Attack Vector:

View full snippet(click to expand)
# Attacker could call:
POST /api/environments
{
  "organizationId": "their-org-id",
  "type": "PRODUCTION"
}

# This would create a production environment without:
# - KYC verification
# - Regulatory compliance checks
# - Proper approval workflow

Root Cause: create-environment.use-case.ts only checked if a production environment already exists, but didn't prevent initial creation:

View full snippet(click to expand)
// OLD CODE (VULNERABLE):
if (type === EnvironmentType.PRODUCTION) {
  const existing = await this.environmentRepository.findByOrganizationAndType(
    dto.organizationId,
    EnvironmentType.PRODUCTION,
  );
  if (existing) {
    throw new ConflictException('Production environment already exists');
  }
  // ❌ Proceeds to create production environment without KYC!
}

Fix Implemented

File: apps/api/src/modules/environments/application/use-cases/create-environment.use-case.ts

Change: Block ALL direct production environment creation through the generic create endpoint:

View full snippet(click to expand)
// NEW CODE (SECURE):
if (type === EnvironmentType.PRODUCTION) {
  throw new ForbiddenException(
    'Direct production environment creation is not allowed. ' +
    'Please use the production access request flow which includes KYC verification.'
  );
}

Impact:

  • ✅ Production environments can ONLY be created through the KYC-gated flow
  • ✅ Enforces regulatory compliance requirements
  • ✅ Prevents subscription enforcement bypass

Defense-in-Depth Security Layers

Layer 1: Application-Level Validation

File: service-enrollment.service.ts:122-126

View full snippet(click to expand)
// SECURITY: Validate environment type is one of the expected values
// This defends against database corruption or manipulation
if (environment.type !== EnvironmentType.SANDBOX &&
    environment.type !== EnvironmentType.PRODUCTION) {
  throw new BadRequestException(`Invalid environment type: ${environment.type}`);
}

Purpose: Detects and blocks invalid environment types even if database data is corrupted or manipulated.

Layer 2: Database CHECK Constraint

File: apps/api/prisma/migrations/20260113001932_security_environment_type_constraint/migration.sql

View full snippet(click to expand)
ALTER TABLE "Environment" ADD CONSTRAINT "chk_environment_type"
CHECK (type IN ('SANDBOX', 'PRODUCTION'));

Purpose: Prevents database-level manipulation of environment types, including:

  • Direct SQL updates
  • Migration errors
  • ORM bypass attacks

Layer 3: API Endpoint Access Control

File: create-environment.use-case.ts:32-39

  • Blocks production type in the generic create endpoint
  • Forces use of KYC-gated production provisioning flow

Layer 4: Organization Ownership Verification

File: service-enrollment.service.ts:119-120

View full snippet(click to expand)
if (!environment || environment.organizationId !== organizationId) {
  throw new NotFoundException('Environment not found for organization');
}

Purpose: Prevents cross-organization environment access attempts.


Correct Production Environment Creation Flow

Step 1: Request Production Access

Endpoint: POST /environments/production/request Use Case: RequestProductionAccessUseCase

  1. Creates production environment with PENDING_KYC status
  2. Initiates KYC verification session
  3. Records KYC audit trail

Step 2: Complete KYC Verification

External Process: Dojah KYC verification

  1. User completes identity verification
  2. Business verification completed
  3. Regulatory compliance checks pass

Step 3: Provision Production Environment

Endpoint: POST /environments/production/provision Use Case: ProvisionProductionUseCase

  1. Verifies KYC completion (checks status)
  2. Enables services with subscription entitlement checks
  3. Mirrors FIRS configuration from sandbox
  4. Sets up crypto keysets

Step 4: Activate Production

Endpoint: POST /environments/production/activate Use Case: ActivateProductionUseCase

  1. Final activation check
  2. Enables production traffic

Testing

Security Tests Added

File: create-environment.security.spec.ts

The test suite verifies:

  1. ✅ Direct production creation is blocked
  2. ✅ Block works even when no production environment exists
  3. ✅ Block applies to all environment statuses
  4. ✅ Sandbox creation still works normally
  5. ✅ Default type is SANDBOX when not specified
  6. ✅ Documents correct production creation flow
  7. ✅ Documents security rationale for f863fe4

Run Tests

View full snippet(click to expand)
cd apps/api
npm test -- create-environment.security.spec.ts

Configuration Requirements

Production Environment Variables

File: .env (production)

View full snippet(click to expand)
# ✅ MUST be set to true in production
SUBSCRIPTION_ENTITLEMENTS_ENFORCED=true

# Define active subscription statuses
SUBSCRIPTION_ACTIVE_STATUSES=ACTIVE,TRIALING,TRIAL

# Enable trial enforcement
TRIAL_ENFORCEMENT_ENABLED=true
TRIAL_SANDBOX_ONLY=true

Security Impact of Feature Flags

| Flag | Value | Impact | |------|-------|--------| | SUBSCRIPTION_ENTITLEMENTS_ENFORCED=false | Development/Test | ALL environments skip subscription checks | | SUBSCRIPTION_ENTITLEMENTS_ENFORCED=true | Production | Only SANDBOX skips checks, PRODUCTION requires valid subscription |

Warning: Setting SUBSCRIPTION_ENTITLEMENTS_ENFORCED=false in production would disable subscription checks for ALL environments, including production. Ensure this is NEVER set to false in production.


Migration Deployment

Apply Database Constraint

View full snippet(click to expand)
cd apps/api
npx prisma migrate deploy

This applies the CHECK constraint to the Environment table, preventing type manipulation at the database level.

Rollback (if needed)

If the constraint causes issues (it shouldn't), it can be dropped:

View full snippet(click to expand)
ALTER TABLE "Environment" DROP CONSTRAINT "chk_environment_type";

Security Checklist

  • [x] Direct production environment creation blocked
  • [x] Application-level type validation added
  • [x] Database CHECK constraint created
  • [x] Organization ownership verified in all paths
  • [x] Environment type immutability maintained
  • [x] Security tests added and passing
  • [x] Documentation updated
  • [x] Production .env verified (SUBSCRIPTION_ENTITLEMENTS_ENFORCED=true)

Conclusion

The subscription enforcement architecture is now secure:

  1. Sandbox Exemption (f863fe4): ✅ Secure - environment types are immutable and verified
  2. Production Access: ✅ Secure - requires KYC through dedicated flow
  3. Defense-in-Depth: ✅ Implemented - multiple validation layers prevent bypass
  4. Testing: ✅ Complete - security tests verify all scenarios

No further action required - all identified gaps have been closed.


References

  • Commit f863fe4: "Added sandbox requests no longer require subscription entitlements"
  • docs/Subscription_Onboarding_Mode.md: Subscription and entitlement architecture
  • service-enrollment.service.ts: Subscription enforcement logic
  • request-production-access.use-case.ts: KYC-gated production flow
  • provision-production.use-case.ts: Production provisioning logic