# Secure Development Guidelines This document provides comprehensive guidelines for developing secure code within the Trilium codebase, covering secure coding practices, vulnerability prevention, and security testing. ## Secure Coding Principles ### Input Validation and Sanitization #### Server-Side Validation Always validate input on the server side, regardless of client-side validation: ```typescript // Good: Server-side validation with type checking function validateNoteTitle(title: string): boolean { if (typeof title !== 'string') { throw new Error('Invalid title type'); } if (title.length > 1000) { throw new Error('Title too long'); } if (title.trim().length === 0) { throw new Error('Title cannot be empty'); } return true; } // Bad: Assuming client validation is sufficient function saveNote(data: any) { // No validation - dangerous! sql.execute('INSERT INTO notes (title) VALUES (?)', [data.title]); } ``` #### HTML Sanitization Sanitize HTML content to prevent XSS attacks: ```typescript import DOMPurify from 'isomorphic-dompurify'; // Good: Sanitize HTML content function sanitizeHTML(content: string): string { return DOMPurify.sanitize(content, { ALLOWED_TAGS: ['p', 'b', 'i', 'u', 'strong', 'em', 'ul', 'ol', 'li'], ALLOWED_ATTR: ['class', 'id'], FORBID_SCRIPT: true }); } // Bad: Directly inserting user content function displayNote(content: string) { element.innerHTML = content; // XSS vulnerability } ``` #### SQL Injection Prevention Always use parameterized queries: ```typescript // Good: Parameterized query function getNoteById(noteId: string) { return sql.getRow('SELECT * FROM notes WHERE noteId = ?', [noteId]); } // Bad: String concatenation function getNoteById(noteId: string) { return sql.getRow(`SELECT * FROM notes WHERE noteId = '${noteId}'`); } ``` ### Authentication and Authorization #### Password Handling Never store passwords in plain text: ```typescript // Good: Proper password hashing import passwordEncryption from './encryption/password_encryption.js'; function setPassword(password: string): void { if (password.length < 8) { throw new Error('Password too short'); } const hash = passwordEncryption.hashPassword(password); optionService.setOption('passwordHash', hash); } // Bad: Plain text password storage function setPassword(password: string): void { optionService.setOption('password', password); // Never do this! } ``` #### Session Management Implement secure session handling: ```typescript // Good: Secure session creation function createSession(userId: string): string { const sessionId = crypto.randomBytes(32).toString('hex'); const expires = Date.now() + SESSION_TIMEOUT; sessionStore.set(sessionId, { userId, expires, csrfToken: generateCSRFToken() }); return sessionId; } // Bad: Predictable session IDs function createSession(userId: string): string { const sessionId = `user_${userId}_${Date.now()}`; // Predictable return sessionId; } ``` ### Encryption and Cryptography #### Key Management Follow secure key management practices: ```typescript // Good: Secure key generation and storage function generateDataKey(): Buffer { const key = crypto.randomBytes(32); // Encrypt with password-derived key before storage const encryptedKey = dataEncryption.encrypt(passwordKey, key); optionService.setOption('encryptedDataKey', encryptedKey); return key; } // Bad: Hardcoded or weak keys const SECRET_KEY = 'mySecretKey123'; // Never hardcode keys ``` #### Encryption Implementation Use proven encryption libraries and algorithms: ```typescript // Good: Using established encryption function encryptSensitiveData(data: string, key: Buffer): string { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); let encrypted = cipher.update(data, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return iv.toString('hex') + ':' + encrypted + ':' + authTag.toString('hex'); } // Bad: Custom encryption algorithms function customEncrypt(data: string): string { // Never implement custom encryption return data.split('').reverse().join(''); // Terrible encryption } ``` ### Error Handling and Logging #### Secure Error Messages Don't leak sensitive information in error messages: ```typescript // Good: Safe error messages function authenticateUser(username: string, password: string): boolean { const user = getUserByUsername(username); if (!user || !verifyPassword(password, user.passwordHash)) { // Generic error message throw new Error('Invalid credentials'); } return true; } // Bad: Information disclosure function authenticateUser(username: string, password: string): boolean { const user = getUserByUsername(username); if (!user) { throw new Error('User not found'); // Reveals user existence } if (!verifyPassword(password, user.passwordHash)) { throw new Error('Invalid password'); // Confirms user exists } return true; } ``` #### Security Logging Log security events appropriately: ```typescript // Good: Security event logging function logSecurityEvent(event: SecurityEvent): void { const logEntry = { timestamp: new Date().toISOString(), event: event.type, severity: event.severity, user: event.userId ? hashUserId(event.userId) : 'anonymous', ip: hashIP(event.sourceIP), details: sanitizeLogData(event.details) }; securityLogger.log(logEntry); } // Bad: Logging sensitive data function logSecurityEvent(event: SecurityEvent): void { console.log(`User ${event.password} failed login`); // Logs password! } ``` ## Vulnerability Prevention ### Cross-Site Scripting (XSS) Prevention #### Content Security Policy Implement and maintain a strict CSP: ```typescript // Good: Strict CSP configuration const cspPolicy = { 'default-src': ["'self'"], 'script-src': ["'self'", "'unsafe-inline'"], // Minimize unsafe-inline 'style-src': ["'self'", "'unsafe-inline'"], 'img-src': ["'self'", 'data:', 'https:'], 'connect-src': ["'self'"], 'font-src': ["'self'"], 'object-src': ["'none'"], 'media-src': ["'self'"], 'frame-src': ["'none'"] }; ``` #### Output Encoding Properly encode output based on context: ```typescript // Good: Context-aware encoding function renderNoteTitle(title: string): string { return htmlEncode(title); // HTML context } function renderJavaScriptData(data: any): string { return JSON.stringify(data); // JavaScript context } // Bad: No encoding function renderNoteTitle(title: string): string { return title; // Potential XSS } ``` ### Cross-Site Request Forgery (CSRF) Prevention #### CSRF Token Implementation Implement proper CSRF protection: ```typescript // Good: CSRF token validation function validateCSRFToken(req: Request): boolean { const tokenFromHeader = req.headers['x-csrf-token']; const tokenFromCookie = req.cookies['_csrf']; if (!tokenFromHeader || !tokenFromCookie) { return false; } return crypto.timingSafeEqual( Buffer.from(tokenFromHeader), Buffer.from(tokenFromCookie) ); } // Bad: Missing CSRF protection function processForm(req: Request): void { // Process form without CSRF validation - vulnerable! updateUserData(req.body); } ``` ### SQL Injection Prevention #### Parameterized Queries Always use parameterized queries: ```typescript // Good: Parameterized query with proper typing function searchNotes(searchTerm: string, limit: number): Note[] { const sql = ` SELECT noteId, title, content FROM notes WHERE title LIKE ? ORDER BY dateModified DESC LIMIT ? `; return db.getRows(sql, [`%${searchTerm}%`, limit]); } // Bad: Dynamic query construction function searchNotes(searchTerm: string, limit: number): Note[] { const sql = ` SELECT noteId, title, content FROM notes WHERE title LIKE '%${searchTerm}%' ORDER BY dateModified DESC LIMIT ${limit} `; return db.getRows(sql); // SQL injection vulnerability } ``` ### Path Traversal Prevention #### File Access Controls Validate and restrict file access: ```typescript // Good: Safe file access function getAttachment(attachmentId: string): Buffer { // Validate attachment ID format if (!/^[a-zA-Z0-9_-]+$/.test(attachmentId)) { throw new Error('Invalid attachment ID'); } const safePath = path.join(ATTACHMENTS_DIR, attachmentId); // Ensure path is within allowed directory if (!safePath.startsWith(ATTACHMENTS_DIR)) { throw new Error('Path traversal attempt detected'); } return fs.readFileSync(safePath); } // Bad: Unsafe file access function getAttachment(filename: string): Buffer { const filePath = path.join(ATTACHMENTS_DIR, filename); return fs.readFileSync(filePath); // Path traversal vulnerability } ``` ## Security Testing ### Unit Tests for Security #### Authentication Tests ```typescript describe('Authentication Security', () => { test('should reject weak passwords', () => { expect(() => { passwordService.setPassword('123'); }).toThrow('Password too short'); }); test('should use timing-safe comparison', () => { const validHash = passwordService.hashPassword('correct-password'); const start1 = process.hrtime.bigint(); passwordService.verifyPassword('wrong-password', validHash); const time1 = process.hrtime.bigint() - start1; const start2 = process.hrtime.bigint(); passwordService.verifyPassword('correct-password', validHash); const time2 = process.hrtime.bigint() - start2; // Times should be similar (within 10ms) const timeDiff = Math.abs(Number(time1 - time2)) / 1000000; expect(timeDiff).toBeLessThan(10); }); }); ``` #### Input Validation Tests ```typescript describe('Input Validation', () => { test('should sanitize HTML content', () => { const maliciousHTML = '

Safe content

'; const sanitized = htmlSanitizer.sanitize(maliciousHTML); expect(sanitized).not.toContain('