okta-mcp-server
Version:
Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching
204 lines • 6.42 kB
JavaScript
/**
* PII (Personally Identifiable Information) masking utilities
*/
import { createHash } from 'crypto';
export class PIIMasker {
config;
salt;
constructor(config, salt = process.env['AUDIT_SALT'] || 'default-salt') {
this.config = config;
this.salt = salt;
}
/**
* Mask sensitive data in an object
*/
maskObject(obj) {
if (!obj || typeof obj !== 'object') {
return obj;
}
// Handle arrays
if (Array.isArray(obj)) {
return obj.map((item) => this.maskObject(item));
}
// Deep clone to avoid mutating original
const masked = JSON.parse(JSON.stringify(obj));
// Apply field-based masking
for (const field of this.config.fields) {
this.maskField(masked, field);
}
// Apply pattern-based masking
this.applyPatternMasking(masked);
return masked;
}
/**
* Mask a specific field in an object
*/
maskField(obj, fieldPath) {
const parts = fieldPath.split('.');
let current = obj;
for (let i = 0; i < parts.length - 1; i++) {
if (!current[parts[i]])
return;
current = current[parts[i]];
}
const fieldName = parts[parts.length - 1];
if (fieldName in current) {
current[fieldName] = this.maskValue(current[fieldName], fieldName);
}
}
/**
* Apply pattern-based masking recursively
*/
applyPatternMasking(obj) {
for (const key in obj) {
if (!obj.hasOwnProperty(key))
continue;
const value = obj[key];
if (typeof value === 'string') {
obj[key] = this.maskStringByPatterns(value);
}
else if (typeof value === 'object' && value !== null) {
this.applyPatternMasking(value);
}
}
}
/**
* Mask a string value based on patterns
*/
maskStringByPatterns(value) {
let masked = value;
for (const pattern of this.config.patterns) {
masked = masked.replace(pattern, (match) => {
return this.maskValue(match, 'pattern');
});
}
return masked;
}
/**
* Mask a single value based on strategy
*/
maskValue(value, context) {
if (value === null || value === undefined) {
return value;
}
const stringValue = String(value);
switch (this.config.maskingStrategy) {
case 'hash':
return this.hashValue(stringValue);
case 'partial':
return this.partialMask(stringValue, context);
case 'remove':
return '[REDACTED]';
case 'encrypt':
// In production, use proper encryption
return `[ENCRYPTED:${Buffer.from(stringValue).toString('base64')}]`;
default:
return '[MASKED]';
}
}
/**
* Hash a value with salt
*/
hashValue(value) {
const hash = createHash('sha256');
hash.update(this.salt + value);
return `[HASH:${hash.digest('hex').substring(0, 16)}]`;
}
/**
* Partially mask a value based on context
*/
partialMask(value, context) {
// Email: show first letter and domain
if (context === 'email' || value.includes('@')) {
const [local, domain] = value.split('@');
if (domain) {
return `${local[0]}***@${domain}`;
}
}
// Phone: show area code only
if (context === 'phone' || /^\+?\d{10,}$/.test(value)) {
return value.substring(0, 3) + '*'.repeat(value.length - 3);
}
// SSN: show last 4 digits
if (context === 'ssn' || /^\d{3}-?\d{2}-?\d{4}$/.test(value)) {
const digits = value.replace(/\D/g, '');
return `***-**-${digits.substring(5)}`;
}
// Credit card: show last 4 digits
if (context === 'creditCard' || /^\d{13,19}$/.test(value.replace(/\s/g, ''))) {
const digits = value.replace(/\D/g, '');
return '*'.repeat(digits.length - 4) + digits.substring(digits.length - 4);
}
// Default: show first and last character
if (value.length > 2) {
return value[0] + '*'.repeat(value.length - 2) + value[value.length - 1];
}
return '*'.repeat(value.length);
}
/**
* Get default PII patterns
*/
static getDefaultPatterns() {
return [
// Email addresses
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
// Phone numbers (various formats)
/\b(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})\b/g,
// SSN
/\b\d{3}-\d{2}-\d{4}\b/g,
// Credit card numbers
/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,
// IP addresses
/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g,
// API keys (generic pattern)
/\b[A-Za-z0-9]{32,}\b/g,
];
}
/**
* Get default fields to mask
*/
static getDefaultFields() {
return [
'password',
'secret',
'token',
'apiKey',
'apiToken',
'accessToken',
'refreshToken',
'privateKey',
'ssn',
'socialSecurityNumber',
'creditCard',
'creditCardNumber',
'cvv',
'pin',
'email',
'emailAddress',
'phone',
'phoneNumber',
'mobile',
'mobileNumber',
'address',
'streetAddress',
'profile.email',
'profile.phone',
'profile.mobile',
'profile.ssn',
'credentials.password',
'credentials.token',
];
}
}
/**
* Create a default PII masker instance
*/
export function createDefaultPIIMasker() {
return new PIIMasker({
fields: PIIMasker.getDefaultFields(),
patterns: PIIMasker.getDefaultPatterns(),
maskingStrategy: 'partial',
preserveFormat: true,
});
}
//# sourceMappingURL=pii-masker.js.map