pseudonym-mcp 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -441,6 +441,18 @@ In `hybrid` mode, Ollama runs after the regex pass so the LLM never sees already
441
441
  - **No model training.** The local Ollama model operates entirely offline. Your data is not used to train any model.
442
442
  - **Strict validation by default.** Invalid SSNs (area 000/666/900+), failed-Luhn credit card numbers, and invalid-checksum PESELs are not masked, preventing false positives from OCR errors or random digit sequences.
443
443
 
444
+ ## Limitations
445
+
446
+ pseudonym-mcp is a technical privacy control, not a legal guarantee of compliance.
447
+
448
+ - Detection is best-effort — false negatives and false positives are possible.
449
+ - Indirect references (e.g. _"the tall guy from accounting"_) are not detected.
450
+ - If plaintext is logged before being passed to `mask_text`, pseudonym-mcp cannot protect it.
451
+ - The mapping store is process-local; restarting the server ends the session.
452
+ - Re-identification is possible for anyone with access to the local mapping store — this is pseudonymization, not anonymization.
453
+
454
+ > Under GDPR Art. 4(5), pseudonymized data is still personal data in your system. pseudonym-mcp substantially reduces risk but does not eliminate your legal obligations.
455
+
444
456
  ## Development
445
457
 
446
458
  ```sh
@@ -469,10 +481,6 @@ Contributions are welcome. Please follow [Conventional Commits](https://www.conv
469
481
 
470
482
  Language pack contributions are especially welcome — German (Personalausweis, Steuer-ID), French (NIR, SIRET), Spanish (DNI/NIE) and others would significantly expand the tool's usefulness.
471
483
 
472
- ## Keyword index
473
-
474
- > For discoverability: **AI privacy**, **LLM data privacy**, **PII masking**, **PII redaction**, **PII detection**, **data pseudonymization**, **GDPR LLM compliance**, **GDPR AI**, **EU AI Act**, **CCPA compliance**, **HIPAA AI**, **PCI DSS tokenization**, **SOC 2 data handling**, **personal data protection**, **sensitive data scrubbing**, **NER anonymization**, **named entity recognition privacy**, **Claude privacy layer**, **MCP privacy proxy**, **local AI processing**, **on-premise AI**, **zero-trust AI**, **data minimisation**, **privacy by design**, **SSN masking**, **credit card masking**, **Luhn validation**, **PESEL masking**, **Polish PII**, **RODO**, **UODO compliance**, **healthcare AI privacy**, **financial data redaction**, **PSD2 privacy**, **tokenization NLP**, **prompt sanitization**, **context window privacy**, **offline NER**, **Ollama privacy**, **local LLM privacy**, **cross-border data transfer**, **data protection by design**, **PIPEDA**, **LGPD**, **POPIA**.
475
-
476
484
  ## License
477
485
 
478
486
  MIT — Adrian Wolczuk
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/languages/pl/rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAOhD,eAAO,MAAM,WAAW,EAAE,aAEzB,CAAA"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/languages/pl/rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAQhD,eAAO,MAAM,WAAW,EAAE,aAEzB,CAAA"}
@@ -2,8 +2,9 @@ import { toPatternDef } from '../../patterns/types.js';
2
2
  import { peselRule } from '../../patterns/locale/pl/pesel.js';
3
3
  import { plIbanRule } from '../../patterns/locale/pl/iban.js';
4
4
  import { plPhoneRule } from '../../patterns/locale/pl/phone.js';
5
+ import { nipRule } from '../../patterns/locale/pl/nip.js';
5
6
  import { emailRule } from '../../patterns/global/email.js';
6
7
  export const PolishRules = {
7
- patterns: [peselRule, plIbanRule, emailRule, plPhoneRule].map(toPatternDef),
8
+ patterns: [peselRule, plIbanRule, emailRule, plPhoneRule, nipRule].map(toPatternDef),
8
9
  };
9
10
  //# sourceMappingURL=rules.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/languages/pl/rules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAA;AAE1D,MAAM,CAAC,MAAM,WAAW,GAAkB;IACxC,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;CAC5E,CAAA"}
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/languages/pl/rules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,iCAAiC,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAA;AAE1D,MAAM,CAAC,MAAM,WAAW,GAAkB;IACxC,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;CACrF,CAAA"}
@@ -16,8 +16,8 @@ function nipChecksum(raw) {
16
16
  export const nipRule = {
17
17
  id: 'pl.nip',
18
18
  entityType: 'NIP',
19
- // 10 digits, optionally separated by spaces or dashes in groups: XXX-XXX-XX-XX or XXX-XX-XX-XXX
20
- pattern: /\b\d{3}[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}\b/g,
19
+ // 10 digits in XXX-XXX-XX-XX format (hyphens required)
20
+ pattern: /\b\d{3}-\d{3}-\d{2}-\d{2}\b/g,
21
21
  locales: ['pl'],
22
22
  engines: ['strict', 'paranoid'],
23
23
  description: 'Polish tax identification number (NIP) — 10 digits with checksum',
@@ -1 +1 @@
1
- {"version":3,"file":"nip.js","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/nip.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IAC1C,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAC3C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC5D,MAAM,KAAK,GAAG,GAAG,GAAG,EAAE,CAAA;IACtB,OAAO,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACvC,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAgB;IAClC,EAAE,EAAE,QAAQ;IACZ,UAAU,EAAE,KAAK;IACjB,gGAAgG;IAChG,OAAO,EAAE,gDAAgD;IACzD,OAAO,EAAE,CAAC,IAAI,CAAC;IACf,OAAO,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC/B,WAAW,EAAE,kEAAkE;IAC/E,QAAQ,EAAE,WAAW;CACtB,CAAA"}
1
+ {"version":3,"file":"nip.js","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/nip.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IAC1C,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAC3C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC5D,MAAM,KAAK,GAAG,GAAG,GAAG,EAAE,CAAA;IACtB,OAAO,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACvC,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAgB;IAClC,EAAE,EAAE,QAAQ;IACZ,UAAU,EAAE,KAAK;IACjB,uDAAuD;IACvD,OAAO,EAAE,8BAA8B;IACvC,OAAO,EAAE,CAAC,IAAI,CAAC;IACf,OAAO,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC/B,WAAW,EAAE,kEAAkE;IAC/E,QAAQ,EAAE,WAAW;CACtB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"pesel.d.ts","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/pesel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAgBjD,eAAO,MAAM,SAAS,EAAE,WASvB,CAAA"}
1
+ {"version":3,"file":"pesel.d.ts","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/pesel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAiBjD,eAAO,MAAM,SAAS,EAAE,WASvB,CAAA"}
@@ -3,20 +3,21 @@
3
3
  * Weights: [1, 3, 7, 9, 1, 3, 7, 9, 1, 3]
4
4
  * Check digit = (10 - (weighted_sum % 10)) % 10
5
5
  */
6
- function peselChecksum(pesel) {
7
- if (!/^\d{11}$/.test(pesel))
6
+ function peselChecksum(input) {
7
+ const digits = input.replace(/\D/g, '');
8
+ if (digits.length !== 11)
8
9
  return false;
9
10
  const weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
10
- const digits = pesel.split('').map(Number);
11
- const sum = weights.reduce((acc, w, i) => acc + w * digits[i], 0);
11
+ const d = digits.split('').map(Number);
12
+ const sum = weights.reduce((acc, w, i) => acc + w * d[i], 0);
12
13
  const check = (10 - (sum % 10)) % 10;
13
- return check === digits[10];
14
+ return check === d[10];
14
15
  }
15
16
  export const peselRule = {
16
17
  id: 'pl.pesel',
17
18
  entityType: 'PESEL',
18
- // Exactly 11 consecutive digits NOT adjacent to another digit
19
- pattern: /(?<!\d)\d{11}(?!\d)/g,
19
+ // Matches "PESEL XXXXXXXXXXX" (whole phrase) or standalone 11 digits
20
+ pattern: /(?:PESEL\s+)?(?<!\d)\d{11}(?!\d)/g,
20
21
  locales: ['pl'],
21
22
  engines: ['balanced', 'strict', 'paranoid'],
22
23
  description: 'Polish national identification number (PESEL) — 11 digits with checksum',
@@ -1 +1 @@
1
- {"version":3,"file":"pesel.js","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/pesel.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACzC,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACjE,MAAM,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAA;IACpC,OAAO,KAAK,KAAK,MAAM,CAAC,EAAE,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAgB;IACpC,EAAE,EAAE,UAAU;IACd,UAAU,EAAE,OAAO;IACnB,8DAA8D;IAC9D,OAAO,EAAE,sBAAsB;IAC/B,OAAO,EAAE,CAAC,IAAI,CAAC;IACf,OAAO,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC;IAC3C,WAAW,EAAE,yEAAyE;IACtF,QAAQ,EAAE,aAAa;CACxB,CAAA"}
1
+ {"version":3,"file":"pesel.js","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/pesel.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAa;IAClC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IACvC,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE;QAAE,OAAO,KAAK,CAAA;IACtC,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAC9C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC5D,MAAM,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAA;IACpC,OAAO,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAgB;IACpC,EAAE,EAAE,UAAU;IACd,UAAU,EAAE,OAAO;IACnB,qEAAqE;IACrE,OAAO,EAAE,mCAAmC;IAC5C,OAAO,EAAE,CAAC,IAAI,CAAC;IACf,OAAO,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC;IAC3C,WAAW,EAAE,yEAAyE;IACtF,QAAQ,EAAE,aAAa;CACxB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"phone.d.ts","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/phone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjD,eAAO,MAAM,WAAW,EAAE,WAYzB,CAAA"}
1
+ {"version":3,"file":"phone.d.ts","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/phone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjD,eAAO,MAAM,WAAW,EAAE,WAazB,CAAA"}
@@ -1,11 +1,12 @@
1
1
  export const plPhoneRule = {
2
2
  id: 'pl.phone',
3
3
  entityType: 'PHONE',
4
- // Three alternatives:
4
+ // Four alternatives:
5
5
  // 1. International prefix: +48 or 0048, then 9 digits (with optional spaces/dashes)
6
6
  // 2. 9-digit mobile starting with 4–8 (Polish numbering plan)
7
7
  // 3. Landline with area code in parens: (XX) XXX-XX-XX
8
- pattern: /(?:\+48|0048)[\s\-]?\d{3}[\s\-]?\d{3}[\s\-]?\d{3}|\b[4-8]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}\b|\(\d{2}\)[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}/g,
8
+ // 4. Landline without prefix: 2-digit area code + 7 digits (XX XXX XX XX or XX XXXXXXX)
9
+ pattern: /(?:\+48|0048)[\s\-]?\d{3}[\s\-]?\d{3}[\s\-]?\d{3}|\b[4-8]\d{2}[\s\-]?\d{3}[\s\-]?\d{3}\b|\(\d{2}\)[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}|\b[1-9]\d[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}\b/g,
9
10
  locales: ['pl'],
10
11
  engines: ['balanced', 'strict', 'paranoid'],
11
12
  description: 'Polish phone number (+48 / 0048 prefix, 9-digit mobile, landline)',
@@ -1 +1 @@
1
- {"version":3,"file":"phone.js","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/phone.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,EAAE,EAAE,UAAU;IACd,UAAU,EAAE,OAAO;IACnB,sBAAsB;IACtB,oFAAoF;IACpF,8DAA8D;IAC9D,uDAAuD;IACvD,OAAO,EACL,yIAAyI;IAC3I,OAAO,EAAE,CAAC,IAAI,CAAC;IACf,OAAO,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC;IAC3C,WAAW,EAAE,mEAAmE;CACjF,CAAA"}
1
+ {"version":3,"file":"phone.js","sourceRoot":"","sources":["../../../../src/patterns/locale/pl/phone.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,EAAE,EAAE,UAAU;IACd,UAAU,EAAE,OAAO;IACnB,qBAAqB;IACrB,oFAAoF;IACpF,8DAA8D;IAC9D,uDAAuD;IACvD,wFAAwF;IACxF,OAAO,EACL,yLAAyL;IAC3L,OAAO,EAAE,CAAC,IAAI,CAAC;IACf,OAAO,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC;IAC3C,WAAW,EAAE,mEAAmE;CACjF,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pseudonym-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "mcpName": "io.github.woladi/pseudonym-mcp",
5
5
  "description": "MCP server for privacy-preserving pseudonymization of sensitive data before cloud LLM processing",
6
6
  "type": "module",
@@ -109,7 +109,8 @@
109
109
  "git": {
110
110
  "commitMessage": "chore: release v${version}",
111
111
  "tagName": "v${version}",
112
- "push": false
112
+ "push": false,
113
+ "requireCleanWorkingDir": false
113
114
  },
114
115
  "npm": {
115
116
  "publish": true