/n8n-tutorials

How to protect sensitive user data in prompts passed through n8n?

Learn how to protect sensitive user data in n8n prompts with encryption, environment variables, access controls, secure logging, and network security best practices.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to protect sensitive user data in prompts passed through n8n?

To protect sensitive user data in prompts passed through n8n, you should implement encryption for credentials, use environment variables for sensitive information, leverage the credentials feature, configure proper access controls, use redaction techniques for logs, and ensure proper network security measures. This comprehensive approach will safeguard user data throughout the workflow automation process.

 

Comprehensive Guide: Protecting Sensitive User Data in n8n Prompts

 

n8n is a powerful workflow automation tool that often requires handling sensitive user information. When working with prompts that contain personal data, credit card information, or other confidential details, proper protection measures are essential. This guide will walk you through detailed steps to secure sensitive user data in n8n workflows.

 

Step 1: Understand the Security Risks

 

Before implementing protection measures, it's important to understand the potential security risks when handling sensitive data in n8n:

  • Data exposure in workflow configurations
  • Insecure storage of credentials
  • Unencrypted data transmission
  • Logging of sensitive information
  • Unauthorized access to workflows containing sensitive data
  • Data leakage through API responses

 

Step 2: Use Environment Variables for Sensitive Information

 

Environment variables allow you to store sensitive information outside your workflow configuration, making them a secure option for handling sensitive data.

  • Create a .env file in your n8n root directory (if using self-hosted n8n)
  • Add your sensitive variables following the pattern: VARIABLE\_NAME=value
  • Reference these variables in your workflows using the n8n expression syntax

Example .env file:


DATABASE\_PASSWORD=super-secret-password
API\_KEY=abcd1234efgh5678
ENCRYPTION\_KEY=my-secure-encryption-key

To use these variables in your workflow:


// In expression fields
{{$env.DATABASE\_PASSWORD}}
{{$env.API\_KEY}}

 

Step 3: Leverage n8n's Credentials Feature

 

n8n has a built-in credentials management system that securely stores authentication details:

  • Never hardcode credentials directly in your workflow
  • Create credential entries for all service connections
  • n8n encrypts credentials at rest

To create credentials:

  1. Navigate to the Credentials section in n8n
  2. Click "Create New"
  3. Select the appropriate credential type
  4. Fill in the required information
  5. Save the credential
  6. Reference it in your nodes instead of inputting raw credentials

 

Step 4: Implement Data Encryption for Sensitive Fields

 

For sensitive data that must pass through your workflows, implement encryption and decryption steps:

  1. Create a "Function" node before processing sensitive data

// Function node to encrypt sensitive data
const crypto = require('crypto');

// Get encryption key from environment variable
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY;
// Key should be 32 bytes (256 bits) for aes-256-cbc
const IV\_LENGTH = 16; // For AES, this is always 16

function encrypt(text) {
  const iv = crypto.randomBytes(IV\_LENGTH);
  const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION\_KEY), iv);
  let encrypted = cipher.update(text);
  encrypted = Buffer.concat([encrypted, cipher.final()]);
  return iv.toString('hex') + ':' + encrypted.toString('hex');
}

// Encrypt sensitive fields in the input
const items = $input.all();
const processedItems = items.map(item => {
  const newItem = {...item};
  
  // Encrypt sensitive fields
  if (newItem.json.creditCardNumber) {
    newItem.json.creditCardNumber = encrypt(newItem.json.creditCardNumber);
  }
  
  if (newItem.json.socialSecurityNumber) {
    newItem.json.socialSecurityNumber = encrypt(newItem.json.socialSecurityNumber);
  }
  
  return newItem;
});

return processedItems;
  1. Create a "Function" node to decrypt data when needed:

// Function node to decrypt sensitive data
const crypto = require('crypto');

// Get encryption key from environment variable
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY;
const IV\_LENGTH = 16;

function decrypt(text) {
  const textParts = text.split(':');
  const iv = Buffer.from(textParts.shift(), 'hex');
  const encryptedText = Buffer.from(textParts.join(':'), 'hex');
  const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION\_KEY), iv);
  let decrypted = decipher.update(encryptedText);
  decrypted = Buffer.concat([decrypted, decipher.final()]);
  return decrypted.toString();
}

// Decrypt sensitive fields in the input
const items = $input.all();
const processedItems = items.map(item => {
  const newItem = {...item};
  
  // Decrypt sensitive fields
  if (newItem.json.creditCardNumber) {
    newItem.json.creditCardNumber = decrypt(newItem.json.creditCardNumber);
  }
  
  if (newItem.json.socialSecurityNumber) {
    newItem.json.socialSecurityNumber = decrypt(newItem.json.socialSecurityNumber);
  }
  
  return newItem;
});

return processedItems;

 

Step 5: Implement Data Masking

 

For data that needs to be displayed or logged but should be partially hidden:


// Function node to mask sensitive data
const items = $input.all();
const processedItems = items.map(item => {
  const newItem = {...item};
  
  // Mask credit card number (show only last 4 digits)
  if (newItem.json.creditCardNumber) {
    const ccNum = newItem.json.creditCardNumber;
    newItem.json.creditCardNumber\_masked = 'XXXX-XXXX-XXXX-' + ccNum.slice(-4);
    // Remove the original number if you don't need it downstream
    delete newItem.json.creditCardNumber;
  }
  
  // Mask social security number (show only last 4 digits)
  if (newItem.json.socialSecurityNumber) {
    const ssn = newItem.json.socialSecurityNumber;
    newItem.json.socialSecurityNumber\_masked = 'XXX-XX-' + ssn.slice(-4);
    // Remove the original SSN if you don't need it downstream
    delete newItem.json.socialSecurityNumber;
  }
  
  return newItem;
});

return processedItems;

 

Step 6: Configure Access Controls

 

If you're using n8n with multiple users, proper access control is essential:

  1. Use n8n's role-based access control features (available in paid plans)
  2. Create specific roles with limited permissions for users who should not have access to workflows containing sensitive data
  3. Configure workflow sharing permissions carefully

For self-hosted n8n:

  • Set up proper user authentication
  • Consider implementing additional authentication layers (e.g., VPN, IP restrictions)
  • Use environment variables to control access permissions

 

Step 7: Configure Network Security

 

Ensure proper network security for n8n instances:

  • Always use HTTPS for production n8n instances
  • Implement proper SSL/TLS certificates
  • Consider using a reverse proxy for additional security
  • Configure firewalls to restrict access to the n8n instance

Example nginx configuration for secure proxy:


server {
    listen 443 ssl;
    server\_name n8n.yourdomain.com;
    
    ssl\_certificate /path/to/your/certificate.crt;
    ssl_certificate_key /path/to/your/private.key;
    
    ssl\_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server\_ciphers on;
    ssl\_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
    
    location / {
        proxy\_pass http://localhost:5678;
        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;
    }
}

 

Step 8: Implement Secure Data Transmission

 

When sending data between systems:

  • Always use encrypted connections (HTTPS, FTPS, SFTP, etc.)
  • Verify SSL/TLS certificates when connecting to external services
  • Consider using VPN tunnels for highly sensitive data transfers

Example of forcing HTTPS in HTTP Request nodes:


// In a Function node before HTTP Request
const items = $input.all();
const processedItems = items.map(item => {
  const newItem = {...item};
  
  // Ensure we're using HTTPS
  if (newItem.json.url && newItem.json.url.startsWith('http:')) {
    newItem.json.url = newItem.json.url.replace('http:', 'https:');
  }
  
  return newItem;
});

return processedItems;

 

Step 9: Implement Proper Error Handling

 

Configure error handling to prevent sensitive data leakage:


// Function node for secure error handling
try {
  // Your data processing code here
  
  // Example operation that might fail
  const result = someOperationThatMightFail();
  
  return items;
} catch (error) {
  // Create a sanitized error message
  const sanitizedError = {
    message: error.message,
    occurred: true,
    // Do not include the actual data in error logs
    // Do not include stack traces in production
  };
  
  // Log sanitized error
  console.log('Error occurred:', sanitizedError);
  
  // You can either throw a sanitized error or handle it gracefully
  return {
    json: {
      error: true,
      message: 'An error occurred processing your request'
    }
  };
}

 

Step 10: Configure Secure Logging

 

When using n8n's logging features:

  • Disable workflow execution data for sensitive workflows
  • Implement log redaction for sensitive fields
  • Configure appropriate log retention policies

For self-hosted n8n, configure logging settings in your .env file:


# Disable execution data for better security
EXECUTIONS\_DATA=none

# Or set minimal logging
EXECUTIONS\_DATA=minimal

# Set appropriate log level
LOG\_LEVEL=warning

# Configure database for execution data
DB\_TYPE=postgresdb
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_HOST=localhost
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_USER=username
DB_POSTGRESDB_PASSWORD=secure-password

To implement custom log redaction in a Function node:


// Function node for log redaction
const items = $input.all();

// Define sensitive fields to redact
const sensitiveFields = [
  'creditCardNumber',
  'password',
  'socialSecurityNumber',
  'secretKey',
  'accessToken'
];

// Create a deep copy with redacted fields for logging
function redactSensitiveData(obj) {
  if (!obj || typeof obj !== 'object') return obj;
  
  const redacted = Array.isArray(obj) ? [] : {};
  
  for (const key in obj) {
    if (sensitiveFields.includes(key)) {
      redacted[key] = '[REDACTED]';
    } else if (typeof obj[key] === 'object') {
      redacted[key] = redactSensitiveData(obj[key]);
    } else {
      redacted[key] = obj[key];
    }
  }
  
  return redacted;
}

// Redact sensitive data before logging
const safeToLog = items.map(item => redactSensitiveData(item));
console.log('Processing data:', JSON.stringify(safeToLog, null, 2));

// Return the original, unredacted items for further processing
return items;

 

Step 11: Implement Data Validation and Sanitization

 

Validate and sanitize input data to prevent security vulnerabilities:


// Function node for input validation and sanitization
const items = $input.all();
const processedItems = items.map(item => {
  const newItem = {...item};
  
  // Validate credit card number format (basic example)
  if (newItem.json.creditCardNumber) {
    const ccNum = newItem.json.creditCardNumber.replace(/\D/g, ''); // Remove non-digits
    
    // Check if the format is valid (simplified check)
    if (ccNum.length < 13 || ccNum.length > 19) {
      throw new Error('Invalid credit card number format');
    }
    
    // Store sanitized version
    newItem.json.creditCardNumber = ccNum;
  }
  
  // Sanitize input for potential XSS attacks
  if (newItem.json.userInput) {
    // Basic sanitization example
    const sanitized = newItem.json.userInput
      .replace(//g, '>')
      .replace(/"/g, '"')
      .replace(/'/g, ''')
      .replace(///g, '/');
    
    newItem.json.userInput = sanitized;
  }
  
  return newItem;
});

return processedItems;

 

Step 12: Implement Data Minimization

 

Only collect and process the minimum amount of sensitive data needed:


// Function node for data minimization
const items = $input.all();
const processedItems = items.map(item => {
  // Create a new object with only the fields we need
  const minimizedData = {
    // Include only necessary fields
    id: item.json.id,
    name: item.json.name,
    email: item.json.email,
    // Include last 4 digits only for reference
    cardLastFour: item.json.creditCardNumber ? item.json.creditCardNumber.slice(-4) : null
  };
  
  return { json: minimizedData };
});

return processedItems;

 

Step 13: Implement Proper Webhook Security

 

If you're using webhooks in n8n to receive data:

  • Use webhook authentication
  • Validate webhook signatures
  • Implement rate limiting

Example of validating a webhook signature from Stripe:


// Function node to validate Stripe webhook signature
const crypto = require('crypto');

// Get webhook secret from environment variables
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET;

function validateStripeSignature(payload, signature) {
  const signatureItems = signature.split(',').map(item => item.trim());
  const timestamp = signatureItems.find(item => item.startsWith('t=')).substring(2);
  const signatureHash = signatureItems.find(item => item.startsWith('v1=')).substring(3);
  
  const payloadToSign = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', STRIPE_WEBHOOK_SECRET)
    .update(payloadToSign)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signatureHash),
    Buffer.from(expectedSignature)
  );
}

// Get the raw request from the webhook
const rawBody = $node['Webhook'].json.rawBody;
const stripeSignature = $node['Webhook'].json.headers['stripe-signature'];

// Validate the signature
const isValid = validateStripeSignature(rawBody, stripeSignature);

if (!isValid) {
  throw new Error('Invalid webhook signature');
}

// Proceed with processing the webhook data
return $input.all();

 

Step 14: Implement Secure Storage for Workflow Data

 

For self-hosted n8n, configure database security:

  • Use encrypted database connections
  • Implement database-level encryption for sensitive columns
  • Set up proper database backup encryption
  • Configure appropriate access controls for the database

PostgreSQL example with encryption:


# In your .env file
DB\_TYPE=postgresdb
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_HOST=localhost
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_USER=username
DB_POSTGRESDB_PASSWORD=secure-password
POSTGRESDB_SSL_CA=/path/to/ca.cert
POSTGRESDB_SSL_CERT=/path/to/client.cert
POSTGRESDB_SSL_KEY=/path/to/client.key

 

Step 15: Implement Data Retention Policies

 

Regularly clean up sensitive data:


// Function node to implement data retention
// This would typically be run on a schedule

// Define maximum age for sensitive data (e.g., 30 days)
const MAX_AGE_DAYS = 30;
const now = new Date();
const cutoffDate = new Date(now.setDate(now.getDate() - MAX_AGE_DAYS));

// Example database query to delete old data (pseudocode)
// This would be implemented in a node that connects to your database
const deleteQuery = \`
  DELETE FROM sensitive_data_table 
  WHERE created\_at < '${cutoffDate.toISOString()}'
\`;

// Return information about the cleanup
return {
  json: {
    action: 'data_retention_cleanup',
    cutoff\_date: cutoffDate.toISOString(),
    completed: true,
    timestamp: new Date().toISOString()
  }
};

 

Step 16: Regular Security Auditing

 

Implement regular security audits:

  • Review workflows for hardcoded credentials or sensitive data
  • Check access controls and permissions
  • Verify encryption implementations
  • Test security measures with penetration testing
  • Keep n8n updated to the latest version

Create a workflow that scans your other workflows for potential security issues:


// Function node to scan workflows for security issues
// This requires n8n to be configured with appropriate permissions

// Get all workflows using the n8n API
const N8N_URL = process.env.N8N_URL || 'http://localhost:5678';
const N8N_API_KEY = process.env.N8N_API_KEY;

const options = {
  headers: {
    'X-N8N-API-KEY': N8N_API_KEY
  }
};

// Get all workflows
const response = await $http.get(`${N8N_URL}/api/v1/workflows`, options);
const workflows = response.data.data;

// Patterns to search for
const securityPatterns = [
  { pattern: /"password":\s\*"[^"]+"/g, issue: 'Hardcoded password' },
  { pattern: /"apiKey":\s\*"[^"]+"/g, issue: 'Hardcoded API key' },
  { pattern: /"secret":\s\*"[^"]+"/g, issue: 'Hardcoded secret' },
  { pattern: /"token":\s\*"[^"]+"/g, issue: 'Hardcoded token' },
  { pattern: /[A-Za-z0-9+/=]{40,}/g, issue: 'Possible encoded credential' },
];

// Results array
const securityIssues = [];

// Scan each workflow
workflows.forEach(workflow => {
  const workflowStr = JSON.stringify(workflow);
  
  securityPatterns.forEach(({ pattern, issue }) => {
    const matches = workflowStr.match(pattern);
    if (matches) {
      securityIssues.push({
        workflowId: workflow.id,
        workflowName: workflow.name,
        issue,
        matchCount: matches.length
      });
    }
  });
});

// Return results
return {
  json: {
    scanCompleted: true,
    timestamp: new Date().toISOString(),
    totalWorkflows: workflows.length,
    issuesFound: securityIssues.length,
    issues: securityIssues
  }
};

 

Step 17: Document Security Practices

 

Create comprehensive documentation for your team:

  • Document all security measures implemented
  • Create guidelines for handling sensitive data in workflows
  • Establish incident response procedures
  • Document compliance requirements and how they're met

 

Conclusion: Maintaining Ongoing Security

 

Protecting sensitive user data in n8n prompts requires a comprehensive approach incorporating encryption, access controls, secure coding practices, and regular auditing. By implementing the steps outlined in this guide, you'll significantly reduce the risk of data breaches and unauthorized access to sensitive information.

Remember that security is an ongoing process. Regularly review and update your security measures as new versions of n8n are released and as security best practices evolve. Stay informed about security vulnerabilities and patches, and make security a central part of your workflow automation strategy.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022