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:
-
Environment Type Immutability: The
typefield is only set during environment creation and is NOT included inUpdateEnvironmentInput(environment.repository.ts:13-19) -
Organization Ownership Verification: The
getEnvironmentForOrganizationmethod verifies that the environment belongs to the requesting organization before checking the type (service-enrollment.service.ts:119-120) -
Database-Sourced Type: The environment type is read from the database, not from user input during service operations
-
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
- Creates production environment with
PENDING_KYCstatus - Initiates KYC verification session
- Records KYC audit trail
Step 2: Complete KYC Verification
External Process: Dojah KYC verification
- User completes identity verification
- Business verification completed
- Regulatory compliance checks pass
Step 3: Provision Production Environment
Endpoint: POST /environments/production/provision
Use Case: ProvisionProductionUseCase
- Verifies KYC completion (checks status)
- Enables services with subscription entitlement checks
- Mirrors FIRS configuration from sandbox
- Sets up crypto keysets
Step 4: Activate Production
Endpoint: POST /environments/production/activate
Use Case: ActivateProductionUseCase
- Final activation check
- Enables production traffic
Testing
Security Tests Added
File: create-environment.security.spec.ts
The test suite verifies:
- ✅ Direct production creation is blocked
- ✅ Block works even when no production environment exists
- ✅ Block applies to all environment statuses
- ✅ Sandbox creation still works normally
- ✅ Default type is SANDBOX when not specified
- ✅ Documents correct production creation flow
- ✅ 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:
- Sandbox Exemption (f863fe4): ✅ Secure - environment types are immutable and verified
- Production Access: ✅ Secure - requires KYC through dedicated flow
- Defense-in-Depth: ✅ Implemented - multiple validation layers prevent bypass
- 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 architectureservice-enrollment.service.ts: Subscription enforcement logicrequest-production-access.use-case.ts: KYC-gated production flowprovision-production.use-case.ts: Production provisioning logic