Security Features
Comprehensive guide to Lokus security features, best practices, and threat mitigation strategies.
Security Architecture
Lokus implements defense-in-depth security:
- File System Sandboxing - Restricted file access
- Plugin Permissions - Granular permission system
- Secure Storage - Platform-native credential storage
- Input Validation - XSS and injection prevention
- Network Security - HTTPS enforcement, CORS protection
- OAuth Security - Secure authentication flows
File System Security
Path Validation
All file operations validate paths to prevent directory traversal attacks:
function isValidPath(path: string): boolean {
// Prevent directory traversal
if (path.includes('..')) {
return false;
}
// Ensure path is within workspace
const normalizedPath = normalizePath(path);
const workspacePath = getWorkspacePath();
return normalizedPath.startsWith(workspacePath);
}
Protected Against:
../../../etc/passwd
- Directory traversalfile:///etc/passwd
- Absolute paths outside workspace- Symbolic link attacks
- Race conditions (TOCTOU)
File Type Validation
const ALLOWED_FILE_TYPES = [
'.md', '.txt', '.json',
'.js', '.ts', '.jsx', '.tsx',
'.html', '.css', '.yaml', '.toml'
];
function validateFileType(path: string): boolean {
const ext = path.toLowerCase().match(/\.[^.]+$/)?.[0];
return ALLOWED_FILE_TYPES.includes(ext);
}
Configure allowed types:
{
"security": {
"allowedFileTypes": [".md", ".txt", ".json"],
"validateFileTypes": true
}
}
File Size Limits
{
"security": {
"maxFileSize": 10485760,
"maxUploadSize": 5242880
}
}
Default limits:
- Max file size: 10MB
- Max upload size: 5MB
- Max workspace size: Unlimited (user discretion)
Plugin Security
Permission System
Plugins must declare required permissions:
{
"name": "example-plugin",
"permissions": [
"read:files",
"write:files",
"network:https"
]
}
Available Permissions:
read:files
- Read workspace fileswrite:files
- Write to workspace filesread:workspace
- Read workspace metadatawrite:workspace
- Modify workspaceexecute:commands
- Execute system commandsnetwork:http
- HTTP requestsnetwork:https
- HTTPS requestsui:editor
- Modify editorui:sidebar
- Add UI panelsstorage:local
- Local storage accessclipboard:read
- Read clipboardclipboard:write
- Write clipboard
Permission Enforcement
class PluginAPI {
async readFile(path: string): Promise<string> {
// Check permission
if (!this.hasPermission('read:files')) {
throw new Error('Plugin lacks read:files permission');
}
// Validate path
if (!isValidPath(path)) {
throw new Error('Invalid file path');
}
// Perform operation
return await invoke('read_file_content', { path });
}
}
Plugin Sandboxing
{
"security": {
"sandboxPlugins": true,
"pluginTimeout": 30000,
"maxPluginMemory": 104857600
}
}
Sandbox Features:
- Isolated execution context
- Memory limits (100MB default)
- Timeout protection (30s default)
- No access to Node.js APIs
- No eval() or Function() constructor
Plugin Validation
async function validatePlugin(manifest: PluginManifest): Promise<ValidationResult> {
const errors: ValidationError[] = [];
// Validate name
if (!/^[a-zA-Z0-9-_]+$/.test(manifest.name)) {
errors.push({
field: 'name',
message: 'Invalid plugin name'
});
}
// Validate version
if (!semver.valid(manifest.version)) {
errors.push({
field: 'version',
message: 'Invalid semantic version'
});
}
// Validate permissions
for (const permission of manifest.permissions) {
if (!VALID_PERMISSIONS.includes(permission)) {
errors.push({
field: 'permissions',
message: `Invalid permission: ${permission}`
});
}
}
return {
valid: errors.length === 0,
errors,
warnings: []
};
}
Authentication Security
OAuth Security
Gmail OAuth Flow:
- Generate state parameter (CSRF protection)
- Request authorization with state
- Validate state on callback
- Exchange code for tokens
- Store tokens securely
async function initiateOAuth(): Promise<string> {
// Generate random state
const state = generateRandomString(32);
// Store state temporarily
sessionStorage.setItem('oauth_state', state);
// Build auth URL
const authUrl = buildOAuthUrl({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: SCOPES,
state,
response_type: 'code'
});
return authUrl;
}
async function handleCallback(code: string, state: string) {
// Validate state
const savedState = sessionStorage.getItem('oauth_state');
if (state !== savedState) {
throw new Error('Invalid OAuth state');
}
// Clear state
sessionStorage.removeItem('oauth_state');
// Exchange code for tokens
const tokens = await exchangeCode(code);
// Store tokens securely
await storeTokensSecurely(tokens);
}
Secure Token Storage
Platform-specific secure storage:
macOS - Keychain:
use keyring::Keyring;
fn store_token(service: &str, account: &str, token: &str) -> Result<(), String> {
let keyring = Keyring::new(service, account);
keyring.set_password(token)
.map_err(|e| e.to_string())
}
fn retrieve_token(service: &str, account: &str) -> Result<String, String> {
let keyring = Keyring::new(service, account);
keyring.get_password()
.map_err(|e| e.to_string())
}
Windows - Credential Manager:
use windows::Security::Credentials::PasswordVault;
fn store_token_windows(resource: &str, username: &str, password: &str) {
let vault = PasswordVault::new().unwrap();
let credential = PasswordCredential::CreatePasswordCredential(
resource,
username,
password
).unwrap();
vault.Add(&credential).unwrap();
}
Never store tokens in:
- Plain text files
- Local storage
- Session storage
- Configuration files
- Version control
Token Refresh
class TokenManager {
private accessToken: string | null = null;
private refreshToken: string | null = null;
private expiresAt: number = 0;
async getAccessToken(): Promise<string> {
// Check if token is expired
if (Date.now() >= this.expiresAt) {
await this.refreshAccessToken();
}
return this.accessToken!;
}
async refreshAccessToken() {
const newTokens = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
refresh_token: this.refreshToken,
grant_type: 'refresh_token'
})
}).then(r => r.json());
this.accessToken = newTokens.access_token;
this.expiresAt = Date.now() + (newTokens.expires_in * 1000);
// Store new token securely
await invoke('store_token_securely', {
service: 'lokus-gmail',
account: 'access_token',
token: this.accessToken
});
}
}
Network Security
HTTPS Enforcement
function ensureHttps(url: string): string {
if (url.startsWith('http://')) {
return url.replace('http://', 'https://');
}
return url;
}
// In configuration
{
"security": {
"enforceHttps": true,
"allowInsecureConnections": false
}
}
CORS Protection
const ALLOWED_ORIGINS = [
'https://api.example.com',
'https://mail.google.com'
];
function validateOrigin(origin: string): boolean {
return ALLOWED_ORIGINS.includes(origin);
}
Request Validation
interface RequestOptions {
url: string;
method: string;
headers?: Record<string, string>;
body?: string;
}
async function makeSecureRequest(options: RequestOptions) {
// Validate URL
if (!isValidUrl(options.url)) {
throw new Error('Invalid URL');
}
// Enforce HTTPS
if (!options.url.startsWith('https://')) {
throw new Error('HTTPS required');
}
// Validate headers
validateHeaders(options.headers);
// Make request
return await fetch(options.url, {
method: options.method,
headers: options.headers,
body: options.body
});
}
function validateHeaders(headers?: Record<string, string>) {
if (!headers) return;
// Block dangerous headers
const dangerous = ['Cookie', 'Set-Cookie', 'Authorization'];
for (const header of Object.keys(headers)) {
if (dangerous.includes(header)) {
throw new Error(`Header ${header} not allowed`);
}
}
}
Content Security
XSS Prevention
Input Sanitization:
import DOMPurify from 'dompurify';
function sanitizeHTML(dirty: string): string {
return DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'a', 'code', 'pre'],
ALLOWED_ATTR: ['href', 'class']
});
}
// Usage
function renderUserContent(content: string) {
const clean = sanitizeHTML(content);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
Output Encoding:
function escapeHTML(text: string): string {
const map: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return text.replace(/[&<>"'\/]/g, (char) => map[char]);
}
Content Security Policy
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;">
External Content
{
"security": {
"allowExternalImages": true,
"allowExternalLinks": true,
"blockTrackers": true,
"validateImageUrls": true
}
}
Image URL Validation:
function isValidImageUrl(url: string): boolean {
// Only allow HTTPS
if (!url.startsWith('https://')) {
return false;
}
// Check allowed domains
const allowedDomains = ['imgur.com', 'github.com'];
const domain = new URL(url).hostname;
return allowedDomains.some(allowed =>
domain.endsWith(allowed)
);
}
Data Protection
Encryption at Rest
Encrypt sensitive settings:
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
fn encrypt_data(data: &str, key: &[u8]) -> Result<Vec<u8>, String> {
let cipher = Aes256Gcm::new(Key::from_slice(key));
let nonce = Nonce::from_slice(b"unique nonce");
cipher.encrypt(nonce, data.as_bytes())
.map_err(|e| e.to_string())
}
fn decrypt_data(encrypted: &[u8], key: &[u8]) -> Result<String, String> {
let cipher = Aes256Gcm::new(Key::from_slice(key));
let nonce = Nonce::from_slice(b"unique nonce");
let decrypted = cipher.decrypt(nonce, encrypted)
.map_err(|e| e.to_string())?;
String::from_utf8(decrypted)
.map_err(|e| e.to_string())
}
Encryption in Transit
All network communication uses TLS 1.3:
const agent = new https.Agent({
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
rejectUnauthorized: true
});
await fetch(url, { agent });
Security Best Practices
For Users
- Keep Lokus Updated - Install security updates promptly
- Use Strong Passwords - For workspace encryption
- Review Plugin Permissions - Only install trusted plugins
- Enable 2FA - For connected services (Gmail, etc.)
- Regular Backups - Protect against data loss
- Secure Workspace - Use full disk encryption
- Log Out - When using shared computers
For Plugin Developers
- Minimal Permissions - Request only needed permissions
- Validate Input - Sanitize all user input
- Secure API Keys - Use plugin settings storage
- HTTPS Only - Never use HTTP
- Error Handling - Don’t expose sensitive info
- Regular Updates - Fix security issues promptly
- Code Review - Review for vulnerabilities
For Self-Hosters
- Firewall Rules - Restrict network access
- Regular Updates - Update OS and dependencies
- Access Control - Limit user permissions
- Monitoring - Log and monitor access
- Backup Strategy - Automated, encrypted backups
- Incident Response - Have a plan
- Security Audits - Regular security reviews
Security Auditing
Logging
class SecurityLogger {
logAuthAttempt(user: string, success: boolean) {
console.log(`[AUTH] ${user} - ${success ? 'SUCCESS' : 'FAILURE'}`);
}
logFileAccess(plugin: string, path: string, operation: string) {
console.log(`[FILE] ${plugin} - ${operation} - ${path}`);
}
logPermissionDenied(plugin: string, permission: string) {
console.warn(`[SECURITY] ${plugin} denied ${permission}`);
}
logSuspiciousActivity(activity: string, details: any) {
console.error(`[SECURITY ALERT] ${activity}`, details);
}
}
Monitoring
class SecurityMonitor {
private failedAttempts = new Map<string, number>();
recordFailedAuth(user: string) {
const attempts = this.failedAttempts.get(user) || 0;
this.failedAttempts.set(user, attempts + 1);
// Alert after 5 failed attempts
if (attempts >= 5) {
this.alertSecurityIncident('Brute force attempt', { user });
}
}
alertSecurityIncident(type: string, details: any) {
// Send alert notification
console.error(`SECURITY INCIDENT: ${type}`, details);
// Could integrate with external monitoring
}
}
Vulnerability Reporting
If you discover a security vulnerability:
- Do Not publicly disclose the vulnerability
- Email security@lokus.app with details
- Include steps to reproduce
- Wait for response before disclosure
- Receive credit in security advisories
Bug Bounty Program: Coming soon
Security Updates
Stay informed about security updates:
- GitHub Security Advisories - Watch repository
- Release Notes - Read security sections
- Newsletter - Subscribe for alerts
- RSS Feed - Follow security feed
Compliance
GDPR Compliance
Lokus is designed for GDPR compliance:
- Data Minimization - Collect only necessary data
- Right to Access - Export all user data
- Right to Erasure - Delete user data
- Data Portability - Standard file formats
- Privacy by Design - Security by default
- Transparency - Clear privacy policy
Data Retention
{
"security": {
"dataRetention": {
"logs": 90,
"cache": 7,
"backups": 365
}
}
}
Next Steps
- Troubleshooting - Security issues
- Configuration - Security settings
- Plugin API - Secure plugin development