LeakDatabase (Identity Leak)

1. Overview

The LeakDatabase module provides a local persistence layer for BastionGuard’s Identity Leak subsystem. It uses SQLite to store monitored identities (by hashed identifier) and individual leak/breach records, enabling:

  • Tracking of identities already enrolled in monitoring
  • Detection of new vs already-known leak events
  • Local auditability of breach history without re-querying external providers

The database is created and managed automatically under the user’s home directory and includes an embedded schema initialization routine.


2. Storage Location

The database is stored in the user data directory:

~/.local/share/BastionGuard/identity_leaks.db

On startup, the module ensures the parent directory exists by creating:

~/.local/share/BastionGuard

This makes the component resilient across first-run installations and clean user profiles.


3. Schema and Data Model

3.1 Schema Initialization

The module embeds its schema as a static SQL string (SCHEMA_SQL) and applies it using sqlite3_exec() at construction time.


3.2 identity Table

The identity table records the identities that have been observed/registered, keyed by a hash:

CREATE TABLE IF NOT EXISTS identity (
    email_hash TEXT PRIMARY KEY,
    first_seen INTEGER DEFAULT (strftime('%s','now'))
);

Fields:

  • email_hash – primary key; hashed identity (e.g., SHA256(email))
  • first_seen – UNIX epoch timestamp (seconds) automatically set on first insert

3.3 leak Table

The leak table stores per-breach entries for each identity:

CREATE TABLE IF NOT EXISTS leak (
    email_hash TEXT NOT NULL,
    breach_name TEXT NOT NULL,
    breach_date TEXT NOT NULL,
    severity INTEGER NOT NULL,
    provider TEXT,
    description TEXT,
    PRIMARY KEY (email_hash, breach_name, breach_date)
);

Fields:

  • email_hash – identity key linking the event to a monitored identity
  • breach_name – breach identifier/name (canonical string)
  • breach_date – breach date (string; provider-dependent)
  • severity – integer severity (serialized from LeakRecord::Severity)
  • provider – source provider name (optional)
  • description – provider-provided description (optional)

The composite primary key prevents duplicate inserts of the same breach event for the same identity and breach date.


4. Threading and Concurrency Model

The module serializes all database operations using:

std::mutex dbMutex;

Each public operation acquires a std::lock_guard<std::mutex>, ensuring thread-safe access from monitoring threads and UI threads that may interact with leak persistence.

This design provides correctness and simplicity at the cost of single-threaded DB throughput, which is acceptable given the expected low query volume.


5. Lifecycle and Initialization

5.1 Constructor Behavior

On construction, the module:

  1. Computes the base storage directory (~/.local/share/BastionGuard)
  2. Creates the directory if missing
  3. Sets the database file path (identity_leaks.db)
  4. Calls open() to open the SQLite database
  5. Calls initSchema() to create tables if missing

This ensures the database is always ready for use immediately after instantiation.


6. Database Operations

6.1 open()

Opens the SQLite database using sqlite3_open(). On failure, it throws:

std::runtime_error("SQLite: open failed")

The error string is localized via _().


6.2 initSchema()

Applies the embedded schema using sqlite3_exec(). On schema execution failure:

  • The SQLite error message is captured (if available)
  • The error string is freed via sqlite3_free()
  • A std::runtime_error is thrown with diagnostic details

This provides actionable feedback when the database cannot be initialized (e.g., corrupted file, filesystem permissions).


6.3 ensureIdentity(emailHash)

Ensures the identity exists in the identity table. The operation is idempotent using:

INSERT OR IGNORE INTO identity(email_hash) VALUES (?);

Behavior:

  • If the identity does not exist, it is inserted with first_seen default timestamp
  • If the identity already exists, no change occurs

6.4 isNewLeak(emailHash, record)

Checks whether a leak record is already stored for the given identity. The lookup uses the composite key components:

SELECT 1 FROM leak
WHERE email_hash=? AND breach_name=? AND breach_date=?;

Return semantics:

  • Returns true if no row is found (leak is new)
  • Returns false if a row exists (leak already known)

Implementation detail: the function returns “new” when sqlite3_step() does not yield SQLITE_ROW.


6.5 storeLeak(emailHash, record)

Persists a leak record into the leak table:

INSERT INTO leak
(email_hash, breach_name, breach_date, severity, provider, description)
VALUES (?, ?, ?, ?, ?, ?);

Bindings:

  • Text fields are bound using SQLITE_TRANSIENT to ensure safety if the source strings are modified after binding
  • Severity is stored as an integer using static_cast<int>(record.severity)

The composite primary key provides duplicate protection; however, this function does not currently interpret or surface the “constraint violation” result explicitly. Upstream code should call isNewLeak() before storeLeak() when strict “new event only” semantics are required.


7. Error Handling Semantics

  • Constructor path: failures in open() or initSchema() throw exceptions and will prevent the database from being used
  • CRUD operations: ensureIdentity(), isNewLeak(), and storeLeak() do not throw explicitly in the current implementation, but SQLite preparation failures are not actively checked; failures may result in no-op behavior or undefined outcomes if sqlite3_prepare_v2 fails

If stronger fault guarantees are desired, add explicit checks on return codes of:

  • sqlite3_prepare_v2()
  • sqlite3_bind_*
  • sqlite3_step()

8. Runtime and Security Considerations

  • PII minimization: the database stores email_hash rather than raw emails, reducing direct disclosure risk
  • Local persistence: breach history is stored locally; filesystem permissions and user profile security determine access control
  • Thread safety: a single mutex protects all SQLite usage, preventing concurrent access hazards
  • Schema durability: CREATE TABLE IF NOT EXISTS ensures backward-compatible startup when tables already exist
  • Duplicate control: primary key constraints prevent exact duplicates by (identity, breach name, breach date)
  • Provider variability: breach_date is stored as text; providers that do not supply a date may reduce deduplication precision unless a consistent placeholder is used