Security Best Practices for Workflow Automation [2025]
![Security Best Practices for Workflow Automation [2025]](/blog/images/security-best-practices.jpg)
Security Best Practices for Workflow Automation
Automation workflows often have access to your most sensitive systems and data. A single misconfigured workflow can expose customer data, compromise API credentials, or create backdoors into your infrastructure. This comprehensive guide covers everything you need to secure your automation platform from development through production.
Understanding the Threat Landscape
Before implementing security measures, understand what you're protecting against:
Common Attack Vectors
- Credential theft: Exposed API keys, database passwords, OAuth tokens
- Code injection: SQL injection, command injection, SSRF attacks
- Data exfiltration: Unauthorized data access and export
- Privilege escalation: Gaining admin access through workflow manipulation
- Supply chain attacks: Compromised dependencies and integrations
- Denial of service: Resource exhaustion through malicious workflows
- Man-in-the-middle: Intercepting data in transit
- Insider threats: Malicious or negligent internal users
Threat Modeling Framework
┌─────────────────────────────────────────────────────────────┐
│ Threat Model │
│ │
│ ┌────────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Entry Points │────▶│ Assets │◀──│ Attackers │ │
│ │ │ │ │ │ │ │
│ │ • Webhooks │ │ • Credentials│ │ • External │ │
│ │ • APIs │ │ • Data │ │ • Internal │ │
│ │ • UI │ │ • Workflows │ │ • Automated │ │
│ │ • Integrations │ │ • Systems │ │ │ │
│ └────────────────┘ └──────────────┘ └─────────────┘ │
│ │ │ │ │
│ └──────────────────────┼────────────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Threats & │ │
│ │ Mitigations │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Layer 1: Authentication and Authorization
Multi-Factor Authentication
Always require MFA for platform access:
// n8n webhook for MFA verification
{
"name": "MFA Verification",
"nodes": [
{
"name": "Verify TOTP",
"type": "n8n-nodes-base.function",
"parameters": {
"functionCode": `
const speakeasy = require('speakeasy');
const userSecret = $node["Get User MFA Secret"].json.mfa_secret;
const providedToken = $json.body.mfa_token;
const verified = speakeasy.totp.verify({
secret: userSecret,
encoding: 'base32',
token: providedToken,
window: 2 // Allow 2 time steps (60 seconds) of drift
});
if (!verified) {
throw new Error('Invalid MFA token');
}
// Generate session token
const sessionToken = crypto.randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + 3600000); // 1 hour
return {
session_token: sessionToken,
expires_at: expiresAt,
user_id: $json.body.user_id
};
`
}
}
]
}
Role-Based Access Control (RBAC)
Implement fine-grained permissions:
-- RBAC schema
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE permissions (
id SERIAL PRIMARY KEY,
resource VARCHAR(100) NOT NULL, -- workflow, credential, execution
action VARCHAR(50) NOT NULL, -- create, read, update, delete, execute
description TEXT,
UNIQUE(resource, action)
);
CREATE TABLE role_permissions (
role_id INTEGER REFERENCES roles(id),
permission_id INTEGER REFERENCES permissions(id),
PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE user_roles (
user_id VARCHAR(255),
role_id INTEGER REFERENCES roles(id),
organization_id INTEGER,
granted_at TIMESTAMP DEFAULT NOW(),
granted_by VARCHAR(255),
PRIMARY KEY (user_id, role_id, organization_id)
);
-- Pre-defined roles
INSERT INTO roles (name, description) VALUES
('admin', 'Full access to all resources'),
('developer', 'Create and manage workflows'),
('operator', 'Execute workflows and view results'),
('viewer', 'Read-only access');
-- Permission examples
INSERT INTO permissions (resource, action) VALUES
('workflow', 'create'),
('workflow', 'read'),
('workflow', 'update'),
('workflow', 'delete'),
('workflow', 'execute'),
('credential', 'create'),
('credential', 'read'),
('credential', 'update'),
('credential', 'delete'),
('execution', 'read'),
('execution', 'retry');
Permission Checking Function
// Check user permissions before workflow execution
const checkPermission = async (userId, resource, action) => {
const result = await db.query(
`
SELECT EXISTS (
SELECT 1
FROM user_roles ur
JOIN role_permissions rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE ur.user_id = $1
AND p.resource = $2
AND p.action = $3
AND ur.organization_id = current_setting('app.current_organization')::int
) AS has_permission
`,
[userId, resource, action]
);
if (!result.rows[0].has_permission) {
throw new Error(`Permission denied: ${action} on ${resource}`);
}
return true;
};
// Use in workflow
const executeWorkflow = async (userId, workflowId) => {
await checkPermission(userId, 'workflow', 'execute');
// Log access attempt
await auditLog({
user_id: userId,
action: 'workflow.execute',
resource_id: workflowId,
timestamp: new Date(),
ip_address: request.ip,
});
// Execute workflow
return await runWorkflow(workflowId);
};
Layer 2: Secrets Management
Never Hard-Code Credentials
Bad example:
// ❌ NEVER DO THIS
const apiKey = 'sk-1234567890abcdefg';
const dbPassword = 'MyP@ssw0rd123';
Good example:
// ✅ Use environment variables and secret managers
const apiKey = process.env.API_KEY;
const dbPassword = await getSecret('database/password');
HashiCorp Vault Integration
// Secure credential management with Vault
const Vault = require('node-vault');
class SecretManager {
constructor() {
this.vault = Vault({
apiVersion: 'v1',
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_TOKEN,
});
}
async getSecret(path) {
try {
const result = await this.vault.read(`secret/data/${path}`);
return result.data.data;
} catch (error) {
console.error(`Failed to retrieve secret: ${path}`, error);
throw new Error('Secret retrieval failed');
}
}
async createSecret(path, data, metadata = {}) {
// Validate secret before storing
this.validateSecret(data);
return await this.vault.write(`secret/data/${path}`, {
data: data,
metadata: {
created_by: metadata.user_id,
created_at: new Date().toISOString(),
rotation_period: metadata.rotation_period || 90,
},
});
}
async rotateSecret(path) {
const secret = await this.getSecret(path);
// Generate new secret (implementation depends on secret type)
const newSecret = await this.generateNewSecret(secret);
// Store new version
await this.createSecret(path, newSecret);
// Update dependent systems
await this.updateDependentSystems(path, newSecret);
return newSecret;
}
validateSecret(data) {
// Check for common weak patterns
const weakPatterns = [/password123/i, /admin/i, /12345/, /qwerty/i];
const values = Object.values(data).join(' ');
for (const pattern of weakPatterns) {
if (pattern.test(values)) {
throw new Error('Weak secret detected');
}
}
// Check minimum complexity
if (values.length < 20) {
throw new Error('Secret too short');
}
}
async listSecretsNeedingRotation() {
const secrets = await this.vault.list('secret/metadata');
const needRotation = [];
for (const secretPath of secrets.data.keys) {
const metadata = await this.vault.read(`secret/metadata/${secretPath}`);
const createdAt = new Date(metadata.data.created_time);
const daysSinceCreation = (Date.now() - createdAt) / (1000 * 60 * 60 * 24);
const rotationPeriod = metadata.data.custom_metadata?.rotation_period || 90;
if (daysSinceCreation > rotationPeriod) {
needRotation.push({
path: secretPath,
age_days: Math.floor(daysSinceCreation),
rotation_period: rotationPeriod,
});
}
}
return needRotation;
}
}
// Use in n8n workflow
const secretManager = new SecretManager();
const apiCredentials = await secretManager.getSecret('integrations/stripe/api_key');
Credential Encryption at Rest
// Encrypt credentials before storing in database
const crypto = require('crypto');
class CredentialEncryption {
constructor(masterKey) {
this.algorithm = 'aes-256-gcm';
this.masterKey = Buffer.from(masterKey, 'hex');
}
encrypt(data) {
// Generate unique IV for each encryption
const iv = crypto.randomBytes(16);
// Create cipher
const cipher = crypto.createCipheriv(this.algorithm, this.masterKey, iv);
// Encrypt data
const encrypted = Buffer.concat([cipher.update(JSON.stringify(data), 'utf8'), cipher.final()]);
// Get auth tag
const authTag = cipher.getAuthTag();
// Return IV + authTag + encrypted data
return {
encrypted: Buffer.concat([iv, authTag, encrypted]).toString('base64'),
algorithm: this.algorithm,
};
}
decrypt(encryptedData) {
const buffer = Buffer.from(encryptedData.encrypted, 'base64');
// Extract components
const iv = buffer.slice(0, 16);
const authTag = buffer.slice(16, 32);
const encrypted = buffer.slice(32);
// Create decipher
const decipher = crypto.createDecipheriv(this.algorithm, this.masterKey, iv);
decipher.setAuthTag(authTag);
// Decrypt
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
return JSON.parse(decrypted.toString('utf8'));
}
rotateEncryption(oldMasterKey, newMasterKey) {
// Re-encrypt all credentials with new master key
const oldCrypto = new CredentialEncryption(oldMasterKey);
const newCrypto = new CredentialEncryption(newMasterKey);
// Get all encrypted credentials from database
const credentials = db.query('SELECT id, encrypted_data FROM credentials');
for (const cred of credentials) {
const decrypted = oldCrypto.decrypt(cred.encrypted_data);
const reEncrypted = newCrypto.encrypt(decrypted);
db.query('UPDATE credentials SET encrypted_data = $1 WHERE id = $2', [reEncrypted, cred.id]);
}
}
}
Layer 3: Input Validation and Sanitization
Prevent Injection Attacks
// SQL Injection Prevention
class SecureDatabase {
async query(sql, params = []) {
// ALWAYS use parameterized queries
return await this.pool.query(sql, params);
}
// ❌ NEVER DO THIS
async unsafeQuery(userId) {
return await this.pool.query(`SELECT * FROM users WHERE id = ${userId}`);
}
// ✅ DO THIS
async safeQuery(userId) {
return await this.pool.query('SELECT * FROM users WHERE id = $1', [userId]);
}
// Validate and sanitize input
validateInput(input, schema) {
const Joi = require('joi');
const { error, value } = schema.validate(input);
if (error) {
throw new Error(`Invalid input: ${error.message}`);
}
return value;
}
}
// Input validation schemas
const schemas = {
webhook: Joi.object({
user_id: Joi.string().uuid().required(),
action: Joi.string().valid('create', 'update', 'delete').required(),
data: Joi.object().max(100).required(), // Limit object size
}),
email: Joi.object({
to: Joi.string().email().required(),
subject: Joi.string().max(200).required(),
body: Joi.string().max(10000).required(),
}),
};
Command Injection Prevention
// Safely execute external commands
const { execFile } = require('child_process');
const { promisify } = require('util');
const execFileAsync = promisify(execFile);
// ❌ NEVER DO THIS
const unsafeCommand = async userInput => {
const { exec } = require('child_process');
return exec(`convert ${userInput} output.pdf`); // Command injection risk!
};
// ✅ DO THIS
const safeCommand = async userInput => {
// Whitelist allowed commands
const allowedCommands = ['convert', 'imagemagick'];
// Validate input
const sanitizedInput = path.basename(userInput); // Remove path traversal
// Use execFile with separate arguments (not shell)
return await execFileAsync('convert', [sanitizedInput, 'output.pdf'], {
timeout: 30000,
maxBuffer: 10 * 1024 * 1024, // 10MB
});
};
SSRF Prevention
// Prevent Server-Side Request Forgery
const validUrl = require('valid-url');
const ipRangeCheck = require('ip-range-check');
const isUrlSafe = (url) => {
// Validate URL format
if (!validUrl.isUri(url)) {
throw new Error('Invalid URL format');
}
const parsedUrl = new URL(url);
// Block internal IP ranges
const blockedRanges = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8',
'169.254.0.0/16',
'fc00::/7',
'fe80::/10',
'::1/128'
];
// Resolve hostname to IP
const dns = require('dns').promises;
const addresses = await dns.resolve4(parsedUrl.hostname);
for (const ip of addresses) {
if (ipRangeCheck(ip, blockedRanges)) {
throw new Error('Access to internal resources blocked');
}
}
// Whitelist allowed domains
const allowedDomains = [
'api.stripe.com',
'api.github.com',
'hooks.slack.com'
];
if (!allowedDomains.some(domain => parsedUrl.hostname.endsWith(domain))) {
throw new Error('Domain not in allowlist');
}
return true;
};
// Use in HTTP Request node
const safeHttpRequest = async (url, options) => {
await isUrlSafe(url);
return await fetch(url, {
...options,
timeout: 10000, // 10 second timeout
size: 10485760, // 10MB max response size
redirect: 'manual' // Don't follow redirects automatically
});
};
Layer 4: Network Security
TLS/SSL Configuration
# nginx.conf - Secure TLS configuration
server {
listen 443 ssl http2;
server_name automation.company.com;
# SSL certificates
ssl_certificate /etc/ssl/certs/automation.crt;
ssl_certificate_key /etc/ssl/private/automation.key;
# Modern TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_req zone=api burst=20 nodelay;
location / {
proxy_pass http://n8n-cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name automation.company.com;
return 301 https://$server_name$request_uri;
}
Network Segmentation
# Kubernetes NetworkPolicy for n8n isolation
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: n8n-network-policy
namespace: n8n
spec:
podSelector:
matchLabels:
app: n8n
policyTypes:
- Ingress
- Egress
# Ingress rules
ingress:
# Allow from load balancer
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 5678
# Allow from monitoring
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 9090
# Egress rules
egress:
# Allow to PostgreSQL
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
# Allow to Redis
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
# Allow DNS
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
# Allow HTTPS to external APIs
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
Layer 5: Data Protection
Data Classification
// Classify data and apply appropriate protections
const DataClassification = {
PUBLIC: 'public',
INTERNAL: 'internal',
CONFIDENTIAL: 'confidential',
RESTRICTED: 'restricted',
};
const dataHandlers = {
[DataClassification.PUBLIC]: {
encrypt: false,
log: true,
retention_days: 365,
},
[DataClassification.INTERNAL]: {
encrypt: true,
log: true,
retention_days: 90,
access_control: 'authenticated',
},
[DataClassification.CONFIDENTIAL]: {
encrypt: true,
log: true,
retention_days: 30,
access_control: 'role_based',
audit: true,
},
[DataClassification.RESTRICTED]: {
encrypt: true,
log: false, // Don't log content
retention_days: 7,
access_control: 'explicit_grant',
audit: true,
mfa_required: true,
masking: true,
},
};
// Automatic data classification
const classifyData = data => {
const patterns = {
[DataClassification.RESTRICTED]: [
/\b\d{3}-\d{2}-\d{4}\b/, // SSN
/\b\d{16}\b/, // Credit card
/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i, // Email (PII)
],
[DataClassification.CONFIDENTIAL]: [/password/i, /secret/i, /api[_-]?key/i, /token/i],
};
const dataString = JSON.stringify(data);
for (const [classification, regexList] of Object.entries(patterns)) {
for (const regex of regexList) {
if (regex.test(dataString)) {
return classification;
}
}
}
return DataClassification.INTERNAL; // Default
};
// Apply data handling policies
const handleData = async (data, classification) => {
const policy = dataHandlers[classification];
let processedData = data;
// Encryption
if (policy.encrypt) {
processedData = await encrypt(processedData);
}
// Masking for restricted data
if (policy.masking) {
processedData = maskSensitiveFields(processedData);
}
// Audit logging
if (policy.audit) {
await auditLog({
action: 'data_access',
classification: classification,
user_id: getCurrentUser(),
timestamp: new Date(),
});
}
// Set retention policy
await setRetention(processedData, policy.retention_days);
return processedData;
};
// Mask sensitive data
const maskSensitiveFields = data => {
const masked = { ...data };
// Credit cards: show last 4 digits
if (masked.credit_card) {
masked.credit_card = masked.credit_card.replace(/\d(?=\d{4})/g, '*');
}
// SSN: show last 4 digits
if (masked.ssn) {
masked.ssn = masked.ssn.replace(/\d(?=\d{4})/g, '*');
}
// Email: show first letter and domain
if (masked.email) {
masked.email = masked.email.replace(/^(.)(.*)(@.*)$/, '$1***$3');
}
return masked;
};
Data Loss Prevention (DLP)
// DLP rules engine
const DLPRules = {
rules: [
{
name: 'Block SSN in external webhooks',
pattern: /\b\d{3}-\d{2}-\d{4}\b/,
action: 'block',
scope: 'external_webhooks',
},
{
name: 'Alert on credit card transmission',
pattern: /\b\d{16}\b/,
action: 'alert',
scope: 'all',
},
{
name: 'Encrypt health data',
pattern: /patient|medical|diagnosis/i,
action: 'encrypt',
scope: 'all',
},
],
async check(data, scope) {
const dataString = JSON.stringify(data);
const violations = [];
for (const rule of this.rules) {
if (rule.scope !== 'all' && rule.scope !== scope) {
continue;
}
if (rule.pattern.test(dataString)) {
violations.push(rule);
switch (rule.action) {
case 'block':
throw new Error(`DLP violation: ${rule.name}`);
case 'alert':
await this.sendAlert(rule, data);
break;
case 'encrypt':
data = await this.enforceEncryption(data);
break;
}
}
}
return {
data,
violations,
};
},
async sendAlert(rule, data) {
await sendToSecurityTeam({
severity: 'high',
rule: rule.name,
data_summary: this.createSummary(data),
timestamp: new Date(),
});
},
createSummary(data) {
// Create non-sensitive summary for alerting
return {
size: JSON.stringify(data).length,
fields: Object.keys(data),
classification: classifyData(data),
};
},
};
Layer 6: Monitoring and Incident Response
Security Event Monitoring
// Security event detection
const SecurityMonitor = {
events: [],
async track(event) {
this.events.push({
...event,
timestamp: new Date(),
});
// Check for suspicious patterns
await this.analyzeThreat(event);
},
async analyzeThreat(event) {
// Multiple failed authentication attempts
if (event.type === 'auth_failure') {
const recentFailures = this.events.filter(
e =>
e.type === 'auth_failure' &&
e.user_id === event.user_id &&
Date.now() - e.timestamp < 300000 // Last 5 minutes
);
if (recentFailures.length >= 5) {
await this.respondToThreat({
threat: 'brute_force_attack',
user_id: event.user_id,
action: 'lock_account',
});
}
}
// Unusual data access patterns
if (event.type === 'data_access') {
const recentAccess = this.events.filter(
e =>
e.type === 'data_access' &&
e.user_id === event.user_id &&
Date.now() - e.timestamp < 3600000 // Last hour
);
if (recentAccess.length > 100) {
await this.respondToThreat({
threat: 'data_exfiltration_attempt',
user_id: event.user_id,
action: 'alert_security_team',
});
}
}
// Privilege escalation attempts
if (event.type === 'permission_denied' && event.resource === 'admin') {
await this.respondToThreat({
threat: 'privilege_escalation_attempt',
user_id: event.user_id,
action: 'alert_security_team',
});
}
},
async respondToThreat(threat) {
// Log to SIEM
await this.logToSIEM(threat);
// Execute automated response
switch (threat.action) {
case 'lock_account':
await lockUserAccount(threat.user_id);
break;
case 'alert_security_team':
await sendSecurityAlert(threat);
break;
case 'block_ip':
await blockIPAddress(threat.ip_address);
break;
}
// Create incident ticket
await createIncident(threat);
},
async logToSIEM(event) {
// Send to Splunk, ELK, or other SIEM
await fetch(process.env.SIEM_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...event,
source: 'n8n-automation',
timestamp: new Date().toISOString(),
}),
});
},
};
Incident Response Playbook
# incident-response.yml
incident_types:
credential_leak:
severity: critical
steps:
- action: rotate_all_credentials
timeout: 15m
- action: revoke_active_sessions
timeout: 5m
- action: notify_security_team
immediate: true
- action: audit_recent_access
window: 24h
- action: notify_affected_users
sla: 2h
data_breach:
severity: critical
steps:
- action: isolate_affected_systems
timeout: 5m
- action: preserve_evidence
immediate: true
- action: notify_legal_team
immediate: true
- action: begin_forensics
sla: 1h
- action: notify_authorities
sla: 72h # GDPR requirement
unauthorized_access:
severity: high
steps:
- action: lock_account
immediate: true
- action: review_access_logs
sla: 1h
- action: check_lateral_movement
sla: 2h
- action: reset_credentials
sla: 4h
dos_attack:
severity: medium
steps:
- action: enable_rate_limiting
immediate: true
- action: activate_waf_rules
timeout: 5m
- action: scale_infrastructure
timeout: 10m
- action: identify_attack_source
sla: 30m
Compliance Frameworks
GDPR Compliance
// GDPR data subject rights implementation
const GDPRCompliance = {
// Right to access
async exportUserData(userId) {
const userData = {
personal_info: await db.query('SELECT * FROM users WHERE id = $1', [userId]),
workflows: await db.query('SELECT * FROM workflows WHERE user_id = $1', [userId]),
executions: await db.query('SELECT * FROM executions WHERE user_id = $1', [userId]),
audit_log: await db.query('SELECT * FROM audit_log WHERE user_id = $1', [userId]),
};
return {
format: 'json',
data: userData,
generated_at: new Date().toISOString(),
};
},
// Right to erasure
async deleteUserData(userId) {
await db.query('BEGIN');
try {
// Anonymize instead of delete for audit trail
await db.query(
`
UPDATE users
SET email = 'deleted-' || id || '@anonymized.local',
name = 'Deleted User',
deleted_at = NOW()
WHERE id = $1
`,
[userId]
);
// Delete personal data from workflows
await db.query('DELETE FROM workflows WHERE user_id = $1', [userId]);
// Anonymize execution data
await db.query(
`
UPDATE executions
SET user_id = NULL,
data = NULL
WHERE user_id = $1
`,
[userId]
);
await db.query('COMMIT');
// Log deletion for compliance
await auditLog({
action: 'gdpr_data_deletion',
user_id: userId,
timestamp: new Date(),
});
} catch (error) {
await db.query('ROLLBACK');
throw error;
}
},
// Right to rectification
async updateUserData(userId, updates) {
await db.query(
`
UPDATE users
SET ${Object.keys(updates)
.map((k, i) => `${k} = $${i + 2}`)
.join(', ')}
WHERE id = $1
`,
[userId, ...Object.values(updates)]
);
await auditLog({
action: 'gdpr_data_rectification',
user_id: userId,
changes: Object.keys(updates),
timestamp: new Date(),
});
},
// Data breach notification
async notifyDataBreach(incident) {
const affectedUsers = await this.getAffectedUsers(incident);
// Must notify within 72 hours of discovery
const deadline = new Date(incident.discovered_at);
deadline.setHours(deadline.getHours() + 72);
// Notify supervisory authority
await this.notifySupervisoryAuthority({
incident: incident,
affected_count: affectedUsers.length,
deadline: deadline,
});
// Notify affected individuals
for (const user of affectedUsers) {
await this.notifyUser(user, {
incident_type: incident.type,
data_affected: incident.data_types,
actions_taken: incident.mitigation_steps,
contact: 'privacy@company.com',
});
}
},
};
SOC 2 Controls
// SOC 2 control implementation
const SOC2Controls = {
// CC6.1 - Logical Access Controls
async enforceAccessControls() {
// Implement least privilege
await this.reviewUserPermissions();
// Enforce MFA
const usersWithoutMFA = await db.query(`
SELECT user_id FROM users
WHERE mfa_enabled = false
AND last_login > NOW() - INTERVAL '30 days'
`);
for (const user of usersWithoutMFA.rows) {
await this.enforceMFA(user.user_id);
}
},
// CC7.2 - System Monitoring
async monitorSystems() {
// Monitor for security events
const suspiciousActivities = await SecurityMonitor.getRecentThreats();
// Check system health
const healthMetrics = await this.collectHealthMetrics();
// Generate compliance report
return {
security_events: suspiciousActivities.length,
system_uptime: healthMetrics.uptime,
failed_logins: suspiciousActivities.filter(a => a.type === 'auth_failure').length,
timestamp: new Date(),
};
},
// CC8.1 - Change Management
async trackChanges(change) {
await db.query(
`
INSERT INTO change_log (
change_type,
resource_id,
old_value,
new_value,
changed_by,
approved_by,
timestamp
) VALUES ($1, $2, $3, $4, $5, $6, NOW())
`,
[
change.type,
change.resource_id,
JSON.stringify(change.old_value),
JSON.stringify(change.new_value),
change.changed_by,
change.approved_by,
]
);
},
};
Security Testing
Automated Security Scanning
# .github/workflows/security-scan.yml
name: Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 0 * * *' # Daily
jobs:
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Snyk dependency scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
sast-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
container-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build -t n8n-app .
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: n8n-app
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
Penetration Testing Checklist
- Authentication bypass attempts
- Authorization testing (vertical and horizontal privilege escalation)
- Input validation testing (SQL injection, XSS, command injection)
- Session management testing
- Cryptography testing
- Business logic testing
- API security testing
- Configuration testing
- Error handling testing
- Denial of service testing
Security Checklist for Production
Pre-Deployment
- All secrets stored in secret manager (Vault/AWS Secrets Manager)
- Database connections use encrypted connections (SSL/TLS)
- All API endpoints require authentication
- Rate limiting configured on all public endpoints
- Input validation implemented on all user inputs
- RBAC properly configured
- Audit logging enabled for all sensitive operations
- Security headers configured (CSP, HSTS, etc.)
- Network policies restrict unnecessary communication
- Backup and disaster recovery tested
Post-Deployment
- Security monitoring and alerting active
- Regular security scans scheduled
- Incident response plan documented and tested
- Security training completed for all team members
- Regular access reviews scheduled
- Vulnerability management process in place
- Compliance requirements met and documented
- Third-party security audit completed
Join the Community
Security is an ongoing journey, not a destination. The House of Loops community includes security professionals, compliance experts, and automation engineers who share knowledge about securing automation workflows.
Join us to:
- Get security reviews of your workflows
- Access security-hardened templates
- Participate in security workshops
- Stay updated on emerging threats
- Connect with security-focused developers
Join House of Loops Today and build secure automation systems with confidence.
Have security questions? Our community has experts ready to help secure your automation platform!
House of Loops Team
House of Loops is a technology-focused community for learning and implementing advanced automation workflows using n8n, Strapi, AI/LLM, and DevSecOps tools.
Join Our Community
![Enterprise Automation Architecture: Best Practices [2025]](/blog/images/enterprise-automation.jpg)
![Building AI Agents with n8n and OpenAI: Complete Guide [2025]](/blog/images/building-ai-agents.jpg)