Skip to main content

Service Hierarchy Security Considerations Guide

Purpose: Authorisation, cascade protection, and security patterns for service hierarchy operations.


Authorisation Checks for Hierarchy Operations

Multi-Tenant Isolation (CRITICAL)

Requirement: All hierarchy queries MUST filter by sellerId to prevent cross-tenant data access.

Incorrect

No tenant filter

async getHierarchy(parentId: string): Promise<ServiceTemplate[]> {
return this.templateRepo.find({
where: { parentServiceTemplateId: parentId },
});
}
Correct

Tenant-scoped query

async getHierarchy(parentId: string, sellerId: string): Promise<ServiceTemplate[]> {
return this.templateRepo.find({
where: {
parentServiceTemplateId: parentId,
sellerId: sellerId, // MANDATORY
},
});
}

RBAC for Hierarchy Operations

OperationRequired RoleRationale
View hierarchycatalog.readRead-only access
Create templatecatalog.writeModify catalogue
Modify parentcatalog.adminStructural change
Delete templatecatalog.adminDestructive action
Activate serviceservice.writeCustomer operation
Terminate cascadeservice.adminBulk operation
@Controller('catalog/templates')
export class ServiceTemplateController {
@Get(':code/hierarchy')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('catalog.read')
async getHierarchy(@Param('code') code: string, @CurrentUser() user: User) {
return this.service.getHierarchy(code, user.sellerId);
}

@Put(':code/parent')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('catalog.admin')
async updateParent(
@Param('code') code: string,
@Body() dto: UpdateParentDto,
@CurrentUser() user: User
) {
return this.service.updateParent(code, dto.parentCode, user.sellerId);
}
}

Customer Context Validation

// Verify customer belongs to tenant before hierarchy operations
async validateCustomerContext(
customerId: string,
sellerId: string
): Promise<void> {
const customer = await this.customerRepo.findOne({
where: { id: customerId, sellerId },
});

if (!customer) {
throw new ForbiddenException(
'Customer not found in current tenant context'
);
}
}

Cascade Termination Protection

HRT Protection for Cascade Operations

High-Risk Transactions (HRT) require additional verification before cascade operations.

@Injectable()
export class CascadeTerminationService {
async terminateWithChildren(
serviceId: string,
context: OperationContext
): Promise<TerminationResult> {
// 1. Check if HRT protection required
const service = await this.serviceRepo.findOne({ where: { id: serviceId } });
const childCount = await this.getActiveChildCount(serviceId);

if (childCount > 0) {
// 2. Require HRT verification for cascade
if (!context.hrtVerified) {
throw new HrtRequiredException(
`Cascade termination affects ${childCount} child services. HRT verification required.`
);
}
}

// 3. Verify MFA if required
if (this.requiresMfa(service)) {
if (!context.mfaVerified) {
throw new MfaRequiredException(
'MFA verification required for termination'
);
}
}

// 4. Perform cascade termination
return this.executeTermination(serviceId, context);
}
}

Cascade Operation Limits

const CASCADE_LIMITS = {
maxChildrenForAutoTerminate: 10, // Auto-approve if <= 10 children
maxChildrenForHrtOnly: 50, // HRT only if <= 50 children
maxChildrenAbsolute: 100, // Block if > 100 children (admin review required)
};

async validateCascadeOperation(serviceId: string): Promise<CascadeValidation> {
const childCount = await this.getActiveChildCount(serviceId);

if (childCount <= CASCADE_LIMITS.maxChildrenForAutoTerminate) {
return { allowed: true, requiresHrt: false, requiresApproval: false };
}

if (childCount <= CASCADE_LIMITS.maxChildrenForHrtOnly) {
return { allowed: true, requiresHrt: true, requiresApproval: false };
}

if (childCount <= CASCADE_LIMITS.maxChildrenAbsolute) {
return { allowed: true, requiresHrt: true, requiresApproval: true };
}

return {
allowed: false,
reason: `Cannot cascade to ${childCount} children. Contact support.`,
};
}

Cross-Customer Isolation

Instance-Level Isolation

// Always verify service belongs to customer
async getServiceInstance(
instanceId: string,
customerId: string,
sellerId: string
): Promise<ServiceInstance> {
const instance = await this.instanceRepo.findOne({
where: {
id: instanceId,
customerId: customerId,
sellerId: sellerId, // Triple isolation
},
});

if (!instance) {
throw new NotFoundException('Service instance not found');
}

return instance;
}

// Verify parent-child relationship is within same customer
async validateParentChildRelation(
parentId: string,
childId: string,
customerId: string
): Promise<void> {
const parent = await this.getServiceInstance(parentId, customerId);
const child = await this.getServiceInstance(childId, customerId);

if (parent.customerId !== child.customerId) {
throw new SecurityException(
'Parent and child must belong to same customer'
);
}
}

SQL Injection Prevention

Incorrect

String interpolation

const query = `
SELECT * FROM cat_service_template
WHERE code = '${userInput}'
`;
Correct

Parameterised query

const templates = await this.templateRepo.find({
where: { code: userInput }, // TypeORM handles escaping
});

// Raw query with parameters
const result = await this.dataSource.query(
`SELECT * FROM cat_service_template WHERE code = $1`,
[userInput]
);

Permission Inheritance Patterns

Hierarchical Permission Model

interface HierarchyPermissions {
canView: boolean;
canActivate: boolean;
canModify: boolean;
canTerminate: boolean;
cascadePermissions: boolean; // If true, applies to children
}

async getEffectivePermissions(
userId: string,
serviceCode: string
): Promise<HierarchyPermissions> {
// 1. Get direct permissions
const directPerms = await this.permissionService.getUserPermissions(
userId,
serviceCode
);

// 2. If no direct permissions, check parent
const template = await this.templateRepo.findOne({
where: { code: serviceCode },
relations: ['parent'],
});

if (!directPerms && template.parent) {
const parentPerms = await this.getEffectivePermissions(
userId,
template.parent.code
);
if (parentPerms.cascadePermissions) {
return parentPerms;
}
}

return directPerms || this.getDefaultPermissions();
}

Role-Based Cascade Control

User RoleCan View HierarchyCan Modify StructureCan Terminate Cascade
CustomerOwn services onlyNoNo
SupportAssigned customersNoSingle services
ManagerAll customersTemplates onlyWith approval
AdminAllAllAll

Audit Logging for Hierarchy Changes

Required Audit Events

enum HierarchyAuditEvent {
TEMPLATE_CREATED = 'HIERARCHY.TEMPLATE_CREATED',
TEMPLATE_MODIFIED = 'HIERARCHY.TEMPLATE_MODIFIED',
PARENT_CHANGED = 'HIERARCHY.PARENT_CHANGED',
TEMPLATE_DELETED = 'HIERARCHY.TEMPLATE_DELETED',
CASCADE_ACTIVATION = 'HIERARCHY.CASCADE_ACTIVATION',
CASCADE_SUSPENSION = 'HIERARCHY.CASCADE_SUSPENSION',
CASCADE_TERMINATION = 'HIERARCHY.CASCADE_TERMINATION',
}

Audit Log Structure

interface HierarchyAuditLog {
id: string;
timestamp: Date;
event: HierarchyAuditEvent;
actor: {
userId: string;
userEmail: string;
role: string;
ipAddress: string;
};
target: {
serviceCode: string;
serviceId: string;
customerId?: string;
sellerId: string;
};
changes: {
field: string;
oldValue: any;
newValue: any;
}[];
context: {
hrtVerified: boolean;
mfaVerified: boolean;
cascadeCount: number;
};
}

Logging Implementation

@Injectable()
export class HierarchyAuditService {
async logParentChange(
template: ServiceTemplate,
oldParent: string | null,
newParent: string | null,
context: OperationContext
): Promise<void> {
await this.auditRepo.save({
timestamp: new Date(),
event: HierarchyAuditEvent.PARENT_CHANGED,
actor: {
userId: context.userId,
userEmail: context.userEmail,
role: context.role,
ipAddress: context.ipAddress,
},
target: {
serviceCode: template.code,
serviceId: template.id,
sellerId: context.sellerId,
},
changes: [{
field: 'parentServiceTemplate',
oldValue: oldParent,
newValue: newParent,
}],
context: {
hrtVerified: context.hrtVerified || false,
mfaVerified: context.mfaVerified || false,
cascadeCount: 0,
},
});
}

async logCascadeTermination(
rootService: ServiceInstance,
terminatedChildren: ServiceInstance[],
context: OperationContext
): Promise<void> {
await this.auditRepo.save({
timestamp: new Date(),
event: HierarchyAuditEvent.CASCADE_TERMINATION,
actor: { /* ... */ },
target: {
serviceCode: rootService.template.code,
serviceId: rootService.id,
customerId: rootService.customerId,
sellerId: context.sellerId,
},
changes: terminatedChildren.map(child => ({
field: 'status',
oldValue: 'ACTIVE',
newValue: 'TERMINATED',
childServiceCode: child.template.code,
})),
context: {
hrtVerified: context.hrtVerified,
mfaVerified: context.mfaVerified,
cascadeCount: terminatedChildren.length,
},
});
}
}

OWASP Top 10 Considerations

A01:2021 - Broken Access Control

Risk: Unauthorised access to hierarchy data across tenants.

Mitigations:

  • ✅ sellerId filter on all queries
  • ✅ Role-based access control
  • ✅ Customer context validation

A02:2021 - Cryptographic Failures

Risk: Exposure of sensitive hierarchy data in transit/at rest.

Mitigations:

  • ✅ TLS 1.3 for all API calls
  • ✅ Encrypted database connections
  • ✅ No sensitive data in service codes

A03:2021 - Injection

Risk: SQL injection through hierarchy queries.

Mitigations:

  • ✅ Parameterised queries only
  • ✅ Input validation on service codes
  • ✅ TypeORM query builder

A04:2021 - Insecure Design

Risk: Hierarchy allows privilege escalation.

Mitigations:

  • ✅ Separate template vs instance permissions
  • ✅ Cascade limits
  • ✅ HRT for destructive operations

A07:2021 - Identification and Authentication Failures

Risk: Unauthenticated hierarchy modification.

Mitigations:

  • ✅ JWT authentication required
  • ✅ MFA for sensitive operations
  • ✅ Session validation

A09:2021 - Security Logging and Monitoring Failures

Risk: Hierarchy changes not tracked.

Mitigations:

  • ✅ Comprehensive audit logging
  • ✅ Cascade operation logging
  • ✅ Tamper-proof audit trail

Privacy Act Compliance

PII in Hierarchy Data

Assessment: Service hierarchy data typically does NOT contain PII.

Data TypeContains PIIProtection Required
Service template codesNoNone
Service descriptionsNoNone
Hierarchy relationshipsNoNone
Service instance IDsNo (UUID)None
Customer IDsYes (identifier)Tenant isolation
Activation datesNoNone

Data Retention

// Hierarchy audit logs must be retained per Privacy Act
const HIERARCHY_AUDIT_RETENTION = {
standard: 7 * 365 * 24 * 60 * 60 * 1000, // 7 years
pii: 7 * 365 * 24 * 60 * 60 * 1000, // 7 years
};

// Purge old audit logs
async purgeExpiredAuditLogs(): Promise<void> {
const cutoff = new Date(Date.now() - HIERARCHY_AUDIT_RETENTION.standard);
await this.auditRepo.delete({
timestamp: LessThan(cutoff),
});
}

SOCI Act Compliance

Critical Infrastructure Considerations

Assessment: Service hierarchy IS critical infrastructure for telecommunications.

Requirements:

  1. Risk Management: Document hierarchy security risks
  2. Incident Response: Procedures for hierarchy compromise
  3. Access Control: Role-based access with MFA
  4. Audit Trail: Complete logging of hierarchy changes

Hierarchy-Specific Controls

// SOCI Act: Critical operations require enhanced verification
const SOCI_CRITICAL_OPERATIONS = [
'CASCADE_TERMINATION',
'PARENT_CHANGE',
'TEMPLATE_DELETION',
];

async verifySociCompliance(
operation: string,
context: OperationContext
): Promise<void> {
if (SOCI_CRITICAL_OPERATIONS.includes(operation)) {
// 1. Verify MFA
if (!context.mfaVerified) {
throw new SociComplianceException('MFA required for critical operation');
}

// 2. Verify authorised personnel
if (!this.isAuthorisedForCriticalOps(context.userId)) {
throw new SociComplianceException('User not authorised for critical operation');
}

// 3. Log for SOCI reporting
await this.sociAuditService.logCriticalOperation(operation, context);
}
}

Essential Eight Compliance

Application Control

Requirement: Only authorised applications can modify hierarchy.

Implementation:

  • API authentication required
  • Admin portal with MFA
  • No direct database access

Restrict Administrative Privileges

Requirement: Limit hierarchy admin access.

Implementation:

  • Separate catalog.admin role
  • Time-limited admin sessions
  • Just-in-time access for hierarchy changes

Multi-Factor Authentication

Requirement: MFA for privileged access.

Implementation:

@UseGuards(JwtAuthGuard, MfaGuard)
@Roles('catalog.admin')
async deleteTemplate(code: string, context: OperationContext) {
// MFA already verified by guard
return this.service.deleteTemplate(code, context.sellerId);
}

Daily Backups

Requirement: Backup hierarchy data daily.

Implementation:

  • Database backup includes all hierarchy tables
  • Point-in-time recovery enabled
  • Tested restore procedures

Security Checklist

Template Configuration Security

  • All templates have sellerId assigned
  • No orphaned templates (missing parent)
  • No circular dependencies
  • Charges properly configured
  • Eligibility constraints validated

Instance Security

  • All instances linked to valid customer
  • Parent-child relationships validated
  • Status transitions logged
  • Cascade operations limited

Access Control Security

  • RBAC implemented for all operations
  • MFA required for admin operations
  • HRT protection for cascades
  • Session validation on each request

Audit Security

  • All changes logged
  • Audit logs tamper-proof
  • Retention policy implemented
  • Logs available for compliance