Security Guide
This guide covers essential security practices for Qirvo plugin development, including input validation, secure data handling, authentication, and protection against common vulnerabilities.
Table of Contents
- Security Fundamentals
- Input Validation
- Secure Data Storage
- Authentication & Authorization
- Network Security
- Common Vulnerabilities
Security Fundamentals
Security-First Development
// Security validation framework
export class SecurityValidator {
static validateInput(input: any, schema: ValidationSchema): ValidationResult {
const errors: string[] = [];
// Type validation
if (schema.type && typeof input !== schema.type) {
errors.push(`Expected ${schema.type}, got ${typeof input}`);
}
// String validation
if (schema.type === 'string' && typeof input === 'string') {
if (schema.minLength && input.length < schema.minLength) {
errors.push(`Minimum length is ${schema.minLength}`);
}
if (schema.maxLength && input.length > schema.maxLength) {
errors.push(`Maximum length is ${schema.maxLength}`);
}
if (schema.pattern && !schema.pattern.test(input)) {
errors.push('Invalid format');
}
}
// Sanitization
const sanitized = this.sanitizeInput(input, schema);
return {
isValid: errors.length === 0,
errors,
sanitizedValue: sanitized
};
}
private static sanitizeInput(input: any, schema: ValidationSchema): any {
if (typeof input === 'string') {
// Remove potentially dangerous characters
let sanitized = input.replace(/[<>\"']/g, '');
// Trim whitespace
sanitized = sanitized.trim();
// Apply custom sanitizer if provided
if (schema.sanitizer) {
sanitized = schema.sanitizer(sanitized);
}
return sanitized;
}
return input;
}
}
interface ValidationSchema {
type: 'string' | 'number' | 'boolean' | 'object';
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
sanitizer?: (value: string) => string;
}
interface ValidationResult {
isValid: boolean;
errors: string[];
sanitizedValue: any;
}
Input Validation
Comprehensive Input Validation
// Input validation for plugin configurations
export class ConfigValidator {
private schemas: Map<string, ValidationSchema> = new Map();
constructor() {
this.setupSchemas();
}
private setupSchemas(): void {
this.schemas.set('apiKey', {
type: 'string',
required: true,
minLength: 10,
maxLength: 100,
pattern: /^[a-zA-Z0-9_-]+$/,
sanitizer: (value) => value.replace(/\s/g, '')
});
this.schemas.set('email', {
type: 'string',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
sanitizer: (value) => value.toLowerCase().trim()
});
this.schemas.set('url', {
type: 'string',
required: false,
pattern: /^https?:\/\/.+/,
sanitizer: (value) => value.trim()
});
}
validateConfig(config: Record<string, any>): ConfigValidationResult {
const errors: Record<string, string[]> = {};
const sanitized: Record<string, any> = {};
for (const [key, value] of Object.entries(config)) {
const schema = this.schemas.get(key);
if (!schema) continue;
const result = SecurityValidator.validateInput(value, schema);
if (!result.isValid) {
errors[key] = result.errors;
} else {
sanitized[key] = result.sanitizedValue;
}
}
return {
isValid: Object.keys(errors).length === 0,
errors,
sanitizedConfig: sanitized
};
}
}
interface ConfigValidationResult {
isValid: boolean;
errors: Record<string, string[]>;
sanitizedConfig: Record<string, any>;
}
Secure Data Storage
Encrypted Storage Service
// Secure storage with encryption
export class SecureStorage {
private encryptionKey: CryptoKey;
private storage: StorageAPI;
constructor(storage: StorageAPI, encryptionKey: CryptoKey) {
this.storage = storage;
this.encryptionKey = encryptionKey;
}
async setSecure<T>(key: string, value: T): Promise<void> {
try {
const serialized = JSON.stringify(value);
const encrypted = await this.encrypt(serialized);
await this.storage.set(key, encrypted);
} catch (error) {
throw new Error(`Failed to store secure data: ${error.message}`);
}
}
async getSecure<T>(key: string): Promise<T | null> {
try {
const encrypted = await this.storage.get(key);
if (!encrypted) return null;
const decrypted = await this.decrypt(encrypted);
return JSON.parse(decrypted);
} catch (error) {
console.error('Failed to retrieve secure data:', error);
return null;
}
}
private async encrypt(data: string): Promise<string> {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
this.encryptionKey,
dataBuffer
);
const combined = new Uint8Array(iv.length + encrypted.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encrypted), iv.length);
return btoa(String.fromCharCode(...combined));
}
private async decrypt(encryptedData: string): Promise<string> {
const combined = new Uint8Array(
atob(encryptedData).split('').map(char => char.charCodeAt(0))
);
const iv = combined.slice(0, 12);
const encrypted = combined.slice(12);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
this.encryptionKey,
encrypted
);
const decoder = new TextDecoder();
return decoder.decode(decrypted);
}
}
Authentication & Authorization
Secure API Client
// Secure HTTP client with authentication
export class SecureHttpClient {
private baseUrl: string;
private authToken: string | null = null;
private refreshToken: string | null = null;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
setAuthTokens(accessToken: string, refreshToken?: string): void {
this.authToken = accessToken;
this.refreshToken = refreshToken;
}
async request<T>(
endpoint: string,
options: RequestOptions = {}
): Promise<T> {
const url = new URL(endpoint, this.baseUrl);
// Validate URL
if (!this.isValidUrl(url)) {
throw new Error('Invalid URL');
}
const headers = new Headers(options.headers);
// Add authentication
if (this.authToken) {
headers.set('Authorization', `Bearer ${this.authToken}`);
}
// Add security headers
headers.set('X-Requested-With', 'XMLHttpRequest');
headers.set('Content-Type', 'application/json');
try {
const response = await fetch(url.toString(), {
...options,
headers,
credentials: 'same-origin'
});
// Handle authentication errors
if (response.status === 401) {
if (this.refreshToken) {
await this.refreshAuthToken();
return this.request(endpoint, options);
}
throw new Error('Authentication required');
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
throw new Error(`Request failed: ${error.message}`);
}
}
private isValidUrl(url: URL): boolean {
// Only allow HTTPS in production
if (process.env.NODE_ENV === 'production' && url.protocol !== 'https:') {
return false;
}
// Validate hostname
const allowedHosts = ['api.qirvo.ai', 'localhost'];
return allowedHosts.includes(url.hostname);
}
private async refreshAuthToken(): Promise<void> {
if (!this.refreshToken) {
throw new Error('No refresh token available');
}
const response = await fetch(`${this.baseUrl}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: this.refreshToken })
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const { accessToken, refreshToken } = await response.json();
this.setAuthTokens(accessToken, refreshToken);
}
}
interface RequestOptions extends RequestInit {
timeout?: number;
}
Network Security
Content Security Policy
// CSP configuration for plugin security
export const cspConfig = {
directives: {
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-inline'"],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", "data:", "https:"],
'connect-src': ["'self'", "https://api.qirvo.ai"],
'font-src': ["'self'"],
'object-src': ["'none'"],
'media-src': ["'self'"],
'frame-src': ["'none'"]
},
generateHeader(): string {
return Object.entries(this.directives)
.map(([directive, sources]) => `${directive} ${sources.join(' ')}`)
.join('; ');
}
};
Common Vulnerabilities
XSS Prevention
// XSS prevention utilities
export class XSSProtection {
static sanitizeHtml(html: string): string {
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
}
static escapeHtml(text: string): string {
const escapeMap: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return text.replace(/[&<>"'/]/g, (char) => escapeMap[char]);
}
static validateUrl(url: string): boolean {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}
}
CSRF Protection
// CSRF token management
export class CSRFProtection {
private token: string | null = null;
async getToken(): Promise<string> {
if (!this.token) {
this.token = await this.fetchToken();
}
return this.token;
}
private async fetchToken(): Promise<string> {
const response = await fetch('/api/csrf-token');
const { token } = await response.json();
return token;
}
async addTokenToRequest(options: RequestInit): Promise<RequestInit> {
const token = await this.getToken();
const headers = new Headers(options.headers);
headers.set('X-CSRF-Token', token);
return {
...options,
headers
};
}
}
This security guide provides essential protection mechanisms for building secure Qirvo plugins.
Next: Performance Guide