Security Guide ​
This guide covers security best practices, vulnerability reporting, and secure usage patterns for the ziegel library ecosystem.
Overview ​
Security is a fundamental concern in the ziegel framework. This guide provides comprehensive information about security considerations, best practices, and how to use ziegel libraries securely in your applications.
Security Features ​
Core Security Components ​
Input Validation ​
import { ArgumentException, ValidationException } from '@breadstone/ziegel-core';
export class UserValidator {
static validateEmail(email: string): void {
if (!email) {
throw new ArgumentException('Email is required', 'email');
}
if (!RegexPatterns.email.test(email)) {
throw new ValidationException('Invalid email format', 'email');
}
// Additional security checks
if (email.length > 254) {
throw new ValidationException('Email address too long', 'email');
}
}
static validatePassword(password: string): void {
const result = PasswordStrengthValidator.validate(password);
if (!result.isStrong) {
throw new ValidationException(
`Password does not meet security requirements: ${result.weaknesses.join(', ')}`,
'password'
);
}
}
}Secure String Handling ​
import { SecureString, CryptoHelper } from '@breadstone/ziegel-core';
// Secure password handling
export class AuthenticationService {
async authenticateUser(username: string, password: SecureString): Promise<AuthResult> {
try {
// Hash password securely
const hashedPassword = await CryptoHelper.hashPassword(password.value);
// Compare with stored hash
const user = await this.userRepository.findByUsername(username);
if (!user || !await CryptoHelper.verifyPassword(password.value, user.passwordHash)) {
throw new UnauthorizedException('Invalid credentials');
}
return new AuthResult(user, this.generateSecureToken(user));
} finally {
// Clear sensitive data from memory
password.dispose();
}
}
private generateSecureToken(user: User): string {
return CryptoHelper.generateSecureToken({
userId: user.id,
roles: user.roles,
expiresAt: DateTime.now().addHours(24)
});
}
}Encryption and Hashing ​
import { SHA256, AES, RSA } from '@breadstone/ziegel-core';
export class EncryptionService {
// Hash sensitive data
static hashSensitiveData(data: string): string {
const salt = CryptoHelper.generateSalt();
return SHA256.hash(data + salt);
}
// Encrypt data at rest
static encryptData(data: string, key: string): string {
return AES.encrypt(data, key);
}
// Decrypt data
static decryptData(encryptedData: string, key: string): string {
return AES.decrypt(encryptedData, key);
}
// Generate secure keys
static generateSecureKey(): string {
return CryptoHelper.generateRandomBytes(32).toString('hex');
}
}HTTP Security ​
Secure HTTP Client Configuration ​
import { HttpClient, SecurityInterceptor } from '@breadstone/ziegel-platform-http';
// Configure secure HTTP client
const httpClient = new HttpClient({
baseUrl: 'https://api.example.com',
timeout: 30000,
security: {
// Enable HTTPS only
allowInsecureConnections: false,
// Certificate validation
validateCertificates: true,
// Security headers
headers: {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
}
}
});
// Add security interceptor
httpClient.addInterceptor(new SecurityInterceptor({
sanitizeHeaders: true,
logSecurityEvents: true,
preventCSRF: true
}));Authentication Interceptor ​
export class AuthenticationInterceptor implements HttpInterceptor {
constructor(
private readonly tokenProvider: ITokenProvider,
private readonly logger: ILogger
) {}
async request(request: HttpRequest): Promise<HttpRequest> {
try {
const token = await this.tokenProvider.getValidToken();
if (token) {
// Add authorization header
request.headers['Authorization'] = `Bearer ${token}`;
// Add security headers
request.headers['X-Requested-With'] = 'XMLHttpRequest';
request.headers['X-API-Version'] = '1.0';
}
return request;
} catch (error) {
this.logger.error('Authentication failed', error);
throw new UnauthorizedException('Authentication required');
}
}
async response(response: HttpResponse): Promise<HttpResponse> {
// Check for security-related response headers
if (response.status === 401) {
await this.tokenProvider.clearToken();
throw new UnauthorizedException('Token expired or invalid');
}
return response;
}
}Request Sanitization ​
export class RequestSanitizer {
static sanitizeQueryParameters(params: Record<string, any>): Record<string, any> {
const sanitized: Record<string, any> = {};
for (const [key, value] of Object.entries(params)) {
// Validate parameter names
if (!this.isValidParameterName(key)) {
throw new SecurityException(`Invalid parameter name: ${key}`);
}
// Sanitize values
sanitized[key] = this.sanitizeValue(value);
}
return sanitized;
}
static sanitizeHeaders(headers: Record<string, string>): Record<string, string> {
const sanitized: Record<string, string> = {};
const allowedHeaders = [
'content-type', 'authorization', 'accept', 'x-api-key'
];
for (const [key, value] of Object.entries(headers)) {
const lowerKey = key.toLowerCase();
if (allowedHeaders.includes(lowerKey)) {
sanitized[key] = this.sanitizeHeaderValue(value);
}
}
return sanitized;
}
private static sanitizeValue(value: any): any {
if (typeof value === 'string') {
// Remove potentially dangerous characters
return value.replace(/[<>\"']/g, '');
}
return value;
}
private static isValidParameterName(name: string): boolean {
// Only allow alphanumeric characters and underscores
return /^[a-zA-Z0-9_]+$/.test(name);
}
}Data Security ​
Secure Data Repository ​
export class SecureUserRepository implements IUserRepository {
constructor(
private readonly dataSource: IDataSource,
private readonly encryptionService: IEncryptionService,
private readonly auditLogger: IAuditLogger
) {}
async getById(id: string): Promise<User> {
// Validate input
if (!Guid.isValid(id)) {
throw new ArgumentException('Invalid user ID format', 'id');
}
try {
const encryptedData = await this.dataSource.query(
'SELECT * FROM users WHERE id = @id',
{ id }
);
if (!encryptedData) {
throw new NotFoundException(`User ${id} not found`);
}
// Decrypt sensitive fields
const user = this.decryptUserData(encryptedData);
// Log access for audit trail
await this.auditLogger.logDataAccess({
operation: 'READ',
table: 'users',
recordId: id,
timestamp: DateTime.now()
});
return user;
} catch (error) {
await this.auditLogger.logSecurityEvent({
type: 'DATA_ACCESS_FAILED',
details: `Failed to retrieve user ${id}`,
error: error.message
});
throw error;
}
}
async save(user: User): Promise<void> {
// Validate user data
this.validateUserData(user);
// Encrypt sensitive fields
const encryptedData = this.encryptUserData(user);
try {
await this.dataSource.execute(
'INSERT OR UPDATE users SET email = @email, password_hash = @passwordHash WHERE id = @id',
encryptedData
);
await this.auditLogger.logDataAccess({
operation: 'WRITE',
table: 'users',
recordId: user.id,
timestamp: DateTime.now()
});
} catch (error) {
await this.auditLogger.logSecurityEvent({
type: 'DATA_WRITE_FAILED',
details: `Failed to save user ${user.id}`,
error: error.message
});
throw error;
}
}
private encryptUserData(user: User): any {
return {
id: user.id,
email: this.encryptionService.encrypt(user.email),
passwordHash: user.passwordHash, // Already hashed
createdAt: user.createdAt
};
}
private decryptUserData(encryptedData: any): User {
return new User({
id: encryptedData.id,
email: this.encryptionService.decrypt(encryptedData.email),
passwordHash: encryptedData.passwordHash,
createdAt: encryptedData.createdAt
});
}
}SQL Injection Prevention ​
export class SecureQueryBuilder {
private parameters: Map<string, any> = new Map();
private query: string = '';
select(fields: string[]): this {
// Validate field names to prevent injection
const validFields = fields.filter(field => this.isValidFieldName(field));
this.query += `SELECT ${validFields.join(', ')} `;
return this;
}
where(field: string, operator: string, value: any): this {
if (!this.isValidFieldName(field)) {
throw new SecurityException(`Invalid field name: ${field}`);
}
if (!this.isValidOperator(operator)) {
throw new SecurityException(`Invalid operator: ${operator}`);
}
const paramName = `param_${this.parameters.size}`;
this.parameters.set(paramName, value);
this.query += `WHERE ${field} ${operator} @${paramName} `;
return this;
}
build(): { query: string; parameters: Record<string, any> } {
return {
query: this.query,
parameters: Object.fromEntries(this.parameters)
};
}
private isValidFieldName(field: string): boolean {
// Only allow alphanumeric characters and underscores
return /^[a-zA-Z0-9_]+$/.test(field);
}
private isValidOperator(operator: string): boolean {
const allowedOperators = ['=', '!=', '<', '>', '<=', '>=', 'LIKE', 'IN'];
return allowedOperators.includes(operator.toUpperCase());
}
}Configuration Security ​
Secure Configuration Management ​
import { ConfigurationManager, SecureConfigurationProvider } from '@breadstone/ziegel-platform-configuration';
export class SecurityConfiguration {
private readonly config: ConfigurationManager;
constructor() {
this.config = new ConfigurationManager([
new SecureConfigurationProvider({
encryption: {
enabled: true,
algorithm: 'AES-256-GCM',
keySource: 'environment' // or 'vault', 'file'
},
validation: {
required: ['JWT_SECRET', 'DATABASE_URL', 'ENCRYPTION_KEY'],
format: {
'JWT_SECRET': /^[A-Za-z0-9+/=]{32,}$/,
'DATABASE_URL': /^[a-zA-Z]+:\/\/.+$/
}
}
})
]);
}
getJwtSecret(): string {
const secret = this.config.getValue<string>('JWT_SECRET');
if (!secret || secret.length < 32) {
throw new SecurityException('JWT secret must be at least 32 characters');
}
return secret;
}
getDatabaseConnectionString(): string {
const connectionString = this.config.getValue<string>('DATABASE_URL');
// Ensure HTTPS/TLS is used
if (!connectionString.startsWith('postgres://') && !connectionString.startsWith('postgresql://')) {
throw new SecurityException('Database connection must use secure protocol');
}
return connectionString;
}
getEncryptionKey(): string {
return this.config.getValue<string>('ENCRYPTION_KEY');
}
}Environment Variable Security ​
export class SecureEnvironment {
private static readonly SENSITIVE_KEYS = [
'JWT_SECRET', 'DATABASE_PASSWORD', 'API_KEY', 'ENCRYPTION_KEY'
];
static getSecureValue(key: string): string {
const value = process.env[key];
if (!value) {
throw new ConfigurationException(`Required environment variable ${key} not found`);
}
// Validate sensitive values
if (this.SENSITIVE_KEYS.includes(key)) {
this.validateSensitiveValue(key, value);
}
return value;
}
static validateEnvironment(): void {
const missing: string[] = [];
for (const key of this.SENSITIVE_KEYS) {
if (!process.env[key]) {
missing.push(key);
}
}
if (missing.length > 0) {
throw new ConfigurationException(
`Missing required environment variables: ${missing.join(', ')}`
);
}
}
private static validateSensitiveValue(key: string, value: string): void {
switch (key) {
case 'JWT_SECRET':
if (value.length < 32) {
throw new SecurityException('JWT secret must be at least 32 characters');
}
break;
case 'DATABASE_PASSWORD':
if (value.length < 12) {
throw new SecurityException('Database password must be at least 12 characters');
}
break;
default:
if (value.length < 16) {
throw new SecurityException(`${key} must be at least 16 characters`);
}
}
}
}Logging and Monitoring ​
Security Event Logging ​
export class SecurityAuditLogger {
constructor(
private readonly logger: ILogger,
private readonly alertService: IAlertService
) {}
logAuthenticationAttempt(event: AuthenticationEvent): void {
this.logger.info('Authentication attempt', {
userId: event.userId,
ipAddress: this.sanitizeIpAddress(event.ipAddress),
userAgent: this.sanitizeUserAgent(event.userAgent),
success: event.success,
timestamp: event.timestamp
});
// Alert on suspicious activity
if (!event.success) {
this.checkForBruteForceAttack(event);
}
}
logDataAccess(event: DataAccessEvent): void {
this.logger.info('Data access', {
operation: event.operation,
table: event.table,
recordId: event.recordId,
userId: event.userId,
timestamp: event.timestamp
});
// Alert on sensitive data access
if (this.isSensitiveTable(event.table)) {
this.alertService.sendSecurityAlert({
type: 'SENSITIVE_DATA_ACCESS',
details: `User ${event.userId} accessed ${event.table} table`,
severity: 'MEDIUM'
});
}
}
logSecurityViolation(violation: SecurityViolation): void {
this.logger.error('Security violation detected', {
type: violation.type,
details: violation.details,
userId: violation.userId,
ipAddress: this.sanitizeIpAddress(violation.ipAddress),
timestamp: violation.timestamp
});
// Immediate alert for security violations
this.alertService.sendSecurityAlert({
type: 'SECURITY_VIOLATION',
details: violation.details,
severity: 'HIGH'
});
}
private checkForBruteForceAttack(event: AuthenticationEvent): void {
// Implementation for detecting brute force attacks
// This would typically involve rate limiting and IP tracking
}
private isSensitiveTable(table: string): boolean {
const sensitiveTables = ['users', 'payments', 'personal_data'];
return sensitiveTables.includes(table);
}
private sanitizeIpAddress(ip: string): string {
// Mask part of IP for privacy while keeping it useful for security
return ip.replace(/(\d+\.\d+\.\d+)\.\d+/, '$1.xxx');
}
private sanitizeUserAgent(userAgent: string): string {
// Remove potentially sensitive information
return userAgent.replace(/\(.*?\)/g, '(...)');
}
}Rate Limiting and Throttling ​
import { RateLimiter } from '@breadstone/ziegel-platform-http';
export class SecurityRateLimiter {
private readonly limiters = new Map<string, RateLimiter>();
constructor() {
// Different limits for different operations
this.limiters.set('auth', new RateLimiter({
maxRequests: 5,
windowMs: 15 * 60 * 1000, // 15 minutes
skipSuccessfulRequests: true
}));
this.limiters.set('api', new RateLimiter({
maxRequests: 100,
windowMs: 60 * 1000, // 1 minute
skipSuccessfulRequests: false
}));
this.limiters.set('password-reset', new RateLimiter({
maxRequests: 3,
windowMs: 60 * 60 * 1000, // 1 hour
skipSuccessfulRequests: true
}));
}
async checkLimit(operation: string, identifier: string): Promise<void> {
const limiter = this.limiters.get(operation);
if (!limiter) {
throw new Error(`Unknown operation: ${operation}`);
}
const allowed = await limiter.checkLimit(identifier);
if (!allowed) {
throw new TooManyRequestsException(
`Rate limit exceeded for ${operation}`,
limiter.getResetTime(identifier)
);
}
}
}Error Handling Security ​
export class SecureErrorHandler {
static handleError(error: Error, context: string): never {
// Log full error details for debugging
console.error(`Error in ${context}:`, error);
// Return sanitized error to client
if (error instanceof ValidationException) {
throw new ValidationException(error.message, error.field);
}
if (error instanceof UnauthorizedException) {
throw new UnauthorizedException('Access denied');
}
if (error instanceof NotFoundException) {
throw new NotFoundException('Resource not found');
}
// Don't leak internal error details
throw new InternalServerErrorException('An unexpected error occurred');
}
static sanitizeErrorForClient(error: Error): ClientError {
// Remove stack traces and internal details
return {
message: this.getSafeErrorMessage(error),
code: this.getErrorCode(error),
timestamp: new Date().toISOString()
};
}
private static getSafeErrorMessage(error: Error): string {
const safeErrors = [
'ValidationException',
'UnauthorizedException',
'NotFoundException',
'TooManyRequestsException'
];
if (safeErrors.includes(error.constructor.name)) {
return error.message;
}
return 'An unexpected error occurred';
}
}Best Practices ​
Input Validation ​
// Always validate and sanitize inputs
export class InputValidator {
static validateUserInput(input: UserInput): void {
// Check for required fields
if (!input.email || !input.name) {
throw new ValidationException('Email and name are required');
}
// Validate format
if (!RegexPatterns.email.test(input.email)) {
throw new ValidationException('Invalid email format');
}
// Check length limits
if (input.name.length > 100) {
throw new ValidationException('Name too long');
}
// Sanitize input
input.name = this.sanitizeString(input.name);
input.email = input.email.toLowerCase().trim();
}
private static sanitizeString(input: string): string {
// Remove potentially dangerous characters
return input.replace(/[<>\"'&]/g, '').trim();
}
}Secure Defaults ​
// Use secure defaults in configuration
export class SecureDefaults {
static readonly HTTP_CLIENT_CONFIG = {
timeout: 30000,
maxRedirects: 3,
validateCertificates: true,
allowInsecureConnections: false
};
static readonly PASSWORD_REQUIREMENTS = {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
preventCommonPasswords: true
};
static readonly SESSION_CONFIG = {
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
};
}Security Checklist ​
Development ​
- [ ] Input validation on all user inputs
- [ ] Output encoding for all dynamic content
- [ ] Parameterized queries for database access
- [ ] Secure error handling without information leakage
- [ ] Proper authentication and authorization
- [ ] Session management security
- [ ] HTTPS enforcement
- [ ] Security headers implementation
Testing ​
- [ ] Security unit tests
- [ ] Input validation testing
- [ ] Authentication bypass testing
- [ ] SQL injection testing
- [ ] XSS prevention testing
- [ ] CSRF protection testing
Deployment ​
- [ ] Environment variable validation
- [ ] Secure configuration management
- [ ] Security monitoring setup
- [ ] Rate limiting configuration
- [ ] Audit logging enabled
- [ ] Security headers configured
Vulnerability Reporting ​
Reporting Security Issues ​
If you discover a security vulnerability in ziegel:
- Do not create a public GitHub issue
- Send an email to: awehlert@breadstone.de
- Include detailed information about the vulnerability
- Allow time for investigation and resolution
Response Process ​
- Acknowledgment: We'll acknowledge receipt within 24 hours
- Investigation: We'll investigate and assess the issue
- Resolution: We'll develop and test a fix
- Disclosure: We'll coordinate responsible disclosure
Security Updates ​
Security updates are published as:
- Patch releases for minor vulnerabilities
- Emergency releases for critical vulnerabilities
- Security advisories for all vulnerabilities
Subscribe to security notifications to stay informed about security updates.
Compliance ​
Data Protection ​
ziegel libraries are designed to support compliance with:
- GDPR (General Data Protection Regulation)
- CCPA (California Consumer Privacy Act)
- SOX (Sarbanes-Oxley Act)
- HIPAA (Health Insurance Portability and Accountability Act)
Industry Standards ​
The framework follows security standards including:
- OWASP Top 10
- ISO 27001
- NIST Cybersecurity Framework
- CIS Controls
By following this security guide and implementing the recommended practices, you can build secure applications with the ziegel framework. Remember that security is an ongoing process, not a one-time implementation.