Security

Credential rotation, network hardening, security enforcement.

Overview

Security Overview

Credential rotation, network hardening, and security enforcement protocols.

Owner: John Last Verified: 2026-02-17

Contents

To be populated from ~/system/rules/security.md

Security Overview

Security Overview — BasicAS Group

Last Updated: 2026-02-10 Security Lead: John (AI Director) Approval Authority: Alem (CEO)


Executive Summary

This document provides a high-level overview of security practices, policies, and systems for BasicAS Group. It consolidates information from multiple security workstreams and serves as a navigation hub for detailed security documentation.


Security Posture

Current State (2026-02-10)

Risk Level: MEDIUM


Security Domains

1. Network Security

Status: Configuration ready, deployment blocked

Key Controls:

See: network-hardening.md


2. Credential Management

Status: Infrastructure complete, awaiting execution

Key Controls:

Services Covered:

See: credential-rotation.md


3. Application Security

Docker Services

Service Port External Access Auth Method
Mattermost 8065 Cloudflare Tunnel Password + optional MFA
Planka 3100 Cloudflare Tunnel Password
Documenso 3003 Cloudflare Tunnel Password
BookStack 6875 LAN only Password + API token
MC Dashboard 3030 LAN only No auth (trusted network)

Security Gaps:

Recommendations:

  1. Change all default passwords
  2. Enable MFA for external-facing services
  3. Add authentication to MC Dashboard
  4. Shorten token expiry periods
  5. Disable public signup where not needed

4. Data Security

Databases

All databases are:

Backups

Sensitive Data Storage


5. Access Control

User Management

User Role Services
Alem Admin All services (full access)
John (AI) System Admin CLI tools, read-only on critical files
Edita (AI) Assistant Limited access via John delegation
External users Team members Mattermost, Planka (invite-only)

API Access


6. Monitoring & Logging

Current Logging

Log Locations

Monitoring Gaps

Recommendation: Implement health check daemon + log aggregation (ELK stack or Loki)


7. Incident Response

Current Procedures

  1. Detection: Manual monitoring, user reports
  2. Triage: John investigates, escalates to Alem
  3. Containment: Stop affected service, isolate system
  4. Recovery: Restore from backup, rotate credentials
  5. Post-Mortem: Document in ~/system/reports/security/

Gaps

Recommendation: Create formal IRP + incident runbooks (task #323-326)


8. Compliance & Governance

Policies (In Development)

Compliance Frameworks


Security Roadmap

Immediate (Next 30 Days)

  1. Deploy network hardening - Alem to execute firewall + SSH disablement (15 min)
  2. Rotate all credentials - Follow credential-rotation.md process (30 min)
  3. Change default passwords - BookStack, Planka, Documenso (10 min)
  4. Enable Mattermost MFA - task #309 (30 min)
  5. Move Docker secrets - Extract from docker-compose.yml - task #310 (2 hours)

Short-Term (Next 90 Days)

  1. Automated backups - task #262 (cron + rsync to external disk)
  2. Health check daemon - task #259 (monitor all services)
  3. Security policies - tasks #323-326 (ISO 27001 alignment)
  4. Incident response plan - Formal IRP + runbooks
  5. MC Dashboard auth - Add password or token-based access

Long-Term (Next 180 Days)

  1. Central secrets vault - Migrate to HashiCorp Vault or 1Password
  2. Log aggregation - ELK stack or Loki + Grafana
  3. Intrusion detection - Deploy Snort or Suricata
  4. Zero-trust architecture - mTLS for service-to-service comms
  5. ISO 27001 certification - External audit + certification

Security Contacts

Internal

External


Security Tools & Resources

Tools In Use

Tools Planned


Security Documentation

Operational Documentation

Governance


Audit Log

Date Change Author Approval
2026-01-31 Credential rotation infrastructure built John Alem
2026-01-31 Network hardening config prepared John Alem
2026-02-10 Security docs consolidated John Pending
TBD Network hardening deployed Alem -
TBD Credentials rotated Alem -

Maintained by: John (AI Director) Reviewed by: Alem (CEO) Next Review: 2026-03-10 (monthly)

Credential Rotation Guide

Credential Rotation — Consolidated Guide

Last Updated: 2026-02-10 Status: Infrastructure Complete, Awaiting Execution Original Docs: Consolidated from 8 separate files (2026-01-31)


Overview

Comprehensive credential rotation system for BasicAS Group. Handles API keys, tokens, and service credentials across 5+ platforms.

Key Principle: Manual collection (30 min) → Automated storage (2 min) → Secure keychain


Why Manual Collection is Required

Technical Reality:

  1. All services require authenticated browser login
  2. No programmatic API exists to regenerate credentials without authentication
  3. Alem's login credentials not stored in system (by design)
  4. MFA/CAPTCHA may be required at some services
  5. OAuth flows require interactive user approval

Result: Automation handles storage and verification. Human handles credential generation.


Services Requiring Rotation

Service Type Location Rotation Frequency
Anthropic API Key console.anthropic.com/account/keys 90 days
ElevenLabs API Key elevenlabs.io/app/profile/api-keys 90 days
Telegram Bot Token @BotFather in Telegram app As needed
Discord Bot Token discord.com/developers/applications As needed
Z.ai API Key z.ai dashboard 90 days
one.com SMTP Password one.com webmail settings 180 days
Cloudflare API Token dash.cloudflare.com/profile/api-tokens 90 days

Current Credential Status (as of 2026-01-31)

✅ Found in System

❌ Not Found


Rotation Process

Step 1: Review Services (5 min)

Identify which credentials need rotation based on:

Step 2: Collect New Credentials (25-30 min)

Visit each service and generate new credentials:

Anthropic (5 min)

  1. Visit https://console.anthropic.com/account/keys
  2. Login with Alem's credentials
  3. Click "Create Key"
  4. Name: BasicAS-Production-YYYY-MM-DD
  5. Copy key (starts with sk-ant-api03-)
  6. Save to temp file

ElevenLabs (5 min)

  1. Visit https://elevenlabs.io/app/profile/api-keys
  2. Login with Alem's credentials
  3. Click "Generate API Key"
  4. Copy key (starts with sk_)
  5. Save to temp file

Telegram Bot (5 min)

  1. Open Telegram app
  2. Message @BotFather
  3. Command: /mybots
  4. Select bot → API Token → Regenerate Token
  5. Copy token (format: NNNNN:AAAAAAAA...)
  6. Save to temp file

Discord Bot (5 min)

  1. Visit https://discord.com/developers/applications
  2. Login with Alem's credentials
  3. Select application → Bot → Reset Token
  4. Copy token
  5. Save to temp file

Z.ai (5-10 min)

  1. Visit https://z.ai
  2. Login with Alem's credentials
  3. Navigate to API settings
  4. Generate new token
  5. Copy token
  6. Save to temp file

Step 3: Create Credential File (2 min)

Format: ~/temp-credentials.txt

ANTHROPIC_API_KEY=sk-ant-api03-...
ELEVENLABS_API_KEY=sk_...
TELEGRAM_BOT_TOKEN=1234567890:AAAAA...
DISCORD_BOT_TOKEN=...
ZAI_API_KEY=...

Step 4: Run Import Tool (2 min)

# Import to macOS Keychain + update configs
node ~/system/tools/credential-import.js ~/temp-credentials.txt

# Verify storage
security find-generic-password -s "ANTHROPIC_API_KEY"
security find-generic-password -s "ELEVENLABS_API_KEY"

Step 5: Update Service Configs (5 min)

The import tool auto-updates:

Manual verification required for:

Step 6: Test Services (10 min)

# Test Anthropic
curl -H "x-api-key: $ANTHROPIC_API_KEY" https://api.anthropic.com/v1/models

# Test ElevenLabs
curl -H "xi-api-key: $ELEVENLABS_API_KEY" https://api.elevenlabs.io/v1/user

# Test Telegram Bot
curl https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getMe

Step 7: Revoke Old Credentials (5 min)

Go back to each service and delete/revoke old credentials to prevent unauthorized use.


Automation Tool: credential-import.js

Location: ~/system/tools/credential-import.js (500+ lines) Status: ✅ READY TO USE (built 2026-01-31)

Features:

Usage:

node ~/system/tools/credential-import.js <credentials-file>

Output:


Security Notes

✅ Secure Practices

❌ Current Weaknesses

  1. Move Docker secrets: Extract passwords from docker-compose.yml to Docker secrets
  2. Automated expiry alerts: Script to check credential age and send alerts
  3. Central vault: Migrate to HashiCorp Vault or 1Password for centralized management
  4. Audit logging: Track who accessed which credential when

Compliance & Best Practices

Rotation Schedule

Documentation Requirements

Incident Response

If credential compromise suspected:

  1. Immediately rotate affected credential
  2. Audit logs for unauthorized usage
  3. Revoke old credential
  4. Document incident in ~/system/reports/security/
  5. Review access policies

Original Files (Archived)

All originals preserved in: ~/system/context/docs/security/ (timestamped)


Next Steps

  1. Schedule rotation: Add to calendar (quarterly for API keys)
  2. Execute rotation: Follow Step 1-7 above
  3. Document completion: Update this file with new dates
  4. Improve automation: Build expiry monitoring script

Maintained by: John (AI Director) Reviewed by: Alem (CEO) Next Review: 2026-05-10 (90 days)

Network Hardening

Last Verified: 2026-02-17 | Owner: John

Network Hardening — Consolidated Guide

Last Updated: 2026-02-10 System: Darwin 25.2.0 (M3 Mac Studio, 96GB RAM) Status: Configuration Complete, Deployment Blocked (see below) Original Docs: Consolidated from 2 separate files (2026-01-31)


Overview

Comprehensive network hardening and firewall lockdown configuration for macOS. Implements defense-in-depth strategy with application firewall, service disablement, and local-only access controls.


Hardening Strategy

Core Principles

Default-Deny: Block all external traffic by default ✅ Local-Only: Services bind to 127.0.0.1 or RFC 1918 (LAN) only ✅ Minimal Attack Surface: Disable unnecessary services ✅ Defense-in-Depth: Multiple layers of protection ✅ Logging: Track all network access attempts

Scope


Current Hardening Status

✅ Already Secured

⚠️ Requires Configuration

🚫 Deployment Blocker

CRITICAL: Full hardening requires sudo access. John (AI) cannot execute sudo commands without Alem's explicit approval and password entry.

Impact: Configuration scripts ready, but not yet applied.


Hardening Configuration

TASK 1: Enable macOS Firewall (ALF)

The Application Layer Firewall provides per-application traffic control.

Commands (requires sudo):

# Enable firewall globally
sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1

# Enable logging of blocked connections
sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -int 1

# Enable stealth mode (no response to ping/port scan)
sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -int 1

# Whitelist mode (allow only listed apps)
sudo defaults write /Library/Preferences/com.apple.alf allowdownloadsigned -int 1

Verification:

# Should return 1 for each:
defaults read /Library/Preferences/com.apple.alf globalstate
defaults read /Library/Preferences/com.apple.alf loggingenabled
defaults read /Library/Preferences/com.apple.alf stealthenabled

Logs:

# View firewall log
sudo log show --predicate 'subsystem == "com.apple.alf"' --last 1h

TASK 2: Disable SSH Remote Access

SSH is a common attack vector. Disable if not needed.

Commands (requires sudo):

# Check current status
sudo systemsetup -getremotelogin

# Disable SSH
sudo systemsetup -setremotelogin off

Verification:

sudo systemsetup -getremotelogin
# Expected: "Remote Login: Off"

TASK 3: Disable Bluetooth

Bluetooth can be exploited (BlueBorne, etc.). Disable if not needed.

Commands (requires sudo):

# Disable Bluetooth
sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 0

# Kill Bluetooth daemon to apply immediately
sudo killall -HUP blued

Verification:

defaults read /Library/Preferences/com.apple.Bluetooth ControllerPowerState
# Expected: 0 (disabled)

Re-enable (if needed):

sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 1
sudo killall -HUP blued

TASK 4: Restrict Service Bindings (Localhost Only)

Ensure services listen on 127.0.0.1 (local) or 192.168.x.x (LAN), never 0.0.0.0 (all interfaces).

Check Current Bindings:

sudo lsof -iTCP -sTCP:LISTEN -P -n

Key Services to Review:

Service Current Port Should Bind To
Mission Control Dashboard 3030 0.0.0.0 (LAN accessible, intended)
Mattermost 8065 0.0.0.0 (via Cloudflare, external)
Planka 3100 0.0.0.0 (via Cloudflare, external)
Documenso 3003 0.0.0.0 (via Cloudflare, external)
BookStack 6875 0.0.0.0 (LAN only, no tunnel yet)
PostgreSQL (Docker) 5432 127.0.0.1 (internal only)
Redis (Docker) 6379 127.0.0.1 (internal only)

Note: Services exposed via Cloudflare Tunnel (Mattermost, Planka, Documenso) listen on 0.0.0.0 but are protected by tunnel authentication.

Fix Services Listening on 0.0.0.0:

For Node.js apps (e.g., mc-dashboard.js):

// BAD: Listens on all interfaces
app.listen(3030);

// GOOD: Listens on localhost only
app.listen(3030, '127.0.0.1');

// BETTER: Configurable via environment
const HOST = process.env.HOST || '127.0.0.1';
app.listen(3030, HOST);

For Docker services:

# BAD: Exposed to all interfaces
ports:
  - "8065:8065"

# GOOD: Bound to localhost only
ports:
  - "127.0.0.1:8065:8065"

TASK 5: Verify System Integrity Protection (SIP)

SIP protects critical system files from tampering (even by root).

Check Status:

csrutil status

Expected: System Integrity Protection status: enabled.

If Disabled (DO NOT disable without reason):

  1. Reboot into Recovery Mode (hold Cmd+R during startup)
  2. Open Terminal
  3. Run: csrutil enable
  4. Reboot

Firewall Whitelist (Allowed Applications)

Once firewall is enabled, explicitly allow these applications:

Internal Services (LAN accessible)

External Services (via Cloudflare Tunnel)

Development Tools (localhost only)

How to add:

# Allow specific app through firewall
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add /opt/homebrew/bin/node
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --unblockapp /opt/homebrew/bin/node

Cloudflare Tunnel Security

External access is routed through Cloudflare Tunnel (zero-trust, no open ports).

Current Tunnels

Hostname Target Service
mm.basicconsulting.no localhost:8065 Mattermost
boards.basicconsulting.no localhost:3100 Planka
sign.basicconsulting.no localhost:3003 Documenso

Tunnel Benefits

Tunnel Config

Location: ~/.cloudflared/config.yml

Status Check:

cloudflared tunnel list
cloudflared tunnel info <tunnel-name>

Network Access Logging

macOS Firewall Logs

# Real-time monitoring
sudo log stream --predicate 'subsystem == "com.apple.alf"'

# Last hour of blocks
sudo log show --predicate 'subsystem == "com.apple.alf"' --last 1h --style compact

Connection Tracking

# Active connections
sudo lsof -i -P -n | grep LISTEN

# Who's connected to what
sudo lsof -i -P -n | grep ESTABLISHED

Docker Container Logs

# Service-specific logs
docker logs mattermost --tail 100
docker logs planka --tail 100
docker logs documenso --tail 100

Deployment Blocker (CRITICAL)

Why Hardening Not Yet Applied

Technical Reason: All hardening commands require sudo (superuser) privileges. John (AI agent) does not have:

  1. Password for sudo access
  2. Authorization to execute system-level changes
  3. Ability to modify macOS security settings

Security By Design: This is intentional. AI agents should NOT have root access. All system hardening must be:

Current State:

Deployment Instructions for Alem

  1. Review this document (understand what each command does)
  2. Backup current config:
    sudo /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate > ~/pre-hardening-firewall-state.txt
    sudo systemsetup -getremotelogin > ~/pre-hardening-ssh-state.txt
    
  3. Execute TASK 1-5 (copy-paste commands, enter password when prompted)
  4. Verify each task (run verification commands)
  5. Test services (ensure Mission Control, Docker, Cloudflare still work)
  6. Document completion (update this file with deployment date)

Estimated Time: 15-20 minutes


Rollback Procedure

If hardening causes issues:

Disable Firewall

sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 0

Re-enable SSH

sudo systemsetup -setremotelogin on

Re-enable Bluetooth

sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 1
sudo killall -HUP blued

Original Files (Archived)

All originals preserved in: ~/system/context/docs/security/ (timestamped)


Compliance & Best Practices

Industry Standards

Audit Checklist


Next Steps

  1. Alem deployment: Execute hardening commands (15-20 min)
  2. Verification: Run post-deployment checks
  3. Documentation: Update this file with completion date
  4. Monitoring: Set up monthly log review
  5. Improvements: Consider additional hardening (see below)

Future Enhancements


Maintained by: John (AI Director) Deployed by: Alem (CEO) - AWAITING DEPLOYMENT Next Review: After deployment + 30 days

Audits & Reports

Audits & Reports

Security Audit

Drop Security Audit (originally FontelePay)

Rebrand note: FontelePay was renamed to Drop. This audit predates the backend implementation and PSD2 pass-through architecture. Many issues identified here (localStorage PIN, client-side auth) have been addressed in the current backend. See docs/security/SECURITY-ARCHITECTURE.md for current security posture.

Date: 2026-02-05 Auditor: John (AI Director) Scope: Full MVP application scan (pre-backend, client-side only)


Executive Summary

Category Status Issues
Dependencies ✅ PASS 0 vulnerabilities
Hardcoded Secrets ✅ PASS None found
XSS Prevention ✅ PASS No dangerous patterns
Data Storage ⚠️ WARNING PIN in localStorage
Authentication ⚠️ WARNING Client-side only
Input Validation ⚠️ WARNING Minimal validation

Overall: ACCEPTABLE FOR MVP DEMO, NOT PRODUCTION READY


1. Dependency Audit

$ npm audit
found 0 vulnerabilities

✅ All npm packages are up to date with no known vulnerabilities.


2. Secret Management

Checked For:

Findings:

✅ No hardcoded secrets in source code ✅ No .env files committed ✅ Mock tokens are clearly labeled as mocks

Recommendation:


3. XSS Prevention

Checked For:

Findings:

✅ No dangerous DOM manipulation patterns found ✅ React's built-in XSS protection active


4. Data Storage (CRITICAL)

Issue: PIN Stored in localStorage

// AppContext.tsx:87
localStorage.setItem(STORAGE_KEY, JSON.stringify(user));
// user object includes plaintext PIN!

Risk Level: HIGH for production

Why It Matters:

Fix Required for Production:

// Option 1: Never store PIN client-side
// Use server-side session + httpOnly cookies

// Option 2: Store hashed PIN (still not ideal)
import bcrypt from 'bcryptjs';
const hashedPin = await bcrypt.hash(pin, 10);

// Option 3: Use Web Crypto API
const encoder = new TextEncoder();
const data = encoder.encode(pin);
const hash = await crypto.subtle.digest('SHA-256', data);

Immediate Mitigation:


5. Authentication Analysis

Current State:

OWASP Top 10 Review:

Vulnerability Status Notes
A01 Broken Access Control ⚠️ No server-side checks
A02 Cryptographic Failures PIN not hashed
A03 Injection React prevents XSS
A04 Insecure Design ⚠️ Client-only auth
A05 Security Misconfiguration N/A for static
A06 Vulnerable Components npm audit clean
A07 Auth Failures ⚠️ No rate limiting
A08 Data Integrity No deserialization
A09 Logging Failures ⚠️ No audit logging
A10 SSRF No server requests

6. Input Validation

Checked Areas:

Input Validation Status
Phone number Length >= 6 ⚠️ Weak
OTP Length === 6 ⚠️ No server verify
PIN Length === 4 ✅ OK
Transfer amount > 0, <= balance ✅ OK
IBAN None ⚠️ Should validate format
Card number Length === 16 ⚠️ No Luhn check

Recommendations:


7. Production Readiness Checklist

Must Have Before Launch:

Nice to Have:


8. Recommendations Summary

Immediate (Before any real users):

  1. Move authentication to server-side
  2. Hash PINs with bcrypt
  3. Add rate limiting

Short-term (Before production):

  1. Implement proper session management
  2. Add audit logging
  3. Setup error monitoring
  4. Add input validation

Long-term (For compliance):

  1. PCI-DSS audit for card handling
  2. Penetration testing
  3. Security certification

Conclusion

The Drop MVP (originally FontelePay) is acceptable for demonstration purposes but requires significant security improvements before handling real user data or money.

Key Risk: Plaintext PIN storage in localStorage

Mitigation: This is a mock/demo environment with no real financial transactions. All "money" is simulated.

Next Steps: Implement server-side auth before any production deployment.


Audit completed by automated security scan + manual code review.

Audits & Reports

Security QA Audit

Drop App Security & QA Audit Report

Date: 2026-02-11 Auditor: John (AI Director) Scope: Next.js 16 fintech app with SQLite database, JWT auth, 24 API routes

HISTORICAL NOTE (2026-03-03): This audit was performed against the pre-ADR-014 codebase which used SQLite + dual-driver. The SQLite backend and better-sqlite3 dependency have since been removed. Current architecture: PostgreSQL 16 (all environments) + Drizzle ORM (ADR-014). SQLite-specific findings and the "SQLite in production" recommendation are resolved. Total API Code: 1,102 lines


Executive Summary

Drop is a payment application for all residents in Norway/Scandinavia, with remittance and QR payment features. Drop uses a PSD2 pass-through model — it never holds customer money. This audit reviewed all source code against OWASP Top 10 security risks, authentication/authorization implementation, database security, and code quality.

Overall Risk Level: MEDIUM-HIGH Critical Issues: 3 High Priority Issues: 7 Medium Priority Issues: 5 Low Priority Issues: 4

The application has solid foundations (bcrypt password hashing, parameterized SQL queries, JWT authentication) but has critical gaps in production readiness including hardcoded secrets, missing CSRF protection, exposed sensitive data, and insufficient rate limiting.


CRITICAL ISSUES (Fix Immediately)

1. JWT Secret Hardcoded with Weak Default

File: src/lib/auth.ts:5 Risk: Authentication bypass, token forgery Issue:

const jwtSecretRaw = process.env.JWT_SECRET || "drop-dev-secret-DO-NOT-USE-IN-PROD";

Default fallback is a publicly visible hardcoded string. While there's a warning logged, the app will still start in production with this weak secret.

Impact: Attacker can forge valid JWT tokens and impersonate any user if JWT_SECRET env var is not set.

Fix:

const jwtSecretRaw = process.env.JWT_SECRET;
if (!jwtSecretRaw) {
  throw new Error("FATAL: JWT_SECRET environment variable is required");
}
const JWT_SECRET = new TextEncoder().encode(jwtSecretRaw);

Recommendation: NEVER allow fallback secrets. Fail fast and loud.


2. Full Card Numbers and CVV Exposed via API

File: src/app/api/cards/[id]/route.ts:28-31 Risk: PCI-DSS violation, credential theft Issue:

data: {
  cardNumber: card.card_number,  // FULL 16-digit number
  cvv: card.cvv,                 // CVV exposed
}

The GET endpoint returns the full card number and CVV in plaintext. This violates PCI-DSS compliance and enables account takeover if tokens are compromised.

Impact:

Fix:

data: {
  lastFour: card.last_four,     // Only last 4 digits
  expiry: card.expiry,
  // NEVER expose full cardNumber or CVV via GET
}

Recommendation:


3. Insufficient Balance Validation (Race Condition Risk)

File: src/app/api/transactions/remittance/route.ts:68-74 Risk: Negative balance, double-spend Issue:

const result = db.prepare(
  "UPDATE users SET balance = balance - ? WHERE id = ? AND balance >= ?"
).run(total, u.id, total);
if (result.changes === 0) {
  throw new Error("insufficient_balance");
}

While there's optimistic locking, concurrent requests can still cause race conditions because:

  1. Rate limiting is per-IP (10 req/min), not per-user
  2. User can send 10 parallel requests before first one completes
  3. Each checks balance BEFORE deduction

Impact: User can overdraw account by sending multiple remittances simultaneously.

Fix:

// Add per-user transaction lock
const userLocks = new Map<string, Promise<void>>();

export async function POST(request: NextRequest) {
  const { user, error } = await requireAuth();
  if (error) return error;

  const userId = (user as Record<string, unknown>).id as string;

  // Wait for previous transaction from same user
  if (userLocks.has(userId)) {
    await userLocks.get(userId);
  }

  const txPromise = (async () => {
    // ... transaction logic
  })();

  userLocks.set(userId, txPromise);
  try {
    return await txPromise;
  } finally {
    userLocks.delete(userId);
  }
}

Recommendation: Implement per-user transaction serialization or use database-level row locking.


HIGH PRIORITY ISSUES (Fix Before Deployment)

4. No CSRF Protection

Files: All API routes Risk: Cross-Site Request Forgery attacks Issue:

Impact: Attacker can trick authenticated user into making unwanted transactions via malicious website.

Attack Vector:

<!-- Attacker's site -->
<form action="https://drop.app/api/transactions/remittance" method="POST">
  <input name="recipientId" value="attacker_recipient">
  <input name="amount" value="50000">
</form>
<script>document.forms[0].submit()</script>

Fix: Implement Next.js CSRF protection:

// middleware.ts (create in root)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // CSRF check for state-changing methods
  if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method)) {
    const origin = request.headers.get('origin');
    const host = request.headers.get('host');

    // Block requests from different origins
    if (origin && !origin.endsWith(host || '')) {
      return new NextResponse('CSRF validation failed', { status: 403 });
    }
  }
  return NextResponse.next();
}

export const config = {
  matcher: '/api/:path*',
};

Recommendation: Add CSRF token validation or strict origin checking.


5. In-Memory Rate Limiter Resets on Process Restart

File: src/lib/middleware.ts:5 Risk: Rate limit bypass, brute force attacks Issue:

const rateLimitMap = new Map<string, { count: number; resetAt: number }>();

Rate limits are stored in process memory. Resets on every deployment or crash.

Impact:

Fix: Use persistent rate limiting:

// Option 1: Redis (production)
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);

export async function rateLimit(ip: string, limit: number, windowMs: number): Promise<boolean> {
  const key = `ratelimit:${ip}`;
  const count = await redis.incr(key);
  if (count === 1) {
    await redis.pexpire(key, windowMs);
  }
  return count <= limit;
}

// Option 2: SQLite (MVP fallback)
db.prepare(`
  CREATE TABLE IF NOT EXISTS rate_limits (
    ip TEXT PRIMARY KEY,
    count INTEGER,
    reset_at INTEGER
  )
`);

Recommendation: Migrate to Redis or database-backed rate limiting before production.


6. Weak Password Hashing for Demo User (Migration Risk)

File: src/lib/db.ts:141 Risk: Account compromise Issue:

// SHA-256 of "demo1234" — NOT production-safe, just for MVP demo
"0ead2060b65992dca4769af601a1b3a35ef38cfad2c2c465bb160ea764157c5d",

Demo user password is SHA-256 (unsalted, fast hash). While login endpoint auto-migrates to bcrypt on successful login, this creates a window where:

  1. If demo credentials leak, password is crackable
  2. Migration only happens on successful login, so if user never logs in, weak hash remains

Impact: Demo/legacy accounts vulnerable to rainbow table attacks.

Fix:

// Remove demo user from seed data
// OR force migration on app startup
function migrateWeakHashes(db: Database.Database) {
  const weak = db.prepare(
    "SELECT id, email FROM users WHERE LENGTH(password_hash) = 64"
  ).all();

  for (const user of weak) {
    console.warn(`User ${user.email} has weak hash - forcing password reset`);
    // Option 1: Delete weak accounts
    // Option 2: Mark as requiring password reset
    db.prepare("UPDATE users SET kyc_status = 'locked' WHERE id = ?").run(user.id);
  }
}

Recommendation: Remove demo user from production seed, or pre-hash with bcrypt.


7. No Content-Security-Policy Headers

Files: All routes Risk: XSS attacks, clickjacking Issue: No CSP headers set. While no dangerouslySetInnerHTML was found, defense-in-depth requires CSP.

Impact:

Fix: Add CSP headers in next.config.ts:

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: [
              "default-src 'self'",
              "script-src 'self' 'unsafe-inline' 'unsafe-eval'", // Next.js requires unsafe-inline/eval in dev
              "style-src 'self' 'unsafe-inline'",
              "img-src 'self' data: https:",
              "font-src 'self' data:",
              "connect-src 'self'",
              "frame-ancestors 'none'",
            ].join('; '),
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
          {
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin',
          },
        ],
      },
    ];
  },
};

Recommendation: Implement strict CSP before production deployment.


8. SQL Injection Risk in Dynamic WHERE Clause

File: src/app/api/merchants/dashboard/route.ts:22-28 Risk: SQL injection (low probability, but present) Issue:

let dateOffset: string;
if (period === "week") {
  dateOffset = "-7 days";
} else if (period === "month") {
  dateOffset = "-30 days";
} else {
  dateOffset = "start of day";
}

const stats = db.prepare(`
  ... WHERE ... AND created_at >= datetime('now', ?)
`).get(merchant.id, dateOffset);

While there's a whitelist check, the dateOffset variable is passed directly to SQLite's datetime() function. SQLite's datetime accepts arbitrary strings, and malicious input could cause unexpected behavior.

Current State: The whitelist prevents injection (period is checked), but code is fragile.

Fix: Use parameterized datetime calculations:

const periodMap = {
  today: 0,
  week: 7,
  month: 30,
} as const;

const days = periodMap[period as keyof typeof periodMap] ?? 0;

const stats = db.prepare(`
  SELECT ...
  FROM transactions
  WHERE merchant_id = ?
    AND type = 'qr_payment'
    AND status = 'completed'
    AND julianday('now') - julianday(created_at) <= ?
`).get(merchant.id, days);

Recommendation: Avoid passing strings to SQL datetime functions. Use numeric calculations.


9. Insufficient Input Validation on Amount Fields

Files: Multiple transaction endpoints Risk: Business logic bypass, financial loss Issue:

// remittance: amount must be 100-50,000
// top-up: amount must be 0-100,000
// qr-payment: amount >= 1

Issues:

  1. No validation for negative amounts (though DB would reject, better to fail fast)
  2. No validation for decimal precision (could cause rounding errors)
  3. Top-up has NO payment verification — user can add unlimited NOK to balance
  4. No daily/monthly transaction limits

Fix:

// 1. Validate amount precision
function validateAmount(amount: number): string[] {
  const errors: string[] = [];
  if (amount < 0) errors.push("Amount cannot be negative");
  if (!Number.isFinite(amount)) errors.push("Amount must be a valid number");
  if (Math.round(amount * 100) !== amount * 100) {
    errors.push("Amount can only have 2 decimal places");
  }
  return errors;
}

// 2. Add transaction limits per user
const dailyLimit = db.prepare(`
  SELECT COALESCE(SUM(amount), 0) as total
  FROM transactions
  WHERE user_id = ? AND type = 'remittance' AND created_at >= date('now')
`).get(userId) as { total: number };

if (dailyLimit.total + amount > 50000) {
  return jsonError("limit_exceeded", "Daily remittance limit (50,000 NOK) exceeded", 429);
}

// 3. Top-up requires payment verification
// CRITICAL: Remove mock top-up, integrate real payment gateway

Recommendation: Add comprehensive amount validation and real payment integration for top-up.


10. No Audit Logging for Sensitive Operations

Files: All transaction routes Risk: Compliance violation, no forensics Issue: No audit trail for:

Impact:

Fix:

// Create audit log table
CREATE TABLE audit_logs (
  id TEXT PRIMARY KEY,
  user_id TEXT,
  ip TEXT,
  action TEXT,  -- 'login', 'transaction.create', 'card.freeze', etc.
  entity_type TEXT,
  entity_id TEXT,
  old_value TEXT,  -- JSON snapshot before change
  new_value TEXT,  -- JSON snapshot after change
  timestamp TEXT DEFAULT (datetime('now'))
);

// Log every sensitive operation
function auditLog(userId: string, ip: string, action: string, details: object) {
  db.prepare(`
    INSERT INTO audit_logs (id, user_id, ip, action, new_value)
    VALUES (?, ?, ?, ?, ?)
  `).run(randomId('audit'), userId, ip, action, JSON.stringify(details));
}

// Usage
auditLog(user.id, getClientIp(request), 'transaction.remittance.create', {
  amount, recipientId, txId
});

Recommendation: Implement comprehensive audit logging before handling real money.


MEDIUM PRIORITY ISSUES (Fix in Next Sprint)

11. Database File Location Not Configurable

File: src/lib/db.ts:5 Risk: Backup/restore complexity, data loss Issue:

const DB_PATH = path.join(process.cwd(), "drop.db");

Database stored in app directory. In production, this could be ephemeral (e.g., Docker container, serverless).

Fix:

const DB_PATH = process.env.DATABASE_PATH || path.join(process.cwd(), "drop.db");

Recommendation: Use external volume or managed database in production.


12. Mock Data File Still Present

File: src/lib/mock-data.ts Risk: Confusion, accidental use Issue: Mock data file exists but is unused (grep confirms no imports). Should be removed to avoid future mistakes.

Fix: Delete file or move to tests/fixtures/ if needed for testing.

Recommendation: Remove unused code before deployment.


13. Health Check Does Not Test Foreign Keys

File: src/app/api/health/route.ts:7 Risk: False positive health status Issue:

const result = db.prepare("SELECT 1 as ok").get() as { ok: number };

Health check only tests basic connectivity. Does not verify:

Fix:

export async function GET() {
  try {
    const db = getDb();

    // Test basic query
    const basic = db.prepare("SELECT 1 as ok").get() as { ok: number };

    // Test foreign keys enabled
    const fk = db.pragma("foreign_keys", { simple: true });

    // Test table existence
    const tables = db.prepare(
      "SELECT COUNT(*) as c FROM sqlite_master WHERE type='table'"
    ).get() as { c: number };

    return NextResponse.json({
      status: "ok",
      db: basic.ok === 1 ? "connected" : "error",
      foreignKeys: fk === 1 ? "enabled" : "DISABLED",
      tables: tables.c,
      timestamp: new Date().toISOString(),
      version: "1.0.0",
    });
  } catch (error) {
    return NextResponse.json(
      {
        status: "error",
        db: "disconnected",
        error: (error as Error).message,
        timestamp: new Date().toISOString()
      },
      { status: 503 }
    );
  }
}

Recommendation: Enhance health check to verify critical database state.


14. No Email Validation Beyond Basic @ Check

File: src/app/api/auth/register/route.ts:26 Risk: Invalid emails in database Issue:

if (!email || typeof email !== "string" || !email.includes("@"))
  errors.push("Valid email required");

Accepts invalid emails like user@, @domain.com, user space@domain.com.

Fix:

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email || !emailRegex.test(email)) {
  errors.push("Valid email required");
}

// Better: use email-validator package
import validator from 'validator';
if (!validator.isEmail(email)) {
  errors.push("Valid email required");
}

Recommendation: Use proper email validation library.


15. Card Number Generation Not Luhn-Valid

File: src/app/api/cards/route.ts:49 Risk: Card validation failures Issue:

const cardNumber = "4532" + Array.from({ length: 12 }, () =>
  Math.floor(Math.random() * 10)
).join("");

Generates random digits without Luhn checksum. While this is a virtual card, some payment processors validate Luhn algorithm.

Fix:

function generateLuhnValid(prefix: string, length: number): string {
  const digits = prefix.split('').map(Number);

  // Generate random digits (all but last)
  while (digits.length < length - 1) {
    digits.push(Math.floor(Math.random() * 10));
  }

  // Calculate Luhn checksum
  let sum = 0;
  let parity = (length - 1) % 2;
  for (let i = 0; i < length - 1; i++) {
    let digit = digits[i];
    if (i % 2 === parity) {
      digit *= 2;
      if (digit > 9) digit -= 9;
    }
    sum += digit;
  }

  const checksum = (10 - (sum % 10)) % 10;
  digits.push(checksum);

  return digits.join('');
}

const cardNumber = generateLuhnValid("4532", 16);

Recommendation: Generate Luhn-valid card numbers for compatibility.


LOW PRIORITY ISSUES (Nice to Have)

16. Error Messages Leak Implementation Details

Files: Various API routes Risk: Information disclosure Issue: Error messages like "insufficient_balance", "not_found" help attackers enumerate valid IDs.

Recommendation: Use generic error messages for client, detailed logs server-side.


17. No Request ID for Debugging

Risk: Difficult to correlate logs Recommendation: Add request ID to all API responses:

export function jsonError(error: string, message: string, status: number) {
  const requestId = crypto.randomUUID();
  console.error(`[${requestId}] ${error}: ${message}`);
  return NextResponse.json({
    error,
    message,
    requestId
  }, { status });
}

18. TypeScript Strict Mode Enabled But Type Assertions Used

File: tsconfig.json:7 Risk: Runtime type errors Issue: Strict mode enabled (good), but extensive use of as Record<string, unknown> bypasses type safety.

Recommendation: Define proper TypeScript interfaces for database models.


19. No Password Complexity Requirements

File: src/app/api/auth/register/route.ts:27 Risk: Weak passwords Issue: Only requires 8 characters, no complexity rules.

Recommendation:

function validatePassword(password: string): string[] {
  const errors: string[] = [];
  if (password.length < 12) errors.push("Password must be at least 12 characters");
  if (!/[A-Z]/.test(password)) errors.push("Password must contain uppercase letter");
  if (!/[a-z]/.test(password)) errors.push("Password must contain lowercase letter");
  if (!/[0-9]/.test(password)) errors.push("Password must contain number");
  return errors;
}

CODE QUALITY OBSERVATIONS

Positive

  1. Parameterized SQL Queries - All queries use ? placeholders (GOOD)
  2. bcrypt for Password Hashing - 12 rounds, industry standard (GOOD)
  3. JWT with jose Library - Modern, secure JWT implementation (GOOD)
  4. httpOnly Cookies - Prevents XSS token theft (GOOD)
  5. Foreign Keys Enabled - Data integrity enforced (GOOD)
  6. WAL Mode - Better concurrency for SQLite (GOOD)
  7. Database Transactions - Atomic balance updates (GOOD)
  8. TypeScript Strict Mode - Type safety enabled (GOOD)
  9. No dangerouslySetInnerHTML - XSS prevention (GOOD)

Needs Improvement

  1. No TypeScript Interfaces for DB Models - Excessive type assertions
  2. No Error Boundary - Unhandled exceptions may leak stack traces
  3. No Logging Framework - Only console.error in auth.ts
  4. No Monitoring/Metrics - No instrumentation for performance tracking
  5. No API Documentation - No OpenAPI/Swagger spec
  6. No Tests - No unit/integration tests found
  7. Mixed Concerns - db.ts contains both schema and seed data

DEPENDENCY AUDIT

Current Versions (2026-02-11)

Outdated Packages (Non-Critical)

Recommendation: Update React to 19.2.4 for bug fixes. Defer @types/node and eslint major updates until tested.

Known Vulnerabilities

Run npm audit to check for CVEs. No critical vulnerabilities detected in package.json review.


DEPLOYMENT READINESS CHECKLIST

BLOCKER ISSUES (must fix before production):

HIGH PRIORITY (fix before MVP launch):


SECURITY SCORE

Category Score Notes
Authentication 6/10 Good JWT + bcrypt, but weak secret fallback
Authorization 7/10 Proper user-scoped queries, but no RBAC beyond user/merchant
Injection 9/10 All parameterized, minor dateOffset risk
XSS 8/10 No dangerouslySetInnerHTML, but no CSP
CSRF 2/10 SameSite=strict helps, but no CSRF tokens
Data Exposure 4/10 CVV exposed, no audit logs
Rate Limiting 5/10 Implemented but in-memory
Cryptography 8/10 Strong bcrypt, secure JWT, but secrets management issues
Configuration 5/10 Hardcoded paths, missing env vars
Logging 3/10 Minimal logging, no audit trail

Overall Security Score: 57/100 (MEDIUM)


RECOMMENDATIONS SUMMARY

Immediate Actions (Before Production)

  1. Remove JWT_SECRET fallback, fail if not set
  2. Never expose CVV via API
  3. Implement CSRF protection (origin checking or tokens)
  4. Migrate rate limiting to Redis/database
  5. Add audit logging for compliance
  6. Set CSP headers in next.config.ts
  7. Remove or properly hash demo user password

Next Sprint

  1. Add comprehensive input validation
  2. Implement transaction limits (daily/monthly)
  3. Add per-user transaction locks
  4. Write security tests
  5. Document all API endpoints
  6. Set up error monitoring (Sentry, etc.)

Future Enhancements

  1. Implement 2FA for high-value transactions
  2. Add fraud detection rules
  3. Encrypt sensitive data at rest
  4. Set up automated security scanning (Snyk, Dependabot)
  5. Conduct penetration testing before launch

CONCLUSION

Drop has a solid technical foundation with proper use of bcrypt, parameterized SQL, and JWT authentication. However, it is NOT production-ready due to:

  1. CRITICAL: Hardcoded secret fallbacks
  2. CRITICAL: Exposed sensitive payment data (CVV)
  3. CRITICAL: Missing CSRF protection
  4. HIGH: In-memory rate limiting (easily bypassed)
  5. HIGH: No audit logging (compliance risk)

Estimated Remediation Time:

Sign-off: This audit was conducted on source code only. A runtime penetration test is recommended before handling real financial transactions.


Audit Report Generated: 2026-02-11 Next Review: After critical fixes implemented

Audits & Reports

Compliance Overview

Drop Compliance Status

Last updated: 2026-02-13 Source: legal/ directory (16 regulatory documents), security/ directory (5 security documents), legal/drop-gap-analysis-v2.md, legal/drop-regulatory-map-v2.md


Overall Compliance Readiness: 8/100

Drop is an MVP-stage application using a PSD2 pass-through model (AISP reads bank balances, PISP initiates payments — Drop never holds customer money). Regulatory compliance is not expected at this stage, but documentation is being prepared. Cards are a FUTURE feature, gated behind feature flags.


Regulatory Framework

Applicable Regulations

Regulation Norwegian Law Relevance
PSD2 Betalingstjenesteloven (LOV-2018-11-23-85) Core -- payment services regulation
AML/KYC Hvitvaskingsloven (LOV-2018-06-01-23) Core -- anti-money laundering
GDPR Personopplysningsloven (LOV-2018-06-15-38) Core -- personal data protection
ICT Security IKT-forskriften / DORA Required for financial enterprises
Financial Enterprise Finansforetaksloven (LOV-2015-04-10-17) Licensing and governance
Currency Registry Valutaregisterloven Cross-border payment reporting
Consumer Protection Finansavtaleloven Terms and user rights

Source: legal/drop-regulatory-map-v2.md:1-80


Compliance Readiness by Area

1. Licensing (0% ready)

Source: legal/drop-gap-analysis-v2.md:31-50

Requirement Status Gap
Finanstilsynet license Not applied FULL GAP
Client fund safeguarding Not applicable (demo) FULL GAP
Initial capital (20K-125K EUR) Not secured FULL GAP
Business plan Exists as draft PARTIAL
Agent arrangement None FULL GAP

2. PSD2 / SCA (10% ready)

Source: legal/drop-gap-analysis-v2.md:53-78

Requirement Status Code Reference
Strong Customer Authentication NOT IMPLEMENTED No BankID, email+password only
BankID integration NOT IMPLEMENTED Mentioned in architecture, not in code
Dynamic linking NOT IMPLEMENTED No amount+payee tied to auth
Open Banking AISP/PISP NOT IMPLEMENTED Balance is local, not from bank
Framework agreement PARTIAL Landing page has vilkar.html
Fee transparency pre-auth PARTIAL Fee shown in API after submission
Session management IMPLEMENTED lib/auth.ts, lib/middleware.ts

3. AML/KYC (5% ready)

Requirement Status Gap
Customer identification Mock only Auto-approve KYC
Transaction monitoring NOT IMPLEMENTED No monitoring system
Suspicious activity reporting NOT IMPLEMENTED No SAR capability
Risk assessment Document exists legal/risikovurdering-hvitvasking.md
AML procedures Document exists legal/hvitvaskingsrutiner.md

4. GDPR (15% ready)

Requirement Status Document
Privacy notice EXISTS (draft) legal/personvernerklaering.md
DPIA EXISTS (draft) legal/dpia-vurdering.md
Terms of service EXISTS (draft) legal/brukervilkar.md
Processing register NOT CREATED --
DPO appointment NOT DONE --
Data retention policy NOT DEFINED --
Consent management NOT IMPLEMENTED --

5. ICT Security (25% ready)

Source: security/security-rapport-2026-02-12.md:187-188

Requirement Status Document/Code
Security policy EXISTS (draft) legal/ikt-sikkerhetspolicy.md
Incident handling EXISTS (draft) legal/hendelseshaandtering.md
Business continuity EXISTS (draft) legal/beredskapsplan.md
Outsourcing policy EXISTS (draft) legal/utkontraktering-policy.md
Security audit COMPLETED security/drop-security-rapport.md
Penetration testing NOT DONE --
Security hardening IN PROGRESS security/security-hardening-implementation.md

Security Audit Summary

Date: 2026-02-12 Source: security/drop-security-rapport.md

Before Hardening

After Hardening (2026-02-13)

Source: security/security-hardening-implementation.md

Key Remediations Completed

  1. C1 -- Card data: Schema now stores only last_four and token_ref (no PAN/CVV)
  2. C2 -- Demo credentials: Gated behind NODE_ENV !== 'production'
  3. C4 -- SHA-256 passwords: Removed entirely, bcrypt only
  4. C6/H1 -- Session revocation: Implemented and active
  5. H4 -- Input sanitization: Applied to all text fields
  6. M5 -- Notification IDs: Validated (max 100, format check)
  7. M6 -- Settings: Currency/language validated against whitelists

Location: ~/ALAI/products/Drop/legal/

Document File Status
Privacy notice personvernerklaering.md Draft
DPIA assessment dpia-vurdering.md Draft
Terms of service brukervilkar.md Draft
AML procedures hvitvaskingsrutiner.md Draft
AML risk assessment risikovurdering-hvitvasking.md Draft
ICT security policy ikt-sikkerhetspolicy.md Draft
Incident handling hendelseshaandtering.md Draft
Business continuity beredskapsplan.md Draft
Outsourcing policy utkontraktering-policy.md Draft
Internal control internkontroll.md Draft
Suitability assessment egnethetsvurdering.md Draft
Complaint handling klagebehandling.md Draft
Licensing preparation konsesjonssoknad-forberedelse.md Draft
Business plan virksomhetsplan.md Draft
Gap analysis v2 drop-gap-analysis-v2.md Complete
Regulatory map v2 drop-regulatory-map-v2.md Complete

Security Documents Inventory

Location: ~/ALAI/products/Drop/security/

Document File Status
Security audit rapport drop-security-rapport.md Complete (2026-02-12)
Gap analysis gap-analysis.md Complete (2026-02-12)
Hardening checklist hardening-checklist.md In progress
Hardening implementation security-hardening-implementation.md Complete (2026-02-13)
Formal assessment security-rapport-2026-02-12.md Complete (2026-02-12)

Remediation Phases

Phase 1 -- Current Sprint (in progress)

Security fixes, architecture cleanup, test suite, CI/CD.

Phase 2 -- Banking Integration (pending partner selection)

BankID, Open Banking AISP/PISP, real KYC, PostgreSQL migration.

Phase 3 -- Production Launch (after Phase 2 + follow-up audit)

Audit logging, error handling, monitoring, staging environment, load testing, external penetration test.

Source: security/security-rapport-2026-02-12.md:196-220

Audits & Reports

Security Architecture

Drop Security Architecture

Last updated: 2026-02-14 Source: Security audit (security/drop-security-rapport.md), hardening implementation (security/security-hardening-implementation.md), source code

Architecture model: Drop uses a PSD2 pass-through model. It never holds customer money — AISP reads bank balances via Open Banking, PISP initiates payments from the user's bank account. Cards are a FUTURE feature, gated behind feature flags (all default to false).


Authentication

JWT Token Management

Library: jose ^6.1.3 (well-maintained, no known vulnerabilities) Algorithm: HS256 with explicit setProtectedHeader Source: src/drop-app/src/lib/auth.ts

Setting Value Source
Algorithm HS256 auth.ts
Expiry 24 hours auth.ts cookie maxAge: 60 * 60 * 24
setIssuedAt() Yes Prevents token reuse
Secret (production) JWT_SECRET env var Fatal error if missing
Secret (development) process.cwd() hash Dev fallback for uniqueness

Source: src/drop-app/src/lib/auth.ts:48-54

Property Value Purpose
httpOnly true Prevents JavaScript access (XSS mitigation)
secure true (production) HTTPS-only cookie transport
sameSite "strict" CSRF prevention
maxAge 24 hours Session lifetime
path "/" Cookie scope

Password Hashing

Library: bcryptjs ^3.0.3 (pure JS, no native compilation issues) Source: src/drop-app/src/lib/utils-server.ts:8-16

Setting Value
Algorithm bcrypt
Cost factor 12
SHA-256 fallback Removed (security fix C4)

After hardening, verifyPassword() only accepts bcrypt hashes. SHA-256 legacy support has been removed entirely.

Session Management

Source: src/drop-app/src/lib/auth.ts:45-65, src/lib/middleware.ts:42-77

Sessions are tracked in the sessions table:

Column Type Purpose
id TEXT PK Session identifier (format: ses_<hex16>)
user_id TEXT FK References users.id
token_hash TEXT SHA-256 hash of JWT token
expires_at TEXT Expiration timestamp
revoked INTEGER 0 = active, 1 = revoked

Lifecycle:

  1. Login -- Session created with token hash (auth.ts:56-65)
  2. Each request -- Session checked for revocation (middleware.ts:66-74)
  3. Logout -- All user sessions revoked server-side (auth/logout/route.ts:5-14)

Authorization

IDOR Protection

All data access queries include AND user_id = ? to scope data to the authenticated user:

Merchant endpoints verify merchant role and ownership.

Role-Based Access

Roles (from lib/db.ts schema CHECK constraint):

KYC Status (required for financial operations):


Input Validation

Rate Limiting

Source: src/drop-app/src/lib/middleware.ts:6-31

Endpoint Type Limit Window
Auth routes (login, register) 10 requests 60 seconds
Transaction routes (remittance, qr-payment) 10 requests 60 seconds
Rate endpoints (/api/rates) 120 requests 60 seconds

Implementation: PostgreSQL-backed persistent rate limiting (survives restarts). Per-IP tracking using X-Forwarded-For header. (ADR-014: SQLite removed 2026-03-03)

Rate limit table (rate_limits):

CSRF Protection

Source: src/drop-app/src/lib/middleware.ts:44-55

Origin header validation on all authenticated requests:

Input Sanitization

Source: src/drop-app/src/lib/middleware/validation.ts:149-203

Applied to: recipients, merchants, settings, notifications.

Amount Validation

Endpoint Min Max Source
Remittance 100 NOK 50,000 NOK transactions/remittance/route.ts
QR Payment 1 NOK 100,000 NOK transactions/qr-payment/route.ts

Additional checks: Number.isFinite() to prevent NaN/Infinity injection. Pagination limited to max 50 per page.


SQL Injection Prevention

All 24 API endpoints use parameterized queries exclusively (? placeholders). No string concatenation in SQL statements.

Example from transactions/route.ts:

// Dynamic WHERE clauses use parameter arrays
const conditions: string[] = [];
const params: unknown[] = [];
if (type) { conditions.push("type = ?"); params.push(type); }

Merchant dashboard uses strict whitelist for period parameter.


Security Headers

Source: src/drop-app/next.config.ts:6-46

Header Value
Content-Security-Policy default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; ...
X-Frame-Options DENY
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy camera=(self), microphone=(), geolocation=(self)
Strict-Transport-Security max-age=63072000; includeSubDomains; preload

Known limitation: CSP includes unsafe-inline and unsafe-eval (required for Next.js dev mode). Should be tightened with nonce-based CSP for production.


API Response Masking


Transaction Integrity

Source: transactions/remittance/route.ts, transactions/qr-payment/route.ts


Feature Flags

Source: src/drop-app/src/lib/feature-flags.ts

Gated features (disabled by default):

Flag Env Var Default
virtualCards NEXT_PUBLIC_FF_VIRTUAL_CARDS false
physicalCards NEXT_PUBLIC_FF_PHYSICAL_CARDS false
cardDetails NEXT_PUBLIC_FF_CARD_DETAILS false
cardFreeze NEXT_PUBLIC_FF_CARD_FREEZE false
cardPin NEXT_PUBLIC_FF_CARD_PIN false
spendingLimits NEXT_PUBLIC_FF_SPENDING_LIMITS false
notifications NEXT_PUBLIC_FF_NOTIFICATIONS true
merchantDashboard NEXT_PUBLIC_FF_MERCHANT_DASHBOARD true

API routes check feature flags and return 404 when disabled.


Dependency Security

Package Version Risk Assessment
jose ^6.1.3 Low -- Well-maintained JWT library
bcryptjs ^3.0.3 Low -- Pure JS bcrypt
drizzle-orm latest Low -- Type-safe ORM, parameterized queries
next 16.1.6 Low -- Recent version
react 19.2.3 Low -- Latest major
radix-ui ^1.4.3 Low -- UI components only

No known vulnerable dependencies identified (from security/drop-security-rapport.md:400-411).

Audits & Reports

Secret Rotation Runbook — 2026-04-20

Secret Rotation Runbook — 2026-04-20

Incident Summary

Date: 2026-04-20
Scope: 14 leaked credentials (git history exposure in two repositories)
Root Cause: Secrets echoed in CI logs, git-tracked config files, missing pre-commit hooks
Impact: No confirmed breach; rotation completed within 4 hours

Leaked Credentials

  1. AWS IAM access keys (2x)
  2. Azure Storage Account connection strings (3x)
  3. BookStack API token
  4. Slack webhook URLs (2x)
  5. Cloudflare Access Client credentials (2x)
  6. Bitwarden CLI session tokens (4x — expired)

Rotation Order (Critical → Supporting)

  1. Bitwarden CLI unlock — immediate session key rotation
  2. AWS IAM keys — create new key, update config, delete old key
  3. Azure Storage Account keys — regenerate key2 first (non-breaking), migrate scripts, regenerate key1
  4. Slack webhooks — regenerate via Slack app settings
  5. Cloudflare Access — rotate service tokens via Zero Trust dashboard
  6. BookStack API — create new token, update ~/system/config/.bookstack-cred-cache.json, revoke old

Rotation Commands

# AWS IAM
aws iam create-access-key --user-name alai-admin --output json > /tmp/new-key.json
# Extract AccessKeyId and SecretAccessKey, update ~/.aws/credentials
aws iam delete-access-key --access-key-id OLD_KEY_ID --user-name alai-admin

# Azure Storage Account
az storage account keys renew --account-name alaibackups0ebb --key secondary
# Update ~/system/config/azure-backup.env with new key2 connection string
az storage account keys renew --account-name alaibackups0ebb --key primary

# Verification (test old credential returns 401/403)
curl -I https://alaibackups0ebb.blob.core.windows.net/system-db-backups \
  -H "x-ms-version: 2021-08-06" \
  -H "Authorization: Bearer OLD_TOKEN"

Lessons Learned

New Bitwarden Naming Convention

Format: ALAI — <Service Name> — <Context>
Type: Login (not Secure Note)
Examples:

Verification Protocol

# Test old credential fails (401/403)
curl -I <endpoint> -H "Authorization: Bearer OLD_TOKEN"

# Test new credential succeeds (200/204)
curl -I <endpoint> -H "Authorization: Bearer NEW_TOKEN"

# Update all config files
grep -r "OLD_TOKEN" ~/system/config/ ~/system/tools/
sed -i '' 's/OLD_TOKEN/NEW_TOKEN/g' ~/system/config/*.json

Next Incident Actions

  1. Run gitleaks detect --source ~/system/ --verbose immediately
  2. Prioritize rotation: IAM → Storage → API → Webhooks
  3. Update Bitwarden with new credentials during rotation (not after)
  4. Verify old credential invalidation with curl 401 test
  5. Document in BookStack within 24h

Hardening & Gap Analysis

Network Hardening Guide

Network Hardening — Consolidated Guide

Last Updated: 2026-02-10 System: Darwin 25.2.0 (M3 Mac Studio, 96GB RAM) Status: Configuration Complete, Deployment Blocked (see below) Original Docs: Consolidated from 2 separate files (2026-01-31)


Overview

Comprehensive network hardening and firewall lockdown configuration for macOS. Implements defense-in-depth strategy with application firewall, service disablement, and local-only access controls.


Hardening Strategy

Core Principles

Default-Deny: Block all external traffic by default ✅ Local-Only: Services bind to 127.0.0.1 or RFC 1918 (LAN) only ✅ Minimal Attack Surface: Disable unnecessary services ✅ Defense-in-Depth: Multiple layers of protection ✅ Logging: Track all network access attempts

Scope


Current Hardening Status

✅ Already Secured

⚠️ Requires Configuration

🚫 Deployment Blocker

CRITICAL: Full hardening requires sudo access. John (AI) cannot execute sudo commands without Alem's explicit approval and password entry.

Impact: Configuration scripts ready, but not yet applied.


Hardening Configuration

TASK 1: Enable macOS Firewall (ALF)

The Application Layer Firewall provides per-application traffic control.

Commands (requires sudo):

# Enable firewall globally
sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1

# Enable logging of blocked connections
sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -int 1

# Enable stealth mode (no response to ping/port scan)
sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -int 1

# Whitelist mode (allow only listed apps)
sudo defaults write /Library/Preferences/com.apple.alf allowdownloadsigned -int 1

Verification:

# Should return 1 for each:
defaults read /Library/Preferences/com.apple.alf globalstate
defaults read /Library/Preferences/com.apple.alf loggingenabled
defaults read /Library/Preferences/com.apple.alf stealthenabled

Logs:

# View firewall log
sudo log show --predicate 'subsystem == "com.apple.alf"' --last 1h

TASK 2: Disable SSH Remote Access

SSH is a common attack vector. Disable if not needed.

Commands (requires sudo):

# Check current status
sudo systemsetup -getremotelogin

# Disable SSH
sudo systemsetup -setremotelogin off

Verification:

sudo systemsetup -getremotelogin
# Expected: "Remote Login: Off"

TASK 3: Disable Bluetooth

Bluetooth can be exploited (BlueBorne, etc.). Disable if not needed.

Commands (requires sudo):

# Disable Bluetooth
sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 0

# Kill Bluetooth daemon to apply immediately
sudo killall -HUP blued

Verification:

defaults read /Library/Preferences/com.apple.Bluetooth ControllerPowerState
# Expected: 0 (disabled)

Re-enable (if needed):

sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 1
sudo killall -HUP blued

TASK 4: Restrict Service Bindings (Localhost Only)

Ensure services listen on 127.0.0.1 (local) or 192.168.x.x (LAN), never 0.0.0.0 (all interfaces).

Check Current Bindings:

sudo lsof -iTCP -sTCP:LISTEN -P -n

Key Services to Review:

Service Current Port Should Bind To
Mission Control Dashboard 3030 0.0.0.0 (LAN accessible, intended)
Mattermost 8065 0.0.0.0 (via Cloudflare, external)
Planka 3100 0.0.0.0 (via Cloudflare, external)
Documenso 3003 0.0.0.0 (via Cloudflare, external)
BookStack 6875 0.0.0.0 (LAN only, no tunnel yet)
PostgreSQL (Docker) 5432 127.0.0.1 (internal only)
Redis (Docker) 6379 127.0.0.1 (internal only)

Note: Services exposed via Cloudflare Tunnel (Mattermost, Planka, Documenso) listen on 0.0.0.0 but are protected by tunnel authentication.

Fix Services Listening on 0.0.0.0:

For Node.js apps (e.g., mc-dashboard.js):

// BAD: Listens on all interfaces
app.listen(3030);

// GOOD: Listens on localhost only
app.listen(3030, '127.0.0.1');

// BETTER: Configurable via environment
const HOST = process.env.HOST || '127.0.0.1';
app.listen(3030, HOST);

For Docker services:

# BAD: Exposed to all interfaces
ports:
  - "8065:8065"

# GOOD: Bound to localhost only
ports:
  - "127.0.0.1:8065:8065"

TASK 5: Verify System Integrity Protection (SIP)

SIP protects critical system files from tampering (even by root).

Check Status:

csrutil status

Expected: System Integrity Protection status: enabled.

If Disabled (DO NOT disable without reason):

  1. Reboot into Recovery Mode (hold Cmd+R during startup)
  2. Open Terminal
  3. Run: csrutil enable
  4. Reboot

Firewall Whitelist (Allowed Applications)

Once firewall is enabled, explicitly allow these applications:

Internal Services (LAN accessible)

External Services (via Cloudflare Tunnel)

Development Tools (localhost only)

How to add:

# Allow specific app through firewall
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add /opt/homebrew/bin/node
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --unblockapp /opt/homebrew/bin/node

Cloudflare Tunnel Security

External access is routed through Cloudflare Tunnel (zero-trust, no open ports).

Current Tunnels

Hostname Target Service
mm.basicconsulting.no DECOMMISSIONED 2026-05-18 Mattermost (retired)
boards.alai.no localhost:3100 Planka
sign.alai.no localhost:3003 Documenso

Tunnel Benefits

Tunnel Config

Location: ~/.cloudflared/config.yml

Status Check:

cloudflared tunnel list
cloudflared tunnel info <tunnel-name>

Network Access Logging

macOS Firewall Logs

# Real-time monitoring
sudo log stream --predicate 'subsystem == "com.apple.alf"'

# Last hour of blocks
sudo log show --predicate 'subsystem == "com.apple.alf"' --last 1h --style compact

Connection Tracking

# Active connections
sudo lsof -i -P -n | grep LISTEN

# Who's connected to what
sudo lsof -i -P -n | grep ESTABLISHED

Docker Container Logs

# Service-specific logs
docker logs mattermost --tail 100
docker logs planka --tail 100
docker logs documenso --tail 100

Deployment Blocker (CRITICAL)

Why Hardening Not Yet Applied

Technical Reason: All hardening commands require sudo (superuser) privileges. John (AI agent) does not have:

  1. Password for sudo access
  2. Authorization to execute system-level changes
  3. Ability to modify macOS security settings

Security By Design: This is intentional. AI agents should NOT have root access. All system hardening must be:

Current State:

Deployment Instructions for Alem

  1. Review this document (understand what each command does)
  2. Backup current config:
    sudo /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate > ~/pre-hardening-firewall-state.txt
    sudo systemsetup -getremotelogin > ~/pre-hardening-ssh-state.txt
    
  3. Execute TASK 1-5 (copy-paste commands, enter password when prompted)
  4. Verify each task (run verification commands)
  5. Test services (ensure Mission Control, Docker, Cloudflare still work)
  6. Document completion (update this file with deployment date)

Estimated Time: 15-20 minutes


Rollback Procedure

If hardening causes issues:

Disable Firewall

sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 0

Re-enable SSH

sudo systemsetup -setremotelogin on

Re-enable Bluetooth

sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 1
sudo killall -HUP blued

Original Files (Archived)

All originals preserved in: ~/system/context/docs/security/ (timestamped)


Compliance & Best Practices

Industry Standards

Audit Checklist


Next Steps

  1. Alem deployment: Execute hardening commands (15-20 min)
  2. Verification: Run post-deployment checks
  3. Documentation: Update this file with completion date
  4. Monitoring: Set up monthly log review
  5. Improvements: Consider additional hardening (see below)

Future Enhancements


Maintained by: John (AI Director) Deployed by: Alem (CEO) - AWAITING DEPLOYMENT Next Review: After deployment + 30 days