@link-foundation/sensitive-data-sanitizer 0.0.0-stage → 0.12.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/CHANGELOG.md +578 -0
- package/LICENSE +24 -0
- package/README.md +184 -2
- package/bin/legacy-example-package-name.js +95 -0
- package/bin/sensitive-data-sanitizer.js +430 -0
- package/bin/stream.js +56 -0
- package/docs/API.md +336 -0
- package/docs/COVERAGE.md +136 -0
- package/docs/HISTORY-REMEDIATION.md +41 -0
- package/examples/combined-scanners.mjs +23 -0
- package/examples/presidio-bridge.py +32 -0
- package/examples/realistic-fakes.mjs +18 -0
- package/examples/sanitize-session.mjs +27 -0
- package/examples/shared-tool-logs.mjs +21 -0
- package/examples/stream-jsonl.mjs +17 -0
- package/package.json +124 -4
- package/src/adapters.js +204 -0
- package/src/arithmetic.js +11 -0
- package/src/confidence.js +65 -0
- package/src/context.js +424 -0
- package/src/credentials.js +54 -0
- package/src/detection.js +293 -0
- package/src/encoded.js +233 -0
- package/src/engines.js +348 -0
- package/src/entities.js +166 -0
- package/src/escape-spans.js +20 -0
- package/src/eslint-plugin.d.ts +10 -0
- package/src/eslint-plugin.js +193 -0
- package/src/external.js +169 -0
- package/src/fake-names.js +222 -0
- package/src/fake.js +524 -0
- package/src/files.js +131 -0
- package/src/gitleaks.js +194 -0
- package/src/history-rewrite.js +339 -0
- package/src/history.js +103 -0
- package/src/identity.js +274 -0
- package/src/index.d.ts +321 -0
- package/src/index.js +40 -0
- package/src/jsonl-stream.js +198 -0
- package/src/keywords.js +45 -0
- package/src/known.js +164 -0
- package/src/legacy.d.ts +3 -0
- package/src/legacy.js +2 -0
- package/src/locales.js +217 -0
- package/src/log-bytes.js +85 -0
- package/src/names.js +65 -0
- package/src/national-checks.js +129 -0
- package/src/national.js +128 -0
- package/src/outbound.js +28 -0
- package/src/personal-context.js +85 -0
- package/src/personal.js +248 -0
- package/src/projection.js +126 -0
- package/src/propagation.js +163 -0
- package/src/public-verifier.js +77 -0
- package/src/public.js +153 -0
- package/src/record-worker.js +20 -0
- package/src/rules.js +210 -0
- package/src/sanitizer.js +794 -0
- package/src/specimens.js +34 -0
- package/src/stream-identity.js +129 -0
- package/src/stream-worker.js +18 -0
- package/src/stream.js +449 -0
- package/src/structured.js +278 -0
- package/src/transforms.js +192 -0
- package/src/transliteration.js +75 -0
- package/src/utf8.js +60 -0
- package/src/vendor/entities/aws.json +42 -0
- package/src/vendor/entities/azure.json +182 -0
- package/src/vendor/entities/google.json +219 -0
- package/src/vendor/gitleaks/LICENSE +21 -0
- package/src/vendor/gitleaks/provenance.json +8 -0
- package/src/vendor/gitleaks/rules-0.json +268 -0
- package/src/vendor/gitleaks/rules-1.json +1794 -0
- package/src/vendor/gitleaks/rules-2.json +292 -0
- package/src/vendor/gitleaks/rules-3.json +263 -0
- package/src/vendor/gitleaks/rules-4.json +277 -0
- package/src/vendor/gitleaks/rules-5.json +154 -0
- package/src/worker-options.js +27 -0
package/README.md
CHANGED
|
@@ -1,3 +1,185 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sensitive Data Sanitizer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An offline JavaScript library, detector framework, and CLI for redacting credentials and personal data from logs, AI sessions, and text files, including logs with invalid UTF-8 bytes. It combines required Secretlint and 221 bundled Gitleaks text rules with contextual credentials, default multilingual name/PII recognition, known values and bounded encoding detection. Optional local Gitleaks, TruffleHog, and Presidio detectors extend the union.
|
|
4
|
+
|
|
5
|
+
Library calls without a profile replace every actionable finding completely with `[REDACTED]`. Personal findings below the default confidence threshold of 0.5 remain inspectable without replacing ordinary log numbers; credentials always qualify. Publication methods verify full redaction and block output if a required detector fails or finds a residual. Optional typed transformations, compatible partial masks and encoding preservation are described in the API. Findings contain offsets and rule metadata; they omit matched values and source excerpts.
|
|
6
|
+
|
|
7
|
+
For publishing sessions, use `createSanitizer({profile: 'publication'})`:
|
|
8
|
+
the personal threshold is 0.3, identity values use the fixed first/last-two
|
|
9
|
+
`***` mask, and fake-identity exemptions remain disabled. CLI `redact` defaults
|
|
10
|
+
to this profile. Explicit configuration such as `identityMask: false` selects
|
|
11
|
+
complete redaction. Credentials always receive complete redaction unless an
|
|
12
|
+
explicit credential transformation is selected.
|
|
13
|
+
|
|
14
|
+
Detection has limits: the default 17-locale name gazetteer has finite vocabulary; unfamiliar names need an additional NER model or known-personal dictionary. New token formats, unsupported encodings, and unconfigured languages can escape detection. Successful verification means the enabled detectors found no residual, not that the input is universally anonymous. See [coverage and limits](docs/COVERAGE.md) and the [issue #1 case study](docs/case-studies/issue-1/README.md).
|
|
15
|
+
|
|
16
|
+
For real session files whose personal values are confirmed later, use atomic
|
|
17
|
+
two-pass publication:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
sensitive-data-sanitizer redact session.jsonl --stream --jsonl --two-pass --output safe.jsonl
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
Sanitizer APIs require Node.js 22.13 or later. Template arithmetic utilities are available under `@link-foundation/sensitive-data-sanitizer/legacy`; the existing universal example uses that browser-compatible entry. From this checkout:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm ci
|
|
29
|
+
node bin/sensitive-data-sanitizer.js redact session.jsonl --output session.safe.jsonl
|
|
30
|
+
node bin/sensitive-data-sanitizer.js scan ./logs
|
|
31
|
+
node bin/sensitive-data-sanitizer.js history ./repository
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The npm package is `@link-foundation/sensitive-data-sanitizer`; the installed executable is `sensitive-data-sanitizer`. This PR prepares its release through Changesets.
|
|
35
|
+
|
|
36
|
+
First npm publication currently requires owner setup; see the
|
|
37
|
+
[verified release blockers and exact setup steps](docs/case-studies/issue-32/REQUIREMENTS.md#exact-npm-owner-steps).
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import {
|
|
41
|
+
createSanitizer,
|
|
42
|
+
knownSecretsFromEnv,
|
|
43
|
+
} from '@link-foundation/sensitive-data-sanitizer';
|
|
44
|
+
|
|
45
|
+
const sanitizer = createSanitizer({
|
|
46
|
+
profile: 'publication',
|
|
47
|
+
knownSecrets: knownSecretsFromEnv(), // Explicitly include local credentials.
|
|
48
|
+
});
|
|
49
|
+
const result = await sanitizer.sanitize('password=short person@example.org');
|
|
50
|
+
console.log(result.text); // password=[REDACTED] [REDACTED]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`createSanitizer()` requires the pinned Secretlint recommended rules to succeed. The synchronous `sanitize()` and `inspect()` functions use the native rules only. `redact()` applies supplied findings without a residual scan. Prefer the asynchronous sanitizer for publication.
|
|
54
|
+
|
|
55
|
+
## CLI
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
printf 'password=short\n' | sensitive-data-sanitizer redact -
|
|
59
|
+
sensitive-data-sanitizer redact input.txt --in-place
|
|
60
|
+
sensitive-data-sanitizer scan logs --config /private/policy.json
|
|
61
|
+
sensitive-data-sanitizer redact input.txt --gitleaks --trufflehog --output safe.txt
|
|
62
|
+
sensitive-data-sanitizer redact input.txt --presidio /path/presidio-bridge.py --model en_core_web_sm --language en
|
|
63
|
+
sensitive-data-sanitizer redact large-session.jsonl --jsonl --stream --output safe-session.jsonl
|
|
64
|
+
sensitive-data-sanitizer history rewrite ./repository --output ./private-preview
|
|
65
|
+
sensitive-data-sanitizer --help
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`--output` creates a new file without clobbering an existing one. `--in-place` replaces the input atomically after verification. Intermediate and output files use mode `0600` on POSIX. Symlinks and NUL-containing binary input are refused. Plain logs preserve invalid UTF-8 bytes exactly; JSON/JSONL and configuration files require valid UTF-8. Directory scans omit `.git` and `node_modules`; skipped binary/malformed-structured/oversized files make the report incomplete. Stdout is written after the complete operation succeeds.
|
|
69
|
+
|
|
70
|
+
| Exit | Meaning |
|
|
71
|
+
| ---- | ------------------------------------------------------------------------ |
|
|
72
|
+
| 0 | Redaction succeeded, or a complete scan found nothing |
|
|
73
|
+
| 1 | A complete scan/history audit found sensitive data |
|
|
74
|
+
| 2 | Error, detector failure, or incomplete scan; redaction output is blocked |
|
|
75
|
+
|
|
76
|
+
Defaults: 32 MiB whole-text input bytes, 100,000 findings and 10,000 files. `--stream` permits 1 GiB total with plain-text records up to the total input limit or 8 MiB JSONL records, bounded workers and atomic publication. `--max-bytes` sets the byte limit; JSON config sets the other sanitizer limits. `--paranoid` adds entropy heuristics and increases false positives. `--native-only` explicitly disables Secretlint.
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
For AI sessions, `--jsonl` preserves JSON records and declares `id`, `type`,
|
|
81
|
+
`name`, and `description` as metadata. `--json` handles a single JSON document;
|
|
82
|
+
use JSONL for streaming. Known private values and credential formats still
|
|
83
|
+
override declarations. Set `structuralFields` in private config to choose your
|
|
84
|
+
own property names, or use an empty array to scan every field normally.
|
|
85
|
+
The library provides `structured: 'json' | 'jsonl'` and
|
|
86
|
+
`engine.sanitizeJsonl(text)` with an empty structural policy by default.
|
|
87
|
+
|
|
88
|
+
For input above the whole-text limit, pass an iterable to
|
|
89
|
+
`sanitizeStream(source, {structured: 'jsonl', profile: 'publication'})` or use `sanitizeStreamToFile` for
|
|
90
|
+
atomic output. JSONL defaults to two ordered record workers. See [the streaming
|
|
91
|
+
example](examples/stream-jsonl.mjs).
|
|
92
|
+
|
|
93
|
+
Native coverage includes tolerant passport MRZ fragments, country passport
|
|
94
|
+
formats, PNR/ticket/visa identifiers, contextual birth dates, national IDs and
|
|
95
|
+
messenger phone numbers. `identityMask: true` (the publication/CLI redact default) uses
|
|
96
|
+
2+2 characters with a fixed `***` marker. Opt-in fake identity policies retain
|
|
97
|
+
audited specimen/synthetic values while credentials remain protected.
|
|
98
|
+
See [the API](docs/API.md) and [all issue 13 requirements and research](docs/case-studies/repository-issue-13/REQUIREMENTS.md).
|
|
99
|
+
|
|
100
|
+
`transformation: {mode: 'fake', key}` generates deterministic names, valid
|
|
101
|
+
dates/MRZs/checksummed IDs and reserved email/domain destinations. Credentials
|
|
102
|
+
remain fully redacted by default. Keep the key private: this preserves relations
|
|
103
|
+
and structure and is pseudonymisation. See [realistic fakes](examples/realistic-fakes.mjs)
|
|
104
|
+
and [the complete issue 24 requirements, research and solution plans](docs/case-studies/issue-24/REQUIREMENTS.md).
|
|
105
|
+
|
|
106
|
+
Keep policies containing real private values outside Git. Built-in exact public knowledge and narrowly scoped role/domain heuristics are enabled offline. Explicit private literals override public exemptions. Keep custom public policies reviewed against evidence; `--verify-public` separately opts into Wikidata name lookup:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"knownSecrets": [],
|
|
111
|
+
"knownPersonal": [{ "type": "PERSON", "value": "Private Person" }],
|
|
112
|
+
"publicEntities": [
|
|
113
|
+
{
|
|
114
|
+
"type": "EMAIL",
|
|
115
|
+
"value": "contact@example.org",
|
|
116
|
+
"source": "https://example.org/contact",
|
|
117
|
+
"reviewedAt": "2026-10-07"
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Custom policies preserve exact reviewed `PERSON`, `ORGANIZATION` and `EMAIL` detections. Built-in public resolvers/documentation IPs are also exempt. Credential detections always win, including a public email used as a password or authorization value. Evidence is an explicit caller decision and does not make every occurrence harmless.
|
|
124
|
+
|
|
125
|
+
## Extend detection
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
import {
|
|
129
|
+
createSanitizer,
|
|
130
|
+
createGitleaksDetector,
|
|
131
|
+
createTrufflehogDetector,
|
|
132
|
+
createPresidioDetector,
|
|
133
|
+
} from '@link-foundation/sensitive-data-sanitizer';
|
|
134
|
+
|
|
135
|
+
const sanitizer = createSanitizer({
|
|
136
|
+
detectors: [
|
|
137
|
+
createGitleaksDetector(),
|
|
138
|
+
createTrufflehogDetector(),
|
|
139
|
+
createPresidioDetector({ args: ['/absolute/path/presidio-bridge.py'] }),
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Install external binaries and language models separately. TruffleHog runs with verification and updates disabled. The Presidio example checks for an installed model and never downloads it at analysis time. Each selected detector is required, including during residual verification. See [the API and adapter contract](docs/API.md), [combined scanners](examples/combined-scanners.mjs), and [the local NER bridge](examples/presidio-bridge.py).
|
|
145
|
+
|
|
146
|
+
The framework also accepts Gitleaks, TruffleHog, detect-secrets, Presidio, or explicitly typed offset reports. Cloud reports can be converted locally; the package itself makes no cloud requests.
|
|
147
|
+
|
|
148
|
+
For CLI name recognition, `--presidio PATH` selects a local Python JSON bridge. `--python COMMAND` selects its interpreter, and `--model` / `--language` select an already installed model. These options require the bridge and participate in final-output verification.
|
|
149
|
+
|
|
150
|
+
## Git history
|
|
151
|
+
|
|
152
|
+
`history` audits reachable blobs, commit messages/authors, annotated tags, and reflog-reachable objects without changing refs. It can find deleted files. It reports object hashes and safe findings, and refuses limits or reports skipped binary objects as incomplete. The `history rewrite SOURCE --output NEW_DIRECTORY` command previews private replacement/mailmap files in a fresh mirror clone; add `--apply` to rewrite only that clone and re-audit blobs, metadata, paths and refs. It never pushes. Remote caches, forks, artifacts and unreachable objects require separate remediation; see the [history guide](docs/HISTORY-REMEDIATION.md).
|
|
153
|
+
|
|
154
|
+
## Contributing
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
npm test
|
|
158
|
+
npm run check
|
|
159
|
+
bun test --timeout 30000
|
|
160
|
+
deno test --allow-read --allow-env
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
See [contributing](docs/CONTRIBUTING.md). `add`, `multiply` and `delay` live in the `legacy` subpath, with the `sensitive-data-sanitizer-legacy` CLI and retained universal app; inherited pipeline instructions are in [the template guide](docs/TEMPLATE-PIPELINE.md).
|
|
164
|
+
|
|
165
|
+
### Auto-regenerated preview screenshots
|
|
166
|
+
|
|
167
|
+
The universal app's preview images regenerate on pushes to the default branch. Run `npm run example:web:preview-images` to regenerate them locally; see [the template guide](docs/TEMPLATE-PIPELINE.md) for setup.
|
|
168
|
+
|
|
169
|
+
For shared tool logs, `--credentials-only --env-secrets --gh-config` preserves
|
|
170
|
+
public account details while redacting credential patterns and exact local
|
|
171
|
+
secret values. Environment discovery matches credential-related variable names,
|
|
172
|
+
including AI-provider keys; `--gh-config` reads every host/account token from the
|
|
173
|
+
local GitHub CLI `hosts.yml`. Both local sources are opt-in. Literal references
|
|
174
|
+
such as `${{ secrets.NPM_TOKEN }}` and `$(gh auth token)` remain readable; actual
|
|
175
|
+
credential values are still redacted.
|
|
176
|
+
|
|
177
|
+
Long plain-text records are held until a safe boundary, including credentials
|
|
178
|
+
longer than a 64 KiB input chunk. Memory grows with the largest held record;
|
|
179
|
+
set `--max-record-bytes` for a smaller deployment budget. Crossing an explicit
|
|
180
|
+
record or total limit blocks atomic publication. See [the tool-log integration
|
|
181
|
+
example](examples/shared-tool-logs.mjs) and [API byte handling](docs/API.md).
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
Released under the [Unlicense](LICENSE). External components keep their own licenses; see [component provenance](docs/case-studies/issue-1/COMPETITORS.md).
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath, URL } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { add, multiply } from '../src/legacy.js';
|
|
7
|
+
|
|
8
|
+
const COMMANDS = {
|
|
9
|
+
add,
|
|
10
|
+
multiply,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function usage() {
|
|
14
|
+
return [
|
|
15
|
+
'Usage: sensitive-data-sanitizer-legacy <command> <left> <right>',
|
|
16
|
+
'',
|
|
17
|
+
'Commands:',
|
|
18
|
+
' add Add two numbers',
|
|
19
|
+
' multiply Multiply two numbers',
|
|
20
|
+
'',
|
|
21
|
+
'Options:',
|
|
22
|
+
' --help Show this help',
|
|
23
|
+
' --version Show package version',
|
|
24
|
+
].join('\n');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readVersion() {
|
|
28
|
+
const packageUrl = new URL('../package.json', import.meta.url);
|
|
29
|
+
const packageJson = JSON.parse(readFileSync(packageUrl, 'utf8'));
|
|
30
|
+
return packageJson.version;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseNumber(value, label) {
|
|
34
|
+
const parsed = Number(value);
|
|
35
|
+
|
|
36
|
+
if (!Number.isFinite(parsed)) {
|
|
37
|
+
throw new Error(`${label} must be a finite number`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function runCli(
|
|
44
|
+
argv,
|
|
45
|
+
{ stderr = console.error, stdout = console.log } = {}
|
|
46
|
+
) {
|
|
47
|
+
const [command, left, right] = argv;
|
|
48
|
+
|
|
49
|
+
if (!command || command === '--help' || command === '-h') {
|
|
50
|
+
stdout(usage());
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (command === '--version' || command === '-v') {
|
|
55
|
+
stdout(readVersion());
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const operation = COMMANDS[command];
|
|
60
|
+
|
|
61
|
+
if (!operation) {
|
|
62
|
+
stderr(`Unknown command: ${command}`);
|
|
63
|
+
stderr(usage());
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const leftNumber = parseNumber(left, 'left');
|
|
69
|
+
const rightNumber = parseNumber(right, 'right');
|
|
70
|
+
stdout(String(operation(leftNumber, rightNumber)));
|
|
71
|
+
return 0;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
stderr(error.message);
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isCliEntryPoint() {
|
|
79
|
+
if (!process.argv[1]) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
return (
|
|
85
|
+
realpathSync(process.argv[1]) ===
|
|
86
|
+
realpathSync(fileURLToPath(import.meta.url))
|
|
87
|
+
);
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (isCliEntryPoint()) {
|
|
94
|
+
process.exitCode = runCli(process.argv.slice(2));
|
|
95
|
+
}
|