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.
- package/README.md +152 -0
- package/data/llm_cache/cache.json +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +233 -0
- package/dist/cli.js.map +1 -0
- package/dist/detectors/address.d.ts +48 -0
- package/dist/detectors/address.d.ts.map +1 -0
- package/dist/detectors/address.js +262 -0
- package/dist/detectors/address.js.map +1 -0
- package/dist/detectors/base.d.ts +43 -0
- package/dist/detectors/base.d.ts.map +1 -0
- package/dist/detectors/base.js +6 -0
- package/dist/detectors/base.js.map +1 -0
- package/dist/detectors/driver_licence.d.ts +38 -0
- package/dist/detectors/driver_licence.d.ts.map +1 -0
- package/dist/detectors/driver_licence.js +80 -0
- package/dist/detectors/driver_licence.js.map +1 -0
- package/dist/detectors/ird.d.ts +37 -0
- package/dist/detectors/ird.d.ts.map +1 -0
- package/dist/detectors/ird.js +103 -0
- package/dist/detectors/ird.js.map +1 -0
- package/dist/detectors/maori_name.d.ts +36 -0
- package/dist/detectors/maori_name.d.ts.map +1 -0
- package/dist/detectors/maori_name.js +251 -0
- package/dist/detectors/maori_name.js.map +1 -0
- package/dist/detectors/nhi.d.ts +44 -0
- package/dist/detectors/nhi.d.ts.map +1 -0
- package/dist/detectors/nhi.js +147 -0
- package/dist/detectors/nhi.js.map +1 -0
- package/dist/detectors/phone.d.ts +49 -0
- package/dist/detectors/phone.d.ts.map +1 -0
- package/dist/detectors/phone.js +126 -0
- package/dist/detectors/phone.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/report.d.ts +14 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +90 -0
- package/dist/report.js.map +1 -0
- package/dist/scanner.d.ts +51 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +114 -0
- package/dist/scanner.js.map +1 -0
- package/dist/verification.d.ts +25 -0
- package/dist/verification.d.ts.map +1 -0
- package/dist/verification.js +89 -0
- package/dist/verification.js.map +1 -0
- package/dist/verifiers/base.d.ts +27 -0
- package/dist/verifiers/base.d.ts.map +1 -0
- package/dist/verifiers/base.js +24 -0
- package/dist/verifiers/base.js.map +1 -0
- package/dist/verifiers/cache.d.ts +23 -0
- package/dist/verifiers/cache.d.ts.map +1 -0
- package/dist/verifiers/cache.js +103 -0
- package/dist/verifiers/cache.js.map +1 -0
- package/dist/verifiers/gemini.d.ts +38 -0
- package/dist/verifiers/gemini.d.ts.map +1 -0
- package/dist/verifiers/gemini.js +121 -0
- package/dist/verifiers/gemini.js.map +1 -0
- package/dist/verifiers/index.d.ts +8 -0
- package/dist/verifiers/index.d.ts.map +1 -0
- package/dist/verifiers/index.js +16 -0
- package/dist/verifiers/index.js.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* NZ address detector.
|
|
4
|
+
*
|
|
5
|
+
* Address detection combines three signals to flag values that look like a
|
|
6
|
+
* New Zealand street address:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Street-suffix shape** — a numeric prefix followed by 1–4 words and a
|
|
9
|
+
* recognised NZ street-type suffix (`Street`, `Road`, `Avenue`, `Drive`,
|
|
10
|
+
* `Lane`, etc., and their common abbreviations).
|
|
11
|
+
* 2. **NZ postcode** — a 4-digit token (NZ Post uses 4-digit postcodes).
|
|
12
|
+
* 3. **NZ location name** — one of the 16 regions, major cities, or common
|
|
13
|
+
* metropolitan suburbs from the bundled gazetteer.
|
|
14
|
+
*
|
|
15
|
+
* Severity is MEDIUM because an address is a quasi-identifier under the
|
|
16
|
+
* Privacy Act 2020. Confidence scales with the number of corroborating
|
|
17
|
+
* signals:
|
|
18
|
+
*
|
|
19
|
+
* - 0.5 — street-suffix shape only
|
|
20
|
+
* - 0.7 — street-suffix shape + postcode OR location
|
|
21
|
+
* - 0.9 — street-suffix shape + postcode + location
|
|
22
|
+
*
|
|
23
|
+
* TypeScript port of `src/nz_privacy_auditor/detectors/address.py`. The
|
|
24
|
+
* NZ_LOCATIONS list is a verbatim mirror of the Python `_NZ_LOCATIONS`
|
|
25
|
+
* frozenset — any change to one must be applied to the other. A parity
|
|
26
|
+
* check across the two lists lives in the JS test suite.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.AddressDetector = exports.NZ_LOCATIONS = void 0;
|
|
30
|
+
const STREET_SUFFIXES = [
|
|
31
|
+
// Full forms
|
|
32
|
+
"street",
|
|
33
|
+
"road",
|
|
34
|
+
"avenue",
|
|
35
|
+
"drive",
|
|
36
|
+
"lane",
|
|
37
|
+
"place",
|
|
38
|
+
"crescent",
|
|
39
|
+
"terrace",
|
|
40
|
+
"boulevard",
|
|
41
|
+
"highway",
|
|
42
|
+
"court",
|
|
43
|
+
"close",
|
|
44
|
+
"parade",
|
|
45
|
+
"mall",
|
|
46
|
+
"quay",
|
|
47
|
+
"square",
|
|
48
|
+
"loop",
|
|
49
|
+
"rise",
|
|
50
|
+
"grove",
|
|
51
|
+
"way",
|
|
52
|
+
"heights",
|
|
53
|
+
"park",
|
|
54
|
+
"esplanade",
|
|
55
|
+
"promenade",
|
|
56
|
+
// Abbreviations
|
|
57
|
+
"st",
|
|
58
|
+
"rd",
|
|
59
|
+
"ave",
|
|
60
|
+
"av",
|
|
61
|
+
"dr",
|
|
62
|
+
"ln",
|
|
63
|
+
"pl",
|
|
64
|
+
"cres",
|
|
65
|
+
"tce",
|
|
66
|
+
"blvd",
|
|
67
|
+
"hwy",
|
|
68
|
+
"ct",
|
|
69
|
+
"cl",
|
|
70
|
+
"pde",
|
|
71
|
+
"sq",
|
|
72
|
+
];
|
|
73
|
+
/**
|
|
74
|
+
* NZ regions, major cities, and common metropolitan suburbs.
|
|
75
|
+
*
|
|
76
|
+
* IMPORTANT: this list is a mirror of `_NZ_LOCATIONS` in the Python
|
|
77
|
+
* detector at `src/nz_privacy_auditor/detectors/address.py`. Any addition
|
|
78
|
+
* or removal must be applied to both files simultaneously.
|
|
79
|
+
*/
|
|
80
|
+
exports.NZ_LOCATIONS = [
|
|
81
|
+
// 16 regions
|
|
82
|
+
"Northland",
|
|
83
|
+
"Auckland",
|
|
84
|
+
"Waikato",
|
|
85
|
+
"Bay of Plenty",
|
|
86
|
+
"Gisborne",
|
|
87
|
+
"Hawke's Bay",
|
|
88
|
+
"Hawkes Bay",
|
|
89
|
+
"Taranaki",
|
|
90
|
+
"Manawatu-Whanganui",
|
|
91
|
+
"Manawatū-Whanganui",
|
|
92
|
+
"Wellington",
|
|
93
|
+
"Tasman",
|
|
94
|
+
"Nelson",
|
|
95
|
+
"Marlborough",
|
|
96
|
+
"West Coast",
|
|
97
|
+
"Canterbury",
|
|
98
|
+
"Otago",
|
|
99
|
+
"Southland",
|
|
100
|
+
// Major cities / towns
|
|
101
|
+
"Whangarei",
|
|
102
|
+
"Whangārei",
|
|
103
|
+
"Hamilton",
|
|
104
|
+
"Tauranga",
|
|
105
|
+
"Rotorua",
|
|
106
|
+
"Taupo",
|
|
107
|
+
"Taupō",
|
|
108
|
+
"Napier",
|
|
109
|
+
"Hastings",
|
|
110
|
+
"New Plymouth",
|
|
111
|
+
"Palmerston North",
|
|
112
|
+
"Whanganui",
|
|
113
|
+
"Lower Hutt",
|
|
114
|
+
"Upper Hutt",
|
|
115
|
+
"Porirua",
|
|
116
|
+
"Kapiti",
|
|
117
|
+
"Kāpiti",
|
|
118
|
+
"Blenheim",
|
|
119
|
+
"Greymouth",
|
|
120
|
+
"Christchurch",
|
|
121
|
+
"Timaru",
|
|
122
|
+
"Dunedin",
|
|
123
|
+
"Queenstown",
|
|
124
|
+
"Invercargill",
|
|
125
|
+
// Common Auckland suburbs
|
|
126
|
+
"Albany",
|
|
127
|
+
"Botany",
|
|
128
|
+
"Devonport",
|
|
129
|
+
"Epsom",
|
|
130
|
+
"Glenfield",
|
|
131
|
+
"Grey Lynn",
|
|
132
|
+
"Henderson",
|
|
133
|
+
"Howick",
|
|
134
|
+
"Manukau",
|
|
135
|
+
"Mt Albert",
|
|
136
|
+
"Mt Eden",
|
|
137
|
+
"Mount Albert",
|
|
138
|
+
"Mount Eden",
|
|
139
|
+
"Newmarket",
|
|
140
|
+
"North Shore",
|
|
141
|
+
"Onehunga",
|
|
142
|
+
"Otahuhu",
|
|
143
|
+
"Otāhuhu",
|
|
144
|
+
"Pakuranga",
|
|
145
|
+
"Papakura",
|
|
146
|
+
"Papatoetoe",
|
|
147
|
+
"Parnell",
|
|
148
|
+
"Ponsonby",
|
|
149
|
+
"Remuera",
|
|
150
|
+
"Takapuna",
|
|
151
|
+
// Common Wellington suburbs
|
|
152
|
+
"Brooklyn",
|
|
153
|
+
"Karori",
|
|
154
|
+
"Kelburn",
|
|
155
|
+
"Khandallah",
|
|
156
|
+
"Miramar",
|
|
157
|
+
"Newtown",
|
|
158
|
+
"Petone",
|
|
159
|
+
"Thorndon",
|
|
160
|
+
// Common Christchurch suburbs
|
|
161
|
+
"Addington",
|
|
162
|
+
"Fendalton",
|
|
163
|
+
"Hornby",
|
|
164
|
+
"Ilam",
|
|
165
|
+
"Linwood",
|
|
166
|
+
"Merivale",
|
|
167
|
+
"Papanui",
|
|
168
|
+
"Riccarton",
|
|
169
|
+
"Sumner",
|
|
170
|
+
"Sydenham",
|
|
171
|
+
];
|
|
172
|
+
function escapeRegex(s) {
|
|
173
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
174
|
+
}
|
|
175
|
+
// Build a case-insensitive alternation, longest first so multi-word
|
|
176
|
+
// locations like "Bay of Plenty" match before "Bay" alone would.
|
|
177
|
+
const LOCATION_PATTERN = new RegExp("\\b(?:" +
|
|
178
|
+
[...exports.NZ_LOCATIONS]
|
|
179
|
+
.sort((a, b) => b.length - a.length)
|
|
180
|
+
.map(escapeRegex)
|
|
181
|
+
.join("|") +
|
|
182
|
+
")\\b", "gi");
|
|
183
|
+
// 4-digit NZ postcode. NZ Post issues postcodes in 0110-9999; values
|
|
184
|
+
// below 0100 are unused.
|
|
185
|
+
const POSTCODE_PATTERN = /\b(0[1-9]\d{2}|[1-9]\d{3})\b/g;
|
|
186
|
+
const SUFFIX_GROUP = STREET_SUFFIXES.join("|");
|
|
187
|
+
// Street-shape: optional unit prefix (e.g. "5/"), street number with optional
|
|
188
|
+
// trailing letter (e.g. "12A"), 1-4 capitalised words, then a recognised suffix.
|
|
189
|
+
const ADDRESS_PATTERN = new RegExp("\\b(" +
|
|
190
|
+
"(?:\\d+\\s*/\\s*)?" +
|
|
191
|
+
"\\d{1,5}[A-Za-z]?" +
|
|
192
|
+
"\\s+" +
|
|
193
|
+
"(?:[A-Z][A-Za-z\\u00C0-\\u017F'\\u2019]*[-\\s]){1,4}" +
|
|
194
|
+
"(?:" +
|
|
195
|
+
SUFFIX_GROUP +
|
|
196
|
+
")" +
|
|
197
|
+
")\\b", "gi");
|
|
198
|
+
function supportingSignals(value, addressStart, addressEnd) {
|
|
199
|
+
const signals = [];
|
|
200
|
+
// Postcode — outside the address span only.
|
|
201
|
+
POSTCODE_PATTERN.lastIndex = 0;
|
|
202
|
+
let pc;
|
|
203
|
+
while ((pc = POSTCODE_PATTERN.exec(value)) !== null) {
|
|
204
|
+
if (pc.index >= addressStart && pc.index + pc[0].length <= addressEnd)
|
|
205
|
+
continue;
|
|
206
|
+
signals.push(`postcode:${pc[0]}`);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
// Location — outside the address span only.
|
|
210
|
+
LOCATION_PATTERN.lastIndex = 0;
|
|
211
|
+
let loc;
|
|
212
|
+
while ((loc = LOCATION_PATTERN.exec(value)) !== null) {
|
|
213
|
+
if (loc.index >= addressStart && loc.index + loc[0].length <= addressEnd)
|
|
214
|
+
continue;
|
|
215
|
+
signals.push(`location:${loc[0]}`);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
return signals;
|
|
219
|
+
}
|
|
220
|
+
function confidenceFor(signals) {
|
|
221
|
+
const hasPostcode = signals.some((s) => s.startsWith("postcode:"));
|
|
222
|
+
const hasLocation = signals.some((s) => s.startsWith("location:"));
|
|
223
|
+
if (hasPostcode && hasLocation)
|
|
224
|
+
return 0.9;
|
|
225
|
+
if (hasPostcode || hasLocation)
|
|
226
|
+
return 0.7;
|
|
227
|
+
return 0.5;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Detects NZ street addresses via shape + postcode + gazetteer signals.
|
|
231
|
+
*
|
|
232
|
+
* Emits a finding for each street-shape match. Confidence reflects how
|
|
233
|
+
* many corroborating signals (postcode, NZ region / city / suburb) are
|
|
234
|
+
* present in the same value.
|
|
235
|
+
*/
|
|
236
|
+
class AddressDetector {
|
|
237
|
+
constructor() {
|
|
238
|
+
this.name = "address";
|
|
239
|
+
this.severity = "medium";
|
|
240
|
+
}
|
|
241
|
+
scan(value) {
|
|
242
|
+
const findings = [];
|
|
243
|
+
ADDRESS_PATTERN.lastIndex = 0;
|
|
244
|
+
let match;
|
|
245
|
+
while ((match = ADDRESS_PATTERN.exec(value)) !== null) {
|
|
246
|
+
const raw = match[1].trim();
|
|
247
|
+
const signals = supportingSignals(value, match.index, match.index + match[1].length);
|
|
248
|
+
findings.push({
|
|
249
|
+
detector: this.name,
|
|
250
|
+
severity: this.severity,
|
|
251
|
+
value: raw,
|
|
252
|
+
start: match.index,
|
|
253
|
+
end: match.index + match[1].length,
|
|
254
|
+
confidence: confidenceFor(signals),
|
|
255
|
+
context: { signals },
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return findings;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
exports.AddressDetector = AddressDetector;
|
|
262
|
+
//# sourceMappingURL=address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.js","sourceRoot":"","sources":["../../src/detectors/address.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base detector interface and shared types.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Severity of a Privacy Act 2020 finding.
|
|
6
|
+
*
|
|
7
|
+
* - HIGH — directly identifying, regulated identifiers (IRD, NHI, driver licence).
|
|
8
|
+
* - MEDIUM — quasi-identifiers that combine readily (phone, address).
|
|
9
|
+
* - LOW — context-sensitive signals (te reo names, generic name tokens).
|
|
10
|
+
*/
|
|
11
|
+
export type Severity = "high" | "medium" | "low";
|
|
12
|
+
/** A single PII match within a value. */
|
|
13
|
+
export interface Finding {
|
|
14
|
+
/** Detector name (e.g. `"ird"`, `"nhi"`). */
|
|
15
|
+
detector: string;
|
|
16
|
+
/** Severity of the finding. */
|
|
17
|
+
severity: Severity;
|
|
18
|
+
/** The matched substring (raw). */
|
|
19
|
+
value: string;
|
|
20
|
+
/** Zero-indexed character offset within the source value. */
|
|
21
|
+
start: number;
|
|
22
|
+
/** Exclusive end offset. */
|
|
23
|
+
end: number;
|
|
24
|
+
/** Detector confidence, 0..1. 1.0 for checksum-verified matches. */
|
|
25
|
+
confidence: number;
|
|
26
|
+
/** Detector-specific context (e.g. normalised form). */
|
|
27
|
+
context: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Abstract base for all PII detectors.
|
|
31
|
+
*
|
|
32
|
+
* Implementations must be stateless — a single instance may be called
|
|
33
|
+
* across many rows / columns of a dataset.
|
|
34
|
+
*/
|
|
35
|
+
export interface Detector {
|
|
36
|
+
/** Machine-readable detector name (e.g. `"ird"`). */
|
|
37
|
+
readonly name: string;
|
|
38
|
+
/** Severity emitted by this detector. */
|
|
39
|
+
readonly severity: Severity;
|
|
40
|
+
/** Scan a single string value and yield findings. */
|
|
41
|
+
scan(value: string): Finding[];
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/detectors/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEjD,yCAAyC;AACzC,MAAM,WAAW,OAAO;IACtB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,qDAAqD;IACrD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/detectors/base.ts"],"names":[],"mappings":";AAAA;;GAEG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NZ driver licence number detector.
|
|
3
|
+
*
|
|
4
|
+
* NZ driver licence numbers are issued by Waka Kotahi | NZ Transport Agency
|
|
5
|
+
* in the format `[A-Z]{2}\d{6}` — two uppercase letters followed by six
|
|
6
|
+
* digits, e.g. `BQ739482` or `UE093153`. The licence number remains the
|
|
7
|
+
* same when a new card is issued; the three-digit card version number
|
|
8
|
+
* printed on the card is per-issue and is not detected by this module.
|
|
9
|
+
*
|
|
10
|
+
* Unlike IRD and NHI numbers, NZTA does NOT publish a check-digit algorithm
|
|
11
|
+
* for driver licence numbers. This means a regex alone produces many false
|
|
12
|
+
* positives on dataset columns containing 2-letter prefixed codes (order
|
|
13
|
+
* IDs, SKUs, flight references). To compensate, this detector lowers
|
|
14
|
+
* confidence when no contextual keyword (`licence`, `license`, `driver`,
|
|
15
|
+
* `DL`) appears within a 300-character window of the match — the same
|
|
16
|
+
* approach used by Microsoft Purview's NZ driver licence sensitive-
|
|
17
|
+
* information type.
|
|
18
|
+
*
|
|
19
|
+
* Findings are emitted at confidence 0.9 when a keyword is nearby, and
|
|
20
|
+
* confidence 0.5 otherwise. Downstream code may filter on confidence to
|
|
21
|
+
* trade off precision against recall.
|
|
22
|
+
*
|
|
23
|
+
* TypeScript port of `src/nz_privacy_auditor/detectors/driver_licence.py`.
|
|
24
|
+
*/
|
|
25
|
+
import type { Detector, Finding, Severity } from "./base";
|
|
26
|
+
/**
|
|
27
|
+
* Detects NZ driver licence numbers (`[A-Z]{2}\d{6}`).
|
|
28
|
+
*
|
|
29
|
+
* Confidence is 0.9 when a contextual keyword (licence, license, driver,
|
|
30
|
+
* DL, NZTA, Waka Kotahi) is found within ±300 characters of the match,
|
|
31
|
+
* and 0.5 otherwise. There is no public NZTA checksum to validate against.
|
|
32
|
+
*/
|
|
33
|
+
export declare class DriverLicenceDetector implements Detector {
|
|
34
|
+
readonly name = "driver_licence";
|
|
35
|
+
readonly severity: Severity;
|
|
36
|
+
scan(value: string): Finding[];
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=driver_licence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver_licence.d.ts","sourceRoot":"","sources":["../../src/detectors/driver_licence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAuB1D;;;;;;GAMG;AACH,qBAAa,qBAAsB,YAAW,QAAQ;IACpD,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAU;IAErC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE;CAsB/B"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* NZ driver licence number detector.
|
|
4
|
+
*
|
|
5
|
+
* NZ driver licence numbers are issued by Waka Kotahi | NZ Transport Agency
|
|
6
|
+
* in the format `[A-Z]{2}\d{6}` — two uppercase letters followed by six
|
|
7
|
+
* digits, e.g. `BQ739482` or `UE093153`. The licence number remains the
|
|
8
|
+
* same when a new card is issued; the three-digit card version number
|
|
9
|
+
* printed on the card is per-issue and is not detected by this module.
|
|
10
|
+
*
|
|
11
|
+
* Unlike IRD and NHI numbers, NZTA does NOT publish a check-digit algorithm
|
|
12
|
+
* for driver licence numbers. This means a regex alone produces many false
|
|
13
|
+
* positives on dataset columns containing 2-letter prefixed codes (order
|
|
14
|
+
* IDs, SKUs, flight references). To compensate, this detector lowers
|
|
15
|
+
* confidence when no contextual keyword (`licence`, `license`, `driver`,
|
|
16
|
+
* `DL`) appears within a 300-character window of the match — the same
|
|
17
|
+
* approach used by Microsoft Purview's NZ driver licence sensitive-
|
|
18
|
+
* information type.
|
|
19
|
+
*
|
|
20
|
+
* Findings are emitted at confidence 0.9 when a keyword is nearby, and
|
|
21
|
+
* confidence 0.5 otherwise. Downstream code may filter on confidence to
|
|
22
|
+
* trade off precision against recall.
|
|
23
|
+
*
|
|
24
|
+
* TypeScript port of `src/nz_privacy_auditor/detectors/driver_licence.py`.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.DriverLicenceDetector = void 0;
|
|
28
|
+
// Two letters + six digits, case-insensitive.
|
|
29
|
+
const LICENCE_PATTERN = /\b([A-Z]{2}\d{6})\b/gi;
|
|
30
|
+
// Context keywords. Case-insensitive whole-word match within the proximity
|
|
31
|
+
// window. `DL` is constrained to upper-case-only via a separate pattern
|
|
32
|
+
// below to avoid matching the common English word "dl" in chemistry / units.
|
|
33
|
+
const KEYWORD_PATTERN = /\b(?:licen[cs]e|driver(?:'?s)?|driver\s*licen[cs]e|driving\s*licen[cs]e|nzta|waka\s*kotahi)\b/i;
|
|
34
|
+
const DL_PATTERN = /\bDL\b/;
|
|
35
|
+
// Proximity window (chars) used to look for a contextual keyword around the
|
|
36
|
+
// matched licence-shaped token. Matches Microsoft Purview's NZ DLP setting.
|
|
37
|
+
const PROXIMITY = 300;
|
|
38
|
+
function hasKeywordNear(value, start, end) {
|
|
39
|
+
const lo = Math.max(0, start - PROXIMITY);
|
|
40
|
+
const hi = Math.min(value.length, end + PROXIMITY);
|
|
41
|
+
const window = value.slice(lo, hi);
|
|
42
|
+
return KEYWORD_PATTERN.test(window) || DL_PATTERN.test(window);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Detects NZ driver licence numbers (`[A-Z]{2}\d{6}`).
|
|
46
|
+
*
|
|
47
|
+
* Confidence is 0.9 when a contextual keyword (licence, license, driver,
|
|
48
|
+
* DL, NZTA, Waka Kotahi) is found within ±300 characters of the match,
|
|
49
|
+
* and 0.5 otherwise. There is no public NZTA checksum to validate against.
|
|
50
|
+
*/
|
|
51
|
+
class DriverLicenceDetector {
|
|
52
|
+
constructor() {
|
|
53
|
+
this.name = "driver_licence";
|
|
54
|
+
this.severity = "high";
|
|
55
|
+
}
|
|
56
|
+
scan(value) {
|
|
57
|
+
const findings = [];
|
|
58
|
+
LICENCE_PATTERN.lastIndex = 0;
|
|
59
|
+
let match;
|
|
60
|
+
while ((match = LICENCE_PATTERN.exec(value)) !== null) {
|
|
61
|
+
const raw = match[1];
|
|
62
|
+
const hasKw = hasKeywordNear(value, match.index, match.index + raw.length);
|
|
63
|
+
findings.push({
|
|
64
|
+
detector: this.name,
|
|
65
|
+
severity: this.severity,
|
|
66
|
+
value: raw,
|
|
67
|
+
start: match.index,
|
|
68
|
+
end: match.index + raw.length,
|
|
69
|
+
confidence: hasKw ? 0.9 : 0.5,
|
|
70
|
+
context: {
|
|
71
|
+
normalised: raw.toUpperCase(),
|
|
72
|
+
keyword_proximity: hasKw,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return findings;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.DriverLicenceDetector = DriverLicenceDetector;
|
|
80
|
+
//# sourceMappingURL=driver_licence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver_licence.js","sourceRoot":"","sources":["../../src/detectors/driver_licence.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;;;AAIH,8CAA8C;AAC9C,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD,2EAA2E;AAC3E,wEAAwE;AACxE,6EAA6E;AAC7E,MAAM,eAAe,GACnB,gGAAgG,CAAC;AACnG,MAAM,UAAU,GAAG,QAAQ,CAAC;AAE5B,4EAA4E;AAC5E,4EAA4E;AAC5E,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,SAAS,cAAc,CAAC,KAAa,EAAE,KAAa,EAAE,GAAW;IAC/D,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC;IAC1C,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,GAAG,SAAS,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACnC,OAAO,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;GAMG;AACH,MAAa,qBAAqB;IAAlC;QACW,SAAI,GAAG,gBAAgB,CAAC;QACxB,aAAQ,GAAa,MAAM,CAAC;IAwBvC,CAAC;IAtBC,IAAI,CAAC,KAAa;QAChB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,eAAe,CAAC,SAAS,GAAG,CAAC,CAAC;QAC9B,IAAI,KAA6B,CAAC;QAClC,OAAO,CAAC,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACtD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;YACtB,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;YAC3E,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,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;gBAC7B,OAAO,EAAE;oBACP,UAAU,EAAE,GAAG,CAAC,WAAW,EAAE;oBAC7B,iBAAiB,EAAE,KAAK;iBACzB;aACF,CAAC,CAAC;QACL,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AA1BD,sDA0BC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IRD (Inland Revenue Department) number detector.
|
|
3
|
+
*
|
|
4
|
+
* NZ IRD numbers are 8 or 9 digits with a mod-11 check digit. The algorithm
|
|
5
|
+
* is published by Inland Revenue and uses a primary weight set, falling back
|
|
6
|
+
* to a secondary weight set when the primary calculation yields a check
|
|
7
|
+
* digit of 10.
|
|
8
|
+
*
|
|
9
|
+
* Reference:
|
|
10
|
+
* https://www.ird.govt.nz/-/media/project/ir/home/documents/digital-service-providers/draft-documents/payday-filing/algorithm-for-ird-numbers.pdf
|
|
11
|
+
*
|
|
12
|
+
* Valid IRD numbers fall between 10,000,000 and ~150,000,000. We use the
|
|
13
|
+
* conservative upper bound of 150,000,000 to reject obviously out-of-range
|
|
14
|
+
* candidates while remaining future-proof.
|
|
15
|
+
*
|
|
16
|
+
* TypeScript port of `src/nz_privacy_auditor/detectors/ird.py`. Test vectors
|
|
17
|
+
* for both implementations live in `fixtures/ird.json`.
|
|
18
|
+
*/
|
|
19
|
+
import type { Detector, Finding, Severity } from "./base";
|
|
20
|
+
/**
|
|
21
|
+
* Validate the mod-11 check digit for an 8- or 9-digit IRD string.
|
|
22
|
+
*
|
|
23
|
+
* `digits` must contain only digit characters. Returns true only when the
|
|
24
|
+
* IRD passes range and checksum validation.
|
|
25
|
+
*/
|
|
26
|
+
export declare function checksumOk(digits: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Detects NZ Inland Revenue (IRD) numbers via regex + mod-11 checksum.
|
|
29
|
+
*
|
|
30
|
+
* Only checksum-validated matches are emitted. The detector is stateless.
|
|
31
|
+
*/
|
|
32
|
+
export declare class IRDDetector implements Detector {
|
|
33
|
+
readonly name = "ird";
|
|
34
|
+
readonly severity: Severity;
|
|
35
|
+
scan(value: string): Finding[];
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=ird.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ird.d.ts","sourceRoot":"","sources":["../../src/detectors/ird.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAe1D;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA6BlD;AAED;;;;GAIG;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,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* IRD (Inland Revenue Department) number detector.
|
|
4
|
+
*
|
|
5
|
+
* NZ IRD numbers are 8 or 9 digits with a mod-11 check digit. The algorithm
|
|
6
|
+
* is published by Inland Revenue and uses a primary weight set, falling back
|
|
7
|
+
* to a secondary weight set when the primary calculation yields a check
|
|
8
|
+
* digit of 10.
|
|
9
|
+
*
|
|
10
|
+
* Reference:
|
|
11
|
+
* https://www.ird.govt.nz/-/media/project/ir/home/documents/digital-service-providers/draft-documents/payday-filing/algorithm-for-ird-numbers.pdf
|
|
12
|
+
*
|
|
13
|
+
* Valid IRD numbers fall between 10,000,000 and ~150,000,000. We use the
|
|
14
|
+
* conservative upper bound of 150,000,000 to reject obviously out-of-range
|
|
15
|
+
* candidates while remaining future-proof.
|
|
16
|
+
*
|
|
17
|
+
* TypeScript port of `src/nz_privacy_auditor/detectors/ird.py`. Test vectors
|
|
18
|
+
* for both implementations live in `fixtures/ird.json`.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.IRDDetector = void 0;
|
|
22
|
+
exports.checksumOk = checksumOk;
|
|
23
|
+
// IRD numbers appear as 8 or 9 digit runs, optionally hyphen- or space-separated
|
|
24
|
+
// in groups like 49-091-850 or 49 091 850 or 049091850.
|
|
25
|
+
const IRD_PATTERN = /\b(\d{2,3}[-\s]?\d{3}[-\s]?\d{3})\b/g;
|
|
26
|
+
// Inland Revenue's published weight sets.
|
|
27
|
+
const PRIMARY_WEIGHTS = [3, 2, 7, 6, 5, 4, 3, 2];
|
|
28
|
+
const SECONDARY_WEIGHTS = [7, 4, 3, 2, 5, 2, 7, 6];
|
|
29
|
+
// IRDs below 10_000_000 are not issued.
|
|
30
|
+
const MIN_IRD = 10000000;
|
|
31
|
+
// Upper sanity bound; IR is currently issuing in the 100M+ range.
|
|
32
|
+
const MAX_IRD = 150000000;
|
|
33
|
+
/**
|
|
34
|
+
* Validate the mod-11 check digit for an 8- or 9-digit IRD string.
|
|
35
|
+
*
|
|
36
|
+
* `digits` must contain only digit characters. Returns true only when the
|
|
37
|
+
* IRD passes range and checksum validation.
|
|
38
|
+
*/
|
|
39
|
+
function checksumOk(digits) {
|
|
40
|
+
if (![8, 9].includes(digits.length))
|
|
41
|
+
return false;
|
|
42
|
+
if (!/^\d+$/.test(digits))
|
|
43
|
+
return false;
|
|
44
|
+
// Left-pad 8-digit IRDs to 9 digits for uniform handling.
|
|
45
|
+
const padded = digits.padStart(9, "0");
|
|
46
|
+
const body = padded.slice(0, 8);
|
|
47
|
+
const check = Number(padded[8]);
|
|
48
|
+
const bodyInt = Number(padded);
|
|
49
|
+
if (bodyInt < MIN_IRD || bodyInt > MAX_IRD)
|
|
50
|
+
return false;
|
|
51
|
+
const calc = (weights) => {
|
|
52
|
+
let total = 0;
|
|
53
|
+
for (let i = 0; i < 8; i++) {
|
|
54
|
+
total += Number(body[i]) * weights[i];
|
|
55
|
+
}
|
|
56
|
+
const rem = total % 11;
|
|
57
|
+
return rem === 0 ? 0 : 11 - rem;
|
|
58
|
+
};
|
|
59
|
+
const primary = calc(PRIMARY_WEIGHTS);
|
|
60
|
+
if (primary < 10)
|
|
61
|
+
return primary === check;
|
|
62
|
+
// Primary produced 10 — fall back to secondary weights.
|
|
63
|
+
const secondary = calc(SECONDARY_WEIGHTS);
|
|
64
|
+
// Number is invalid per IR spec if secondary also yields 10.
|
|
65
|
+
if (secondary === 10)
|
|
66
|
+
return false;
|
|
67
|
+
return secondary === check;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Detects NZ Inland Revenue (IRD) numbers via regex + mod-11 checksum.
|
|
71
|
+
*
|
|
72
|
+
* Only checksum-validated matches are emitted. The detector is stateless.
|
|
73
|
+
*/
|
|
74
|
+
class IRDDetector {
|
|
75
|
+
constructor() {
|
|
76
|
+
this.name = "ird";
|
|
77
|
+
this.severity = "high";
|
|
78
|
+
}
|
|
79
|
+
scan(value) {
|
|
80
|
+
const findings = [];
|
|
81
|
+
// Reset lastIndex — the regex is global and shared across calls.
|
|
82
|
+
IRD_PATTERN.lastIndex = 0;
|
|
83
|
+
let match;
|
|
84
|
+
while ((match = IRD_PATTERN.exec(value)) !== null) {
|
|
85
|
+
const raw = match[1];
|
|
86
|
+
const digits = raw.replace(/[-\s]/g, "");
|
|
87
|
+
if (checksumOk(digits)) {
|
|
88
|
+
findings.push({
|
|
89
|
+
detector: this.name,
|
|
90
|
+
severity: this.severity,
|
|
91
|
+
value: raw,
|
|
92
|
+
start: match.index,
|
|
93
|
+
end: match.index + raw.length,
|
|
94
|
+
confidence: 1.0,
|
|
95
|
+
context: { normalised: digits },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return findings;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.IRDDetector = IRDDetector;
|
|
103
|
+
//# sourceMappingURL=ird.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ird.js","sourceRoot":"","sources":["../../src/detectors/ird.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Te reo Māori personal-name detector.
|
|
3
|
+
*
|
|
4
|
+
* Detects predominantly Māori given names and surnames from a curated
|
|
5
|
+
* gazetteer. The detector is **macron-aware**: a value like `Tāne` and its
|
|
6
|
+
* non-macron form `Tane` both match the canonical entry. Confidence is
|
|
7
|
+
* higher (0.9) when the input preserved the macron — a signal that the
|
|
8
|
+
* data was entered with cultural care — and lower (0.7) otherwise.
|
|
9
|
+
*
|
|
10
|
+
* Severity is LOW because a personal name on its own is not necessarily
|
|
11
|
+
* identifying under the Privacy Act 2020. The detector exists so that
|
|
12
|
+
* downstream consumers can apply additional care to records that contain
|
|
13
|
+
* culturally significant te reo Māori identifiers (e.g. IPP 4 collection
|
|
14
|
+
* manner, IPP 8 accuracy obligations around name spelling and macron
|
|
15
|
+
* preservation).
|
|
16
|
+
*
|
|
17
|
+
* TypeScript port of `src/nz_privacy_auditor/detectors/maori_name.py`. The
|
|
18
|
+
* name lists below are verbatim mirrors of the Python `_GIVEN_NAMES` and
|
|
19
|
+
* `_SURNAMES` sets — any change to one must be applied to the other.
|
|
20
|
+
*/
|
|
21
|
+
import type { Detector, Finding, Severity } from "./base";
|
|
22
|
+
/** Casefold and strip combining marks (macrons) for fuzzy matching. */
|
|
23
|
+
export declare function foldMacrons(s: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Detects te reo Māori personal names from a curated gazetteer.
|
|
26
|
+
*
|
|
27
|
+
* The detector is macron-aware and case-insensitive. Findings carry the
|
|
28
|
+
* canonical macron-preserved form in `context.canonical` even when the
|
|
29
|
+
* input did not use macrons.
|
|
30
|
+
*/
|
|
31
|
+
export declare class MaoriNameDetector implements Detector {
|
|
32
|
+
readonly name = "maori_name";
|
|
33
|
+
readonly severity: Severity;
|
|
34
|
+
scan(value: string): Finding[];
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=maori_name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maori_name.d.ts","sourceRoot":"","sources":["../../src/detectors/maori_name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAoK1D,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAiCD;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,QAAQ;IAChD,QAAQ,CAAC,IAAI,gBAAgB;IAC7B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAS;IAEpC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE;CAyB/B"}
|