Security Guidelines
Authentication & Authorization
Firebase Authentication
ChainAlign uses Firebase Authentication for user management and JWT-based authentication.
Session Management
- User session tokens: Expire after 1 hour
- Refresh tokens: Automatically rotated for extended sessions
- Frontend behavior: Automatic logout on token expiration
- Session invalidation: All sessions invalidated on password change or account disable
For Development & Testing
Generating Test Tokens:
# Generate a test token for a specific user
node scripts/generateTestToken.mjs <uid> <email> <displayName>
# Example:
node scripts/generateTestToken.mjs test-user-123 test@example.com "Test User"
Important Notes:
- Test tokens expire quickly (typically within 1 hour)
- NEVER commit hardcoded tokens to Git
- Use environment variables for any long-lived tokens
- Always remove test tokens from code before committing
Backend API Token (Service-to-Service)
For internal backend service communication (e.g., AIManager calling internal APIs), use the BACKEND_API_TOKEN environment variable:
- Generate a token using the script above for a service account user
- Add to
.env(NOT.env.example):BACKEND_API_TOKEN=your_generated_token_here - Use in code:
const backendApiToken = process.env.BACKEND_API_TOKEN;
if (!backendApiToken) {
console.error('BACKEND_API_TOKEN environment variable is not set');
return null;
}
Token Rotation
When to rotate tokens:
- After any suspected security breach
- If a token is accidentally committed to version control
- On a regular schedule (recommended: every 90 days for service accounts)
How to rotate:
-
Generate a new token:
node scripts/generateTestToken.mjs <service-account-uid> <email> <name> -
Update the token in all environments:
- Development: Update
.envlocally - Staging: Update environment variables in staging deployment
- Production: Update environment variables in production deployment
- Development: Update
-
If a token was exposed in Git history:
# Revoke the exposed token immediately via Firebase Console
# - Go to Firebase Console > Authentication > Users
# - Find the user/service account
# - Disable the account or delete and recreate it
# Then generate and deploy a new token
node scripts/generateTestToken.mjs <new-uid> <email> <name> -
Verify the new token works in all environments before removing the old one
Multi-Tenancy Security
ChainAlign is a multi-tenant platform where data isolation is critical. Every tenant's data must be completely isolated from other tenants.
Tenant Isolation Requirements
-
Database Queries: ALWAYS filter by
tenant_id// CORRECT - Tenant-scoped query
const scenarios = await db('scenarios')
.where({ tenant_id: req.user.tenant_id })
.select();
// WRONG - Missing tenant_id filter (security vulnerability!)
const scenarios = await db('scenarios').select(); -
API Request Validation:
// Validate that requested resource belongs to user's tenant
const scenario = await scenariosRepository.findById(scenarioId);
if (scenario.tenant_id !== req.user.tenant_id) {
return res.status(403).json({ error: 'Access denied' });
} -
Repository Pattern: Extend
BaseRepositorywhich enforces tenant scoping// BaseRepository automatically adds tenant_id to queries
const result = await repository.findByTenantId(tenantId, filters);
IDOR Prevention
Insecure Direct Object Reference (IDOR) vulnerabilities occur when users can access resources by guessing IDs. Prevent this by:
- Using UUIDs instead of sequential integers for primary keys
- Always validating
tenant_idmatches the authenticated user - Never trusting client-provided IDs without validation
Testing Tenant Isolation
# Test with different tenant users
npm test -- tenant-isolation.test.js
# Manual testing checklist:
# 1. Create resource as User A (tenant 1)
# 2. Attempt to access resource as User B (tenant 2)
# 3. Verify 403 Forbidden response
Git Pre-Commit Hook
A pre-commit hook is installed at .git/hooks/pre-commit to automatically detect hardcoded secrets before commits.
What it detects:
- JWT tokens (Firebase, Auth0, etc.)
- Bearer tokens in Authorization headers
- API keys and secrets
- AWS access keys
- Private keys (RSA, OpenSSH, etc.)
Hook output:
Success:
🔍 Scanning for hardcoded secrets...
✅ No hardcoded secrets detected
Blocked commit:
🔍 Scanning for hardcoded secrets...
✗ Hardcoded Bearer token found in: backend/src/services/AIManager.js
Pattern: Authorization: Bearer eyJ...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
❌ COMMIT BLOCKED: Hardcoded secrets detected!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Please:
1. Remove hardcoded secrets from the files above
2. Use environment variables instead (see .env.example)
3. For test tokens, use scripts/generateTestToken.mjs
Bypassing the hook (NOT RECOMMENDED):
git commit --no-verify
Only bypass if:
- You're committing example/documentation files
- You're certain there are no real secrets (false positive)
Installing the hook on a new clone:
The hook is located at .git/hooks/pre-commit but is not tracked by Git (.git directory is never committed). To set it up on a new machine:
# Copy the pre-commit hook from the scripts directory
cp scripts/pre-commit-hook .git/hooks/pre-commit
# Make it executable
chmod +x .git/hooks/pre-commit
# Verify installation
.git/hooks/pre-commit
Note: The hook template is stored in scripts/pre-commit-hook and should be maintained in the repository for easy installation.
Environment Variables
Required Variables
See .env.example for a complete list. Key security-related variables:
# Firebase Configuration
GOOGLE_APPLICATION_CREDENTIALS=/path/to/firebase-admin-sdk-key.json
# API Keys (never commit actual values)
GEMINI_API_KEY=your_gemini_api_key_here
TYPESENSE_API_KEY=your_typesense_api_key_here
# Backend Service Token
BACKEND_API_TOKEN=your_backend_api_token_here
# Database Credentials
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_NAME=your_db_name
Best Practices
- Never commit
.envfiles - They're in.gitignorefor a reason - Use strong, unique values for production
- Rotate secrets regularly (every 90 days minimum)
- Use different credentials for dev/staging/production
- Store production secrets in a secure vault (AWS Secrets Manager, GCP Secret Manager, etc.)
Reporting Security Issues
If you discover a security vulnerability:
- DO NOT open a public GitHub issue
- Email the security team directly at: [pramod.opspilot@gmail.com]
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
API Security
Authentication & Request Validation
All API endpoints are protected using the following security layers:
-
JWT Authentication: Implemented in
authMiddleware.js// All routes require valid Firebase JWT token
router.use(authMiddleware.verifyToken); -
Input Validation: Using
validationMiddleware.js// Validate request schema before processing
router.post('/scenarios',
validationMiddleware.validate(scenarioSchema),
scenarioController.create
); -
CORS Configuration: Restricts allowed origins
// Only allow requests from approved domains
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3001']; -
Rate Limiting: Implemented on all endpoints
- 100 requests per 15 minutes per IP for general endpoints
- 10 requests per 15 minutes for authentication endpoints
- 20 requests per hour for AI/LLM endpoints (per tenant)
Input Sanitization
SQL Injection Prevention:
- Always use Knex.js parameterized queries (NEVER raw SQL with string concatenation)
- All repositories extend
BaseRepositorywith built-in sanitization
// CORRECT - Parameterized query
await db('users').where({ email: userInput });
// WRONG - SQL injection vulnerability!
await db.raw(`SELECT * FROM users WHERE email = '${userInput}'`);
XSS Prevention:
- React automatically escapes HTML in JSX
- For
dangerouslySetInnerHTML, always use DOMPurify - API responses sanitize HTML content
import DOMPurify from 'dompurify';
// Safe HTML rendering
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(userContent)
}} />
File Upload Security (CSV Ingestion):
- File type validation (must be
.csv) - File size limits (max 50MB)
- Virus scanning via
data-validation-service - Content validation before processing
- Temporary files deleted after processing
// backend/src/routes/data-onboarding.js
const upload = multer({
limits: { fileSize: 50 * 1024 * 1024 }, // 50MB
fileFilter: (req, file, cb) => {
if (file.mimetype !== 'text/csv') {
return cb(new Error('Only CSV files allowed'));
}
cb(null, true);
}
});
WebSocket Security
Real-time notifications via Socket.io are secured:
// Socket.io connection requires JWT authentication
io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
const decoded = await admin.auth().verifyIdToken(token);
socket.userId = decoded.uid;
socket.tenantId = decoded.tenant_id;
next();
});
// Tenant isolation enforced on all socket events
socket.on('subscribe', (channel) => {
if (channel.startsWith(`tenant:${socket.tenantId}`)) {
socket.join(channel);
} else {
socket.emit('error', 'Access denied');
}
});
WebSocket Rate Limiting:
- Maximum 100 messages per minute per connection
- Automatic disconnect on excessive messages
- Reconnection backoff (exponential)
Database Security
Connection Security
- All database connections use SSL/TLS in production
- Connection pooling with maximum limits (10 connections per backend instance)
- Credentials stored in environment variables (never hardcoded)
Data Protection
- Tenant isolation: UUID-based filtering on all queries
- Row-level security: PostgreSQL RLS policies on sensitive tables
- Encryption at rest: Database volumes encrypted (production only)
- Encryption in transit: SSL enforced for all connections
Query Security
// Always use Knex query builder (prevents SQL injection)
const result = await db('scenarios')
.where({ tenant_id: req.user.tenant_id })
.whereIn('status', ['active', 'pending'])
.select();
// For complex queries, use parameterized raw queries
const result = await db.raw(
'SELECT * FROM scenarios WHERE tenant_id = ? AND created_at > ?',
[tenantId, startDate]
);
Backup & Recovery
- Automated daily backups (production)
- Point-in-time recovery enabled
- Backup encryption with separate keys
- Regular backup restoration tests
AI/LLM Security
ChainAlign uses Google Gemini for AI-powered insights. Protect against prompt injection and data leakage:
Prompt Injection Prevention
// Sanitize user input before sending to LLM
function sanitizePrompt(userInput) {
// Remove potential instruction injections
return userInput
.replace(/ignore (previous|above) instructions?/gi, '')
.replace(/system:?/gi, '')
.trim()
.substring(0, 2000); // Limit length
}
// Use structured prompts with clear boundaries
const prompt = `
Context: ${sanitizePrompt(context)}
---
User Question: ${sanitizePrompt(userQuestion)}
---
Instructions: Answer based only on the context provided.
`;
Rate Limiting AI Calls
// Limit AI API calls per tenant (prevents abuse and cost overrun)
const AI_RATE_LIMIT = {
maxCallsPerHour: 100,
maxCallsPerDay: 500,
maxTokensPerCall: 4000
};
// Track usage in Redis or database
await rateLimiter.checkLimit(tenantId, 'ai_calls');
Sensitive Data Protection
Do NOT include in embeddings or LLM prompts:
- Passwords, API keys, or tokens
- Social Security Numbers or personal identifiers
- Credit card numbers or financial credentials
- Health information (PHI/PII)
// Filter sensitive fields before embedding
const dataForEmbedding = {
name: element.name,
description: element.description,
type: element.type
// NEVER include: api_key, password, ssn, etc.
};
Content Filtering
// Validate LLM responses before returning to users
function validateLLMResponse(response) {
// Check for leaked sensitive patterns
const sensitivePatterns = [
/api[_-]?key/i,
/password/i,
/\b[A-Z0-9]{20,}\b/, // Possible tokens
/-----BEGIN.*KEY-----/
];
for (const pattern of sensitivePatterns) {
if (pattern.test(response)) {
throw new Error('LLM response contains sensitive data');
}
}
return response;
}
Reasoning Chain Audit
All AI reasoning chains are stored via ReasoningBankService for audit purposes:
// Audit reasoning chains for data leakage
await reasoningBankService.create({
tenant_id: tenantId,
user_id: userId,
prompt: sanitizedPrompt,
response: response,
model: 'gemini-pro',
timestamp: new Date()
});
Python Microservices Security
Service Authentication
Python services should NOT be exposed directly to the internet. All requests go through the Node.js backend:
# python-services//app.py
from functools import wraps
def require_backend_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
api_key = request.headers.get('X-Backend-API-Key')
if api_key != os.getenv('BACKEND_SERVICE_KEY'):
return jsonify({'error': 'Unauthorized'}), 401
return f(*args, **kwargs)
return decorated
@app.route('/ingest', methods=['POST'])
@require_backend_auth
def ingest_data():
# Process data
pass
Docker Network Isolation
# docker-compose.yml
services:
:
networks:
- backend-network # Internal network only
# NO port mapping to host (not exposed externally)
chainalign-backend:
networks:
- backend-network
ports:
- "5001:5001" # Only backend exposed
networks:
backend-network:
driver: bridge
internal: false # Backend can reach internet for AI APIs
File Upload Validation
# Validate uploaded files before processing
def validate_file(file):
# Check file size
if len(file.read()) > 50 * 1024 * 1024: # 50MB
raise ValueError('File too large')
file.seek(0)
# Check file type (magic bytes, not just extension)
file_type = magic.from_buffer(file.read(1024), mime=True)
if file_type not in ['text/csv', 'text/plain']:
raise ValueError('Invalid file type')
file.seek(0)
# Virus scan (using ClamAV or similar)
scan_result = clamd.scan_stream(file)
if scan_result['stream'][0] == 'FOUND':
raise ValueError('File contains malware')
Sandboxed Execution
For code execution or data processing:
# Use restricted execution environments
import subprocess
def run_user_code(code):
# NEVER use eval() or exec() on user input!
# Use subprocess with timeout and resource limits
result = subprocess.run(
['python', '-c', code],
timeout=5,
capture_output=True,
text=True,
# Run as restricted user
user='nobody'
)
return result.stdout
Dependency Security
# Audit npm dependencies regularly
npm audit
# Fix vulnerabilities
npm audit fix
# For breaking changes, review manually
npm audit fix --force
Docker Security
- Base images are regularly updated
- No secrets in Dockerfiles or docker-compose.yml
- Use Docker secrets for production deployments
Security Logging & Monitoring
Comprehensive logging is essential for detecting and responding to security incidents.
What to Log
// Authentication events
logger.info('User login successful', {
user_id: user.uid,
tenant_id: user.tenant_id,
ip_address: req.ip,
timestamp: new Date()
});
logger.warn('Failed login attempt', {
email: req.body.email,
ip_address: req.ip,
reason: 'invalid_credentials',
timestamp: new Date()
});
// Authorization failures (potential IDOR attempts)
logger.warn('Unauthorized access attempt', {
user_id: req.user.uid,
tenant_id: req.user.tenant_id,
requested_resource: req.params.scenarioId,
resource_tenant: scenario.tenant_id,
ip_address: req.ip,
timestamp: new Date()
});
// Sensitive operations
logger.info('Sensitive operation performed', {
operation: 'scenario_deleted',
user_id: req.user.uid,
tenant_id: req.user.tenant_id,
resource_id: scenarioId,
timestamp: new Date()
});
// Rate limit violations
logger.warn('Rate limit exceeded', {
user_id: req.user?.uid,
ip_address: req.ip,
endpoint: req.path,
limit: rateLimit,
timestamp: new Date()
});
What NOT to Log
NEVER log sensitive information:
- Passwords or password hashes
- API keys, tokens, or secrets
- Credit card numbers or PII
- Full JWT tokens (log only last 4 characters)
- Session IDs
// WRONG - Logs sensitive data
logger.info('User authenticated', { token: req.headers.authorization });
// CORRECT - Logs only metadata
logger.info('User authenticated', {
user_id: user.uid,
token_last4: req.headers.authorization?.slice(-4)
});
Monitoring & Alerts
Set up automated alerts for:
- Repeated failed login attempts (> 5 within 5 minutes from same IP)
- Authorization failures (> 10 within 1 hour from same user)
- Rate limit violations (persistent violations may indicate attack)
- Unusual API patterns (sudden spike in requests)
- Database query errors (may indicate SQL injection attempts)
- Service errors (500 errors may indicate exploitation)
// Example: Alert on repeated authorization failures
if (failedAuthCount > 10) {
await notificationService.alertSecurityTeam({
severity: 'HIGH',
type: 'repeated_authorization_failures',
user_id: userId,
ip_address: ipAddress,
count: failedAuthCount,
time_window: '1 hour'
});
}
Log Retention
- Development: 7 days
- Staging: 30 days
- Production: 90 days (or as required by compliance)
- Security logs: 1 year minimum
Incident Response Plan
If a security incident occurs, follow this procedure:
1. Identification & Containment
- Identify the breach: Review logs, identify affected systems
- Contain immediately:
# Revoke compromised tokens
# Via Firebase Console or programmatically
await admin.auth().revokeRefreshTokens(userId);
# Disable affected user accounts
await admin.auth().updateUser(userId, { disabled: true });
# Block malicious IPs in firewall
# (depends on infrastructure - AWS Security Groups, etc.)
2. Eradication
- Remove the threat:
- Patch vulnerabilities
- Remove backdoors or malicious code
- Reset all compromised credentials
- Update security rules
3. Recovery
- Restore normal operations:
# Generate new tokens
node scripts/generateTestToken.mjs <new-uid> <email> <name>
# Update environment variables
# (in all environments: dev, staging, production)
# Re-enable services
# Verify security before going live
4. Notification
- Internal: Notify engineering and security teams immediately
- External: Notify affected tenants if their data was compromised
- Within 72 hours (GDPR requirement)
- Include: nature of breach, data affected, mitigation steps
- Contact: security@chainalign.com (or appropriate channel)
5. Post-Mortem
Conduct a post-mortem within 1 week:
- Root cause analysis
- Timeline of events
- What worked / what didn't
- Action items to prevent recurrence
- Update security documentation
Emergency Contacts
- Security Team: security@chainalign.com
- On-Call Engineer: [Slack channel or PagerDuty]
- Infrastructure Team: [Contact info]
Compliance
ChainAlign handles sensitive business data (forecasts, demand plans, financial data). Security and compliance are critical:
Data Protection Regulations
GDPR (General Data Protection Regulation)
- Right to access: Users can request all data stored about them
- Right to deletion: Users can request account and data deletion
- Right to portability: Export user data in machine-readable format
- Breach notification: Notify users within 72 hours of data breach
- Data minimization: Only collect necessary data
- Consent: Explicit consent for data processing
Implementation:
// backend/src/routes/users.js
// Export user data (GDPR Article 20)
router.get('/export', authMiddleware, async (req, res) => {
const userData = await userService.exportUserData(req.user.uid);
res.json(userData);
});
// Delete user account (GDPR Article 17 - Right to be forgotten)
router.delete('/account', authMiddleware, async (req, res) => {
await userService.deleteUserAndData(req.user.uid);
res.json({ message: 'Account deleted successfully' });
});
SOC 2 (Service Organization Control 2)
Key requirements for ChainAlign:
- Security: Access controls, encryption, firewall protection
- Availability: System uptime, disaster recovery, incident response
- Processing Integrity: Data accuracy, completeness, timeliness
- Confidentiality: Tenant isolation, data encryption
- Privacy: Data handling policies, user consent
Compliance Checklist
- Encryption at rest: Database volumes encrypted in production
- Encryption in transit: SSL/TLS for all connections (API, WebSocket, database)
- Access controls: Role-based access control (RBAC) via Firebase
- Audit logging: All sensitive operations logged with user/tenant context
- Tenant isolation: UUID-based filtering on all queries
- Regular security audits: Quarterly penetration testing (production)
- Dependency scanning: Automated
npm auditin CI/CD pipeline - Secrets management: Environment variables, never committed to Git
- Penetration testing: Annual third-party security assessment (recommended)
- Security training: Annual training for all engineers (recommended)
- Bug bounty program: Consider for production launch
Data Classification
| Category | Examples | Storage Requirements | Access Level |
|---|---|---|---|
| Public | Documentation, marketing materials | No encryption required | Everyone |
| Internal | System logs, non-sensitive metrics | Encrypted at rest | Authenticated users |
| Confidential | Business forecasts, demand plans | Encrypted at rest + in transit | Tenant-scoped only |
| Restricted | User credentials, API keys, PII | Encrypted + access logging | Admin only |
Security Audit Log
Maintain an audit trail for:
- User authentication events (login, logout, failed attempts)
- Data access (viewing, downloading, exporting)
- Data modifications (create, update, delete)
- Permission changes (role assignments)
- Configuration changes (system settings)
- AI/LLM queries and responses
// Example: Audit trail for sensitive operations
await auditLogService.create({
tenant_id: req.user.tenant_id,
user_id: req.user.uid,
action: 'scenario_deleted',
resource_type: 'scenario',
resource_id: scenarioId,
ip_address: req.ip,
user_agent: req.headers['user-agent'],
timestamp: new Date()
});
Regular Security Reviews
Schedule and conduct:
- Weekly: Review security logs for anomalies
- Monthly: Update dependencies (
npm audit fix) - Quarterly: Review access controls and permissions
- Annually: Full security audit and penetration testing
- Ongoing: Monitor CVE databases for new vulnerabilities
Security Best Practices Summary
For Developers
- Never commit secrets - Use
.envfiles and environment variables - Always filter by tenant_id - Multi-tenancy isolation is critical
- Use parameterized queries - Prevent SQL injection
- Validate all inputs - Use
validationMiddleware.js - Sanitize LLM prompts - Prevent prompt injection
- Log security events - Authentication, authorization failures
- Test tenant isolation - Write tests for cross-tenant access prevention
- Review dependencies - Run
npm auditregularly - Use HTTPS everywhere - No plain HTTP in production
- Implement rate limiting - Prevent abuse and DoS attacks
For DevOps/Infrastructure
- Rotate secrets regularly - Every 90 days minimum
- Enable database encryption - At rest and in transit
- Use Docker secrets - Not environment variables in compose files
- Restrict network access - Python services not exposed externally
- Enable audit logging - CloudWatch, Stackdriver, or equivalent
- Set up monitoring - Alert on security events
- Backup regularly - Test restoration procedures
- Use separate credentials - Dev, staging, production environments
- Enable firewall rules - Allow only necessary ports
- Keep systems updated - OS, Docker images, dependencies
For Product/Business
- Privacy policy - Clear, compliant with GDPR/CCPA
- Terms of service - Define data handling and security responsibilities
- Incident response plan - Document and communicate
- User education - Best practices for secure password management
- Security roadmap - Continuous improvement
- Compliance certifications - SOC 2, ISO 27001 (for enterprise customers)
- Third-party audits - Independent security assessments
- Bug bounty program - Encourage responsible disclosure
- Security SLAs - Commit to response times for security issues
- Transparency - Communicate security practices to customers
Additional Resources
- OWASP Top 10: https://owasp.org/www-project-top-ten/
- OWASP API Security: https://owasp.org/www-project-api-security/
- Node.js Security Best Practices: https://nodejs.org/en/docs/guides/security/
- Firebase Security Rules: https://firebase.google.com/docs/rules
- Knex.js Security: https://knexjs.org/guide/
- GDPR Compliance: https://gdpr.eu/
- SOC 2 Compliance: https://www.aicpa.org/interestareas/frc/assuranceadvisoryservices/sorhome
Document Version: 2.0 Last Updated: 2025-10-15 Next Review: 2025-12-15