nz-privacy-auditor 0.8.0

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.
Files changed (67) hide show
  1. package/README.md +152 -0
  2. package/data/llm_cache/cache.json +1 -0
  3. package/dist/cli.d.ts +16 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +233 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/detectors/address.d.ts +48 -0
  8. package/dist/detectors/address.d.ts.map +1 -0
  9. package/dist/detectors/address.js +262 -0
  10. package/dist/detectors/address.js.map +1 -0
  11. package/dist/detectors/base.d.ts +43 -0
  12. package/dist/detectors/base.d.ts.map +1 -0
  13. package/dist/detectors/base.js +6 -0
  14. package/dist/detectors/base.js.map +1 -0
  15. package/dist/detectors/driver_licence.d.ts +38 -0
  16. package/dist/detectors/driver_licence.d.ts.map +1 -0
  17. package/dist/detectors/driver_licence.js +80 -0
  18. package/dist/detectors/driver_licence.js.map +1 -0
  19. package/dist/detectors/ird.d.ts +37 -0
  20. package/dist/detectors/ird.d.ts.map +1 -0
  21. package/dist/detectors/ird.js +103 -0
  22. package/dist/detectors/ird.js.map +1 -0
  23. package/dist/detectors/maori_name.d.ts +36 -0
  24. package/dist/detectors/maori_name.d.ts.map +1 -0
  25. package/dist/detectors/maori_name.js +251 -0
  26. package/dist/detectors/maori_name.js.map +1 -0
  27. package/dist/detectors/nhi.d.ts +44 -0
  28. package/dist/detectors/nhi.d.ts.map +1 -0
  29. package/dist/detectors/nhi.js +147 -0
  30. package/dist/detectors/nhi.js.map +1 -0
  31. package/dist/detectors/phone.d.ts +49 -0
  32. package/dist/detectors/phone.d.ts.map +1 -0
  33. package/dist/detectors/phone.js +126 -0
  34. package/dist/detectors/phone.js.map +1 -0
  35. package/dist/index.d.ts +25 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +52 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/report.d.ts +14 -0
  40. package/dist/report.d.ts.map +1 -0
  41. package/dist/report.js +90 -0
  42. package/dist/report.js.map +1 -0
  43. package/dist/scanner.d.ts +51 -0
  44. package/dist/scanner.d.ts.map +1 -0
  45. package/dist/scanner.js +114 -0
  46. package/dist/scanner.js.map +1 -0
  47. package/dist/verification.d.ts +25 -0
  48. package/dist/verification.d.ts.map +1 -0
  49. package/dist/verification.js +89 -0
  50. package/dist/verification.js.map +1 -0
  51. package/dist/verifiers/base.d.ts +27 -0
  52. package/dist/verifiers/base.d.ts.map +1 -0
  53. package/dist/verifiers/base.js +24 -0
  54. package/dist/verifiers/base.js.map +1 -0
  55. package/dist/verifiers/cache.d.ts +23 -0
  56. package/dist/verifiers/cache.d.ts.map +1 -0
  57. package/dist/verifiers/cache.js +103 -0
  58. package/dist/verifiers/cache.js.map +1 -0
  59. package/dist/verifiers/gemini.d.ts +38 -0
  60. package/dist/verifiers/gemini.d.ts.map +1 -0
  61. package/dist/verifiers/gemini.js +121 -0
  62. package/dist/verifiers/gemini.js.map +1 -0
  63. package/dist/verifiers/index.d.ts +8 -0
  64. package/dist/verifiers/index.d.ts.map +1 -0
  65. package/dist/verifiers/index.js +16 -0
  66. package/dist/verifiers/index.js.map +1 -0
  67. package/package.json +96 -0
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ /**
3
+ * Te reo Māori personal-name detector.
4
+ *
5
+ * Detects predominantly Māori given names and surnames from a curated
6
+ * gazetteer. The detector is **macron-aware**: a value like `Tāne` and its
7
+ * non-macron form `Tane` both match the canonical entry. Confidence is
8
+ * higher (0.9) when the input preserved the macron — a signal that the
9
+ * data was entered with cultural care — and lower (0.7) otherwise.
10
+ *
11
+ * Severity is LOW because a personal name on its own is not necessarily
12
+ * identifying under the Privacy Act 2020. The detector exists so that
13
+ * downstream consumers can apply additional care to records that contain
14
+ * culturally significant te reo Māori identifiers (e.g. IPP 4 collection
15
+ * manner, IPP 8 accuracy obligations around name spelling and macron
16
+ * preservation).
17
+ *
18
+ * TypeScript port of `src/nz_privacy_auditor/detectors/maori_name.py`. The
19
+ * name lists below are verbatim mirrors of the Python `_GIVEN_NAMES` and
20
+ * `_SURNAMES` sets — any change to one must be applied to the other.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.MaoriNameDetector = void 0;
24
+ exports.foldMacrons = foldMacrons;
25
+ // Canonical macron-preserved given names.
26
+ const GIVEN_NAMES = [
27
+ "Ahu",
28
+ "Aotea",
29
+ "Aroha",
30
+ "Atareta",
31
+ "Hahana",
32
+ "Hana",
33
+ "Hēnare",
34
+ "Hēni",
35
+ "Hera",
36
+ "Hineata",
37
+ "Hinemoa",
38
+ "Hinerangi",
39
+ "Hineroa",
40
+ "Hiwi",
41
+ "Hōne",
42
+ "Huia",
43
+ "Iwa",
44
+ "Kahu",
45
+ "Kahukura",
46
+ "Kahurangi",
47
+ "Karanga",
48
+ "Kataraina",
49
+ "Kāhu",
50
+ "Kawe",
51
+ "Kura",
52
+ "Maaka",
53
+ "Maata",
54
+ "Maia",
55
+ "Mākareta",
56
+ "Manaaki",
57
+ "Manawanui",
58
+ "Manuwiri",
59
+ "Maraea",
60
+ "Marama",
61
+ "Mareikura",
62
+ "Matiu",
63
+ "Matua",
64
+ "Mātauranga",
65
+ "Mereana",
66
+ "Mihaere",
67
+ "Mihi",
68
+ "Miriama",
69
+ "Moana",
70
+ "Naera",
71
+ "Ngahere",
72
+ "Ngāhuia",
73
+ "Ngaire",
74
+ "Ngāmoko",
75
+ "Nikau",
76
+ "Pania",
77
+ "Paora",
78
+ "Pare",
79
+ "Patiti",
80
+ "Peti",
81
+ "Piripi",
82
+ "Rahera",
83
+ "Rangi",
84
+ "Rangimārie",
85
+ "Rapata",
86
+ "Rāwiri",
87
+ "Rēhia",
88
+ "Rēweti",
89
+ "Riria",
90
+ "Riripeti",
91
+ "Ruia",
92
+ "Tāmati",
93
+ "Tāne",
94
+ "Tangaroa",
95
+ "Tangihia",
96
+ "Tāwhao",
97
+ "Tāwhirimātea",
98
+ "Te Aroha",
99
+ "Te Atatū",
100
+ "Te Rongo",
101
+ "Tia",
102
+ "Tipene",
103
+ "Toi",
104
+ "Tūī",
105
+ "Tui",
106
+ "Waiora",
107
+ "Wairaka",
108
+ "Wairoa",
109
+ "Waka",
110
+ "Whaea",
111
+ "Whaitiri",
112
+ "Wikitōria",
113
+ "Wī",
114
+ ];
115
+ // Canonical macron-preserved surnames.
116
+ const SURNAMES = [
117
+ "Awarau",
118
+ "Hawira",
119
+ "Heihei",
120
+ "Heke",
121
+ "Hēnare",
122
+ "Hirini",
123
+ "Hokopaura",
124
+ "Kaa",
125
+ "Kaihau",
126
+ "Kake",
127
+ "Karena",
128
+ "Karetai",
129
+ "Kaumoana",
130
+ "Kawana",
131
+ "Mahuika",
132
+ "Mahuta",
133
+ "Mareikura",
134
+ "Mikaere",
135
+ "Ngāmoko",
136
+ "Ngāpō",
137
+ "Ngata",
138
+ "Paora",
139
+ "Parata",
140
+ "Paringatai",
141
+ "Pirini",
142
+ "Pomare",
143
+ "Rangihau",
144
+ "Rangitaaua",
145
+ "Rangitīria",
146
+ "Rapata",
147
+ "Reedy",
148
+ "Royal",
149
+ "Sharples",
150
+ "Tahere",
151
+ "Tāmati",
152
+ "Tamihere",
153
+ "Tangohia",
154
+ "Tapsell",
155
+ "Taringa",
156
+ "Tauwhare",
157
+ "Te Aho",
158
+ "Te Atawhai",
159
+ "Te Heuheu",
160
+ "Te Rangi",
161
+ "Te Whaiti",
162
+ "Te Whata",
163
+ "Tinirau",
164
+ "Tipene",
165
+ "Tirikatene",
166
+ "Toia",
167
+ "Tureia",
168
+ "Turia",
169
+ "Uerata",
170
+ "Waiariki",
171
+ "Waipoua",
172
+ "Waititi",
173
+ "Wharepouri",
174
+ "Whaitiri",
175
+ "Witehira",
176
+ "Witika",
177
+ ];
178
+ const ALL_NAMES = [
179
+ ...new Set([...GIVEN_NAMES, ...SURNAMES]),
180
+ ];
181
+ const GIVEN_SET = new Set(GIVEN_NAMES);
182
+ const SURNAME_SET = new Set(SURNAMES);
183
+ /** Casefold and strip combining marks (macrons) for fuzzy matching. */
184
+ function foldMacrons(s) {
185
+ return s.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
186
+ }
187
+ // Pre-compute folded forms for O(1) lookup.
188
+ const FOLDED_TO_CANONICAL = new Map();
189
+ for (const name of ALL_NAMES) {
190
+ FOLDED_TO_CANONICAL.set(foldMacrons(name), name);
191
+ }
192
+ function escapeRegex(s) {
193
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
194
+ }
195
+ // Build the search regex. Include both canonical and folded variants,
196
+ // sorted longest first so multi-word names like "Te Heuheu" win over "Te".
197
+ const NAME_VARIANTS = [
198
+ ...new Set([
199
+ ...ALL_NAMES,
200
+ ...ALL_NAMES.map((n) => foldMacrons(n)),
201
+ ]),
202
+ ].sort((a, b) => b.length - a.length);
203
+ const NAME_PATTERN = new RegExp("\\b(?:" + NAME_VARIANTS.map(escapeRegex).join("|") + ")\\b", "gi");
204
+ function kindFor(canonical) {
205
+ const g = GIVEN_SET.has(canonical);
206
+ const s = SURNAME_SET.has(canonical);
207
+ if (g && s)
208
+ return "given_or_surname";
209
+ return g ? "given" : "surname";
210
+ }
211
+ /**
212
+ * Detects te reo Māori personal names from a curated gazetteer.
213
+ *
214
+ * The detector is macron-aware and case-insensitive. Findings carry the
215
+ * canonical macron-preserved form in `context.canonical` even when the
216
+ * input did not use macrons.
217
+ */
218
+ class MaoriNameDetector {
219
+ constructor() {
220
+ this.name = "maori_name";
221
+ this.severity = "low";
222
+ }
223
+ scan(value) {
224
+ const findings = [];
225
+ NAME_PATTERN.lastIndex = 0;
226
+ let match;
227
+ while ((match = NAME_PATTERN.exec(value)) !== null) {
228
+ const raw = match[0];
229
+ const canonical = FOLDED_TO_CANONICAL.get(foldMacrons(raw));
230
+ if (canonical === undefined)
231
+ continue;
232
+ const macronsPreserved = raw.toLowerCase() === canonical.toLowerCase();
233
+ findings.push({
234
+ detector: this.name,
235
+ severity: this.severity,
236
+ value: raw,
237
+ start: match.index,
238
+ end: match.index + raw.length,
239
+ confidence: macronsPreserved ? 0.9 : 0.7,
240
+ context: {
241
+ canonical,
242
+ kind: kindFor(canonical),
243
+ macrons_preserved: macronsPreserved,
244
+ },
245
+ });
246
+ }
247
+ return findings;
248
+ }
249
+ }
250
+ exports.MaoriNameDetector = MaoriNameDetector;
251
+ //# sourceMappingURL=maori_name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maori_name.js","sourceRoot":"","sources":["../../src/detectors/maori_name.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAuKH,kCAEC;AArKD,0CAA0C;AAC1C,MAAM,WAAW,GAAsB;IACrC,KAAK;IACL,OAAO;IACP,OAAO;IACP,SAAS;IACT,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,MAAM;IACN,MAAM;IACN,SAAS;IACT,SAAS;IACT,WAAW;IACX,SAAS;IACT,MAAM;IACN,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,UAAU;IACV,WAAW;IACX,SAAS;IACT,WAAW;IACX,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,MAAM;IACN,UAAU;IACV,SAAS;IACT,WAAW;IACX,UAAU;IACV,QAAQ;IACR,QAAQ;IACR,WAAW;IACX,OAAO;IACP,OAAO;IACP,YAAY;IACZ,SAAS;IACT,SAAS;IACT,MAAM;IACN,SAAS;IACT,OAAO;IACP,OAAO;IACP,SAAS;IACT,SAAS;IACT,QAAQ;IACR,SAAS;IACT,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,YAAY;IACZ,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,OAAO;IACP,UAAU;IACV,MAAM;IACN,QAAQ;IACR,MAAM;IACN,UAAU;IACV,UAAU;IACV,QAAQ;IACR,cAAc;IACd,UAAU;IACV,UAAU;IACV,UAAU;IACV,KAAK;IACL,QAAQ;IACR,KAAK;IACL,KAAK;IACL,KAAK;IACL,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,MAAM;IACN,OAAO;IACP,UAAU;IACV,WAAW;IACX,IAAI;CACL,CAAC;AAEF,uCAAuC;AACvC,MAAM,QAAQ,GAAsB;IAClC,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,WAAW;IACX,KAAK;IACL,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,SAAS;IACT,UAAU;IACV,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,WAAW;IACX,SAAS;IACT,SAAS;IACT,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,QAAQ;IACR,OAAO;IACP,OAAO;IACP,UAAU;IACV,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,UAAU;IACV,SAAS;IACT,SAAS;IACT,UAAU;IACV,QAAQ;IACR,YAAY;IACZ,WAAW;IACX,UAAU;IACV,WAAW;IACX,UAAU;IACV,SAAS;IACT,QAAQ;IACR,YAAY;IACZ,MAAM;IACN,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,UAAU;IACV,SAAS;IACT,SAAS;IACT,YAAY;IACZ,UAAU;IACV,UAAU;IACV,QAAQ;CACT,CAAC;AAEF,MAAM,SAAS,GAAsB;IACnC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,QAAQ,CAAC,CAAC;CAC1C,CAAC;AAEF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;AACvC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;AAEtC,uEAAuE;AACvE,SAAgB,WAAW,CAAC,CAAS;IACnC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACjE,CAAC;AAED,4CAA4C;AAC5C,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAkB,CAAC;AACtD,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;IAC7B,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,OAAO,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC;AAED,sEAAsE;AACtE,2EAA2E;AAC3E,MAAM,aAAa,GAAG;IACpB,GAAG,IAAI,GAAG,CAAC;QACT,GAAG,SAAS;QACZ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;KACxC,CAAC;CACH,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtC,MAAM,YAAY,GAAG,IAAI,MAAM,CAC7B,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,EAC5D,IAAI,CACL,CAAC;AAEF,SAAS,OAAO,CAAC,SAAiB;IAChC,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACrC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,kBAAkB,CAAC;IACtC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,MAAa,iBAAiB;IAA9B;QACW,SAAI,GAAG,YAAY,CAAC;QACpB,aAAQ,GAAa,KAAK,CAAC;IA2BtC,CAAC;IAzBC,IAAI,CAAC,KAAa;QAChB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,YAAY,CAAC,SAAS,GAAG,CAAC,CAAC;QAC3B,IAAI,KAA6B,CAAC;QAClC,OAAO,CAAC,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACnD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,SAAS,GAAG,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5D,IAAI,SAAS,KAAK,SAAS;gBAAE,SAAS;YACtC,MAAM,gBAAgB,GAAG,GAAG,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,WAAW,EAAE,CAAC;YACvE,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,IAAI,CAAC,IAAI;gBACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,KAAK,EAAE,GAAG;gBACV,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,GAAG,EAAE,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,MAAM;gBAC7B,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;gBACxC,OAAO,EAAE;oBACP,SAAS;oBACT,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC;oBACxB,iBAAiB,EAAE,gBAAgB;iBACpC;aACF,CAAC,CAAC;QACL,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AA7BD,8CA6BC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * NHI (National Health Index) number detector.
3
+ *
4
+ * NZ NHI numbers are 7-character alphanumeric identifiers issued by Health NZ
5
+ * | Te Whatu Ora. Two formats coexist:
6
+ *
7
+ * 1. **Legacy** (AAANNNC) — 3 letters + 3 digits + 1 numeric check digit.
8
+ * Validated with mod-11 checksum. Used since 1993; remaining capacity
9
+ * exhausted by mid-2026.
10
+ *
11
+ * 2. **New** (AAANNAX) — 3 letters + 2 digits + 1 letter + 1 letter check
12
+ * digit. Validated with mod-23 checksum. Issued from 1 July 2026 onwards
13
+ * alongside the legacy format.
14
+ *
15
+ * Both formats exclude the letters `I` and `O` (visually ambiguous with
16
+ * `1` and `0`). Each allowed letter maps to an ordinal 1–24:
17
+ *
18
+ * A=1, B=2, C=3, D=4, E=5, F=6, G=7, H=8, J=9, K=10,
19
+ * L=11, M=12, N=13, P=14, Q=15, R=16, S=17, T=18, U=19,
20
+ * V=20, W=21, X=22, Y=23, Z=24
21
+ *
22
+ * References:
23
+ * https://en.wikipedia.org/wiki/NHI_Number
24
+ * https://www.tewhatuora.govt.nz/health-services-and-programmes/health-identity/national-health-index/upcoming-changes-nhi
25
+ *
26
+ * TypeScript port of `src/nz_privacy_auditor/detectors/nhi.py`. Test vectors
27
+ * for both implementations live in `fixtures/nhi.json`.
28
+ */
29
+ import type { Detector, Finding, Severity } from "./base";
30
+ /** Return true if `nhi` is a valid NHI in either format. */
31
+ export declare function isValidNhi(input: string): boolean;
32
+ /**
33
+ * Detects NZ National Health Index numbers in both legacy and new formats.
34
+ *
35
+ * Only checksum-validated matches are emitted. NHI numbers are highly
36
+ * sensitive health identifiers under the Privacy Act 2020 and the Health
37
+ * Information Privacy Code 2020.
38
+ */
39
+ export declare class NHIDetector implements Detector {
40
+ readonly name = "nhi";
41
+ readonly severity: Severity;
42
+ scan(value: string): Finding[];
43
+ }
44
+ //# sourceMappingURL=nhi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nhi.d.ts","sourceRoot":"","sources":["../../src/detectors/nhi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAsE1D,4DAA4D;AAC5D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOjD;AAED;;;;;;GAMG;AACH,qBAAa,WAAY,YAAW,QAAQ;IAC1C,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAU;IAErC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE;CAsB/B"}
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /**
3
+ * NHI (National Health Index) number detector.
4
+ *
5
+ * NZ NHI numbers are 7-character alphanumeric identifiers issued by Health NZ
6
+ * | Te Whatu Ora. Two formats coexist:
7
+ *
8
+ * 1. **Legacy** (AAANNNC) — 3 letters + 3 digits + 1 numeric check digit.
9
+ * Validated with mod-11 checksum. Used since 1993; remaining capacity
10
+ * exhausted by mid-2026.
11
+ *
12
+ * 2. **New** (AAANNAX) — 3 letters + 2 digits + 1 letter + 1 letter check
13
+ * digit. Validated with mod-23 checksum. Issued from 1 July 2026 onwards
14
+ * alongside the legacy format.
15
+ *
16
+ * Both formats exclude the letters `I` and `O` (visually ambiguous with
17
+ * `1` and `0`). Each allowed letter maps to an ordinal 1–24:
18
+ *
19
+ * A=1, B=2, C=3, D=4, E=5, F=6, G=7, H=8, J=9, K=10,
20
+ * L=11, M=12, N=13, P=14, Q=15, R=16, S=17, T=18, U=19,
21
+ * V=20, W=21, X=22, Y=23, Z=24
22
+ *
23
+ * References:
24
+ * https://en.wikipedia.org/wiki/NHI_Number
25
+ * https://www.tewhatuora.govt.nz/health-services-and-programmes/health-identity/national-health-index/upcoming-changes-nhi
26
+ *
27
+ * TypeScript port of `src/nz_privacy_auditor/detectors/nhi.py`. Test vectors
28
+ * for both implementations live in `fixtures/nhi.json`.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.NHIDetector = void 0;
32
+ exports.isValidNhi = isValidNhi;
33
+ // Letters allowed in NHIs: A–Z excluding I and O.
34
+ const ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ";
35
+ // Letter -> ordinal value (1..24).
36
+ const LETTER_VALUE = {};
37
+ // Ordinal value -> letter (1..24).
38
+ const VALUE_LETTER = {};
39
+ for (let i = 0; i < ALPHABET.length; i++) {
40
+ const ch = ALPHABET[i];
41
+ LETTER_VALUE[ch] = i + 1;
42
+ VALUE_LETTER[i + 1] = ch;
43
+ }
44
+ // Weights applied to the first six characters for both checksum variants.
45
+ const WEIGHTS = [7, 6, 5, 4, 3, 2];
46
+ // Combined regex: 3 letters then either 4 digits (legacy) or 2 digits + 2 letters (new).
47
+ // The `i` flag makes it case-insensitive; input is normalised to uppercase in the detector.
48
+ const NHI_PATTERN = /\b([A-HJ-NP-Z]{3}(?:\d{4}|\d{2}[A-HJ-NP-Z]{2}))\b/gi;
49
+ function charValue(ch) {
50
+ if (/^\d$/.test(ch))
51
+ return Number(ch);
52
+ const v = LETTER_VALUE[ch];
53
+ return v ?? null;
54
+ }
55
+ function weightedSum(chars) {
56
+ let total = 0;
57
+ for (let i = 0; i < WEIGHTS.length; i++) {
58
+ const v = charValue(chars[i]);
59
+ if (v === null)
60
+ return null;
61
+ total += v * WEIGHTS[i];
62
+ }
63
+ return total;
64
+ }
65
+ function legacyValid(nhi) {
66
+ if (nhi.length !== 7)
67
+ return false;
68
+ if (!/^[A-Z]{3}\d{4}$/.test(nhi))
69
+ return false;
70
+ if (/[IO]/.test(nhi.slice(0, 3)))
71
+ return false;
72
+ const total = weightedSum(nhi);
73
+ if (total === null)
74
+ return false;
75
+ const remainder = total % 11;
76
+ // Per HISO spec: remainder of zero yields no valid check digit.
77
+ if (remainder === 0)
78
+ return false;
79
+ let check = 11 - remainder;
80
+ if (check === 10)
81
+ check = 0;
82
+ return check === Number(nhi[6]);
83
+ }
84
+ function newValid(nhi) {
85
+ if (nhi.length !== 7)
86
+ return false;
87
+ if (!/^[A-Z]{3}\d{2}[A-Z]{2}$/.test(nhi))
88
+ return false;
89
+ if (/[IO]/.test(nhi[0]) || /[IO]/.test(nhi[1]) || /[IO]/.test(nhi[2]))
90
+ return false;
91
+ if (/[IO]/.test(nhi[5]) || /[IO]/.test(nhi[6]))
92
+ return false;
93
+ const total = weightedSum(nhi);
94
+ if (total === null)
95
+ return false;
96
+ const remainder = total % 23;
97
+ const checkValue = 23 - remainder;
98
+ const expected = VALUE_LETTER[checkValue];
99
+ return expected !== undefined && expected === nhi[6];
100
+ }
101
+ /** Return true if `nhi` is a valid NHI in either format. */
102
+ function isValidNhi(input) {
103
+ const nhi = input.toUpperCase();
104
+ if (nhi.length === 7 && /^\d{4}$/.test(nhi.slice(3)))
105
+ return legacyValid(nhi);
106
+ if (nhi.length === 7 && /^\d{2}$/.test(nhi.slice(3, 5)) && /^[A-Z]{2}$/.test(nhi.slice(5))) {
107
+ return newValid(nhi);
108
+ }
109
+ return false;
110
+ }
111
+ /**
112
+ * Detects NZ National Health Index numbers in both legacy and new formats.
113
+ *
114
+ * Only checksum-validated matches are emitted. NHI numbers are highly
115
+ * sensitive health identifiers under the Privacy Act 2020 and the Health
116
+ * Information Privacy Code 2020.
117
+ */
118
+ class NHIDetector {
119
+ constructor() {
120
+ this.name = "nhi";
121
+ this.severity = "high";
122
+ }
123
+ scan(value) {
124
+ const findings = [];
125
+ NHI_PATTERN.lastIndex = 0;
126
+ let match;
127
+ while ((match = NHI_PATTERN.exec(value)) !== null) {
128
+ const raw = match[1];
129
+ const upper = raw.toUpperCase();
130
+ if (isValidNhi(upper)) {
131
+ const format = /^\d{4}$/.test(upper.slice(3)) ? "legacy" : "new";
132
+ findings.push({
133
+ detector: this.name,
134
+ severity: this.severity,
135
+ value: raw,
136
+ start: match.index,
137
+ end: match.index + raw.length,
138
+ confidence: 1.0,
139
+ context: { normalised: upper, format },
140
+ });
141
+ }
142
+ }
143
+ return findings;
144
+ }
145
+ }
146
+ exports.NHIDetector = NHIDetector;
147
+ //# sourceMappingURL=nhi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nhi.js","sourceRoot":"","sources":["../../src/detectors/nhi.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * NZ phone number detector.
3
+ *
4
+ * NZ phone numbers follow the New Zealand Numbering Plan administered by the
5
+ * Number Administration Deed. The country code is `+64` and the domestic
6
+ * trunk prefix is `0`. After stripping the country code and trunk prefix,
7
+ * the National Significant Number (NSN) begins with one of:
8
+ *
9
+ * - `2X` — mobile (021, 022, 024, 027, 028, 029 and other 02X blocks).
10
+ * NSN length 8–10 digits.
11
+ * - `3`, `4`, `6`, `7`, `9` — single-digit geographic landline area codes
12
+ * (South Island, Wellington, Lower North Island, Central / Upper North
13
+ * Island, Auckland / Northland). NSN length 8 digits.
14
+ * - `800`, `508` — toll-free. NSN length 9–10 digits.
15
+ * - `900` — premium-rate. NSN length 8–9 digits.
16
+ *
17
+ * Severity is MEDIUM because a phone number alone is a quasi-identifier
18
+ * under the Privacy Act 2020: it does not necessarily identify a natural
19
+ * person on its own, but it readily combines with other attributes.
20
+ *
21
+ * TypeScript port of `src/nz_privacy_auditor/detectors/phone.py`. Test
22
+ * vectors for both implementations live in `fixtures/phone.json`.
23
+ */
24
+ import type { Detector, Finding, Severity } from "./base";
25
+ /**
26
+ * Strip separators and country / trunk prefixes to obtain the NSN.
27
+ *
28
+ * Returns the National Significant Number as a digit-only string, or
29
+ * null if the input cannot be reduced to a plausible NSN.
30
+ */
31
+ export declare function toNsn(raw: string): string | null;
32
+ /** Return the phone-kind label for a valid NSN, or null if invalid. */
33
+ export declare function classify(nsn: string): string | null;
34
+ /** Return the canonical E.164 form for an NZ phone string, or null. */
35
+ export declare function toE164(raw: string): string | null;
36
+ /**
37
+ * Detects NZ phone numbers in international and national formats.
38
+ *
39
+ * Emits findings only for strings whose NSN, after stripping the country
40
+ * code and trunk prefix, matches a known prefix + length rule. Confidence
41
+ * is 1.0 for matches with an explicit `+64` or `0064` country code
42
+ * (unambiguously NZ) and 0.8 for national-form matches.
43
+ */
44
+ export declare class PhoneDetector implements Detector {
45
+ readonly name = "phone";
46
+ readonly severity: Severity;
47
+ scan(value: string): Finding[];
48
+ }
49
+ //# sourceMappingURL=phone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phone.d.ts","sourceRoot":"","sources":["../../src/detectors/phone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAO1D;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWhD;AAED,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkBnD;AAED,uEAAuE;AACvE,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKjD;AAED;;;;;;;GAOG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC5C,QAAQ,CAAC,IAAI,WAAW;IACxB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAY;IAEvC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE;CA2B/B"}
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ /**
3
+ * NZ phone number detector.
4
+ *
5
+ * NZ phone numbers follow the New Zealand Numbering Plan administered by the
6
+ * Number Administration Deed. The country code is `+64` and the domestic
7
+ * trunk prefix is `0`. After stripping the country code and trunk prefix,
8
+ * the National Significant Number (NSN) begins with one of:
9
+ *
10
+ * - `2X` — mobile (021, 022, 024, 027, 028, 029 and other 02X blocks).
11
+ * NSN length 8–10 digits.
12
+ * - `3`, `4`, `6`, `7`, `9` — single-digit geographic landline area codes
13
+ * (South Island, Wellington, Lower North Island, Central / Upper North
14
+ * Island, Auckland / Northland). NSN length 8 digits.
15
+ * - `800`, `508` — toll-free. NSN length 9–10 digits.
16
+ * - `900` — premium-rate. NSN length 8–9 digits.
17
+ *
18
+ * Severity is MEDIUM because a phone number alone is a quasi-identifier
19
+ * under the Privacy Act 2020: it does not necessarily identify a natural
20
+ * person on its own, but it readily combines with other attributes.
21
+ *
22
+ * TypeScript port of `src/nz_privacy_auditor/detectors/phone.py`. Test
23
+ * vectors for both implementations live in `fixtures/phone.json`.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.PhoneDetector = void 0;
27
+ exports.toNsn = toNsn;
28
+ exports.classify = classify;
29
+ exports.toE164 = toE164;
30
+ // Loose match: international (+64 / 0064) form, or national (0) form.
31
+ // Allows spaces, hyphens, dots, and parentheses as separators.
32
+ const PHONE_PATTERN = /(?<!\w)((?:\+64|0064)[\s\-.()]{0,4}\d[\d\s\-.()]{6,14}\d|0[\s\-.()]{0,3}\d[\d\s\-.()]{6,12}\d)(?!\w)/g;
33
+ /**
34
+ * Strip separators and country / trunk prefixes to obtain the NSN.
35
+ *
36
+ * Returns the National Significant Number as a digit-only string, or
37
+ * null if the input cannot be reduced to a plausible NSN.
38
+ */
39
+ function toNsn(raw) {
40
+ let digits = raw.replace(/\D/g, "");
41
+ if (digits.startsWith("0064")) {
42
+ digits = digits.slice(4);
43
+ }
44
+ else if (digits.startsWith("64") && digits.length >= 10) {
45
+ // 64 + NSN (NSN is 8–10 digits, so total 10–12). Avoids stripping the
46
+ // "64" out of, say, an Auckland landline beginning with "964…".
47
+ digits = digits.slice(2);
48
+ }
49
+ if (digits.startsWith("0"))
50
+ digits = digits.slice(1);
51
+ return digits.length > 0 ? digits : null;
52
+ }
53
+ /** Return the phone-kind label for a valid NSN, or null if invalid. */
54
+ function classify(nsn) {
55
+ if (!/^\d+$/.test(nsn))
56
+ return null;
57
+ const n = nsn.length;
58
+ // Mobile: NSN begins with 2 and is 8–10 digits total.
59
+ if (nsn.startsWith("2") && n >= 8 && n <= 10)
60
+ return "mobile";
61
+ // Toll-free / premium share length range with landline so check first.
62
+ if ((nsn.startsWith("800") || nsn.startsWith("508")) && n >= 9 && n <= 10) {
63
+ return "toll_free";
64
+ }
65
+ if (nsn.startsWith("900") && n >= 8 && n <= 9)
66
+ return "premium";
67
+ // Geographic landline: single-digit area code 3, 4, 6, 7, or 9 then 7
68
+ // subscriber digits, NSN = 8 digits.
69
+ if (n === 8 && "34679".includes(nsn[0]))
70
+ return "landline";
71
+ return null;
72
+ }
73
+ /** Return the canonical E.164 form for an NZ phone string, or null. */
74
+ function toE164(raw) {
75
+ const nsn = toNsn(raw);
76
+ if (nsn === null)
77
+ return null;
78
+ if (classify(nsn) === null)
79
+ return null;
80
+ return `+64${nsn}`;
81
+ }
82
+ /**
83
+ * Detects NZ phone numbers in international and national formats.
84
+ *
85
+ * Emits findings only for strings whose NSN, after stripping the country
86
+ * code and trunk prefix, matches a known prefix + length rule. Confidence
87
+ * is 1.0 for matches with an explicit `+64` or `0064` country code
88
+ * (unambiguously NZ) and 0.8 for national-form matches.
89
+ */
90
+ class PhoneDetector {
91
+ constructor() {
92
+ this.name = "phone";
93
+ this.severity = "medium";
94
+ }
95
+ scan(value) {
96
+ const findings = [];
97
+ PHONE_PATTERN.lastIndex = 0;
98
+ let match;
99
+ while ((match = PHONE_PATTERN.exec(value)) !== null) {
100
+ const raw = match[1];
101
+ const nsn = toNsn(raw);
102
+ if (nsn === null)
103
+ continue;
104
+ const kind = classify(nsn);
105
+ if (kind === null)
106
+ continue;
107
+ const hasCountryCode = raw.startsWith("+64") || raw.startsWith("0064");
108
+ findings.push({
109
+ detector: this.name,
110
+ severity: this.severity,
111
+ value: raw,
112
+ start: match.index,
113
+ end: match.index + raw.length,
114
+ confidence: hasCountryCode ? 1.0 : 0.8,
115
+ context: {
116
+ e164: `+64${nsn}`,
117
+ nsn,
118
+ kind,
119
+ },
120
+ });
121
+ }
122
+ return findings;
123
+ }
124
+ }
125
+ exports.PhoneDetector = PhoneDetector;
126
+ //# sourceMappingURL=phone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phone.js","sourceRoot":"","sources":["../../src/detectors/phone.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * nz-privacy-auditor
3
+ *
4
+ * Privacy Act 2020 compliance auditor for ML datasets. Scans text for
5
+ * NZ-specific personal information: IRD, NHI, driver licence, phone,
6
+ * address, te reo Māori names.
7
+ *
8
+ * TypeScript port of the Python package
9
+ * (https://github.com/ShahnawazKakarh/nz-privacy-auditor).
10
+ */
11
+ export type { Detector, Finding, Severity } from "./detectors/base";
12
+ export { IRDDetector, checksumOk as irdChecksumOk } from "./detectors/ird";
13
+ export { NHIDetector, isValidNhi } from "./detectors/nhi";
14
+ export { DriverLicenceDetector } from "./detectors/driver_licence";
15
+ export { PhoneDetector, toE164, toNsn, classify as classifyNzPhone } from "./detectors/phone";
16
+ export { AddressDetector, NZ_LOCATIONS } from "./detectors/address";
17
+ export { MaoriNameDetector, foldMacrons } from "./detectors/maori_name";
18
+ export type { CellFinding, ScanResult, ScannerOptions } from "./scanner";
19
+ export { Scanner, defaultDetectors } from "./scanner";
20
+ export { toDict, toJson, renderConsole } from "./report";
21
+ export { parseCsv } from "./cli";
22
+ export { applyVerification, type ApplyVerificationOptions } from "./verification";
23
+ export { DEFAULT_CACHE_PATH as LLM_DEFAULT_CACHE_PATH, DEFAULT_MODEL as LLM_DEFAULT_MODEL, GeminiVerifier, QuotaExceededError, VerificationCache, DETECTOR_DESCRIPTIONS, type LLMVerifier, type VerificationResult, type Verdict, type GeminiVerifierOptions, } from "./verifiers";
24
+ export declare const VERSION = "0.8.0";
25
+ //# sourceMappingURL=index.d.ts.map