-
Authentication & Authorization
- Password hashing with bcrypt (cost factor 10)
- CSRF token protection on all state-changing operations
- Session-based authentication
- API key authentication with SHA-256 hashing
- Role-based access control (5 roles)
- Account lockout after 5 failed login attempts
-
SQL Injection Prevention
- PDO with prepared statements throughout
- Parameter binding on all queries
- No direct SQL concatenation with user input
-
XSS Prevention
- htmlspecialchars() on all output
- Content-Type headers set correctly
- Input sanitization in SecurityHelper
-
CSRF Protection
- Token generation per session
- Token validation on POST/PUT/DELETE requests
- hash_equals() for timing-safe comparison
-
Rate Limiting
- Login attempts: 5 per 15 minutes
- API calls: 100 per 60 minutes
- Webhooks: 50 per 60 minutes
-
Multi-Tenant Isolation
- tenant_id filtering on all queries
- Middleware verification
- API key scoped to tenant
-
File Upload Security
- MIME type validation
- File extension whitelist
- File size limits
- Sanitized filenames
- No direct file access (served through controller)
-
Webhook Security
- Secret validation
- Rate limiting per IP/tenant/workflow
- Input payload size limits
-
Audit Logging
- User actions logged
- IP address captured
- User agent captured
- Old/new values tracked
-
Password Policy
// Add to ValidationHelper.php private function validatePasswordStrength($password) { // Require uppercase, lowercase, number, special char if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/', $password)) { return false; } return true; }
-
API Key Rotation
- Implement expiration dates for API keys
- Add key rotation mechanism
- Alert when keys are old
-
Content Security Policy
// Add to headers in public/index.php header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';");
-
Sensitive Data Encryption
// Encrypt integration credentials at rest // In Integration model: public function storeAuth($data) { $key = getenv('APP_KEY'); $encrypted = SecurityHelper::encryptData(json_encode($data), $key); return $encrypted; }
-
Two-Factor Authentication
- Add TOTP support for tenant admins
- Use libraries like php-otp
-
Security Headers
# Add to Nginx config add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Content-Security-Policy "default-src 'self';" always; add_header X-Frame-Options "DENY" always;
-
Input Length Limits
- Add max length validation on all text fields
- Prevent DoS through large payloads
-
API Request Signing
- Implement HMAC signature verification
- Add timestamp to prevent replay attacks
-
Webhook Payload Verification
- Add payload signature verification
- Implement nonce to prevent replay
-
Database User Permissions
- Use separate DB users for read/write
- Principle of least privilege
-- Critical indexes for performance
-- Already have primary keys and some foreign keys
-- Workflows
CREATE INDEX idx_workflows_tenant_active ON workflows(tenant_id, is_active);
CREATE INDEX idx_workflows_tenant_created ON workflows(tenant_id, created_at);
-- Flow Instances
CREATE INDEX idx_instances_workflow_status ON flow_instances(workflow_id, status);
CREATE INDEX idx_instances_tenant_created ON flow_instances(tenant_id, created_at DESC);
CREATE INDEX idx_instances_status_created ON flow_instances(status, created_at);
-- Flow Instance Steps
CREATE INDEX idx_instance_steps_instance_started ON flow_instance_steps(instance_id, started_at);
CREATE INDEX idx_instance_steps_status ON flow_instance_steps(status);
-- Users
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_tenant_role ON users(tenant_id, role);
-- Approvals
CREATE INDEX idx_approvals_user_status ON approvals(approver_user_id, status);
CREATE INDEX idx_approvals_instance ON approvals(instance_id);
-- Notifications
CREATE INDEX idx_notifications_user_read ON notifications(user_id, is_read, created_at DESC);
-- Usage Tracking
CREATE INDEX idx_usage_tenant_period ON usage_tracking(tenant_id, period_start);
-- API Keys
CREATE INDEX idx_apikeys_hash ON api_keys(key_hash);
-- Rate Limits
CREATE INDEX idx_ratelimit_id_action_window ON rate_limits(identifier, action_type, window_start);
-- Audit Logs
CREATE INDEX idx_audit_tenant_created ON audit_logs(tenant_id, created_at DESC);
CREATE INDEX idx_audit_user_action ON audit_logs(user_id, action);
-- Scheduled Jobs
CREATE INDEX idx_scheduledjobs_next_active ON scheduled_jobs(next_run_at, is_active);-
Pagination
// Already implemented in Model::paginate() // Good: Uses LIMIT and OFFSET
-
Eager Loading (Implement for N+1 prevention)
// Add to FlowInstance model public function getInstancesWithWorkflow($tenantId, $limit = 50) { return $this->db->fetchAll( "SELECT fi.*, w.name as workflow_name, w.workflow_key FROM flow_instances fi JOIN workflows w ON fi.workflow_id = w.id WHERE fi.tenant_id = :tenant_id ORDER BY fi.created_at DESC LIMIT {$limit}", ['tenant_id' => $tenantId] ); }
-
Query Caching
// Implement Redis caching for frequent queries class CacheHelper { private static $redis; public static function remember($key, $ttl, $callback) { if (self::$redis && $cached = self::$redis->get($key)) { return unserialize($cached); } $result = $callback(); if (self::$redis) { self::$redis->setex($key, $ttl, serialize($result)); } return $result; } }
-
Optimize Heavy Queries
-- Use EXPLAIN to analyze queries EXPLAIN SELECT * FROM flow_instances WHERE tenant_id = 1 ORDER BY created_at DESC;
-
OPcache Configuration
; php.ini opcache.enable=1 opcache.memory_consumption=256 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=20000 opcache.revalidate_freq=2 opcache.fast_shutdown=1 opcache.enable_cli=0
-
Session Storage
// Move to Redis for scalability // In public/index.php ini_set('session.save_handler', 'redis'); ini_set('session.save_path', 'tcp://127.0.0.1:6379');
-
Asset Optimization
- Minify CSS and JavaScript
- Use CDN for static assets
- Enable gzip compression
- Implement browser caching headers
-
Database Connection Pooling
// Use persistent connections $config['options'][PDO::ATTR_PERSISTENT] = true;
-
Workflow Execution
// Move to queue for async processing class WorkflowQueue { public static function dispatch($workflowId, $tenantId, $payload) { // Push to Redis queue or RabbitMQ // Worker process handles execution } }
┌─────────────────┐
│ Load Balancer │ (Nginx, HAProxy, or AWS ALB)
└────────┬────────┘
│
┌────┴────┬─────────┬─────────┐
│ │ │ │
┌───▼───┐ ┌──▼────┐ ┌──▼────┐ ┌──▼────┐
│ Web 1 │ │ Web 2 │ │ Web 3 │ │ Web N │
└───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘
│ │ │ │
└────┬────┴─────────┴─────────┘
│
┌────▼────────────────┐
│ Shared Storage │ (NFS, S3, or GlusterFS)
│ - Uploads │
│ - Session (Redis) │
└─────────────────────┘
│
┌────▼────────────────┐
│ Database Cluster │ (MySQL Master-Slave)
│ - Master (Write) │
│ - Slave (Read) │
└─────────────────────┘
- Auth Service: Handle authentication/authorization
- Workflow Service: Manage workflows
- Execution Service: Execute workflow instances
- Integration Service: Handle external integrations
- Notification Service: Send notifications
- API Gateway: Route requests
// Implement Redis Queue or RabbitMQ
class WorkflowQueue {
private $redis;
public function push($workflowId, $tenantId, $payload) {
$job = [
'workflow_id' => $workflowId,
'tenant_id' => $tenantId,
'payload' => $payload,
'created_at' => time()
];
$this->redis->lpush('workflow_queue', json_encode($job));
}
public function process() {
while (true) {
$job = $this->redis->brpop('workflow_queue', 5);
if ($job) {
$data = json_decode($job[1], true);
$engine = new WorkflowEngine();
$engine->startWorkflow(
$data['workflow_id'],
$data['tenant_id'],
'queued',
$data['payload']
);
}
}
}
}// Implement read/write splitting
class DatabaseManager {
private $writeConnection;
private $readConnections = [];
public function getWriteConnection() {
return $this->writeConnection;
}
public function getReadConnection() {
// Round-robin or random selection
return $this->readConnections[array_rand($this->readConnections)];
}
}// Multi-layer caching
// L1: Application memory (static variables)
// L2: Redis (shared cache)
// L3: Database
class CacheManager {
private static $memory = [];
private $redis;
private $db;
public function get($key, $callable) {
// L1: Memory
if (isset(self::$memory[$key])) {
return self::$memory[$key];
}
// L2: Redis
if ($this->redis && $cached = $this->redis->get($key)) {
self::$memory[$key] = unserialize($cached);
return self::$memory[$key];
}
// L3: Database
$result = $callable();
// Store in caches
if ($this->redis) {
$this->redis->setex($key, 3600, serialize($result));
}
self::$memory[$key] = $result;
return $result;
}
}// Store static assets on CDN
define('CDN_URL', 'https://cdn.splashaiflow.com');
function asset($path) {
if (getenv('APP_ENV') === 'production') {
return CDN_URL . '/' . $path;
}
return '/' . $path;
}// app/core/Container.php
class Container {
private $bindings = [];
private $instances = [];
public function bind($abstract, $concrete) {
$this->bindings[$abstract] = $concrete;
}
public function singleton($abstract, $concrete) {
$this->bind($abstract, $concrete);
// Mark as singleton
}
public function make($abstract) {
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
$concrete = $this->bindings[$abstract] ?? $abstract;
$instance = new $concrete();
$this->instances[$abstract] = $instance;
return $instance;
}
}// app/core/Event.php
class Event {
private static $listeners = [];
public static function listen($event, $callback) {
self::$listeners[$event][] = $callback;
}
public static function fire($event, $data = []) {
if (isset(self::$listeners[$event])) {
foreach (self::$listeners[$event] as $listener) {
call_user_func($listener, $data);
}
}
}
}
// Usage:
Event::listen('workflow.completed', function($data) {
// Send notification
// Update analytics
// Trigger dependent workflows
});
Event::fire('workflow.completed', ['instance_id' => $instanceId]);// app/repositories/WorkflowRepository.php
interface WorkflowRepositoryInterface {
public function findById($id);
public function findByTenant($tenantId);
public function create($data);
public function update($id, $data);
public function delete($id);
}
class WorkflowRepository implements WorkflowRepositoryInterface {
private $db;
public function findById($id) {
// Implementation
}
// ... other methods
}// app/services/WorkflowService.php
class WorkflowService {
private $workflowRepo;
private $stepRepo;
private $triggerRepo;
public function __construct(
WorkflowRepository $workflowRepo,
StepRepository $stepRepo,
TriggerRepository $triggerRepo
) {
$this->workflowRepo = $workflowRepo;
$this->stepRepo = $stepRepo;
$this->triggerRepo = $triggerRepo;
}
public function createWorkflow($data) {
// Business logic
// Validation
// Transaction management
// Event firing
}
}// Support multiple API versions
$router->get('/api/v1/workflows', [ApiV1Controller::class, 'listWorkflows']);
$router->get('/api/v2/workflows', [ApiV2Controller::class, 'listWorkflows']);// tests/TestCase.php
abstract class TestCase {
protected $db;
public function setUp() {
// Setup test database
// Seed test data
}
public function tearDown() {
// Rollback transactions
// Clean test data
}
}
// tests/WorkflowTest.php
class WorkflowTest extends TestCase {
public function testCreateWorkflow() {
$workflow = new Workflow();
$id = $workflow->create([
'tenant_id' => 1,
'name' => 'Test Workflow',
'workflow_key' => 'test_workflow'
]);
$this->assertNotEmpty($id);
}
}// Implement custom exception hierarchy
class SplashAIException extends Exception {}
class ValidationException extends SplashAIException {}
class AuthenticationException extends SplashAIException {}
class QuotaExceededException extends SplashAIException {}
// Global exception handler
set_exception_handler(function($exception) {
Logger::error($exception->getMessage(), [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString()
]);
if ($exception instanceof ValidationException) {
http_response_code(422);
echo json_encode(['error' => $exception->getMessage()]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Internal server error']);
}
});// Centralize configuration
class Config {
private static $config = [];
public static function load($file) {
self::$config = array_merge(
self::$config,
require $file
);
}
public static function get($key, $default = null) {
$keys = explode('.', $key);
$value = self::$config;
foreach ($keys as $k) {
if (!isset($value[$k])) {
return $default;
}
$value = $value[$k];
}
return $value;
}
}
// Usage:
Config::get('database.host', 'localhost');// Implement PSR-3 Logger Interface
class Logger implements Psr\Log\LoggerInterface {
public function emergency($message, array $context = []) { }
public function alert($message, array $context = []) { }
public function critical($message, array $context = []) { }
public function error($message, array $context = []) { }
public function warning($message, array $context = []) { }
public function notice($message, array $context = []) { }
public function info($message, array $context = []) { }
public function debug($message, array $context = []) { }
public function log($level, $message, array $context = []) { }
}// Implement metrics collection
class Metrics {
private $statsd;
public function increment($metric, $tags = []) {
// Send to StatsD or DataDog
}
public function timing($metric, $time, $tags = []) {
// Track timing metrics
}
public function gauge($metric, $value, $tags = []) {
// Track gauge metrics
}
}
// Usage:
Metrics::increment('workflow.executed', ['tenant' => $tenantId]);
Metrics::timing('workflow.duration', $duration, ['workflow' => $workflowKey]);// GET /health
public function health() {
$checks = [
'database' => $this->checkDatabase(),
'redis' => $this->checkRedis(),
'disk' => $this->checkDiskSpace(),
'queue' => $this->checkQueue()
];
$healthy = !in_array(false, $checks);
http_response_code($healthy ? 200 : 503);
return json_encode([
'status' => $healthy ? 'healthy' : 'unhealthy',
'checks' => $checks
]);
}- Add missing database indexes
- Implement API key expiration
- Add security headers
- Setup OPcache
- Implement health check endpoint
- Add Redis caching
- Implement queue system for workflows
- Add 2FA support
- Setup monitoring (New Relic/DataDog)
- Implement automated backups
- Refactor to use DI container
- Implement event system
- Add comprehensive test suite
- Setup CI/CD pipeline
- Migrate to microservices (if scale demands)
- None significant (clean codebase)
- Consider adding type hints (PHP 7.4+)
- Consider adding PHPDoc blocks
- Consider implementing interfaces
Overall Assessment: The codebase is well-structured, secure, and production-ready. Implementing the recommended improvements will enhance scalability, performance, and maintainability.