Insufficient Security Isolation in Schema
Description
Schema designs that fail to isolate high-value or sensitive data from less critical data, creating single points of failure where compromise of one component exposes everything. This includes storing cryptocurrency keys in hot wallets without isolation, mixing PII with public data in the same tables, or lacking proper schema-level segmentation between different security zones.
Illustrative Cantor Point
The Cantor Point occurs when deciding data organization strategy - choosing convenience of "everything in one place" versus security through isolation. The decision to store all assets in a single wallet, or to mix sensitive and non-sensitive data in the same schema, creates a divergent path where a single breach has catastrophic impact rather than limited damage.
Real-World Examples / Observed In
- Coincheck (2018): Stored $530 million in cryptocurrency in a single hot wallet without multi-signature security, allowing complete theft through one compromised key [See: Cases-By-Year/2018 Data Integrity Failures.md#7]
- Retail Chain: Mixed credit card data with order history in same database, expanding PCI scope unnecessarily
- Healthcare System: Patient medical records and public website content in same schema, increasing attack surface
Common Consequences & Impacts
Technical Impacts
- - Single breach exposes all data
- - Cannot apply different security controls
- - Compliance scope explosion
- - Recovery complexity increases
Human/Ethical Impacts
- - Complete identity theft
- - Financial ruin for users
- - Loss of life savings
- - Irreversible privacy breach
Business Impacts
- - Catastrophic financial losses
- - Total business failure risk
- - Regulatory penalties
- - Insurance claim denials
Recovery Difficulty & Escalation
ADI Principles & Axioms Violated
- Principle of Boundary Criticality: Security boundaries must be enforced at schema level
- Principle of Trust Insufficiency: Never trust a single security layer
Detection / 60-Second Audit
-- Check for tables mixing sensitive and non-sensitive data
SELECT
table_name,
array_agg(column_name ORDER BY column_name) as columns,
COUNT(CASE WHEN column_name ~* '(ssn|credit|password|key|secret)' THEN 1 END) as sensitive_columns,
COUNT(CASE WHEN column_name ~* '(public|display|visible)' THEN 1 END) as public_columns
FROM information_schema.columns
WHERE table_schema = 'public'
GROUP BY table_name
HAVING COUNT(CASE WHEN column_name ~* '(ssn|credit|password|key)' THEN 1 END) > 0
AND COUNT(CASE WHEN column_name ~* '(public|display)' THEN 1 END) > 0;
-- Identify high-value tables without proper isolation
SELECT
t.schemaname,
t.tablename,
pg_size_pretty(pg_total_relation_size(t.schemaname||'.'||t.tablename)) as total_size,
CASE
WHEN t.tablename ~* '(wallet|payment|key|secret|private)'
THEN 'HIGH RISK - Requires isolation'
ELSE 'Check manually'
END as risk_assessment
FROM pg_stat_user_tables t
WHERE t.tablename ~* '(wallet|payment|key|secret|private|credential)'
AND t.schemaname = 'public';
-- Schema isolation analysis
SELECT
nspname as schema_name,
COUNT(*) as table_count,
pg_size_pretty(SUM(pg_total_relation_size(nspname||'.'||relname))) as schema_size
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE relkind = 'r' AND nspname NOT IN ('pg_catalog', 'information_schema')
GROUP BY nspname
ORDER BY COUNT(*) DESC;
-- Check for tables mixing sensitive and non-sensitive data
SELECT
table_name,
GROUP_CONCAT(column_name ORDER BY column_name) as columns,
SUM(CASE WHEN column_name REGEXP '(ssn|credit|password|key|secret)' THEN 1 ELSE 0 END) as sensitive_columns,
SUM(CASE WHEN column_name REGEXP '(public|display|visible)' THEN 1 ELSE 0 END) as public_columns
FROM information_schema.columns
WHERE table_schema = DATABASE()
GROUP BY table_name
HAVING sensitive_columns > 0 AND public_columns > 0;
-- Identify high-value tables
SELECT
t.table_name,
t.table_schema,
ROUND(data_length/1024/1024, 2) as size_mb,
CASE
WHEN t.table_name REGEXP '(wallet|payment|key|secret|private)'
THEN 'HIGH RISK - Requires isolation'
ELSE 'Review needed'
END as risk_level
FROM information_schema.tables t
WHERE t.table_schema = DATABASE()
AND t.table_name REGEXP '(wallet|payment|key|secret|private|credential)';
-- Check schema usage
SELECT
table_schema,
COUNT(*) as table_count,
SUM(data_length + index_length)/1024/1024 as total_size_mb
FROM information_schema.tables
WHERE table_type = 'BASE TABLE'
GROUP BY table_schema
ORDER BY table_count DESC;
-- Check for tables mixing sensitive and non-sensitive data
SELECT
t.name as table_name,
STRING_AGG(c.name, ', ') WITHIN GROUP (ORDER BY c.name) as columns,
SUM(CASE WHEN c.name LIKE '%ssn%' OR c.name LIKE '%credit%'
OR c.name LIKE '%password%' OR c.name LIKE '%key%' THEN 1 ELSE 0 END) as sensitive_columns,
SUM(CASE WHEN c.name LIKE '%public%' OR c.name LIKE '%display%' THEN 1 ELSE 0 END) as public_columns
FROM sys.tables t
JOIN sys.columns c ON t.object_id = c.object_id
GROUP BY t.name
HAVING SUM(CASE WHEN c.name LIKE '%ssn%' OR c.name LIKE '%credit%' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN c.name LIKE '%public%' OR c.name LIKE '%display%' THEN 1 ELSE 0 END) > 0;
-- Identify high-value tables
SELECT
s.name as schema_name,
t.name as table_name,
p.rows as row_count,
CASE
WHEN t.name LIKE '%wallet%' OR t.name LIKE '%payment%'
OR t.name LIKE '%key%' OR t.name LIKE '%secret%'
THEN 'HIGH RISK - Requires isolation'
ELSE 'Review needed'
END as risk_assessment
FROM sys.tables t
JOIN sys.schemas s ON t.schema_id = s.schema_id
JOIN sys.partitions p ON t.object_id = p.object_id
WHERE p.index_id IN (0,1)
AND (t.name LIKE '%wallet%' OR t.name LIKE '%payment%'
OR t.name LIKE '%key%' OR t.name LIKE '%secret%')
AND s.name = 'dbo';
Prevention & Mitigation Best Practices
Implement Schema-Level Segmentation:
-- Create separate schemas for different security zones CREATE SCHEMA sensitive_data; CREATE SCHEMA public_data; CREATE SCHEMA financial_data; -- Grant minimal cross-schema permissions REVOKE ALL ON SCHEMA sensitive_data FROM PUBLIC; GRANT USAGE ON SCHEMA sensitive_data TO sensitive_data_role;Isolate High-Value Assets:
-- Bad: Everything in one wallet table CREATE TABLE wallets ( id BIGINT PRIMARY KEY, wallet_type VARCHAR(20), private_key TEXT, -- Both hot and cold keys together! balance DECIMAL(20,8), last_accessed TIMESTAMP ); -- Good: Separated by security requirements -- Cold storage (offline, encrypted, multi-sig) CREATE SCHEMA cold_storage; CREATE TABLE cold_storage.wallets ( id UUID PRIMARY KEY, public_address VARCHAR(100), encrypted_key_shard1 TEXT, -- Multi-party computation encrypted_key_shard2 TEXT, encrypted_key_shard3 TEXT, threshold INTEGER DEFAULT 2 -- Need 2 of 3 shards ); -- Hot wallets (online, limited funds) CREATE SCHEMA hot_wallets; CREATE TABLE hot_wallets.operational ( id UUID PRIMARY KEY, public_address VARCHAR(100), max_balance DECIMAL(10,8) DEFAULT 1000.00, daily_limit DECIMAL(10,8) DEFAULT 5000.00, current_balance DECIMAL(10,8) -- Private keys stored in HSM, not database );Implement Data Classification Tables:
CREATE TABLE data_classification ( table_schema VARCHAR(100), table_name VARCHAR(100), classification VARCHAR(20) CHECK ( classification IN ('PUBLIC', 'INTERNAL', 'CONFIDENTIAL', 'SECRET') ), encryption_required BOOLEAN, audit_required BOOLEAN, retention_days INTEGER, PRIMARY KEY (table_schema, table_name) );Use Row-Level Security for Mixed Tables:
ALTER TABLE customer_data ENABLE ROW LEVEL SECURITY; CREATE POLICY public_data_policy ON customer_data FOR SELECT USING (data_classification = 'PUBLIC'); CREATE POLICY sensitive_data_policy ON customer_data FOR ALL USING ( data_classification IN ('CONFIDENTIAL', 'SECRET') AND current_user_has_role('sensitive_data_access') );Physical Separation: Use different databases for different security levels
Encryption at Rest: Encrypt sensitive schemas/tables
Network Segmentation: Place high-value databases in isolated networks
Regular Security Reviews: Audit data placement decisions
Real World Examples
• Stored all NEM cryptocurrency in a single hot wallet
• No multi-signature security (single point of failure)
• No separation between operational and reserve funds
• Private keys accessible from internet-connected systems
• Result: Complete theft of all customer funds in minutes
• Root cause: "It's easier to manage everything in one place"
-- The dangerous all-in-one table
CREATE TABLE user_data (
id BIGINT PRIMARY KEY,
-- Public profile data
username VARCHAR(50),
avatar_url VARCHAR(255),
bio TEXT,
-- Sensitive PII
ssn VARCHAR(11),
date_of_birth DATE,
-- Financial data
credit_card_number VARCHAR(16),
bank_account VARCHAR(20),
-- Security credentials
password_hash VARCHAR(255),
two_factor_secret VARCHAR(32),
api_private_key TEXT
);
-- One SQL injection exposes EVERYTHING
-- One backup leak exposes EVERYTHING
-- One insider threat accesses EVERYTHING
-- Schema isolation by security classification
CREATE SCHEMA public_data;
CREATE SCHEMA internal_data;
CREATE SCHEMA sensitive_pii;
CREATE SCHEMA financial_data ENCRYPTED;
CREATE SCHEMA security_vault ENCRYPTED;
-- Public data (no encryption needed)
CREATE TABLE public_data.user_profiles (
user_id BIGINT PRIMARY KEY,
username VARCHAR(50) UNIQUE,
display_name VARCHAR(100),
avatar_url VARCHAR(255),
bio TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Internal operational data
CREATE TABLE internal_data.user_metadata (
user_id BIGINT PRIMARY KEY,
account_status VARCHAR(20),
last_login TIMESTAMPTZ,
login_count INTEGER DEFAULT 0,
preferences JSONB
);
-- Sensitive PII (encrypted at rest, limited access)
CREATE TABLE sensitive_pii.user_identity (
user_id BIGINT PRIMARY KEY,
ssn_encrypted BYTEA, -- Application-level encryption
ssn_hash VARCHAR(64) UNIQUE, -- For lookups only
date_of_birth_encrypted BYTEA,
full_name_encrypted BYTEA,
audit_log JSONB DEFAULT '[]'::JSONB
);
-- Financial data (PCI compliant isolation)
CREATE TABLE financial_data.payment_methods (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id BIGINT NOT NULL,
token_id VARCHAR(100), -- From payment processor
last_four VARCHAR(4),
card_type VARCHAR(20),
expires_month INTEGER,
expires_year INTEGER
-- Never store actual card numbers
);
-- Security credentials (highest isolation)
-- Private keys NEVER in database - use HSM
CREATE TABLE security_vault.user_auth (
user_id BIGINT PRIMARY KEY,
password_hash VARCHAR(255), -- bcrypt/argon2
mfa_enabled BOOLEAN DEFAULT FALSE,
mfa_backup_codes_hash TEXT[], -- Hashed backup codes
failed_attempts INTEGER DEFAULT 0,
locked_until TIMESTAMPTZ
);
-- Row-level security for additional protection
ALTER TABLE sensitive_pii.user_identity ENABLE ROW LEVEL SECURITY;
CREATE POLICY pii_access ON sensitive_pii.user_identity
USING (
current_setting('app.user_role') = 'pii_admin'
OR (current_setting('app.user_id')::BIGINT = user_id
AND current_setting('app.access_reason') IS NOT NULL)
);
-- Audit all access to sensitive schemas
CREATE OR REPLACE FUNCTION sensitive_access_audit()
RETURNS event_trigger AS $
BEGIN
INSERT INTO audit.sensitive_data_access (
accessed_at,
schema_name,
table_name,
user_name,
application_name,
client_addr
) VALUES (
NOW(),
TG_TAG,
TG_TABLE_NAME,
current_user,
current_setting('application_name'),
inet_client_addr()
);
END;
$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER audit_sensitive_access
ON ddl_command_end
WHEN TAG IN ('ALTER TABLE', 'DROP TABLE')
EXECUTE FUNCTION sensitive_access_audit();
AI Coding Guidance/Prompt
Prompt: "When designing schemas with sensitive data:"
Rules:
- Flag any mixing of PII with public data
- Require separate schemas for different security classifications
- Warn about single points of failure for high-value assets
- Suggest encryption for sensitive columns
- Require explicit data classification
Example:
# Bad: Mixed sensitivity in one schema
CREATE TABLE users (
id BIGINT PRIMARY KEY,
username VARCHAR(50), -- Public
display_name VARCHAR(100), -- Public
ssn VARCHAR(11), -- Highly sensitive!
credit_card VARCHAR(20), -- PCI data!
bio TEXT, -- Public
private_key TEXT -- Critical security!
);
# Good: Properly isolated by sensitivity
-- Public schema
CREATE SCHEMA public_data;
CREATE TABLE public_data.user_profiles (
user_id BIGINT PRIMARY KEY,
username VARCHAR(50),
display_name VARCHAR(100),
bio TEXT
);
-- PII schema (encrypted at rest)
CREATE SCHEMA pii_data;
CREATE TABLE pii_data.user_identity (
user_id BIGINT PRIMARY KEY,
ssn_encrypted BYTEA,
ssn_hash VARCHAR(64) UNIQUE, -- For lookups
created_at TIMESTAMP WITH TIME ZONE,
accessed_at TIMESTAMP WITH TIME ZONE
);
-- Financial schema (PCI compliant)
CREATE SCHEMA financial_data;
CREATE TABLE financial_data.payment_tokens (
user_id BIGINT,
token_id UUID PRIMARY KEY,
last_four VARCHAR(4),
expiry_month INTEGER,
expiry_year INTEGER
-- Actual card data in separate PCI-compliant vault
);
-- Critical security (HSM-backed)
CREATE SCHEMA security_critical;
-- Private keys never stored in database
-- Only references to HSM key IDs
Relevant Keywords
insufficient security isolation schema Symptoms: slow queries, data inconsistency, constraint violations Preventive: schema validation, constraint enforcement, proper typing Tech stack: PostgreSQL, MySQL, SQL Server, Oracle Industry: all industries, enterprise, SaaS