Skip to main content

mask API

Client-side PII masking with tier-aware strategy enforcement.

npm install @rowops/mask

Configuration Types

MaskConfig

Master configuration for masking behavior.

interface MaskConfig {
// Auto-detection
autoDetectPII?: boolean;

// Default fallback
defaultStrategy?: MaskStrategy;
projectSecret?: string;

// Per-type strategies
emailStrategy?: MaskStrategy;
phoneStrategy?: MaskStrategy;
cardStrategy?: MaskStrategy;
ssnStrategy?: MaskStrategy; // Pro+ tier
passportStrategy?: MaskStrategy; // Pro+ tier
ipStrategy?: MaskStrategy; // Scale+ tier

// Per-column rules
columnRules?: Record<string, MaskStrategy | MaskRuleConfig>;
}

interface MaskRuleConfig {
strategy: MaskStrategy;
options?: {
first?: number; // Chars to show at start
last?: number; // Chars to show at end
fixed?: string; // Replacement string
projectSecret?: string;
pattern?: string; // Regex pattern (regex strategy)
replacement?: string; // Regex replacement template
};
}

MaskStrategy

Available masking strategies.

type MaskStrategy =
| "none" // No masking (pass-through)
| "redact" // Replace with asterisks (****)
| "hash" // SHA-256 hash (one-way)
| "deterministic" // Consistent hash per value
| "partial" // Show first/last N chars
| "fixed" // Replace with fixed string
| "null" // Replace with null
| "shuffle" // Deterministically shuffle characters
| "email" // Mask email local part
| "phone" // Mask phone numbers
| "ssn" // Mask SSNs
| "creditcard" // Mask credit card numbers
| "regex" // Regex-based replacement
| "tokenize"; // Tokenize into vault-backed IDs

MaskRule

Programmatic rule for field-based masking.

interface MaskRule {
/** Field keys in the normalized row that this rule applies to */
fields?: string[];
/** Masking strategy to apply */
strategy: MaskStrategy;
/** Strategy-specific options */
options?: {
first?: number; // Chars to show at start (partial)
last?: number; // Chars to show at end (partial)
fixed?: string; // Replacement string (fixed)
projectSecret?: string; // Override global secret (deterministic/hash)
pattern?: string; // Regex pattern
replacement?: string; // Regex replacement template
};
}

Tier Functions

Use entitlements.tier from resolveBrowserLicense when calling tier helpers.

sanitizeMaskConfigForTier

Removes strategies not allowed for the current tier.

import { sanitizeMaskConfigForTier } from "@rowops/mask";
import { resolveBrowserLicense } from "@rowops/import-core";

const { entitlements } = await resolveBrowserLicense({
projectId: "proj_xxx",
entitlementToken: "eyJ...",
});

const safeConfig = sanitizeMaskConfigForTier(maskConfig, entitlements.tier);

isMaskStrategyAllowed

Check if a strategy is available for a tier.

import { isMaskStrategyAllowed } from "@rowops/mask";

if (isMaskStrategyAllowed(entitlements.tier, "shuffle")) {
// shuffle is allowed on scale tier
}

getAllowedMaskStrategies

Get all strategies available for a tier.

import { getAllowedMaskStrategies } from "@rowops/mask";

const strategies = getAllowedMaskStrategies(entitlements.tier);
// ["none", "redact", "hash", "deterministic", "partial", "fixed", "null", "email", "phone", "ssn", "creditcard"]

hasDisallowedStrategies

Check if config uses any disallowed strategies.

import { hasDisallowedStrategies } from "@rowops/mask";

const disallowed = hasDisallowedStrategies(config, entitlements.tier);
if (disallowed.length > 0) {
console.warn("Upgrade required for:", disallowed);
}

Strategy Examples

Redact (Default)

Replaces all characters with asterisks.

const config: MaskConfig = {
defaultStrategy: "redact",
};

// Input: "john@example.com"
// Output: "****"

Partial Masking

Shows first and/or last N characters.

const config: MaskConfig = {
columnRules: {
phone: {
strategy: "partial",
options: { last: 4 },
},
ssn: {
strategy: "partial",
options: { last: 4 },
},
},
};

// phone: "+1-555-123-4567" -> "***-4567"
// ssn: "123-45-6789" -> "***-**-6789"

Deterministic Hash

Same input always produces same hash (for dedup).

const config: MaskConfig = {
emailStrategy: "deterministic",
projectSecret: "my-secret-key",
};

// "john@example.com" -> "a1b2c3d4..." (consistent)

Per-Column Rules

const config: MaskConfig = {
defaultStrategy: "redact",
columnRules: {
email: "hash",
phone: { strategy: "partial", options: { last: 4 } },
name: "none", // Don't mask
ssn: "redact",
},
};

Tier Restrictions

StrategyFreeProScaleEnterprise
noneYesYesYesYes
redactYesYesYesYes
hashYesYesYesYes
partialYesYesYesYes
nullYesYesYesYes
deterministicNoYesYesYes
fixedNoYesYesYes
emailNoYesYesYes
phoneNoYesYesYes
ssnNoYesYesYes
creditcardNoYesYesYes
shuffleNoNoYesYes
regexNoNoYesYes
tokenizeNoNoYesYes

Usage with Importer

import { RowOpsImporter } from "@rowops/importer";

<RowOpsImporter
projectId="proj_xxx"
schemaId="contacts"
publishableKey="pk_xxx"
maskConfig={{
autoDetectPII: true,
emailStrategy: "hash",
phoneStrategy: "partial",
columnRules: {
ssn: "redact",
credit_card: { strategy: "partial", options: { last: 4 } },
},
}}
onExportChunk={async (rows) => {
// Streamed rows contain masked values
const sample = rows[0];
if (sample) console.log(sample.email); // "a1b2c3d4..."
}}
/>

Security Notes

  1. Masking runs client-side - PII never leaves the browser unmasked
  2. Hash is one-way - Original values cannot be recovered
  3. Deterministic requires secret - Use projectSecret for consistent hashing
  4. Masking is pipeline-final - Runs after validation, before export
  5. Fail-closed - Masking errors stop the pipeline, no raw PII escapes

See Also