@cendor/acttrace 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.
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Offline, deterministic sensitive-data detectors for `@cendor/acttrace` — the TS port of
3
+ * `cendor.acttrace.detectors`.
4
+ *
5
+ * A {@link Detector} is a labelled regex plus an optional checksum/format **validator** that gates
6
+ * loose matches (Luhn for cards, mod-97 for IBANs, Verhoeff for Aadhaar, ABA for US routing numbers,
7
+ * range checks for SSNs). {@link DETECTORS} is the single source of truth for both scanning and
8
+ * redaction, so the built-in `default_redactor` is rebuilt from it and the original six categories
9
+ * still scrub byte-for-byte.
10
+ *
11
+ * Everything here is **local-first**: regex + arithmetic, no network, no model, no account. The
12
+ * registry is ordered original-six-first (`email` → `bearer_token`) so redaction application order —
13
+ * and therefore output — is unchanged for pre-existing payloads.
14
+ *
15
+ * Regex porting notes (vs Python `re`): the `u` flag is intentionally OFF so `\d`/`\w`/`\b` keep
16
+ * ASCII semantics; `i` mirrors `re.IGNORECASE`; `g` is used for finditer/scan and replace.
17
+ */
18
+ /** What a scrubbed span is replaced with. Kept identical to the original redactor's token. */
19
+ export const REDACTED = '<redacted>';
20
+ // --------------------------------------------------------------------------- validators
21
+ function digits(s) {
22
+ const out = [];
23
+ for (const c of s) {
24
+ if (c >= '0' && c <= '9')
25
+ out.push(c.charCodeAt(0) - 48);
26
+ }
27
+ return out;
28
+ }
29
+ /** Luhn (mod-10) check for a 13–19 digit payment card number. */
30
+ export function luhn(number) {
31
+ const d = digits(number);
32
+ if (!(d.length >= 13 && d.length <= 19))
33
+ return false;
34
+ let total = 0;
35
+ const rev = [...d].reverse();
36
+ for (let i = 0; i < rev.length; i++) {
37
+ let digit = rev[i];
38
+ if (i % 2 === 1) {
39
+ digit *= 2;
40
+ if (digit > 9)
41
+ digit -= 9;
42
+ }
43
+ total += digit;
44
+ }
45
+ return total % 10 === 0;
46
+ }
47
+ /** ISO 13616 IBAN check (mod-97 == 1) after the country/check-digit rearrangement. */
48
+ export function ibanMod97(iban) {
49
+ const s = iban.replace(/\s+/g, '').toUpperCase();
50
+ if (!/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(s))
51
+ return false;
52
+ const rearranged = s.slice(4) + s.slice(0, 4);
53
+ let numeric = '';
54
+ for (const c of rearranged) {
55
+ // int(c, 36): '0'-'9' -> 0-9, 'A'-'Z' -> 10-35.
56
+ numeric += c >= '0' && c <= '9' ? c : String(c.charCodeAt(0) - 55);
57
+ }
58
+ return BigInt(numeric) % 97n === 1n;
59
+ }
60
+ // Verhoeff dihedral-group tables (used by the Aadhaar locale pack).
61
+ const VERHOEFF_D = [
62
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
63
+ [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
64
+ [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
65
+ [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
66
+ [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
67
+ [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
68
+ [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
69
+ [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
70
+ [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
71
+ [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
72
+ ];
73
+ const VERHOEFF_P = [
74
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
75
+ [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
76
+ [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
77
+ [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
78
+ [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
79
+ [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
80
+ [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
81
+ [7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
82
+ ];
83
+ /** Verhoeff checksum (used for India's Aadhaar); true when the trailing digit checks out. */
84
+ export function verhoeff(number) {
85
+ const d = digits(number);
86
+ let c = 0;
87
+ const rev = [...d].reverse();
88
+ for (let i = 0; i < rev.length; i++) {
89
+ c = VERHOEFF_D[c][VERHOEFF_P[i % 8][rev[i]]];
90
+ }
91
+ return c === 0;
92
+ }
93
+ /** ABA routing-transit checksum for a 9-digit US routing number. */
94
+ export function abaValid(number) {
95
+ const d = digits(number);
96
+ if (d.length !== 9)
97
+ return false;
98
+ const checksum = 3 * (d[0] + d[3] + d[6]) + 7 * (d[1] + d[4] + d[7]) + (d[2] + d[5] + d[8]);
99
+ return checksum % 10 === 0;
100
+ }
101
+ /** Reject structurally-invalid US SSNs (area 000/666/900-999, group 00, serial 0000). */
102
+ export function ssnValid(ssn) {
103
+ const d = digits(ssn);
104
+ if (d.length !== 9)
105
+ return false;
106
+ const area = d[0] * 100 + d[1] * 10 + d[2];
107
+ const group = d[3] * 10 + d[4];
108
+ const serial = d[5] * 1000 + d[6] * 100 + d[7] * 10 + d[8];
109
+ if (area === 0 || area === 666 || area >= 900)
110
+ return false;
111
+ return group !== 0 && serial !== 0;
112
+ }
113
+ /** A candidate phone number is plausible when it carries 9–15 significant digits. */
114
+ export function phoneValid(s) {
115
+ const n = s.replace(/\D/g, '').length;
116
+ return n >= 9 && n <= 15;
117
+ }
118
+ // ISO 3166-1 alpha-2 codes, used to gate SWIFT/BIC (chars 5–6 are the country) so an arbitrary
119
+ // 8-letter uppercase token isn't mistaken for a bank code.
120
+ const ISO_ALPHA2 = new Set(('AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ ' +
121
+ 'BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM ' +
122
+ 'DO DZ EC EE EG EH ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS ' +
123
+ 'GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN ' +
124
+ 'KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ ' +
125
+ 'MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM ' +
126
+ 'PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV ' +
127
+ 'SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI ' +
128
+ 'VN VU WF WS YE YT ZA ZM ZW').split(' '));
129
+ /** Structural SWIFT/BIC check: 8 or 11 chars with a valid ISO-3166 country code at 5–6. */
130
+ export function bicValid(s) {
131
+ return (s.length === 8 || s.length === 11) && ISO_ALPHA2.has(s.slice(4, 6));
132
+ }
133
+ // --------------------------------------------------------------------------- registry
134
+ /** Compile a pattern (always global; add `i` for IGNORECASE). No `u` flag — ASCII class semantics. */
135
+ function c(pattern, ignoreCase = false) {
136
+ return new RegExp(pattern, ignoreCase ? 'gi' : 'g');
137
+ }
138
+ /**
139
+ * The built-in detectors. Ordered original-six-first so redaction output is byte-identical for
140
+ * pre-existing payloads. `registerDetector` appends to this list (custom detectors run last).
141
+ */
142
+ export const DETECTORS = [
143
+ // -- the original six (order preserved; email first) ------------------------------------------
144
+ {
145
+ category: 'email',
146
+ group: 'pii',
147
+ severity: 'warning',
148
+ pattern: c('\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b'),
149
+ },
150
+ // openai/anthropic sk- keys incl. the hyphenated modern forms (sk-ant-…, sk-proj-…) + legacy
151
+ {
152
+ category: 'api_key',
153
+ group: 'secret',
154
+ severity: 'critical',
155
+ pattern: c('\\bsk-[A-Za-z0-9_-]{8,}'),
156
+ },
157
+ {
158
+ category: 'aws_key',
159
+ group: 'secret',
160
+ severity: 'critical',
161
+ pattern: c('\\bAKIA[0-9A-Z]{16}\\b'),
162
+ },
163
+ {
164
+ category: 'google_api_key',
165
+ group: 'secret',
166
+ severity: 'critical',
167
+ pattern: c('\\bAIza[0-9A-Za-z_-]{35}\\b'),
168
+ },
169
+ {
170
+ category: 'jwt',
171
+ group: 'secret',
172
+ severity: 'critical',
173
+ pattern: c('\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}'),
174
+ },
175
+ {
176
+ category: 'bearer_token',
177
+ group: 'secret',
178
+ severity: 'critical',
179
+ pattern: c('\\b[Bb]earer\\s+[A-Za-z0-9._-]+\\b'),
180
+ },
181
+ // -- additional secrets ----------------------------------------------------------------------
182
+ {
183
+ category: 'github_token',
184
+ group: 'secret',
185
+ severity: 'critical',
186
+ pattern: c('\\bgh[pousr]_[A-Za-z0-9]{36,}\\b'),
187
+ },
188
+ {
189
+ category: 'slack_token',
190
+ group: 'secret',
191
+ severity: 'critical',
192
+ pattern: c('\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b'),
193
+ },
194
+ {
195
+ category: 'private_key',
196
+ group: 'secret',
197
+ severity: 'critical',
198
+ pattern: c('-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----'),
199
+ },
200
+ // -- free-text credentials -------------------------------------------------------------------
201
+ {
202
+ category: 'password',
203
+ group: 'credential',
204
+ severity: 'critical',
205
+ pattern: c('\\b(?:password|passphrase|passwd|pwd)\\b\\s*(?:is|:|=)\\s*\\S+', true),
206
+ },
207
+ // -- financial (validator-gated) -------------------------------------------------------------
208
+ {
209
+ category: 'credit_card',
210
+ group: 'financial',
211
+ severity: 'critical',
212
+ pattern: c('\\b\\d(?:[ -]?\\d){12,18}\\b'),
213
+ validator: luhn,
214
+ },
215
+ {
216
+ category: 'iban',
217
+ group: 'financial',
218
+ severity: 'critical',
219
+ pattern: c('\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b'),
220
+ validator: ibanMod97,
221
+ },
222
+ {
223
+ category: 'us_routing',
224
+ group: 'financial',
225
+ severity: 'critical',
226
+ pattern: c('\\b\\d{9}\\b'),
227
+ validator: abaValid,
228
+ },
229
+ {
230
+ category: 'swift_bic',
231
+ group: 'financial',
232
+ severity: 'critical',
233
+ pattern: c('\\b[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}(?:[A-Z0-9]{3})?\\b'),
234
+ validator: bicValid,
235
+ },
236
+ // -- government IDs (validator-gated) --------------------------------------------------------
237
+ {
238
+ category: 'us_ssn',
239
+ group: 'gov_id',
240
+ severity: 'critical',
241
+ pattern: c('\\b\\d{3}-\\d{2}-\\d{4}\\b'),
242
+ validator: ssnValid,
243
+ },
244
+ // -- remaining PII ---------------------------------------------------------------------------
245
+ {
246
+ category: 'phone',
247
+ group: 'pii',
248
+ severity: 'warning',
249
+ pattern: c('(?<!\\w)(?:\\+?1[ .\\-]?)?(?:\\(\\d{3}\\)[ .\\-]?|\\d{3}[ .\\-])\\d{3}[ .\\-]\\d{4}(?!\\d)' +
250
+ '|(?<!\\w)\\+\\d{9,15}(?!\\d)'),
251
+ validator: phoneValid,
252
+ },
253
+ {
254
+ category: 'ipv4',
255
+ group: 'pii',
256
+ severity: 'warning',
257
+ pattern: c('\\b(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}' +
258
+ '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\b'),
259
+ },
260
+ {
261
+ category: 'ipv6',
262
+ group: 'pii',
263
+ severity: 'warning',
264
+ pattern: c('\\b(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}\\b' +
265
+ '|\\b(?:[A-Fa-f0-9]{1,4}:){1,7}:(?:[A-Fa-f0-9]{1,4}\\b)?'),
266
+ },
267
+ {
268
+ category: 'mac_address',
269
+ group: 'pii',
270
+ severity: 'warning',
271
+ pattern: c('\\b(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\\b'),
272
+ },
273
+ // -- GDPR Art.9 special categories (best-effort keyword lexicon) -----------------------------
274
+ {
275
+ category: 'special_category',
276
+ group: 'special_category',
277
+ severity: 'warning',
278
+ pattern: c('\\b(?:diagnos(?:is|es|ed)?|hiv|pregnan(?:t|cy)|disab(?:led|ility)|biometric|' +
279
+ 'fingerprints?|genetic|ethnicity|religio(?:n|us))\\b', true),
280
+ },
281
+ ];
282
+ /** Add a custom {@link Detector} to the global registry (it runs after the built-ins). */
283
+ export function registerDetector(detector) {
284
+ DETECTORS.push(detector);
285
+ }
286
+ /** A copy of the active detector registry (built-ins plus anything registered). */
287
+ export function detectors() {
288
+ return [...DETECTORS];
289
+ }
290
+ /** The group a category belongs to per the active registry (`null` if unknown). */
291
+ export function groupOf(category) {
292
+ for (const d of DETECTORS) {
293
+ if (d.category === category)
294
+ return d.group;
295
+ }
296
+ return null;
297
+ }
298
+ // --------------------------------------------------------------------------- scan / scrub
299
+ /** A plain `{}`-shaped object (a JSON dict), not a wrapper instance like `PyFloat` or a Date. */
300
+ function isPlainObject(o) {
301
+ if (o === null || typeof o !== 'object')
302
+ return false;
303
+ const proto = Object.getPrototypeOf(o);
304
+ return proto === Object.prototype || proto === null;
305
+ }
306
+ /**
307
+ * Walk `obj` (str/dict/list) and count validated matches per category. Returns an insertion-ordered
308
+ * map `category -> [detector, occurrences]` accumulated across the walk. Never returns raw values.
309
+ */
310
+ export function scanCounts(obj) {
311
+ const counts = new Map();
312
+ const walk = (o) => {
313
+ if (typeof o === 'string') {
314
+ for (const det of DETECTORS) {
315
+ let n = 0;
316
+ for (const match of o.matchAll(det.pattern)) {
317
+ const value = match[0];
318
+ if (det.validator === undefined || det.validator(value))
319
+ n += 1;
320
+ }
321
+ if (n) {
322
+ const slot = counts.get(det.category);
323
+ if (slot === undefined)
324
+ counts.set(det.category, [det, n]);
325
+ else
326
+ slot[1] += n;
327
+ }
328
+ }
329
+ }
330
+ else if (Array.isArray(o)) {
331
+ for (const v of o)
332
+ walk(v);
333
+ }
334
+ else if (isPlainObject(o)) {
335
+ for (const v of Object.values(o))
336
+ walk(v);
337
+ }
338
+ };
339
+ walk(obj);
340
+ return counts;
341
+ }
342
+ /**
343
+ * Return a copy of `obj` with every span matching a category in `categories` replaced. Applies
344
+ * detectors in **registry order** (original six first), so output is byte-identical to the historical
345
+ * redactor. Validator-gated detectors only scrub the spans that actually validate.
346
+ */
347
+ export function scrub(obj, categories) {
348
+ const wanted = new Set(categories);
349
+ const active = DETECTORS.filter((d) => wanted.has(d.category));
350
+ if (active.length === 0)
351
+ return obj;
352
+ const subs = active.map((det) => {
353
+ const validator = det.validator;
354
+ if (validator === undefined)
355
+ return [det.pattern, () => REDACTED];
356
+ return [det.pattern, (m) => (validator(m) ? REDACTED : m)];
357
+ });
358
+ const go = (o) => {
359
+ if (typeof o === 'string') {
360
+ let out = o;
361
+ for (const [pattern, repl] of subs) {
362
+ out = out.replace(pattern, (m) => repl(m));
363
+ }
364
+ return out;
365
+ }
366
+ if (Array.isArray(o))
367
+ return o.map(go);
368
+ if (isPlainObject(o)) {
369
+ const result = {};
370
+ for (const [k, v] of Object.entries(o))
371
+ result[k] = go(v);
372
+ return result;
373
+ }
374
+ return o;
375
+ };
376
+ return go(obj);
377
+ }
378
+ //# sourceMappingURL=detectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectors.js","sourceRoot":"","sources":["../src/detectors.ts"],"names":[],"mappings":"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AC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;CAC/B,CAAC;AACF,MAAM,UAAU,GAAmC;IACjD,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAA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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `guard()` — a batteries-included enforcement callable for `@cendor/core`'s interceptor seam. The
3
+ * TS port of `cendor.acttrace.guard`.
4
+ *
5
+ * The recorder/enforcer split stays intact: acttrace still only *records*. `guard()` returns a plain
6
+ * callable you install via `addInterceptor` — it is core that stops the call. Per call, the active
7
+ * {@link Policy} resolves each detected category to an action:
8
+ *
9
+ * - **block** → record `policy_flag(action="blocked")` → **throw** (the call never runs).
10
+ * - **redact** → scrub the outbound messages via `Reroute(messages=…)` so the *provider* receives
11
+ * cleaned content, record `action="redacted"` → proceed. Tools have no message-rewrite seam, so a
12
+ * redact on tool arguments stays record-only.
13
+ * - **flag** → record `policy_flag(action="flagged")` → proceed.
14
+ * - nothing → proceed untouched.
15
+ */
16
+ import type { AuditLog } from './index.js';
17
+ import { type Finding, Policy } from './policy.js';
18
+ /**
19
+ * Raised by a {@link guard} to block an outbound call whose content a policy forbids. Carries the
20
+ * offending {@link Finding} list on `findings` (categories and counts only — never raw values).
21
+ */
22
+ export declare class PolicyViolation extends Error {
23
+ findings: Finding[];
24
+ constructor(message?: string, findings?: Finding[] | null);
25
+ }
26
+ /** An exception class (called with a message) or a factory `findings -> Error`. */
27
+ export type OnBlock = (new (message: string) => Error) | ((findings: Finding[]) => Error);
28
+ /**
29
+ * Return a pre-call interceptor that enforces `policy` and records refusals via `audit`. Install it
30
+ * on core's seam via `addInterceptor(guard(Policy.gdpr(), log))`.
31
+ *
32
+ * Note that `Policy.default()` never blocks — use `Policy.gdpr()` / `pci()` / `strict()` (or a custom
33
+ * policy) to make a category `block`. `audit` is optional — without it the guard still enforces.
34
+ */
35
+ export declare function guard(policy?: Policy | null, audit?: AuditLog | null, onBlock?: OnBlock): (call: unknown) => unknown;
36
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAQ,MAAM,aAAa,CAAC;AAKzD;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,EAAE,OAAO,EAAE,CAAC;gBACR,OAAO,SAAqB,EAAE,QAAQ,GAAE,OAAO,EAAE,GAAG,IAAW;CAK5E;AAED,mFAAmF;AACnF,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,CAAC;AA0C1F;;;;;;GAMG;AACH,wBAAgB,KAAK,CACnB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,EACvB,OAAO,GAAE,OAAyB,GACjC,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CA+C5B"}
package/dist/guard.js ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * `guard()` — a batteries-included enforcement callable for `@cendor/core`'s interceptor seam. The
3
+ * TS port of `cendor.acttrace.guard`.
4
+ *
5
+ * The recorder/enforcer split stays intact: acttrace still only *records*. `guard()` returns a plain
6
+ * callable you install via `addInterceptor` — it is core that stops the call. Per call, the active
7
+ * {@link Policy} resolves each detected category to an action:
8
+ *
9
+ * - **block** → record `policy_flag(action="blocked")` → **throw** (the call never runs).
10
+ * - **redact** → scrub the outbound messages via `Reroute(messages=…)` so the *provider* receives
11
+ * cleaned content, record `action="redacted"` → proceed. Tools have no message-rewrite seam, so a
12
+ * redact on tool arguments stays record-only.
13
+ * - **flag** → record `policy_flag(action="flagged")` → proceed.
14
+ * - nothing → proceed untouched.
15
+ */
16
+ import { LLMCall, MISS, Reroute, ToolCall } from '@cendor/core';
17
+ import { scrub } from './detectors.js';
18
+ import { Policy, scan } from './policy.js';
19
+ /** Severity ordering, so a grouped flag carries the strongest severity in the group. */
20
+ const SEVERITY_RANK = { info: 0, warning: 1, critical: 2 };
21
+ /**
22
+ * Raised by a {@link guard} to block an outbound call whose content a policy forbids. Carries the
23
+ * offending {@link Finding} list on `findings` (categories and counts only — never raw values).
24
+ */
25
+ export class PolicyViolation extends Error {
26
+ findings;
27
+ constructor(message = 'policy violation', findings = null) {
28
+ super(message);
29
+ this.name = 'PolicyViolation';
30
+ this.findings = findings ?? [];
31
+ }
32
+ }
33
+ /** The caller-supplied content of a call to scan (messages for LLMs, arguments for tools). */
34
+ function content(call) {
35
+ if (call instanceof LLMCall)
36
+ return call.messages;
37
+ if (call instanceof ToolCall)
38
+ return call.arguments;
39
+ return null;
40
+ }
41
+ function maxSeverity(findings) {
42
+ let best = 'warning';
43
+ let bestRank = -1;
44
+ for (const f of findings) {
45
+ const rank = SEVERITY_RANK[f.severity] ?? 0;
46
+ if (rank > bestRank) {
47
+ bestRank = rank;
48
+ best = f.severity;
49
+ }
50
+ }
51
+ return best;
52
+ }
53
+ /** ES6-class detection: distinguishes an exception class from a `findings -> Error` factory. */
54
+ function isClass(fn) {
55
+ return typeof fn === 'function' && /^class[\s{]/.test(Function.prototype.toString.call(fn));
56
+ }
57
+ function makeBlockException(onBlock, findings) {
58
+ const cats = [...new Set(findings.map((f) => f.category))].sort();
59
+ const message = `policy blocked outbound call: ${cats.join(', ')}`;
60
+ if (isClass(onBlock)) {
61
+ const exc = new onBlock(message);
62
+ try {
63
+ exc.findings = findings;
64
+ }
65
+ catch {
66
+ // exception type doesn't tolerate a findings attribute — ignore
67
+ }
68
+ return exc;
69
+ }
70
+ return onBlock(findings);
71
+ }
72
+ /**
73
+ * Return a pre-call interceptor that enforces `policy` and records refusals via `audit`. Install it
74
+ * on core's seam via `addInterceptor(guard(Policy.gdpr(), log))`.
75
+ *
76
+ * Note that `Policy.default()` never blocks — use `Policy.gdpr()` / `pci()` / `strict()` (or a custom
77
+ * policy) to make a category `block`. `audit` is optional — without it the guard still enforces.
78
+ */
79
+ export function guard(policy, audit, onBlock = PolicyViolation) {
80
+ const p = policy ?? Policy.default();
81
+ const auditLog = audit ?? null;
82
+ const record = (action, findings, call, note = '') => {
83
+ if (auditLog === null)
84
+ return;
85
+ const cats = [...new Set(findings.map((f) => f.category))].sort();
86
+ const kind = call instanceof LLMCall ? 'llm_call' : 'tool_call';
87
+ let reason = `${action} ${cats.join(', ')} in outbound ${kind}`;
88
+ if (note)
89
+ reason = `${reason} (${note})`;
90
+ const severity = action === 'redacted' ? 'info' : maxSeverity(findings);
91
+ auditLog.flag(reason, { action, severity, data: cats, extra: { auto: true } });
92
+ };
93
+ return (call) => {
94
+ const c = content(call);
95
+ if (c === null)
96
+ return MISS;
97
+ const findings = scan(c, p);
98
+ if (findings.length === 0)
99
+ return MISS;
100
+ const blocked = findings.filter((f) => f.action === 'block');
101
+ const toRedact = findings.filter((f) => f.action === 'redact');
102
+ const flagged = findings.filter((f) => f.action === 'flag');
103
+ if (blocked.length > 0) {
104
+ record('blocked', blocked, call); // record the refusal *before* throwing
105
+ throw makeBlockException(onBlock, blocked);
106
+ }
107
+ if (flagged.length > 0) {
108
+ record('flagged', flagged, call);
109
+ }
110
+ if (toRedact.length > 0) {
111
+ if (call instanceof LLMCall) {
112
+ // Redact-before-send: scrub the outbound messages and reroute so the provider receives the
113
+ // cleaned content, then record that we did.
114
+ const cleaned = scrub(call.messages, new Set(toRedact.map((f) => f.category)));
115
+ record('redacted', toRedact, call);
116
+ return new Reroute({ messages: cleaned });
117
+ }
118
+ // Tools have no message-rewrite seam, so a redact on tool arguments stays record-only.
119
+ record('flagged', toRedact, call, 'redact-before-send applies to model calls; tool arguments unchanged');
120
+ }
121
+ return MISS;
122
+ };
123
+ }
124
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
package/dist/hash.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /** Lowercase-hex SHA-256 of the UTF-8 bytes of `text`. */
2
+ export declare function sha256Hex(text: string): string;
3
+ /** Lowercase-hex HMAC-SHA256 over the UTF-8 bytes of `text` under `key` (UTF-8 bytes of the passphrase). */
4
+ export declare function hmacSha256Hex(key: string | Uint8Array, text: string): string;
5
+ /** Constant-time string compare (both treated as UTF-8). Returns false on length mismatch. */
6
+ export declare function timingSafeEqualHex(a: string, b: string): boolean;
7
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AA2BA,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,4GAA4G;AAC5G,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,8FAA8F;AAC9F,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAKhE"}
package/dist/hash.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Synchronous SHA-256 + HMAC-SHA256, via `node:crypto`. acttrace hashes/signs inside a synchronous
3
+ * bus subscriber (matching Python), so WebCrypto's async `subtle` is unusable. acttrace is a
4
+ * server-side governance library (signing keys never belong in a browser), so `node:crypto` is the
5
+ * correct dependency; it is also available in edge runtimes with `nodejs_compat`. Loaded via
6
+ * `createRequire` so importing the package does not force `node:crypto` into a browser bundle.
7
+ */
8
+ import { createRequire } from 'node:module';
9
+ const require = createRequire(import.meta.url);
10
+ let cryptoMod;
11
+ function nodeCrypto() {
12
+ if (cryptoMod === undefined)
13
+ cryptoMod = require('node:crypto');
14
+ return cryptoMod;
15
+ }
16
+ /** Lowercase-hex SHA-256 of the UTF-8 bytes of `text`. */
17
+ export function sha256Hex(text) {
18
+ return nodeCrypto().createHash('sha256').update(text, 'utf8').digest('hex');
19
+ }
20
+ /** Lowercase-hex HMAC-SHA256 over the UTF-8 bytes of `text` under `key` (UTF-8 bytes of the passphrase). */
21
+ export function hmacSha256Hex(key, text) {
22
+ return nodeCrypto().createHmac('sha256', key).update(text, 'utf8').digest('hex');
23
+ }
24
+ /** Constant-time string compare (both treated as UTF-8). Returns false on length mismatch. */
25
+ export function timingSafeEqualHex(a, b) {
26
+ const ba = new TextEncoder().encode(a);
27
+ const bb = new TextEncoder().encode(b);
28
+ if (ba.length !== bb.length)
29
+ return false;
30
+ return nodeCrypto().timingSafeEqual(ba, bb);
31
+ }
32
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAY/C,IAAI,SAAiC,CAAC;AACtC,SAAS,UAAU;IACjB,IAAI,SAAS,KAAK,SAAS;QAAE,SAAS,GAAG,OAAO,CAAC,aAAa,CAAe,CAAC;IAC9E,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,UAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,aAAa,CAAC,GAAwB,EAAE,IAAY;IAClE,OAAO,UAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnF,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,kBAAkB,CAAC,CAAS,EAAE,CAAS;IACrD,MAAM,EAAE,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,EAAE,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvC,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1C,OAAO,UAAU,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAC9C,CAAC"}