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
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# nz-privacy-auditor
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/nz-privacy-auditor)
|
|
4
|
+
[](https://www.npmjs.com/package/nz-privacy-auditor)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](https://www.legislation.govt.nz/act/public/2020/0031/latest/LMS23223.html)
|
|
8
|
+
[](https://skakarh.com)
|
|
9
|
+
|
|
10
|
+
> **Privacy Act 2020 compliance auditor for ML datasets.**
|
|
11
|
+
> Scans text for New-Zealand-specific PII — IRD, NHI, driver licence, phone, address, te reo Māori names.
|
|
12
|
+
> Zero dependencies. Works in Node, Deno, and browsers.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## ✨ Why nz-privacy-auditor?
|
|
17
|
+
|
|
18
|
+
NZ practitioners working with health records, government data, or customer corpora need to verify that datasets are free of regulated identifiers **before** they reach an ML pipeline or third-party service. Generic tools miss what actually matters under the [Privacy Act 2020](https://www.legislation.govt.nz/act/public/2020/0031/latest/LMS23223.html) and the Health Information Privacy Code 2020.
|
|
19
|
+
|
|
20
|
+
| Feature | nz-privacy-auditor | Generic PII tools |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| IRD number + mod-11 checksum | ✅ | ❌ |
|
|
23
|
+
| NHI number (legacy + new format) | ✅ | ❌ |
|
|
24
|
+
| NZ driver licence | ✅ | Regex only |
|
|
25
|
+
| NZ phone (NSN validation) | ✅ | Partial |
|
|
26
|
+
| NZ address (postcode + gazetteer) | ✅ | ❌ |
|
|
27
|
+
| Te reo Māori names (macron-aware) | ✅ | ❌ |
|
|
28
|
+
| Zero runtime dependencies | ✅ | Usually not |
|
|
29
|
+
| TypeScript-native | ✅ | Varies |
|
|
30
|
+
|
|
31
|
+
The full Python package (`pip install nz-privacy-auditor`) has [additional detectors, a CLI, Gemini LLM verification, HuggingFace loader, and a live demo](https://github.com/ShahnawazKakarh/nz-privacy-auditor). This TypeScript package is a port focused on the browser and Node ecosystems.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🚀 Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install nz-privacy-auditor
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or with yarn / pnpm:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
yarn add nz-privacy-auditor
|
|
45
|
+
pnpm add nz-privacy-auditor
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Requires Node.js 18+.
|
|
49
|
+
|
|
50
|
+
### CLI
|
|
51
|
+
|
|
52
|
+
A `nz-privacy-auditor` binary ships with the package:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx nz-privacy-auditor scan path/to/data.csv
|
|
56
|
+
npx nz-privacy-auditor scan path/to/data.csv --format json --output report.json
|
|
57
|
+
npx nz-privacy-auditor scan data.csv --detector ird,nhi --min-confidence 0.7
|
|
58
|
+
npx nz-privacy-auditor scan data.csv --fail-on-finding # exit 1 if any PII; CI gate
|
|
59
|
+
npx nz-privacy-auditor scan data.csv --verify-llm # Gemini second-pass on low-confidence findings
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For `--verify-llm`, set `GOOGLE_API_KEY` in your environment (get a free key at [aistudio.google.com/apikey](https://aistudio.google.com/apikey)). Findings are cached at `data/llm_cache/cache.json` so repeat runs cost nothing.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 📖 Usage
|
|
67
|
+
|
|
68
|
+
### Scan a whole CSV file (programmatic)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { readFileSync } from "fs";
|
|
72
|
+
import { Scanner, parseCsv, toJson } from "nz-privacy-auditor";
|
|
73
|
+
|
|
74
|
+
const text = readFileSync("patients.csv", "utf-8");
|
|
75
|
+
const rows = parseCsv(text);
|
|
76
|
+
const result = new Scanner().scanRows(rows);
|
|
77
|
+
|
|
78
|
+
console.log(`${result.totalFindings} findings across ${result.columnsScanned.length} columns`);
|
|
79
|
+
console.log(toJson(result));
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Detect IRD numbers
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { IRDDetector } from "nz-privacy-auditor";
|
|
86
|
+
|
|
87
|
+
const detector = new IRDDetector();
|
|
88
|
+
const findings = detector.scan("Please reference IRD 49-091-850 on the form.");
|
|
89
|
+
|
|
90
|
+
console.log(findings);
|
|
91
|
+
// [{
|
|
92
|
+
// detector: "ird",
|
|
93
|
+
// severity: "high",
|
|
94
|
+
// value: "49-091-850",
|
|
95
|
+
// start: 17,
|
|
96
|
+
// end: 27,
|
|
97
|
+
// confidence: 1.0,
|
|
98
|
+
// context: { normalised: "49091850" }
|
|
99
|
+
// }]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Only checksum-validated matches are emitted — no false positives on random 8-digit strings.
|
|
103
|
+
|
|
104
|
+
### CommonJS
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const { IRDDetector } = require("nz-privacy-auditor");
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Standalone checksum helper
|
|
111
|
+
|
|
112
|
+
If you just need to validate an IRD without the detection pipeline:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { irdChecksumOk } from "nz-privacy-auditor";
|
|
116
|
+
|
|
117
|
+
irdChecksumOk("49091850"); // true
|
|
118
|
+
irdChecksumOk("49091851"); // false (bad check digit)
|
|
119
|
+
irdChecksumOk("00000000"); // false (below MIN_IRD)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 🗺️ Detector roadmap
|
|
125
|
+
|
|
126
|
+
| Detector | Approach | Status |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| **IRD number** | Regex + IR-published mod-11 (primary + secondary weights) | ✅ v0.1.0 |
|
|
129
|
+
| **NHI number** | Regex + Health NZ mod-11 (legacy) / mod-23 (new format per HISO 10046:2024) | ✅ v0.2.0 |
|
|
130
|
+
| **Driver licence** | `[A-Z]{2}\d{6}` + 300-char keyword proximity | ✅ v0.3.0 |
|
|
131
|
+
| **NZ phone** | `+64` / `0x` patterns + NSN validation, E.164 output | ✅ v0.4.0 |
|
|
132
|
+
| **NZ address** | Suffix + postcode + region / suburb gazetteer | ✅ v0.5.0 |
|
|
133
|
+
| **Te reo Māori names** | Macron-aware curated gazetteer | ✅ v0.6.0 |
|
|
134
|
+
| **LLM verification** | Gemini 2.5 Flash second-pass over low-confidence findings | ✅ v0.8.0 |
|
|
135
|
+
|
|
136
|
+
Test vectors are shared 1:1 with the Python implementation via [`fixtures/*.json`](https://github.com/ShahnawazKakarh/nz-privacy-auditor/tree/master/fixtures) — spec parity is enforced by construction, not by discipline.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 🔗 See also
|
|
141
|
+
|
|
142
|
+
- **Python package** with full CLI, LLM verification, and HuggingFace loader: [github.com/ShahnawazKakarh/nz-privacy-auditor](https://github.com/ShahnawazKakarh/nz-privacy-auditor)
|
|
143
|
+
- **Live demo** on HuggingFace Spaces: [huggingface.co/spaces/Shahnawazkakarh/nz-privacy-auditor](https://huggingface.co/spaces/Shahnawazkakarh/nz-privacy-auditor)
|
|
144
|
+
- **Privacy Act 2020 mapping doc**: [docs/privacy-act-mapping.md](https://github.com/ShahnawazKakarh/nz-privacy-auditor/blob/master/docs/privacy-act-mapping.md)
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 📄 License
|
|
149
|
+
|
|
150
|
+
MIT © 2026 [QAPulse by SK](https://skakarh.com).
|
|
151
|
+
|
|
152
|
+
The library reflects publicly available NZ government identifier specifications (Inland Revenue IRD checksum, Health NZ NHI standard HISO 10046:2024) but is **not affiliated with or endorsed by** any NZ government agency. Consult the [Office of the Privacy Commissioner](https://www.privacy.org.nz/) for compliance decisions on production systems.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"1f49c20f5f9ede6ae1048353f9ef4024427fa29e3314cc21d3ba3bcdd3409040":{"verdict":"rejected","confidence":1,"reason":"The value is explicitly referred to as an SKU in the context, not an NZ driver licence number.","created_at":1783314580473}}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Command-line interface for nz-privacy-auditor.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* nz-privacy-auditor scan path/to/data.csv
|
|
7
|
+
* nz-privacy-auditor scan path/to/data.csv --format json --output report.json
|
|
8
|
+
* nz-privacy-auditor scan data.csv --detector ird,nhi --min-confidence 0.7
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Minimal RFC 4180 CSV parser. Handles quoted fields, embedded commas,
|
|
12
|
+
* embedded newlines, and escaped double-quotes. Comma-separated only.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseCsv(text: string): Record<string, string>[];
|
|
15
|
+
export declare function main(argv?: string[]): void;
|
|
16
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG;AA+BH;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAoD/D;AAkBD,wBAAgB,IAAI,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,IAAI,CAyGxD"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Command-line interface for nz-privacy-auditor.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* nz-privacy-auditor scan path/to/data.csv
|
|
8
|
+
* nz-privacy-auditor scan path/to/data.csv --format json --output report.json
|
|
9
|
+
* nz-privacy-auditor scan data.csv --detector ird,nhi --min-confidence 0.7
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.parseCsv = parseCsv;
|
|
46
|
+
exports.main = main;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const commander_1 = require("commander");
|
|
50
|
+
const report_1 = require("./report");
|
|
51
|
+
const scanner_1 = require("./scanner");
|
|
52
|
+
const verification_1 = require("./verification");
|
|
53
|
+
const gemini_1 = require("./verifiers/gemini");
|
|
54
|
+
const address_1 = require("./detectors/address");
|
|
55
|
+
const driver_licence_1 = require("./detectors/driver_licence");
|
|
56
|
+
const ird_1 = require("./detectors/ird");
|
|
57
|
+
const maori_name_1 = require("./detectors/maori_name");
|
|
58
|
+
const nhi_1 = require("./detectors/nhi");
|
|
59
|
+
const phone_1 = require("./detectors/phone");
|
|
60
|
+
const index_1 = require("./index");
|
|
61
|
+
const DETECTOR_CLASSES = {
|
|
62
|
+
ird: ird_1.IRDDetector,
|
|
63
|
+
nhi: nhi_1.NHIDetector,
|
|
64
|
+
driver_licence: driver_licence_1.DriverLicenceDetector,
|
|
65
|
+
phone: phone_1.PhoneDetector,
|
|
66
|
+
address: address_1.AddressDetector,
|
|
67
|
+
maori_name: maori_name_1.MaoriNameDetector,
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Minimal RFC 4180 CSV parser. Handles quoted fields, embedded commas,
|
|
71
|
+
* embedded newlines, and escaped double-quotes. Comma-separated only.
|
|
72
|
+
*/
|
|
73
|
+
function parseCsv(text) {
|
|
74
|
+
const rows = [];
|
|
75
|
+
let cur = [];
|
|
76
|
+
let field = "";
|
|
77
|
+
let inQuotes = false;
|
|
78
|
+
for (let i = 0; i < text.length; i++) {
|
|
79
|
+
const ch = text[i];
|
|
80
|
+
if (inQuotes) {
|
|
81
|
+
if (ch === '"') {
|
|
82
|
+
if (text[i + 1] === '"') {
|
|
83
|
+
field += '"';
|
|
84
|
+
i++;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
inQuotes = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
field += ch;
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (ch === '"') {
|
|
96
|
+
inQuotes = true;
|
|
97
|
+
}
|
|
98
|
+
else if (ch === ",") {
|
|
99
|
+
cur.push(field);
|
|
100
|
+
field = "";
|
|
101
|
+
}
|
|
102
|
+
else if (ch === "\n" || ch === "\r") {
|
|
103
|
+
// Handle \r\n
|
|
104
|
+
if (ch === "\r" && text[i + 1] === "\n")
|
|
105
|
+
i++;
|
|
106
|
+
cur.push(field);
|
|
107
|
+
field = "";
|
|
108
|
+
rows.push(cur);
|
|
109
|
+
cur = [];
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
field += ch;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Trailing field / row
|
|
116
|
+
if (field.length > 0 || cur.length > 0) {
|
|
117
|
+
cur.push(field);
|
|
118
|
+
rows.push(cur);
|
|
119
|
+
}
|
|
120
|
+
if (rows.length === 0)
|
|
121
|
+
return [];
|
|
122
|
+
const header = rows[0];
|
|
123
|
+
return rows.slice(1).map((r) => {
|
|
124
|
+
const obj = {};
|
|
125
|
+
header.forEach((h, idx) => {
|
|
126
|
+
obj[h] = r[idx] ?? "";
|
|
127
|
+
});
|
|
128
|
+
return obj;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function resolveDetectors(csv) {
|
|
132
|
+
if (!csv)
|
|
133
|
+
return (0, scanner_1.defaultDetectors)();
|
|
134
|
+
const keys = csv
|
|
135
|
+
.split(",")
|
|
136
|
+
.map((k) => k.trim())
|
|
137
|
+
.filter(Boolean);
|
|
138
|
+
const unknown = keys.filter((k) => !(k in DETECTOR_CLASSES));
|
|
139
|
+
if (unknown.length > 0) {
|
|
140
|
+
throw new Error(`Unknown detector(s): ${unknown.join(", ")}. ` +
|
|
141
|
+
`Valid: ${Object.keys(DETECTOR_CLASSES).sort().join(", ")}`);
|
|
142
|
+
}
|
|
143
|
+
return keys.map((k) => new DETECTOR_CLASSES[k]());
|
|
144
|
+
}
|
|
145
|
+
function main(argv = process.argv) {
|
|
146
|
+
const program = new commander_1.Command();
|
|
147
|
+
program
|
|
148
|
+
.name("nz-privacy-auditor")
|
|
149
|
+
.description("Privacy Act 2020 compliance auditor for ML datasets")
|
|
150
|
+
.version(index_1.VERSION);
|
|
151
|
+
program
|
|
152
|
+
.command("scan")
|
|
153
|
+
.description("Scan a CSV file for Privacy Act 2020 PII issues")
|
|
154
|
+
.argument("<path>", "Path to CSV file")
|
|
155
|
+
.option("--format <format>", "Output format: console or json", "console")
|
|
156
|
+
.option("-o, --output <file>", "Write output to a file (JSON only)")
|
|
157
|
+
.option("--detector <names>", "Comma-separated detector names. Default: all. Choices: " +
|
|
158
|
+
Object.keys(DETECTOR_CLASSES).sort().join(", "))
|
|
159
|
+
.option("--min-confidence <n>", "Drop findings below this confidence threshold (0-1)", "0.0")
|
|
160
|
+
.option("--fail-on-finding", "Exit with status 1 if any findings emitted", false)
|
|
161
|
+
.option("--verify-llm", "Run Gemini second-pass over low-confidence findings. Requires GOOGLE_API_KEY.", false)
|
|
162
|
+
.option("--llm-threshold <n>", "Only findings with confidence below this threshold are verified", "0.8")
|
|
163
|
+
.action(async (filePath, opts) => {
|
|
164
|
+
const abs = path.resolve(filePath);
|
|
165
|
+
if (!fs.existsSync(abs)) {
|
|
166
|
+
console.error(`Error: file not found: ${abs}`);
|
|
167
|
+
process.exit(2);
|
|
168
|
+
}
|
|
169
|
+
const detectors = resolveDetectors(opts.detector);
|
|
170
|
+
const minConfidence = Number(opts.minConfidence);
|
|
171
|
+
if (Number.isNaN(minConfidence) || minConfidence < 0 || minConfidence > 1) {
|
|
172
|
+
console.error("Error: --min-confidence must be a number in [0, 1]");
|
|
173
|
+
process.exit(2);
|
|
174
|
+
}
|
|
175
|
+
const text = fs.readFileSync(abs, "utf-8");
|
|
176
|
+
const rows = parseCsv(text);
|
|
177
|
+
const scanner = new scanner_1.Scanner({ detectors, minConfidence });
|
|
178
|
+
let result = scanner.scanRows(rows);
|
|
179
|
+
if (opts.verifyLlm) {
|
|
180
|
+
const llmThreshold = Number(opts.llmThreshold);
|
|
181
|
+
if (Number.isNaN(llmThreshold) || llmThreshold < 0 || llmThreshold > 1) {
|
|
182
|
+
console.error("Error: --llm-threshold must be a number in [0, 1]");
|
|
183
|
+
process.exit(2);
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const verifier = new gemini_1.GeminiVerifier();
|
|
187
|
+
try {
|
|
188
|
+
result = await (0, verification_1.applyVerification)(result, verifier, {
|
|
189
|
+
threshold: llmThreshold,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
verifier.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (exc) {
|
|
197
|
+
console.error(`Error: LLM verification failed: ${exc instanceof Error ? exc.message : String(exc)}`);
|
|
198
|
+
process.exit(2);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const outputFormat = String(opts.format);
|
|
202
|
+
if (outputFormat === "json") {
|
|
203
|
+
const payload = (0, report_1.toJson)(result);
|
|
204
|
+
if (opts.output) {
|
|
205
|
+
fs.writeFileSync(String(opts.output), payload, "utf-8");
|
|
206
|
+
console.log(`Wrote report to ${opts.output}`);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
console.log(payload);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else if (outputFormat === "console") {
|
|
213
|
+
if (opts.output) {
|
|
214
|
+
console.error("Error: --output is only supported with --format json");
|
|
215
|
+
process.exit(2);
|
|
216
|
+
}
|
|
217
|
+
console.log((0, report_1.renderConsole)(result));
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
console.error(`Error: unknown --format value: ${outputFormat}`);
|
|
221
|
+
process.exit(2);
|
|
222
|
+
}
|
|
223
|
+
if (opts.failOnFinding && result.totalFindings > 0) {
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
program.parse(argv);
|
|
228
|
+
}
|
|
229
|
+
// Node's ESM/CJS detection: run main if invoked directly, not when required.
|
|
230
|
+
if (require.main === module) {
|
|
231
|
+
main();
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NZ address detector.
|
|
3
|
+
*
|
|
4
|
+
* Address detection combines three signals to flag values that look like a
|
|
5
|
+
* New Zealand street address:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Street-suffix shape** — a numeric prefix followed by 1–4 words and a
|
|
8
|
+
* recognised NZ street-type suffix (`Street`, `Road`, `Avenue`, `Drive`,
|
|
9
|
+
* `Lane`, etc., and their common abbreviations).
|
|
10
|
+
* 2. **NZ postcode** — a 4-digit token (NZ Post uses 4-digit postcodes).
|
|
11
|
+
* 3. **NZ location name** — one of the 16 regions, major cities, or common
|
|
12
|
+
* metropolitan suburbs from the bundled gazetteer.
|
|
13
|
+
*
|
|
14
|
+
* Severity is MEDIUM because an address is a quasi-identifier under the
|
|
15
|
+
* Privacy Act 2020. Confidence scales with the number of corroborating
|
|
16
|
+
* signals:
|
|
17
|
+
*
|
|
18
|
+
* - 0.5 — street-suffix shape only
|
|
19
|
+
* - 0.7 — street-suffix shape + postcode OR location
|
|
20
|
+
* - 0.9 — street-suffix shape + postcode + location
|
|
21
|
+
*
|
|
22
|
+
* TypeScript port of `src/nz_privacy_auditor/detectors/address.py`. The
|
|
23
|
+
* NZ_LOCATIONS list is a verbatim mirror of the Python `_NZ_LOCATIONS`
|
|
24
|
+
* frozenset — any change to one must be applied to the other. A parity
|
|
25
|
+
* check across the two lists lives in the JS test suite.
|
|
26
|
+
*/
|
|
27
|
+
import type { Detector, Finding, Severity } from "./base";
|
|
28
|
+
/**
|
|
29
|
+
* NZ regions, major cities, and common metropolitan suburbs.
|
|
30
|
+
*
|
|
31
|
+
* IMPORTANT: this list is a mirror of `_NZ_LOCATIONS` in the Python
|
|
32
|
+
* detector at `src/nz_privacy_auditor/detectors/address.py`. Any addition
|
|
33
|
+
* or removal must be applied to both files simultaneously.
|
|
34
|
+
*/
|
|
35
|
+
export declare const NZ_LOCATIONS: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Detects NZ street addresses via shape + postcode + gazetteer signals.
|
|
38
|
+
*
|
|
39
|
+
* Emits a finding for each street-shape match. Confidence reflects how
|
|
40
|
+
* many corroborating signals (postcode, NZ region / city / suburb) are
|
|
41
|
+
* present in the same value.
|
|
42
|
+
*/
|
|
43
|
+
export declare class AddressDetector implements Detector {
|
|
44
|
+
readonly name = "address";
|
|
45
|
+
readonly severity: Severity;
|
|
46
|
+
scan(value: string): Finding[];
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=address.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../../src/detectors/address.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AA8C1D;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EA2FzC,CAAC;AA0EF;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAY;IAEvC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE;CAuB/B"}
|