SWE-3005

Insufficient Security Isolation in Schema

Cantorian Technical Debt Magnitude: ℵ₀ (Countable)

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.

Categories: CP-Schema, CP-API, CP-Process

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

8.5
9

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

  1. 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;
    
  2. 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
    );
    
  3. 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)
    );
    
  4. 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')
        );
    
  5. Physical Separation: Use different databases for different security levels

  6. Encryption at Rest: Encrypt sensitive schemas/tables

  7. Network Segmentation: Place high-value databases in isolated networks

  8. 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

Related Patterns

The Cantorian Technical Debt Magnitude scale gives developers an intuitive sense of magnitude beyond simple hour counts - some debts aren't just larger in scale, but qualitatively different in their complexity.

Cantor Points are critical decision junctures—or even moments of non-decision—where seemingly small choices can create drastically divergent futures for a system's integrity, security, and evolvability. These are the "forks in the road" where one path might lead to manageable complexity, while another veers towards systemic entanglement or even chaos. They often appear trivial at the time but can set in motion irreversible or costly-to-reverse consequences.

Applied Data Integrity (ADI) is a framework to understanding the far-reaching consequences of schema and data decisions that impact security and reliability, and accumulate into ethical debt that affects real human lives. Built on research from real-world incidents, ADI uncovered 7 Principles to identify when these decisions are being made, and how to make them better, to avoid future technical debt and potentially catastrophic "butterfly effects" of small decisions that ripple into chaotic technical and ethical debt.