🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@epilot/anonymization

Package Overview
Dependencies
Maintainers
50
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@epilot/anonymization - npm Package Compare versions

Comparing version
0.1.4
to
0.2.0
+23
-13
dist/entity.js

@@ -59,19 +59,22 @@ "use strict";

for (const [key, value] of Object.entries(entity)) {
if (key !== '_title' && defaults_1.SYSTEM_KEEP_KEYS.includes(key)) {
result[key] = value;
continue;
}
const attribute = key === '_title' ? undefined : attributesByName.get(key);
// _title is computed from attributes and can carry PII (e.g. contact names)
if (key === '_title') {
const strategy = (0, exports.getAttributeStrategy)({ slug, attributeName: '_title', overrides });
const strategy = (0, exports.getAttributeStrategy)({ slug, attributeName: key, attribute, overrides });
if (strategy !== 'keep') {
result[key] = (0, exports.applyStrategy)({ value, strategy, pseudonymizer });
continue;
}
if (defaults_1.SYSTEM_KEEP_KEYS.includes(key)) {
result[key] = value;
continue;
}
const strategy = (0, exports.getAttributeStrategy)({
slug,
attributeName: key,
attribute: attributesByName.get(key),
overrides,
});
result[key] = (0, exports.applyStrategy)({ value, strategy, pseudonymizer });
// Kept values fall into two cases:
// - explicitly kept (data_classification 'public', or an override to 'keep'): the org has
// reviewed the field and opted out — pass through untouched.
// - kept by default (unclassified custom fields, computed titles, attribute types we have
// not special-cased): apply the catch-all scrub so a stray email/phone/IBAN embedded in
// the value cannot leak. Only matched identifier substrings are replaced.
result[key] = isExplicitlyKept({ slug, key, attribute, overrides })
? value
: (0, exports.mapStringLeaves)(value, (str) => pseudonymizer.scrubText(str));
}

@@ -81,2 +84,9 @@ return result;

exports.anonymizeEntity = anonymizeEntity;
/** true when the org deliberately opted a field out of anonymization (never re-scrub it) */
const isExplicitlyKept = (params) => {
const { slug, key, attribute, overrides } = params;
return (attribute?.data_classification === 'public' ||
overrides?.[`${slug}:${key}`] === 'keep' ||
overrides?.[`*:${key}`] === 'keep');
};
/**

@@ -83,0 +93,0 @@ * Apply an anonymization strategy to an attribute value, preserving the value's shape

@@ -25,3 +25,5 @@ "use strict";

redact,
scrubText: redact,
// still scrub identifiers OUT of free text (replacing only the matched substrings),
// rather than blanking the whole string — so the catch-all pass keeps non-PII text intact
scrubText: makeScrubText(redact, redact, redact),
};

@@ -50,11 +52,16 @@ }

redact: () => defaults_1.REDACTED_PLACEHOLDER,
scrubText: (value) => value
// skip already-pseudonymized emails so scrubbing stays idempotent
// (phone pseudonyms are idempotent by format, IBAN pseudonyms don't match IBAN_PATTERN)
.replace(EMAIL_PATTERN, (match) => (match.endsWith(`@${defaults_1.PSEUDONYM_EMAIL_DOMAIN}`) ? match : email(match)))
.replace(IBAN_PATTERN, iban)
.replace(PHONE_PATTERN, phone),
scrubText: makeScrubText(email, iban, phone),
};
};
exports.createPseudonymizer = createPseudonymizer;
/**
* Build a text scrubber that replaces email / IBAN / phone substrings embedded in free text,
* leaving the rest of the string intact. `email`/`iban`/`phone` are the pseudonymizer's own
* replacers (or the redactor when no secret is configured). Idempotent: pseudonymized emails
* are skipped, and phone/IBAN pseudonyms do not re-match the patterns.
*/
const makeScrubText = (email, iban, phone) => (value) => value
.replace(EMAIL_PATTERN, (match) => (match.endsWith(`@${defaults_1.PSEUDONYM_EMAIL_DOMAIN}`) ? match : email(match)))
.replace(IBAN_PATTERN, iban)
.replace(PHONE_PATTERN, phone);
const normalize = (value) => value.trim().toLowerCase();
{
"name": "@epilot/anonymization",
"version": "0.1.4",
"description": "Deterministic PII pseudonymization and redaction for epilot entity data and arbitrary JSON — data minimization for AI agents, analytics, logs and integrations",
"version": "0.2.0",
"description": "Deterministic PII pseudonymization and redaction for epilot entity data and arbitrary JSON \u2014 data minimization for AI agents, analytics, logs and integrations",
"license": "MIT",

@@ -6,0 +6,0 @@ "author": "epilot GmbH",

@@ -150,7 +150,10 @@ <h1 align="center"><img alt="epilot" src="https://raw.githubusercontent.com/epilot-dev/sdk-js/main/logo.png" width="200"><br>@epilot/anonymization</h1>

destroys utility.)
2. **Free text is opt-in by default.** Unclassified free-text fields pass through in
schema-aware mode (masking them all would destroy utility). Well-known risky fields
(note/message content, opportunity descriptions, …) are covered by the curated defaults, and
organizations opt further fields in with `data_classification: 'pii'`. Users can still type
PII into fields nobody classified.
2. **Free text is kept by default, but identifiers in it are scrubbed.** Unclassified free-text
fields are not fully masked in schema-aware mode (masking them all would destroy utility),
but a catch-all pass scrubs any email / phone / IBAN embedded in kept string values (custom
fields, computed titles, attribute types not special-cased). Well-known risky fields
(note/message content, opportunity descriptions, …) are fully redacted via the curated
defaults; organizations opt further fields in with `data_classification: 'pii'` or out with
`'public'` (which also exempts them from the catch-all). Names or other non-pattern PII typed
into unclassified fields can still slip through.
3. **Heuristics are best-effort.** The non-schema mode catches common field names and

@@ -157,0 +160,0 @@ email/phone/IBAN patterns. Novel field names or unusual formats can slip through — use