@edgeproc/privacy-core 0.2.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/dist/detect/checksums.d.ts +5 -0
  4. package/dist/detect/checksums.d.ts.map +1 -0
  5. package/dist/detect/checksums.js +37 -0
  6. package/dist/detect/checksums.js.map +1 -0
  7. package/dist/detect/detector.d.ts +12 -0
  8. package/dist/detect/detector.d.ts.map +1 -0
  9. package/dist/detect/detector.js +57 -0
  10. package/dist/detect/detector.js.map +1 -0
  11. package/dist/detect/patterns.d.ts +22 -0
  12. package/dist/detect/patterns.d.ts.map +1 -0
  13. package/dist/detect/patterns.js +40 -0
  14. package/dist/detect/patterns.js.map +1 -0
  15. package/dist/egress.d.ts +85 -0
  16. package/dist/egress.d.ts.map +1 -0
  17. package/dist/egress.js +115 -0
  18. package/dist/egress.js.map +1 -0
  19. package/dist/egressReceipt.d.ts +67 -0
  20. package/dist/egressReceipt.d.ts.map +1 -0
  21. package/dist/egressReceipt.js +41 -0
  22. package/dist/egressReceipt.js.map +1 -0
  23. package/dist/errors.d.ts +62 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +80 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/index.d.ts +24 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +29 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/providers/factory.d.ts +20 -0
  32. package/dist/providers/factory.d.ts.map +1 -0
  33. package/dist/providers/factory.js +27 -0
  34. package/dist/providers/factory.js.map +1 -0
  35. package/dist/providers/nollm.d.ts +12 -0
  36. package/dist/providers/nollm.d.ts.map +1 -0
  37. package/dist/providers/nollm.js +28 -0
  38. package/dist/providers/nollm.js.map +1 -0
  39. package/dist/providers/openrouter.d.ts +18 -0
  40. package/dist/providers/openrouter.d.ts.map +1 -0
  41. package/dist/providers/openrouter.js +43 -0
  42. package/dist/providers/openrouter.js.map +1 -0
  43. package/dist/redact.d.ts +14 -0
  44. package/dist/redact.d.ts.map +1 -0
  45. package/dist/redact.js +79 -0
  46. package/dist/redact.js.map +1 -0
  47. package/dist/rehydrate.d.ts +21 -0
  48. package/dist/rehydrate.d.ts.map +1 -0
  49. package/dist/rehydrate.js +36 -0
  50. package/dist/rehydrate.js.map +1 -0
  51. package/dist/testing.d.ts +14 -0
  52. package/dist/testing.d.ts.map +1 -0
  53. package/dist/testing.js +31 -0
  54. package/dist/testing.js.map +1 -0
  55. package/dist/types.d.ts +27 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +2 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/vault.d.ts +21 -0
  60. package/dist/vault.d.ts.map +1 -0
  61. package/dist/vault.js +38 -0
  62. package/dist/vault.js.map +1 -0
  63. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Harish Seshadri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # @edgeproc/privacy-core
2
+
3
+ A browser-side privacy boundary for LLM calls. Raw private text stays on your
4
+ device; only **policy-approved, redacted** text is allowed to reach an external
5
+ LLM; the model's reply is **rehydrated locally** so you see real values again.
6
+
7
+ ![The wow loop: paste a statement, preview exactly what leaves, send only placeholders, get a rehydrated answer.](docs/wow.png)
8
+
9
+ ## TL;DR
10
+
11
+ - **What it is** — a TypeScript library that redacts sensitive text *before* it
12
+ leaves the browser, swapping each detected value for a typed placeholder
13
+ (`[CARD_1]`, `[NAME_2]`, `[AMOUNT_1]`), then restores the real values locally
14
+ when the model replies. You see and approve the exact text that will be sent.
15
+ - **Why it works** — you can't send raw text by accident. The only way to hand
16
+ text to the model runs through the redaction step, and you see and approve the
17
+ exact placeholders-only version before anything is sent. If code tries to skip
18
+ that and send raw text, it won't even build — the safety is baked into the code
19
+ itself, not a rule you have to remember. (The mechanism is in
20
+ [Under the hood](#under-the-hood-for-developers).)
21
+ - **Why it exists** — people paste bank statements, medical notes, and contracts
22
+ into chatbots every day, and all of it leaves the device in the clear. This
23
+ makes the boundary *visible and approvable* instead of invisible.
24
+ - **Status** — `0.1.0`, graduated from a proven spike. Deterministic detection
25
+ spine + reversible **in-memory** vault + Egress Guard + redact→rehydrate loop +
26
+ runnable demo. Unpublished (pre-registry). The encrypted vault and contextual
27
+ NER are deliberately **deferred** — see [Roadmap](#roadmap).
28
+
29
+ ## Quickstart — one command, see the proof
30
+
31
+ ```bash
32
+ # Node >= 22.13 and pnpm. Then, from the repo root:
33
+ pnpm install && pnpm demo
34
+ ```
35
+
36
+ Open <http://localhost:5173>. A synthetic bank statement is pre-loaded. You will
37
+ see, top to bottom:
38
+
39
+ 1. **The raw text** you'd normally paste into a chatbot.
40
+ 2. **The redaction set + the exact wire payload** — every real value replaced by
41
+ a placeholder. Open your browser's network tab to confirm: only this redacted
42
+ text would leave the device.
43
+ 3. Click **Send**. By default it uses the offline echo provider (runs cold). To
44
+ call a real model, set `OPENROUTER_API_KEY` + `VITE_USE_OPENROUTER=1` (copy
45
+ `.env.example` → `examples/demo/.env`): the demo then routes through a
46
+ same-origin **dev proxy** that injects the key server-side, so it never
47
+ reaches the browser. Either way, **only placeholders cross the wire**.
48
+ 4. **The rehydrated answer** — placeholders swapped back to real values locally,
49
+ values that never left your machine.
50
+
51
+ Prefer to see the guarantee enforced headlessly? `pnpm test:e2e` drives the loop
52
+ in real chromium, intercepts the request, and asserts only placeholders cross the
53
+ wire (the screenshot lands in the gitignored `test-results/`).
54
+
55
+ ## Use it as a library
56
+
57
+ ```ts
58
+ import {
59
+ approve,
60
+ makeProvider,
61
+ redactForEgress,
62
+ rehydrate,
63
+ Vault,
64
+ } from "@edgeproc/privacy-core";
65
+
66
+ const vault = new Vault();
67
+ const { provider } = makeProvider({ apiKey: process.env.OPENROUTER_API_KEY });
68
+
69
+ // detect + vault-write + brand — produces a PendingRedaction: a review PROPOSAL,
70
+ // not yet sendable (no provider will accept it).
71
+ const pending = await redactForEgress(rawStatement, vault);
72
+
73
+ // The explicit review step turns the proposal into a sendable RedactedPayload.
74
+ // The audit sink is REQUIRED, so every approval is observable.
75
+ const payload = approve(pending, (entry) => console.log(entry));
76
+
77
+ // provider.complete accepts ONLY a RedactedPayload — raw text won't compile.
78
+ const response = await provider.complete(payload);
79
+
80
+ // restore real values locally, on-device — bound to the payload's vault.
81
+ const answer = rehydrate(response.redactedText, vault, payload.vaultRef);
82
+ ```
83
+
84
+ ## The honest hard truth (read this first, it is not a footnote)
85
+
86
+ Two limits define what this can and cannot promise. Stating them up front is the
87
+ point — over-claiming privacy is worse than claiming none.
88
+
89
+ - **Detection recall *is* the product. Anything the detector misses leaks
90
+ silently.** No redactor catches everything: regex misses oddly-formatted
91
+ values, NER misses unusual names, novel PII types are invisible to both. So v0
92
+ does **not** promise "we redact everything." The v0 guarantee is scoped to
93
+ **user-confirmed redaction**: the human reviews the proposed redaction set in
94
+ the preview and approves it before send. Misses are caught by a person, not
95
+ promised away by the tool. The guard's job is to make the boundary *visible and
96
+ approvable*, not to claim perfect detection.
97
+
98
+ - **Redaction is not anonymization.** Even with every literal identifier removed,
99
+ the *structure* still leaks a behavioral fingerprint. "`$482.10` + the word
100
+ *insurance* + *early January*" can re-identify a person even though no name,
101
+ card, or SSN survived. Privacy-core reduces direct identifier leakage; it does
102
+ **not** make data anonymous, and it does not defend against a determined
103
+ re-identification attack. Generalization modes (amount bucketing, date
104
+ coarsening) that would start to address this are explicitly **deferred**.
105
+
106
+ If those two limits are unacceptable for a use case, this is the wrong tool.
107
+
108
+ ---
109
+
110
+ ## Under the hood (for developers)
111
+
112
+ The "it won't even build" guarantee is a **type-enforced Egress Guard**, not a
113
+ convention. Provider adapters accept *only* a branded `RedactedPayload`, whose
114
+ sole constructor is the redaction pipeline (`redactForEgress` → `approve`). A
115
+ raw `string` is not assignable to that type, so **handing raw text to a provider
116
+ is a compile error** — not a discipline you have to remember. `pnpm build` (tsc)
117
+ proves it on every build.
118
+
119
+ The same guarantee is re-checked at runtime so a cast can't defeat it: every
120
+ approved payload is registered by identity, and `assertApproved()` rejects a
121
+ hand-forged or spread-cloned look-alike before any network call is made.
122
+
123
+ ## Architecture — maps 1:1 to `src/`
124
+
125
+ ```text
126
+ src/
127
+ ├── index.ts # public API barrel — the production surface, nothing else
128
+ ├── types.ts # shared domain types (EntityType, Span, AuditEntry, …)
129
+ ├── egress.ts # the moat: branded RedactedPayload + LlmProvider + unsafeBypass
130
+ ├── redact.ts # redactForEgress — the ONLY legitimate payload constructor
131
+ ├── rehydrate.ts # local restore of real values after the reply
132
+ ├── vault.ts # Vault — reversible token<->value map (in-memory, v0)
133
+ ├── detect/
134
+ │ ├── detector.ts # detect() — merges patterns + dictionaries, drops overlaps
135
+ │ ├── patterns.ts # the deterministic ruleset (generic + finance packs)
136
+ │ └── checksums.ts # Luhn (cards) + IBAN mod-97
137
+ ├── providers/
138
+ │ ├── factory.ts # makeProvider — picks OpenRouter or the offline echo
139
+ │ ├── nollm.ts # NoLLMProvider — offline echo, runs with no API key
140
+ │ └── openrouter.ts # OpenRouterProvider — OpenAI-compatible chat/completions
141
+ └── testing.ts # SYNTHETIC_STATEMENT fixture — via the ./testing subpath,
142
+ # NEVER from the main barrel
143
+ ```
144
+
145
+ The flow is the portfolio's deterministic-core law: a fast, deterministic
146
+ detection **spine** (`detect`) plus a bounded, optional inference adapter (the
147
+ NER tier — deferred). The net-new product IP is the reversible loop + vault +
148
+ the type-enforced Egress Guard.
149
+
150
+ ### Public API
151
+
152
+ Everything `src/index.ts` exports, and nothing more:
153
+
154
+ | Export | Kind | Role |
155
+ |---|---|---|
156
+ | `detect` | fn | deterministic PII span detection |
157
+ | `redactForEgress` | fn | detect → vault-write → brand → a `PendingRedaction` proposal |
158
+ | `approve` | fn | explicit review step → mints the sendable payload (audit sink required) |
159
+ | `rehydrate` | fn | restore real values locally from placeholders |
160
+ | `Vault` | class | reversible token↔value map |
161
+ | `RedactedPayload` | type | the branded egress type |
162
+ | `LlmProvider` | interface | provider contract — accepts only `RedactedPayload` |
163
+ | `NoLLMProvider` | class | offline echo provider |
164
+ | `OpenRouterProvider` | class | OpenAI-compatible provider |
165
+ | `makeProvider` | fn | env-driven provider selector |
166
+ | `guardedProvider` | fn | wrap a provider so the runtime egress guard runs at one chokepoint |
167
+ | `unsafeBypass` | fn | the explicit, audited escape hatch |
168
+
169
+ The brand factory (`mintPendingRedaction`) and the `SYNTHETIC_STATEMENT` fixture
170
+ are intentionally **not** on the front door — a payload can be earned, not
171
+ forged, and a fixture is never shipped by accident.
172
+
173
+ ## Roadmap (explicitly deferred — not shipped in 0.1.0)
174
+
175
+ Labeled so no one mistakes it for current scope:
176
+
177
+ - **Encrypted IndexedDB vault** (AES-GCM + passphrase KDF). v0 uses an
178
+ **in-memory** vault that clears on reload — plainly stated, by design.
179
+ - **Contextual NER adapter** (names/merchants/locations regex misses) — depends
180
+ on the not-yet-extracted `@edgeproc/browser` runtime; off by default.
181
+ - **Audit-log persistence** (the audit sink is wired today; durable storage is
182
+ deferred).
183
+ - **More domain packs** (medical, legal, HR, identity) beyond the lifted generic
184
+ + finance set, and **generalization / anonymization** modes.
185
+
186
+ ## License
187
+
188
+ MIT. Recognizer patterns are ported from Microsoft Presidio (MIT); the
189
+ redact/rehydrate vault design follows LLM Guard's `Anonymize`/`Vault` (MIT),
190
+ reimplemented here in TypeScript.
@@ -0,0 +1,5 @@
1
+ /** Luhn check for card numbers (digits only). */
2
+ export declare function luhnValid(digits: string): boolean;
3
+ /** ISO 13616 IBAN mod-97 check. */
4
+ export declare function ibanValid(raw: string): boolean;
5
+ //# sourceMappingURL=checksums.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checksums.d.ts","sourceRoot":"","sources":["../../src/detect/checksums.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAejD;AAWD,mCAAmC;AACnC,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ9C"}
@@ -0,0 +1,37 @@
1
+ /** Luhn check for card numbers (digits only). */
2
+ export function luhnValid(digits) {
3
+ const d = digits.replace(/\D/g, "");
4
+ if (d.length < 13 || d.length > 19)
5
+ return false;
6
+ let sum = 0;
7
+ let dbl = false;
8
+ for (let i = d.length - 1; i >= 0; i--) {
9
+ let n = d.charCodeAt(i) - 48;
10
+ if (dbl) {
11
+ n *= 2;
12
+ if (n > 9)
13
+ n -= 9;
14
+ }
15
+ sum += n;
16
+ dbl = !dbl;
17
+ }
18
+ return sum % 10 === 0;
19
+ }
20
+ /** Compute n mod 97 over a decimal string too large for Number. */
21
+ function mod97(numeric) {
22
+ let remainder = 0;
23
+ for (const ch of numeric) {
24
+ remainder = (remainder * 10 + (ch.charCodeAt(0) - 48)) % 97;
25
+ }
26
+ return remainder;
27
+ }
28
+ /** ISO 13616 IBAN mod-97 check. */
29
+ export function ibanValid(raw) {
30
+ const s = raw.replace(/\s+/g, "").toUpperCase();
31
+ if (!/^[A-Z]{2}\d{2}[A-Z0-9]{1,30}$/.test(s))
32
+ return false;
33
+ const rearranged = s.slice(4) + s.slice(0, 4);
34
+ const expanded = rearranged.replace(/[A-Z]/g, (c) => String(c.charCodeAt(0) - 55));
35
+ return mod97(expanded) === 1;
36
+ }
37
+ //# sourceMappingURL=checksums.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checksums.js","sourceRoot":"","sources":["../../src/detect/checksums.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,GAAG,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,EAAE;QAAE,OAAO,KAAK,CAAC;IACjD,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,GAAG,GAAG,KAAK,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,GAAG,EAAE,CAAC;YACR,CAAC,IAAI,CAAC,CAAC;YACP,IAAI,CAAC,GAAG,CAAC;gBAAE,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;QACD,GAAG,IAAI,CAAC,CAAC;QACT,GAAG,GAAG,CAAC,GAAG,CAAC;IACb,CAAC;IACD,OAAO,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,mEAAmE;AACnE,SAAS,KAAK,CAAC,OAAe;IAC5B,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,SAAS,GAAG,CAAC,SAAS,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IAC9D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAChD,IAAI,CAAC,+BAA+B,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3D,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAClD,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAC7B,CAAC;IACF,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Span } from "../types.js";
2
+ /**
3
+ * Detect all PII spans deterministically: structured patterns + checksums plus
4
+ * the finance/name dictionaries. Overlaps are dropped; the result is sorted by
5
+ * start offset and non-overlapping.
6
+ *
7
+ * Recall is the product: anything not matched here leaks unless a human catches
8
+ * it in the preview. The contextual NER tier that would widen recall is
9
+ * deliberately deferred (see Roadmap).
10
+ */
11
+ export declare function detect(text: string): Span[];
12
+ //# sourceMappingURL=detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/detect/detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,IAAI,EAAE,MAAM,aAAa,CAAC;AAiDpD;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,CAO3C"}
@@ -0,0 +1,57 @@
1
+ import { MERCHANTS, NAMES, RULES } from "./patterns.js";
2
+ function dictSpans(text, type, dict) {
3
+ const out = [];
4
+ for (const term of dict) {
5
+ let from = 0;
6
+ let idx = text.indexOf(term, from);
7
+ while (idx !== -1) {
8
+ out.push({ type, value: term, start: idx, end: idx + term.length });
9
+ from = idx + term.length;
10
+ idx = text.indexOf(term, from);
11
+ }
12
+ }
13
+ return out;
14
+ }
15
+ function regexSpans(text, rule) {
16
+ const out = [];
17
+ for (const m of text.matchAll(rule.re)) {
18
+ // If the rule has a capture group, that group is the value; else whole match.
19
+ const value = m[1] ?? m[0];
20
+ if (rule.accept && !rule.accept(value))
21
+ continue;
22
+ const start = m.index + m[0].indexOf(value);
23
+ out.push({ type: rule.type, value, start, end: start + value.length });
24
+ }
25
+ return out;
26
+ }
27
+ /** Drop overlapping spans (earlier/longer wins), keeping a sorted, disjoint set. */
28
+ function dropOverlaps(spans) {
29
+ const sorted = [...spans].sort((a, b) => a.start - b.start || b.end - b.start - (a.end - a.start));
30
+ const kept = [];
31
+ let lastEnd = -1;
32
+ for (const s of sorted) {
33
+ if (s.start >= lastEnd) {
34
+ kept.push(s);
35
+ lastEnd = s.end;
36
+ }
37
+ }
38
+ return kept;
39
+ }
40
+ /**
41
+ * Detect all PII spans deterministically: structured patterns + checksums plus
42
+ * the finance/name dictionaries. Overlaps are dropped; the result is sorted by
43
+ * start offset and non-overlapping.
44
+ *
45
+ * Recall is the product: anything not matched here leaks unless a human catches
46
+ * it in the preview. The contextual NER tier that would widen recall is
47
+ * deliberately deferred (see Roadmap).
48
+ */
49
+ export function detect(text) {
50
+ const all = [
51
+ ...RULES.flatMap((r) => regexSpans(text, r)),
52
+ ...dictSpans(text, "MERCHANT", MERCHANTS),
53
+ ...dictSpans(text, "NAME", NAMES),
54
+ ];
55
+ return dropOverlaps(all);
56
+ }
57
+ //# sourceMappingURL=detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/detect/detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { EntityType } from "../types.js";
2
+ /** A regex recognizer, optionally gated by a checksum/structure accept-test. */
3
+ export interface Rule {
4
+ readonly type: EntityType;
5
+ readonly re: RegExp;
6
+ readonly accept?: (match: string) => boolean;
7
+ }
8
+ /**
9
+ * The Tier-1 deterministic ruleset (generic + finance packs, v0 scope).
10
+ * Patterns + checksums are ported from Microsoft Presidio's recognizer set;
11
+ * we reuse the durable, well-tested structure, not Presidio's Python runtime.
12
+ */
13
+ export declare const RULES: readonly Rule[];
14
+ /** The finance pack's merchant name set (deterministic dictionary lookup). */
15
+ export declare const MERCHANTS: readonly string[];
16
+ /**
17
+ * A tiny known-names dictionary. Real name coverage is the job of the deferred
18
+ * NER adapter (see Roadmap); this keeps the deterministic spine self-contained
19
+ * and the wow-demo runnable with zero download.
20
+ */
21
+ export declare const NAMES: readonly string[];
22
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/detect/patterns.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,gFAAgF;AAChF,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CAC9C;AAED;;;;GAIG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,IAAI,EAcvB,CAAC;AAEX,8EAA8E;AAC9E,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAM7B,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,MAAM,EAIzB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { ibanValid, luhnValid } from "./checksums.js";
2
+ /**
3
+ * The Tier-1 deterministic ruleset (generic + finance packs, v0 scope).
4
+ * Patterns + checksums are ported from Microsoft Presidio's recognizer set;
5
+ * we reuse the durable, well-tested structure, not Presidio's Python runtime.
6
+ */
7
+ export const RULES = [
8
+ { type: "EMAIL", re: /\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g },
9
+ {
10
+ type: "IBAN",
11
+ re: /\b[A-Z]{2}\d{2}(?:\s?[A-Z0-9]{2,4})+\b/g,
12
+ accept: ibanValid,
13
+ },
14
+ { type: "CARD", re: /\b(?:\d[ -]?){13,19}\b/g, accept: luhnValid },
15
+ { type: "SSN", re: /\b\d{3}-\d{2}-\d{4}\b/g },
16
+ { type: "ROUTING", re: /\bRouting number:\s*(\d{9})\b/g },
17
+ { type: "ACCOUNT", re: /\bAccount number:\s*(\d{9,12})\b/g },
18
+ { type: "PHONE", re: /\(\d{3}\)\s?\d{3}-\d{4}\b/g },
19
+ { type: "AMOUNT", re: /\$\d{1,3}(?:,\d{3})*(?:\.\d{2})?\b/g },
20
+ { type: "DATE", re: /\b\d{2}\/\d{2}\/\d{4}\b/g },
21
+ ];
22
+ /** The finance pack's merchant name set (deterministic dictionary lookup). */
23
+ export const MERCHANTS = [
24
+ "Whole Foods",
25
+ "Starbucks",
26
+ "Amazon",
27
+ "Walmart",
28
+ "Costco",
29
+ ];
30
+ /**
31
+ * A tiny known-names dictionary. Real name coverage is the job of the deferred
32
+ * NER adapter (see Roadmap); this keeps the deterministic spine self-contained
33
+ * and the wow-demo runnable with zero download.
34
+ */
35
+ export const NAMES = [
36
+ "Ada Lovelace",
37
+ "Grace Hopper",
38
+ "Alan Turing",
39
+ ];
40
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/detect/patterns.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAStD;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAoB;IACpC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,+BAA+B,EAAE;IACtD;QACE,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,yCAAyC;QAC7C,MAAM,EAAE,SAAS;KAClB;IACD,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,yBAAyB,EAAE,MAAM,EAAE,SAAS,EAAE;IAClE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,wBAAwB,EAAE;IAC7C,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,gCAAgC,EAAE;IACzD,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,mCAAmC,EAAE;IAC5D,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,4BAA4B,EAAE;IACnD,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,qCAAqC,EAAE;IAC7D,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,0BAA0B,EAAE;CACxC,CAAC;AAEX,8EAA8E;AAC9E,MAAM,CAAC,MAAM,SAAS,GAAsB;IAC1C,aAAa;IACb,WAAW;IACX,QAAQ;IACR,SAAS;IACT,QAAQ;CACA,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAsB;IACtC,cAAc;IACd,cAAc;IACd,aAAa;CACL,CAAC"}
@@ -0,0 +1,85 @@
1
+ import { type EgressGovernance } from "./egressReceipt.js";
2
+ import type { AuditSink, VaultRef } from "./types.js";
3
+ /**
4
+ * The egress boundary, expressed as a capability earned in two explicit steps:
5
+ *
6
+ * 1. `redactForEgress` mints a {@link PendingRedaction} — a review PROPOSAL.
7
+ * It is not sendable; no provider accepts it. Zero detections do NOT relax
8
+ * this: an empty redaction set still requires the explicit review step,
9
+ * because "the detector found nothing" is not the same as "a reviewer
10
+ * approved this content to leave the device".
11
+ * 2. `approve` — the explicit review action — converts a pipeline-minted
12
+ * pending into a {@link RedactedPayload}, the sendable capability.
13
+ *
14
+ * Both types carry a compile-time phantom brand (a raw `string` or hand-rolled
15
+ * object literal is not assignable), and every value minted here is registered
16
+ * in a module-private WeakSet so the capability also holds at RUNTIME in plain
17
+ * JS — a structurally identical forgery is rejected by {@link approve} and by
18
+ * every provider adapter (via {@link assertApproved}).
19
+ */
20
+ declare const brand: unique symbol;
21
+ interface Branded<B extends string> {
22
+ readonly [brand]: B;
23
+ }
24
+ /** A redaction proposal awaiting explicit review. NOT sendable. */
25
+ export type PendingRedaction = Branded<"PendingRedaction"> & {
26
+ readonly redactedText: string;
27
+ readonly vaultRef: VaultRef;
28
+ /** The placeholder tokens the reviewer is being asked to approve. */
29
+ readonly placeholders: readonly string[];
30
+ };
31
+ /** The sendable capability: reviewed, redacted content — never raw content. */
32
+ export type RedactedPayload = Branded<"RedactedPayload"> & {
33
+ readonly redactedText: string;
34
+ readonly vaultRef: VaultRef;
35
+ readonly approvedAt: number;
36
+ };
37
+ /** The egress boundary: providers accept ONLY an approved RedactedPayload. */
38
+ export interface LlmProvider {
39
+ complete(payload: RedactedPayload): Promise<import("./types.js").RedactedResponse>;
40
+ }
41
+ /**
42
+ * Runtime half of the egress guard. Every provider adapter MUST call this
43
+ * before doing anything with a payload; it rejects any object that was not
44
+ * minted by {@link approve} or {@link unsafeBypass} — TypeScript branding
45
+ * alone cannot stop a plain-JS caller, this check does.
46
+ */
47
+ export declare function assertApproved(payload: RedactedPayload): void;
48
+ /**
49
+ * Internal factory used by the redact pipeline. Intentionally NOT re-exported
50
+ * from the public barrel — a pending is earned from detection, not built.
51
+ */
52
+ export declare function mintPendingRedaction(redactedText: string, vaultRef: VaultRef, placeholders: readonly string[]): PendingRedaction;
53
+ /**
54
+ * THE explicit approval step — the only way a PendingRedaction becomes
55
+ * sendable. Approval is a deliberate human/reviewer action, never a side
56
+ * effect: even a zero-detection result passes through here. It refuses any
57
+ * object the pipeline did not mint (a hand-built "pending" would smuggle
58
+ * unreviewed text past the guard).
59
+ *
60
+ * The `audit` sink is REQUIRED — an approval that no one can observe is not
61
+ * an approval this boundary will grant. (Same stance as {@link unsafeBypass}.)
62
+ */
63
+ export declare function approve(pending: PendingRedaction, audit: AuditSink): RedactedPayload;
64
+ /**
65
+ * Wrap any {@link LlmProvider} so the runtime egress guard runs at ONE
66
+ * chokepoint before the inner provider ever sees the payload. TypeScript's
67
+ * brand stops a raw string at compile time, but a hand-rolled or third-party
68
+ * provider could still forget to call {@link assertApproved} at runtime — this
69
+ * wrapper makes that impossible for any provider obtained through it.
70
+ *
71
+ * With `governance`, the chokepoint additionally seals EVERY decision — each
72
+ * allow and each fail-closed deny — as a signed Avow effect receipt (see
73
+ * {@link file://./egressReceipt.ts}). The deny receipt digests the redacted
74
+ * text that was ATTEMPTED, and only its hash; nothing reaches the inner
75
+ * provider on a deny either way.
76
+ */
77
+ export declare function guardedProvider(inner: LlmProvider, governance?: EgressGovernance): LlmProvider;
78
+ /**
79
+ * The explicit, named, audited escape hatch — the ONLY way raw text can be
80
+ * made sendable without going through detection + review. It cannot be silent:
81
+ * it always emits an audit entry recording the human-supplied reason.
82
+ */
83
+ export declare function unsafeBypass(raw: string, reason: string, audit: AuditSink): RedactedPayload;
84
+ export {};
85
+ //# sourceMappingURL=egress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"egress.d.ts","sourceRoot":"","sources":["../src/egress.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,CAAC,MAAM,KAAK,EAAE,OAAO,MAAM,CAAC;AAEnC,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM;IAChC,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;CACrB;AAED,mEAAmE;AACnE,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC,GAAG;IAC3D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG;IACzD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CACN,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,OAAO,YAAY,EAAE,gBAAgB,CAAC,CAAC;CACnD;AAYD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAO7D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B,gBAAgB,CAIlB;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,SAAS,GACf,eAAe,CAmBjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,WAAW,EAClB,UAAU,CAAC,EAAE,gBAAgB,GAC5B,WAAW,CAab;AAuBD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,SAAS,GACf,eAAe,CASjB"}
package/dist/egress.js ADDED
@@ -0,0 +1,115 @@
1
+ import { sealEgressReceipt, } from "./egressReceipt.js";
2
+ import { ForgedPayloadError, UnapprovedPayloadError } from "./errors.js";
3
+ /** Module-private: every pending the redaction pipeline actually minted. */
4
+ const mintedPending = new WeakSet();
5
+ /**
6
+ * Module-private: every payload the guard actually approved. A WeakSet keyed
7
+ * by object IDENTITY — callers cannot reproduce membership by building a
8
+ * structurally identical object, and entries vanish with the payloads.
9
+ */
10
+ const approvedPayloads = new WeakSet();
11
+ /**
12
+ * Runtime half of the egress guard. Every provider adapter MUST call this
13
+ * before doing anything with a payload; it rejects any object that was not
14
+ * minted by {@link approve} or {@link unsafeBypass} — TypeScript branding
15
+ * alone cannot stop a plain-JS caller, this check does.
16
+ */
17
+ export function assertApproved(payload) {
18
+ if (!approvedPayloads.has(payload)) {
19
+ throw new UnapprovedPayloadError("payload was not approved by the egress guard — providers only accept " +
20
+ "the capability minted by approve() (or the audited unsafeBypass)");
21
+ }
22
+ }
23
+ /**
24
+ * Internal factory used by the redact pipeline. Intentionally NOT re-exported
25
+ * from the public barrel — a pending is earned from detection, not built.
26
+ */
27
+ export function mintPendingRedaction(redactedText, vaultRef, placeholders) {
28
+ const pending = Object.freeze({ redactedText, vaultRef, placeholders });
29
+ mintedPending.add(pending);
30
+ return pending;
31
+ }
32
+ /**
33
+ * THE explicit approval step — the only way a PendingRedaction becomes
34
+ * sendable. Approval is a deliberate human/reviewer action, never a side
35
+ * effect: even a zero-detection result passes through here. It refuses any
36
+ * object the pipeline did not mint (a hand-built "pending" would smuggle
37
+ * unreviewed text past the guard).
38
+ *
39
+ * The `audit` sink is REQUIRED — an approval that no one can observe is not
40
+ * an approval this boundary will grant. (Same stance as {@link unsafeBypass}.)
41
+ */
42
+ export function approve(pending, audit) {
43
+ if (!mintedPending.has(pending)) {
44
+ throw new ForgedPayloadError("approve() only accepts a PendingRedaction minted by redactForEgress — " +
45
+ "hand-built objects are rejected so unreviewed text cannot be approved");
46
+ }
47
+ const payload = Object.freeze({
48
+ redactedText: pending.redactedText,
49
+ vaultRef: pending.vaultRef,
50
+ approvedAt: Date.now(),
51
+ });
52
+ approvedPayloads.add(payload);
53
+ audit({
54
+ kind: "approve",
55
+ at: payload.approvedAt,
56
+ placeholders: pending.placeholders,
57
+ });
58
+ return payload;
59
+ }
60
+ /**
61
+ * Wrap any {@link LlmProvider} so the runtime egress guard runs at ONE
62
+ * chokepoint before the inner provider ever sees the payload. TypeScript's
63
+ * brand stops a raw string at compile time, but a hand-rolled or third-party
64
+ * provider could still forget to call {@link assertApproved} at runtime — this
65
+ * wrapper makes that impossible for any provider obtained through it.
66
+ *
67
+ * With `governance`, the chokepoint additionally seals EVERY decision — each
68
+ * allow and each fail-closed deny — as a signed Avow effect receipt (see
69
+ * {@link file://./egressReceipt.ts}). The deny receipt digests the redacted
70
+ * text that was ATTEMPTED, and only its hash; nothing reaches the inner
71
+ * provider on a deny either way.
72
+ */
73
+ export function guardedProvider(inner, governance) {
74
+ return {
75
+ async complete(payload) {
76
+ try {
77
+ assertApproved(payload);
78
+ }
79
+ catch (denied) {
80
+ await sealDecision(governance, payload.redactedText, "deny");
81
+ throw denied;
82
+ }
83
+ await sealDecision(governance, payload.redactedText, "allow");
84
+ return inner.complete(payload);
85
+ },
86
+ };
87
+ }
88
+ /** Seal one guard decision and hand it to the governance sink, if governed. */
89
+ async function sealDecision(governance, redactedText, decision) {
90
+ if (!governance)
91
+ return;
92
+ const { provider, seedHex, onReceipt, detectorVersion } = governance;
93
+ onReceipt(await sealEgressReceipt({
94
+ provider,
95
+ redactedText,
96
+ decision,
97
+ ...(detectorVersion === undefined ? {} : { detectorVersion }),
98
+ }, seedHex));
99
+ }
100
+ /**
101
+ * The explicit, named, audited escape hatch — the ONLY way raw text can be
102
+ * made sendable without going through detection + review. It cannot be silent:
103
+ * it always emits an audit entry recording the human-supplied reason.
104
+ */
105
+ export function unsafeBypass(raw, reason, audit) {
106
+ audit({ kind: "unsafe-bypass", at: Date.now(), reason });
107
+ const payload = Object.freeze({
108
+ redactedText: raw,
109
+ vaultRef: { id: "unsafe-no-vault" },
110
+ approvedAt: Date.now(),
111
+ });
112
+ approvedPayloads.add(payload);
113
+ return payload;
114
+ }
115
+ //# sourceMappingURL=egress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"egress.js","sourceRoot":"","sources":["../src/egress.ts"],"names":[],"mappings":"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"}