Pablo Zavala · AI Safety Evaluation · Research Engineering

Inspectable SQLite Privacy Boundaries for Free-Text Datasets

A dependency-free prototype releases structure from a free-text SQLite dataset without exposing raw rows or private base tables. Pattern redaction cleans text before storage, a distinct-text floor of k=5 gates every public topic and aggregate cell, and SQLite's authorizer denies writes, private tables, and sensitive columns on each query.

Six boundary self-checks gate CI: determinism, a k=5 distinct-text floor, a read-only SQL authorizer, redaction at rest, a hash-chained audit, and a private-table-free extract

Public reproducible repo

The controls stay structural rather than semantic; pattern redaction misses novel self-identifying text, and the floor counts distinct text rather than people.

Role: Sole author: boundary design, SQLite authorizer, redaction and deterministic topic layers, hash-chained audit, and the self-check suite.

Evaluation Card

Distinct-text floor

Sample
Five-record cohort against a five-copy duplicate flood
Evaluator
public_topics and aggregate_cells release check at k = 5
Result
The genuine cohort surfaces one public topic while the duplicate flood surfaces zero public topics, because the floor counts distinct normalized text.
Limitation
The floor counts distinct text rather than identities, so it blocks exact-duplicate flooding without proving anonymity.

Read-only SQL guard

Sample
Seven adversarial statements: writes, private-table reads, sensitive columns, sqlite_master, and randomblob
Evaluator
SQLite parsed-statement authorizer under a query_only pragma
Result
The authorizer admits the public views and denies all seven boundary-crossing statements: writes, private reads, sensitive columns, internal schema, and unapproved functions.
Limitation
The guard governs SQLite query paths only, so application logic layered above them stays outside its reach.

Redaction at rest

Sample
Free-text notes carrying emails, phone numbers, and unicode look-alikes
Evaluator
NFKC-normalized pattern redaction applied before truncation
Result
Redaction runs before truncation and strips emails, phones, links, handles, addresses, and ZIP-like runs, including full-width look-alike characters.
Limitation
Pattern redaction catches common identifiers and stops short of parsing every self-identifying sentence.

Tamper-evident audit

Sample
A SHA-256 hash-chained log under a row edit and a tail deletion
Evaluator
verify_chain with an externally pinned head and row count
Result
Chain verification catches a mutated row on its own and catches a clipped tail once the caller pins the expected head and count.
Limitation
Pinning depends on a trusted prior observation of the head and count.

Portable public extract

Sample
A published extract database built after ingesting PII-bearing records
Evaluator
Extract-schema inspection for private tables and residual identifiers
Result
The extract carries only public_topics, aggregate_cells, and meta, omitting private base tables, audit rows, and row-level text.
Limitation
Structural omission falls short of semantic anonymity for an arbitrary dataset.
Evaluation axes with sample size, evaluator, result, and limitation.
AxisSampleEvaluatorResultLimitation
Distinct-text floorFive-record cohort against a five-copy duplicate floodpublic_topics and aggregate_cells release check at k = 5The genuine cohort surfaces one public topic while the duplicate flood surfaces zero public topics, because the floor counts distinct normalized text.The floor counts distinct text rather than identities, so it blocks exact-duplicate flooding without proving anonymity.
Read-only SQL guardSeven adversarial statements: writes, private-table reads, sensitive columns, sqlite_master, and randomblobSQLite parsed-statement authorizer under a query_only pragmaThe authorizer admits the public views and denies all seven boundary-crossing statements: writes, private reads, sensitive columns, internal schema, and unapproved functions.The guard governs SQLite query paths only, so application logic layered above them stays outside its reach.
Redaction at restFree-text notes carrying emails, phone numbers, and unicode look-alikesNFKC-normalized pattern redaction applied before truncationRedaction runs before truncation and strips emails, phones, links, handles, addresses, and ZIP-like runs, including full-width look-alike characters.Pattern redaction catches common identifiers and stops short of parsing every self-identifying sentence.
Tamper-evident auditA SHA-256 hash-chained log under a row edit and a tail deletionverify_chain with an externally pinned head and row countChain verification catches a mutated row on its own and catches a clipped tail once the caller pins the expected head and count.Pinning depends on a trusted prior observation of the head and count.
Portable public extractA published extract database built after ingesting PII-bearing recordsExtract-schema inspection for private tables and residual identifiersThe extract carries only public_topics, aggregate_cells, and meta, omitting private base tables, audit rows, and row-level text.Structural omission falls short of semantic anonymity for an arbitrary dataset.

How to Inspect This Work

What the boundary enforces

The public path exposes only two views, public_topics and aggregate_cells, while SQLite's parsed-statement authorizer denies writes, private base tables, sensitive columns, internal schema tables, and functions outside an explicit allowlist.

Public artifacts

The repository ships the boundary module, the redaction patterns, deterministic topic clustering, a hash-chained audit log, a formal boundary note, and a self-check that CI runs across three Python versions.

Reader check

Start with the formal boundary note, then rerun the self-check and unit suite from a clone to confirm the k=5 floor, the read-only guard, and the private-table-free extract.

Case Study

Problem

Free-text datasets carry incidental identifiers, and a naive SQLite release exposes raw rows and private base tables straight through the query path.

Setup

The prototype stores records in private SQLite tables, redacts free text before storage, groups records into deterministic topics, and exposes only k=5 distinct-text public views.

Method

Every user query runs through SQLite's parsed-statement authorizer under a read-only pragma, a distinct-text floor of k=5 gates topic and aggregate-cell release, and publication writes a separate extract that physically omits private tables and row-level text.

Result

The committed self-check verifies six boundary properties: determinism, the k=5 floor with duplicate-flood resistance, redaction at rest, a read-only SQL guard, hash-chained audit verification, and a private-table-free extract. CI runs the unit suite and the self-check on Python 3.11, 3.12, and 3.13, so a regression on any property breaks the build.

Limitation

The artifact enforces structural controls rather than semantic anonymity or regulatory compliance; pattern redaction misses novel self-identifying text, and the floor counts distinct text rather than distinct people.

Evidence

The dependency-free repository ships the boundary module, the authorizer, the redaction patterns, the audit chain, a formal boundary note, and the self-check that gates CI.

Key Outcomes

  • A distinct-text floor of k=5 gates every public topic and aggregate cell, blocking exact-duplicate flooding
  • SQLite's parsed-statement authorizer denies writes, private base tables, sensitive columns, sqlite_master, and unapproved functions
  • Pattern redaction runs before truncation and normalizes unicode look-alikes, stripping emails, phones, links, handles, addresses, and ZIP-like runs
  • A canonical-view check rejects public views whose SQL drifts from the shipped definition, so a renamed view fails to inherit trust from its name
  • The published extract omits private base tables, audit rows, and row-level text, and pins the source audit head and row count for external verification
  • A hash-chained audit log detects row tampering on its own and tail deletion once the head and count are pinned
  • Six self-checks and a 13-test unit suite run on Python 3.11, 3.12, and 3.13 with zero third-party runtime dependencies

Methods

  • SQLite authorizer sandboxing
  • Pattern-based PII redaction
  • k=5 distinct-text public views
  • Deterministic topic clustering
  • Hash-chained audit log