Quickback Docs

Masking - Field Redaction

Hide sensitive data from unauthorized users with automatic field masking. Secure PII and confidential fields while maintaining access for authorized roles.

Hide sensitive data from unauthorized users while showing it to those with permission.

Automatic Masking (Secure by Default)

Quickback automatically applies masking to columns that match sensitive naming patterns. This ensures a high security posture even if you don't explicitly configure masking.

Sensitive Keywords & Default Masks

PatternDefault MaskDescription
emailemailp***@e******.com
phone, mobile, faxphone******4567
ssn, socialsecurity, nationalidssn*****6789
creditcard, cc, cardnumber, cvvcreditCard************1234
ibancreditCard************1234
password, secret, token, apikey, privatekeyredact[REDACTED]

Smart Pattern Matching

Auto-masking also matches suffixes like workEmail, homePhone, or apiSecret.

Build-Time Alerts

If the compiler auto-detects a sensitive column that you haven't explicitly configured, it will emit a warning:

[Warning] Auto-masking enabled for sensitive column "users.email". Explicitly configure masking to silence this warning.

To silence this warning or change the behavior, simply define the field explicitly in your masking configuration. Explicit configurations always take precedence over auto-detected defaults.

Overriding Defaults

If you want to show a sensitive field to everyone (disable masking) or use a different rule, define it explicitly in the resource.masking block:

export default defineTable(users, {
  masking: {
    // Show email to everyone (overrides auto-masking)
    email: { type: 'email', show: { roles: ['everyone'] } },
    
    // Silence warning but keep secure (owner-only)
    ssn: { type: 'ssn', show: { or: 'owner' } }
  }
});

Basic Usage

// features/employees/employees.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { defineTable } from '@quickback/compiler';

export const employees = sqliteTable('employees', {
  id: text('id').primaryKey(),
  name: text('name').notNull(),
  ssn: text('ssn'),
  salary: integer('salary'),
  email: text('email'),
  organizationId: text('organization_id').notNull(),
});

export default defineTable(employees, {
  firewall: { organization: {} },
  guards: { createable: ["name", "ssn", "salary", "email"], updatable: ["name"] },
  masking: {
    ssn: { type: 'ssn', show: { roles: ['hr', 'admin'] } },
    salary: { type: 'redact', show: { roles: ['hr', 'admin'] } },
    email: { type: 'email', show: { or: 'owner' } },
  },
  crud: {
    // ...
  },
});

Built-in Mask Types

TypeExample InputMasked Output
'email'john@yourdomain.comj***@y*********.com
'phone'555-123-4567******4567
'ssn'123-45-6789*****6789
'creditCard'4111111111111111************1111
'name'John SmithJ*** S****
'redact'anything[REDACTED]
'custom'(your logic)(your output)

Configuration

masking: {
  // Basic masking - everyone sees masked value
  taxId: { type: 'ssn' },

  // Show unmasked to specific roles
  salary: {
    type: 'redact',
    show: { roles: ['admin', 'hr'] }
  },

  // Show unmasked to owner (createdBy === ctx.userId)
  email: {
    type: 'email',
    show: { or: 'owner' }
  },

  // Custom mask function
  apiKey: {
    type: 'custom',
    mask: (value) => value.slice(0, 4) + '...' + value.slice(-4),
    show: { roles: ['admin'] }
  },
}

Show Conditions

show: {
  roles?: string[];    // Unmasked if user has any of these roles
  or?: 'owner';        // Unmasked if user is the record owner
}

The 'owner' condition compares against the owner column configured in your firewall. If you have firewall: { owner: {} }, the owner column (default: ownerId or owner_id) is used to determine ownership. If no owner firewall is configured, it falls back to createdBy.

Complete Example

// features/employees/employees.ts
export default defineTable(employees, {
  firewall: { organization: {} },
  guards: { createable: ["name", "ssn", "salary"], updatable: ["name"] },
  masking: {
    ssn: { type: 'ssn', show: { roles: ['hr', 'admin'] } },
    salary: { type: 'redact', show: { roles: ['hr', 'admin'] } },
    personalEmail: { type: 'email', show: { or: 'owner' } },
    bankAccount: {
      type: 'custom',
      mask: (val) => '****' + val.slice(-4),
      show: { roles: ['payroll'] }
    },
  },
  crud: {
    list: { access: { roles: ["member", "admin"] } },
    get: { access: { roles: ["member", "admin"] } },
    create: { access: { roles: ["hr", "admin"] } },
    update: { access: { roles: ["hr", "admin"] } },
    delete: { access: { roles: ["admin"] } },
  },
});

On this page