This document outlines the security hardening measures implemented in the FlowCore and provides guidance for safely extending the system with custom workflow blocks.
The WorkflowBlockFactory now includes comprehensive security measures to prevent malicious assembly loading:
Default Security Setting:
// Dynamic assembly loading is DISABLED by default for security
var factory = new WorkflowBlockFactory(serviceProvider); // Secure by defaultExplicit Opt-in Required:
// Must explicitly enable dynamic assembly loading
var securityOptions = new WorkflowBlockFactorySecurityOptions
{
AllowDynamicAssemblyLoading = true,
AllowedAssemblyNames = new[] { "MyCompany.WorkflowBlocks", "TrustedBlocks" },
ValidateStrongNameSignatures = true
};
var factory = new WorkflowBlockFactory(serviceProvider, securityOptions);Only assemblies explicitly listed in the whitelist can be loaded:
var securityOptions = new WorkflowBlockFactorySecurityOptions
{
AllowDynamicAssemblyLoading = true,
AllowedAssemblyNames = new[] {
"MyCompany.WorkflowBlocks",
"TrustedBlocks.Library",
"ApprovedExtensions"
}
};All dynamically loaded assemblies must have valid strong-name signatures:
var securityOptions = new WorkflowBlockFactorySecurityOptions
{
AllowDynamicAssemblyLoading = true,
ValidateStrongNameSignatures = true,
AllowedPublicKeyTokens = new[] {
new byte[] { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 }, // Your company's key
new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90 } // Trusted partner's key
}
};Secure Implementation Pattern:
using FlowCore.Core.Interfaces;
public class MyCustomBlock : IWorkflowBlock
{
private readonly ILogger<MyCustomBlock> _logger;
public MyCustomBlock(ILogger<MyCustomBlock> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public string NextBlockOnSuccess => "next_block_name";
public string NextBlockOnFailure => "error_block_name";
public async Task<ExecutionResult> ExecuteAsync(ExecutionContext context)
{
try
{
// Validate input security
if (!IsInputSecure(context.Input))
{
return ExecutionResult.Failure("security_validation_failed");
}
// Perform business logic
var result = await PerformSecureOperation(context);
return ExecutionResult.Success(NextBlockOnSuccess);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in MyCustomBlock");
return ExecutionResult.Failure(NextBlockOnFailure);
}
}
private bool IsInputSecure(object input)
{
// Implement security validation logic
return input != null && IsValidInputType(input);
}
private async Task<object> PerformSecureOperation(ExecutionContext context)
{
// Implement your business logic with security considerations
return await Task.FromResult(new { Processed = true });
}
}Secure Service Registration:
public void ConfigureServices(IServiceCollection services)
{
// Register your custom blocks for DI resolution
services.AddTransient<MyCustomBlock>();
services.AddTransient<IWorkflowBlock, MyCustomBlock>(sp =>
sp.GetRequiredService<MyCustomBlock>());
// Configure security options
services.Configure<WorkflowBlockFactorySecurityOptions>(options =>
{
options.AllowDynamicAssemblyLoading = false; // Disable for maximum security
options.ValidateStrongNameSignatures = true;
});
// Register factory with security options
services.AddTransient<IWorkflowBlockFactory>(sp =>
{
var securityOptions = sp.GetRequiredService<IOptions<WorkflowBlockFactorySecurityOptions>>().Value;
return new WorkflowBlockFactory(sp, securityOptions, sp.GetService<ILogger<WorkflowBlockFactory>>());
});
}Instead of dynamic assembly loading, use pre-registered types:
{
"workflow": {
"id": "secure_workflow",
"nodes": {
"custom_processing": {
"type": "MyCompany.WorkflowBlocks.MyCustomBlock, MyCompany.WorkflowBlocks",
"configuration": {
"setting1": "value1",
"setting2": "value2"
},
"transitions": {
"success": "next_node",
"failure": "error_handler"
}
}
}
}
}DO:
- Use strong-name signing for all custom assemblies
- Register custom blocks through dependency injection
- Validate all inputs in custom blocks
- Implement proper error handling and logging
DON'T:
- Load assemblies dynamically from untrusted sources
- Use weak assembly references
- Skip input validation in custom blocks
- Ignore security warnings in logs
Secure Configuration Pattern:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Load configuration from secure sources only
var securityConfig = LoadSecurityConfiguration();
services.Configure<WorkflowBlockFactorySecurityOptions>(options =>
{
options.AllowDynamicAssemblyLoading = securityConfig.AllowDynamicLoading;
options.AllowedAssemblyNames = securityConfig.AllowedAssemblies;
options.ValidateStrongNameSignatures = true;
options.AllowedPublicKeyTokens = securityConfig.TrustedKeys;
});
}
private WorkflowBlockFactorySecurityOptions LoadSecurityConfiguration()
{
// Load from encrypted configuration files, environment variables, or secure stores
return new WorkflowBlockFactorySecurityOptions
{
AllowDynamicAssemblyLoading = false, // Default to secure
AllowedAssemblyNames = new[] { "MyCompany.TrustedBlocks" },
ValidateStrongNameSignatures = true
};
}
}Implement Runtime Checks:
public class SecureWorkflowBlock : IWorkflowBlock
{
public async Task<ExecutionResult> ExecuteAsync(ExecutionContext context)
{
// Runtime security validation
if (!await ValidateExecutionEnvironment())
{
return ExecutionResult.Failure("environment_validation_failed");
}
if (!await ValidateUserPermissions(context))
{
return ExecutionResult.Failure("permission_denied");
}
// Proceed with business logic only if security checks pass
return await ExecuteBusinessLogic(context);
}
}Before (Insecure):
// ❌ Insecure: No restrictions on assembly loading
var factory = new WorkflowBlockFactory(serviceProvider);After (Secure):
// ✅ Secure: Explicit security configuration
var securityOptions = new WorkflowBlockFactorySecurityOptions
{
AllowDynamicAssemblyLoading = false, // Disable dynamic loading
ValidateStrongNameSignatures = true
};
var factory = new WorkflowBlockFactory(serviceProvider, securityOptions);-
Identify Dynamic Assembly Usage:
- Review all workflow definitions for dynamic assembly references
- Replace with pre-registered types where possible
-
Update Security Configuration:
- Add security options to all factory instantiations
- Configure appropriate whitelists for your environment
-
Test Security Measures:
- Verify that unauthorized assemblies cannot be loaded
- Test that security exceptions are properly handled
- Ensure legitimate workflows continue to function
public class SecurityAwareWorkflowBlock : IWorkflowBlock
{
private readonly ILogger _logger;
public async Task<ExecutionResult> ExecuteAsync(ExecutionContext context)
{
// Log security-relevant events
_logger.LogInformation("Executing block {BlockName} for user {UserId}",
nameof(SecureWorkflowBlock), GetCurrentUserId());
try
{
var result = await PerformSecureOperation(context);
_logger.LogInformation("Block {BlockName} completed successfully",
nameof(SecureWorkflowBlock));
return ExecutionResult.Success();
}
catch (SecurityException ex)
{
_logger.LogWarning(ex, "Security violation in block {BlockName}",
nameof(SecureWorkflowBlock));
return ExecutionResult.Failure("security_violation");
}
}
}- Strong-Name Signing: All custom assemblies must be strong-name signed
- Assembly Whitelisting: Only explicitly allowed assemblies can be loaded
- Input Validation: All block inputs must be validated for security
- Error Handling: Security exceptions should not expose sensitive information
- Logging: Security events must be logged for audit purposes
The security implementation provides comprehensive audit trails:
- Assembly loading attempts (successful and failed)
- Security validation results
- Block execution with security context
- Configuration changes and security policy updates
-
"Assembly not in allowed list"
- Add the assembly to the
AllowedAssemblyNameslist - Ensure proper spelling and case matching
- Add the assembly to the
-
"Strong-name signature validation failed"
- Verify the assembly is strong-name signed
- Check that the public key token is in the allowed list
-
"Dynamic assembly loading disabled"
- Set
AllowDynamicAssemblyLoading = trueif dynamic loading is required - Consider using dependency injection registration instead
- Set
For development environments, you can enable debug logging:
var securityOptions = new WorkflowBlockFactorySecurityOptions
{
AllowDynamicAssemblyLoading = true, // Enable for development
ValidateStrongNameSignatures = false, // Disable for development
AllowedAssemblyNames = new[] { "*" } // Allow all for development
};For security-related questions or issues:
- Review this documentation thoroughly
- Check the security configuration in your application
- Verify that all custom assemblies follow the security guidelines
- Contact the security team for enterprise-specific requirements