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 identitybreach_name– breach identifier/name (canonical string)breach_date– breach date (string; provider-dependent)severity– integer severity (serialized fromLeakRecord::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:
- Computes the base storage directory (
~/.local/share/BastionGuard) - Creates the directory if missing
- Sets the database file path (
identity_leaks.db) - Calls
open()to open the SQLite database - 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_erroris 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_seendefault 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
trueif no row is found (leak is new) - Returns
falseif 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_TRANSIENTto 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()orinitSchema()throw exceptions and will prevent the database from being used - CRUD operations:
ensureIdentity(),isNewLeak(), andstoreLeak()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 ifsqlite3_prepare_v2fails
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_hashrather 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 EXISTSensures backward-compatible startup when tables already exist - Duplicate control: primary key constraints prevent exact duplicates by (identity, breach name, breach date)
- Provider variability:
breach_dateis stored as text; providers that do not supply a date may reduce deduplication precision unless a consistent placeholder is used