Skip to main content

Overview

Consensus implements multiple security layers to protect election integrity and user data. This guide covers the built-in security features and best practices for production deployments.

Authentication & Authorization

Password Security

Consensus uses bcrypt for password hashing with industry-standard parameters:
src/utils/password.ts
import bcrypt from "bcrypt";

const SALT_ROUNDS = 10;

export class PasswordUtil {
    static async hash(plainPassword: string): Promise<string> {
        return await bcrypt.hash(plainPassword, SALT_ROUNDS);
    }

    static async verify(plainPassword: string, hash: string): Promise<boolean> {
        return await bcrypt.compare(plainPassword, hash);
    }
}
Bcrypt with 10 salt rounds provides strong protection against brute-force attacks while maintaining reasonable performance. Each password hash takes approximately 100ms to compute.

Session Management

Consensus uses secure session-based authentication with the following features:
1

Session Storage

Sessions are stored server-side with signed cookies:
{
  secret: config.session.secret,
  cookieName: "consensusSessionId",
  cookie: {
    secure: false, // Set to true with HTTPS
  }
}
2

Session Data

Session data includes:
  • voterID: Authenticated voter identifier
  • voterName: Voter display name
  • isAdmin: Administrator flag
  • adminUsername: Admin username if authenticated
3

Session Validation

Sessions are validated on every request through Fastify hooks (src/web/server.ts:104-138).
Production Requirement: Set cookie.secure = true in src/web/server.ts:73 when deploying with HTTPS. This prevents session cookies from being sent over unencrypted connections.

Role Separation

Consensus enforces strict separation between administrators and voters:
RoleCapabilitiesAuthentication
AdminManage elections, voters, candidates, settingsUsername + password via /admin/login
VoterRegister, view elections, cast votesVoter ID + password via /login
Administrators and voters use separate authentication endpoints and cannot access each other’s interfaces. Admin routes are protected by the /admin prefix.

Default Admin Account

On first startup, Consensus creates a default administrator account:
src/web/server.ts:153-167
const admins = adminRepository.findAll();
if (admins.length === 0) {
    const defaultPassword = process.env.CONSENSUS_ADMIN_DEFAULT_PASSWORD || "admin123";
    const passwordHash = await PasswordUtil.hash(defaultPassword);
    const admin = new Admin(
        uuidv4(),
        "admin",
        passwordHash,
        "Administrator",
        new Date(),
        true // mustChangePassword
    );
    adminRepository.save(admin);
}
CRITICAL SECURITY STEP:
  1. Set CONSENSUS_ADMIN_DEFAULT_PASSWORD to a strong password before first run
  2. Login immediately and change the password
  3. The mustChangePassword flag forces a password change on first login
  4. Never use the default password admin123 in production

Session Secret Security

Importance

The SESSION_SECRET is used to cryptographically sign session cookies. If compromised, attackers can forge valid sessions and impersonate users.

Requirements

1

Minimum Length

Use at least 32 characters (256 bits of entropy recommended)
2

Randomness

Generate using a cryptographically secure random number generator
3

Uniqueness

Use a different secret for each environment (dev, staging, production)
4

Secrecy

Never commit secrets to version control or share them insecurely

Generating Secure Secrets

openssl rand -hex 32
Bad Examples (DO NOT USE):
  • Default value: consensus-secret-key-change-in-production
  • Short strings: mysecret, 12345, password
  • Predictable values: consensus-prod, voting-secret
  • Reused secrets from other applications

Container Security

Non-Root User

The Docker container runs as a non-root user for defense in depth:
Dockerfile:34-35,52
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 -G nodejs

# Switch to non-root user
USER nodejs
Running as UID 1001 (nodejs) prevents privilege escalation attacks and limits damage if the container is compromised.

File Permissions

Files are owned by the nodejs user during the build process:
Dockerfile:38-42
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist

# Create data directory
RUN mkdir -p /app/data && chown nodejs:nodejs /app/data

Read-Only Root Filesystem

For additional security, run the container with a read-only root filesystem:
docker run -d \
  --name consensus \
  --read-only \
  --tmpfs /tmp \
  -v consensus-data:/app/data \
  -p 3000:3000 \
  consensus:latest
Only /app/data needs write access. All other directories can be read-only.

Security Scanning

Regularly scan the Docker image for vulnerabilities:
trivy image consensus:latest

Database Security

SQLite Security Features

Consensus uses SQLite with security best practices:
1

Foreign Key Constraints

Enabled to maintain referential integrity:
src/db/connection.ts:24
DatabaseConnection.instance.pragma("foreign_keys = ON");
2

File Permissions

The database file should only be accessible to the application user:
chmod 600 /app/data/database.sqlite
chown nodejs:nodejs /app/data/database.sqlite
3

Backup Encryption

Encrypt database backups at rest:
# Backup and encrypt
docker cp consensus:/app/data/database.sqlite - | \
  openssl enc -aes-256-cbc -salt -pbkdf2 -out backup.sqlite.enc

# Decrypt and restore
openssl enc -aes-256-cbc -d -pbkdf2 -in backup.sqlite.enc | \
  docker cp - consensus:/app/data/database.sqlite

Preventing SQL Injection

Consensus uses parameterized queries via better-sqlite3 prepared statements:
Example
// SAFE - Parameterized query
const stmt = db.prepare("SELECT * FROM voters WHERE id = ?");
const voter = stmt.get(voterId);

// UNSAFE - String concatenation (NOT used in Consensus)
// const query = "SELECT * FROM voters WHERE id = '" + voterId + "'";
All database queries in Consensus use prepared statements, preventing SQL injection attacks.

Network Security

HTTPS/TLS

Consensus should always be deployed behind HTTPS in production:
1

Use a Reverse Proxy

Deploy nginx, Traefik, or Caddy to handle TLS termination:
nginx.conf
server {
    listen 443 ssl http2;
    server_name voting.example.com;
    
    ssl_certificate /etc/ssl/certs/voting.crt;
    ssl_certificate_key /etc/ssl/private/voting.key;
    
    # Modern TLS configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
2

Enable Secure Cookies

Modify src/web/server.ts:73 to set secure: true:
cookie: {
    secure: true, // Requires HTTPS
}
3

Force HTTPS Redirect

Configure your reverse proxy to redirect HTTP to HTTPS:
server {
    listen 80;
    server_name voting.example.com;
    return 301 https://$server_name$request_uri;
}
Never deploy Consensus directly to the internet without HTTPS. Session cookies and passwords will be transmitted in plaintext.

Firewall Configuration

Restrict network access to the Consensus container:
# Only allow access from reverse proxy
iptables -A INPUT -p tcp --dport 3000 -s 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp --dport 3000 -j DROP

# Docker specific (block external access to container)
docker run -d \
  --name consensus \
  -p 127.0.0.1:3000:3000 \
  consensus:latest

Maintenance Mode

Consensus includes a maintenance mode feature to block voter access during updates:
src/web/server.ts:108-120
if (settings.maintenanceMode && !request.session.isAdmin) {
    const url = request.url;
    // Allow admin routes and static files
    if (!url.startsWith("/admin") && !url.startsWith("/public")) {
        return reply.status(503).view("maintenance.ejs", {
            title: "Under Maintenance",
            message: settings.maintenanceMessage || "We are currently performing maintenance.",
        });
    }
}
Administrators can still access the system during maintenance mode to perform updates or fixes.

Security Checklist

Pre-Deployment

1

Secrets

  • Generate strong SESSION_SECRET (32+ characters)
  • Set secure CONSENSUS_ADMIN_DEFAULT_PASSWORD
  • Store secrets in environment variables, not code
  • Add .env* to .gitignore
2

Configuration

  • Set NODE_ENV=production
  • Enable cookie.secure = true for HTTPS
  • Configure HTTPS reverse proxy
  • Set up automatic HTTPS redirects
3

Container

  • Run container as non-root user (default)
  • Use read-only root filesystem
  • Limit container capabilities
  • Scan image for vulnerabilities
4

Database

  • Set restrictive file permissions on database
  • Configure automated encrypted backups
  • Test backup restoration process

Post-Deployment

1

Initial Access

  • Login as admin immediately
  • Change default admin password
  • Create additional admin accounts if needed
  • Disable or delete default admin account
2

Monitoring

  • Monitor application logs for errors
  • Set up alerts for failed login attempts
  • Review audit logs regularly
  • Monitor database size and performance
3

Maintenance

  • Apply security updates promptly
  • Rotate SESSION_SECRET periodically
  • Review and update admin accounts
  • Test backup restoration quarterly

Audit Logging

Consensus includes built-in audit logging for election events:
src/services/observers/ElectionAuditLogger.ts
export class ElectionAuditLogger implements ElectionObserver {
    constructor(private auditLogRepository: AuditLogRepository) {}

    onElectionCreated(election: Election): void {
        // Log election creation
    }

    onElectionStarted(election: Election): void {
        // Log election start
    }

    onElectionClosed(election: Election): void {
        // Log election closure
    }
}
Audit logs are stored in the database and track all election lifecycle events. Review these logs regularly to detect unauthorized changes.

Security Monitoring

Failed Login Attempts

Monitor logs for repeated failed authentication:
# View recent failed logins
docker logs consensus 2>&1 | grep -i "login failed\|authentication failed"

# Count failed attempts by IP
docker logs consensus 2>&1 | \
  grep "login failed" | \
  awk '{print $NF}' | \
  sort | uniq -c | sort -rn

Database Access

Monitor database file access:
# Check database file permissions
docker exec consensus ls -lh /app/data/database.sqlite

# Monitor database size
watch -n 60 'docker exec consensus du -h /app/data/database.sqlite'

Container Security Events

# View security events in container logs
docker events --filter 'type=container' --filter 'container=consensus'

# Check for unexpected process execution
docker exec consensus ps aux

Incident Response

Suspected Breach

If you suspect a security breach:
1

Enable Maintenance Mode

Access the admin settings and enable maintenance mode to block voter access.
2

Backup Current State

docker cp consensus:/app/data/database.sqlite \
  ./breach-backup-$(date +%Y%m%d-%H%M%S).sqlite
3

Review Audit Logs

Check the audit logs for suspicious activity:
SELECT * FROM audit_logs 
ORDER BY created_at DESC 
LIMIT 100;
4

Rotate Secrets

Generate and deploy new SESSION_SECRET (invalidates all sessions).
5

Force Password Resets

Require all administrators to change passwords immediately.

Compliance Considerations

Data Protection

Consensus stores personal information (voter names, emails, passwords):
  • Encryption at Rest: Encrypt the database file or volume
  • Encryption in Transit: Always use HTTPS
  • Data Retention: Implement policies for deleting old election data
  • Access Controls: Limit admin access to authorized personnel only

Election Integrity

  • Vote Secrecy: Votes are anonymized after casting
  • Audit Trail: All election events are logged
  • Tamper Detection: Database integrity can be verified through backups
  • Access Logging: Monitor who accesses election results

Next Steps

Docker Deployment

Deploy Consensus using Docker

Configuration

Configure environment variables

Build docs developers (and LLMs) love