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
| Strategy | Free | Pro | Scale | Enterprise |
|---|---|---|---|---|
none | Yes | Yes | Yes | Yes |
redact | Yes | Yes | Yes | Yes |
hash | Yes | Yes | Yes | Yes |
partial | Yes | Yes | Yes | Yes |
null | Yes | Yes | Yes | Yes |
deterministic | No | Yes | Yes | Yes |
fixed | No | Yes | Yes | Yes |
email | No | Yes | Yes | Yes |
phone | No | Yes | Yes | Yes |
ssn | No | Yes | Yes | Yes |
creditcard | No | Yes | Yes | Yes |
shuffle | No | No | Yes | Yes |
regex | No | No | Yes | Yes |
tokenize | No | No | Yes | Yes |
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
- Masking runs client-side - PII never leaves the browser unmasked
- Hash is one-way - Original values cannot be recovered
- Deterministic requires secret - Use
projectSecretfor consistent hashing - Masking is pipeline-final - Runs after validation, before export
- Fail-closed - Masking errors stop the pipeline, no raw PII escapes