@wytness/sdk 0.4.0 → 0.6.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/dist/index.cjs +209 -28
- package/dist/index.d.cts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.js +206 -26
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
AuditClient: () => AuditClient,
|
|
34
34
|
AuditEventSchema: () => AuditEventSchema,
|
|
35
|
+
SessionTokenizer: () => SessionTokenizer,
|
|
35
36
|
auditTool: () => auditTool,
|
|
36
37
|
computeEventHash: () => computeEventHash,
|
|
37
38
|
createFileEmitter: () => createFileEmitter,
|
|
@@ -81,7 +82,10 @@ var AuditEventSchema = import_zod.z.object({
|
|
|
81
82
|
retry_count: import_zod.z.number().int().default(0),
|
|
82
83
|
// Chain integrity
|
|
83
84
|
prev_event_hash: import_zod.z.string().default(""),
|
|
84
|
-
signature: import_zod.z.string().default("")
|
|
85
|
+
signature: import_zod.z.string().default(""),
|
|
86
|
+
// Pseudonymization
|
|
87
|
+
encrypted_token_map: import_zod.z.string().default(""),
|
|
88
|
+
pseudonymization_version: import_zod.z.string().default("")
|
|
85
89
|
});
|
|
86
90
|
|
|
87
91
|
// src/signing.ts
|
|
@@ -159,7 +163,7 @@ async function sendOne(apiUrl, apiKey, body) {
|
|
|
159
163
|
body,
|
|
160
164
|
signal: AbortSignal.timeout(1e4)
|
|
161
165
|
});
|
|
162
|
-
if (resp.status !== 201) {
|
|
166
|
+
if (resp.status !== 201 && resp.status !== 202) {
|
|
163
167
|
throw new Error(`HTTP ${resp.status}`);
|
|
164
168
|
}
|
|
165
169
|
return true;
|
|
@@ -232,9 +236,150 @@ async function flushPending() {
|
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
// src/client.ts
|
|
235
|
-
var
|
|
239
|
+
var import_crypto4 = require("crypto");
|
|
236
240
|
var import_fs2 = require("fs");
|
|
237
241
|
var import_path2 = require("path");
|
|
242
|
+
|
|
243
|
+
// src/tokenizer.ts
|
|
244
|
+
var import_crypto3 = require("crypto");
|
|
245
|
+
var import_tweetnacl2 = __toESM(require("tweetnacl"), 1);
|
|
246
|
+
var import_tweetnacl_util2 = require("tweetnacl-util");
|
|
247
|
+
var SECRET_PATTERN = /key|secret|token|password|passwd|pwd|credential|auth/i;
|
|
248
|
+
var PII_PATTERNS = [
|
|
249
|
+
[/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "EMAIL"],
|
|
250
|
+
[/\b\d{3}[-.\s]?\d{2}[-.\s]?\d{4}\b/g, "SSN"],
|
|
251
|
+
[/\b\d{3}[-.\s]?\d{3}[-.\s]?\d{3}\b/g, "TFN"],
|
|
252
|
+
[/\b(?:\d{4}[-\s]?){3}\d{4}\b/g, "CARD"],
|
|
253
|
+
[/\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g, "PHONE"]
|
|
254
|
+
];
|
|
255
|
+
function hmacPseudonym(value, secret, piiType = "PII") {
|
|
256
|
+
const digest = (0, import_crypto3.createHmac)("sha256", secret).update(value, "utf-8").digest("hex");
|
|
257
|
+
return `${piiType}_${digest.slice(0, 8)}`;
|
|
258
|
+
}
|
|
259
|
+
var SessionTokenizer = class {
|
|
260
|
+
_hmacSecret;
|
|
261
|
+
_encryptionPub;
|
|
262
|
+
_piiFields;
|
|
263
|
+
_tokenMap = {};
|
|
264
|
+
// pseudonym → original
|
|
265
|
+
_reverseMap = {};
|
|
266
|
+
// original → pseudonym
|
|
267
|
+
/**
|
|
268
|
+
* @param hmacSecret 32-byte secret for deterministic pseudonyms.
|
|
269
|
+
* @param encryptionPublicKey 32-byte X25519 public key for encrypting the token map.
|
|
270
|
+
* @param piiFields Parameter names whose values should be fully pseudonymized.
|
|
271
|
+
*/
|
|
272
|
+
constructor(hmacSecret, encryptionPublicKey, piiFields = []) {
|
|
273
|
+
this._hmacSecret = hmacSecret;
|
|
274
|
+
this._encryptionPub = encryptionPublicKey;
|
|
275
|
+
this._piiFields = piiFields;
|
|
276
|
+
}
|
|
277
|
+
get tokenMap() {
|
|
278
|
+
return { ...this._tokenMap };
|
|
279
|
+
}
|
|
280
|
+
addMapping(original, pseudonym) {
|
|
281
|
+
this._tokenMap[pseudonym] = original;
|
|
282
|
+
this._reverseMap[original] = pseudonym;
|
|
283
|
+
}
|
|
284
|
+
pseudonymizeValue(value, fieldPath = "") {
|
|
285
|
+
if (!value || !value.trim())
|
|
286
|
+
return value;
|
|
287
|
+
if (this._piiFields.includes(fieldPath)) {
|
|
288
|
+
if (this._reverseMap[value])
|
|
289
|
+
return this._reverseMap[value];
|
|
290
|
+
const pseudonym = hmacPseudonym(value, this._hmacSecret);
|
|
291
|
+
this.addMapping(value, pseudonym);
|
|
292
|
+
return pseudonym;
|
|
293
|
+
}
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
pseudonymizeText(text) {
|
|
297
|
+
if (!text)
|
|
298
|
+
return text;
|
|
299
|
+
const known = Object.keys(this._reverseMap).sort(
|
|
300
|
+
(a, b) => b.length - a.length
|
|
301
|
+
);
|
|
302
|
+
for (const original of known) {
|
|
303
|
+
if (text.includes(original)) {
|
|
304
|
+
text = text.split(original).join(this._reverseMap[original]);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
for (const [pattern, piiType] of PII_PATTERNS) {
|
|
308
|
+
pattern.lastIndex = 0;
|
|
309
|
+
text = text.replace(pattern, (matched) => {
|
|
310
|
+
if (this._reverseMap[matched])
|
|
311
|
+
return this._reverseMap[matched];
|
|
312
|
+
const pseudonym = hmacPseudonym(matched, this._hmacSecret, piiType);
|
|
313
|
+
this.addMapping(matched, pseudonym);
|
|
314
|
+
return pseudonym;
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return text;
|
|
318
|
+
}
|
|
319
|
+
pseudonymizeParams(params) {
|
|
320
|
+
const result = {};
|
|
321
|
+
for (const [k, v] of Object.entries(params)) {
|
|
322
|
+
if (SECRET_PATTERN.test(k)) {
|
|
323
|
+
result[k] = "[REDACTED]";
|
|
324
|
+
} else if (this._piiFields.includes(k)) {
|
|
325
|
+
const val = String(v).slice(0, 500);
|
|
326
|
+
result[k] = this.pseudonymizeValue(val, k);
|
|
327
|
+
} else {
|
|
328
|
+
const val = String(v).slice(0, 500);
|
|
329
|
+
result[k] = this.pseudonymizeText(val);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Encrypt the token map with the customer's X25519 public key.
|
|
336
|
+
*
|
|
337
|
+
* Format: base64([ephemeral_pub 32B][nonce 24B][ciphertext])
|
|
338
|
+
* Uses NaCl box (X25519 + XSalsa20-Poly1305).
|
|
339
|
+
*/
|
|
340
|
+
encryptTokenMap() {
|
|
341
|
+
const plaintext = Buffer.from(
|
|
342
|
+
JSON.stringify(this._tokenMap),
|
|
343
|
+
"utf-8"
|
|
344
|
+
);
|
|
345
|
+
const ephemeral = import_tweetnacl2.default.box.keyPair();
|
|
346
|
+
const nonce = import_tweetnacl2.default.randomBytes(24);
|
|
347
|
+
const ciphertext = import_tweetnacl2.default.box(
|
|
348
|
+
plaintext,
|
|
349
|
+
nonce,
|
|
350
|
+
this._encryptionPub,
|
|
351
|
+
ephemeral.secretKey
|
|
352
|
+
);
|
|
353
|
+
if (!ciphertext) {
|
|
354
|
+
throw new Error("Encryption failed");
|
|
355
|
+
}
|
|
356
|
+
const packed = new Uint8Array(32 + 24 + ciphertext.length);
|
|
357
|
+
packed.set(ephemeral.publicKey, 0);
|
|
358
|
+
packed.set(nonce, 32);
|
|
359
|
+
packed.set(ciphertext, 56);
|
|
360
|
+
return (0, import_tweetnacl_util2.encodeBase64)(packed);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Decrypt a token map using the customer's X25519 private key.
|
|
364
|
+
*
|
|
365
|
+
* @param encryptedB64 Base64-encoded encrypted blob from encryptTokenMap().
|
|
366
|
+
* @param privateKey 32-byte X25519 private (secret) key.
|
|
367
|
+
* @returns The token map (pseudonym → original).
|
|
368
|
+
*/
|
|
369
|
+
static decryptTokenMap(encryptedB64, privateKey) {
|
|
370
|
+
const packed = (0, import_tweetnacl_util2.decodeBase64)(encryptedB64);
|
|
371
|
+
const ephemeralPub = packed.slice(0, 32);
|
|
372
|
+
const nonce = packed.slice(32, 56);
|
|
373
|
+
const ciphertext = packed.slice(56);
|
|
374
|
+
const plaintext = import_tweetnacl2.default.box.open(ciphertext, nonce, ephemeralPub, privateKey);
|
|
375
|
+
if (!plaintext) {
|
|
376
|
+
throw new Error("Decryption failed \u2014 wrong key or corrupted data");
|
|
377
|
+
}
|
|
378
|
+
return JSON.parse(Buffer.from(plaintext).toString("utf-8"));
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// src/client.ts
|
|
238
383
|
var AuditClient = class {
|
|
239
384
|
agentId;
|
|
240
385
|
agentVersion;
|
|
@@ -243,11 +388,12 @@ var AuditClient = class {
|
|
|
243
388
|
_sessionId;
|
|
244
389
|
_secretKey;
|
|
245
390
|
_emit;
|
|
391
|
+
_tokenizer = null;
|
|
246
392
|
constructor(options) {
|
|
247
393
|
this.agentId = options.agentId;
|
|
248
394
|
this.agentVersion = options.agentVersion ?? "0.1.0";
|
|
249
395
|
this.humanOperatorId = options.humanOperatorId ?? "unknown";
|
|
250
|
-
this._sessionId = (0,
|
|
396
|
+
this._sessionId = (0, import_crypto4.randomUUID)();
|
|
251
397
|
if (options.signingKey) {
|
|
252
398
|
this._secretKey = new Uint8Array(Buffer.from(options.signingKey, "base64"));
|
|
253
399
|
} else {
|
|
@@ -272,10 +418,18 @@ var AuditClient = class {
|
|
|
272
418
|
} else {
|
|
273
419
|
this._emit = createFileEmitter(fallback);
|
|
274
420
|
}
|
|
421
|
+
if (options.piiHmacSecret && options.encryptionPublicKey) {
|
|
422
|
+
const hmacBytes = new Uint8Array(Buffer.from(options.piiHmacSecret, "base64"));
|
|
423
|
+
const pubBytes = new Uint8Array(Buffer.from(options.encryptionPublicKey, "base64"));
|
|
424
|
+
this._tokenizer = new SessionTokenizer(hmacBytes, pubBytes, options.piiFields ?? []);
|
|
425
|
+
}
|
|
275
426
|
}
|
|
276
427
|
get sessionId() {
|
|
277
428
|
return this._sessionId;
|
|
278
429
|
}
|
|
430
|
+
get tokenizer() {
|
|
431
|
+
return this._tokenizer;
|
|
432
|
+
}
|
|
279
433
|
/** Record an audit event. Never throws — errors are logged and swallowed. */
|
|
280
434
|
record(event) {
|
|
281
435
|
try {
|
|
@@ -299,9 +453,9 @@ var AuditClient = class {
|
|
|
299
453
|
};
|
|
300
454
|
|
|
301
455
|
// src/decorator.ts
|
|
302
|
-
var
|
|
303
|
-
var
|
|
304
|
-
var
|
|
456
|
+
var import_crypto5 = require("crypto");
|
|
457
|
+
var SECRET_PATTERN2 = /key|secret|token|password|passwd|pwd|credential|auth/i;
|
|
458
|
+
var PII_PATTERNS2 = [
|
|
305
459
|
[/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "[EMAIL_REDACTED]"],
|
|
306
460
|
[/\b\d{3}[-.\s]?\d{2}[-.\s]?\d{4}\b/g, "[SSN_REDACTED]"],
|
|
307
461
|
[/\b\d{3}[-.\s]?\d{3}[-.\s]?\d{3}\b/g, "[TFN_REDACTED]"],
|
|
@@ -310,7 +464,7 @@ var PII_PATTERNS = [
|
|
|
310
464
|
];
|
|
311
465
|
var RESPONSE_MAX_CHARS = 5e3;
|
|
312
466
|
function redactPii(text) {
|
|
313
|
-
for (const [pattern, replacement] of
|
|
467
|
+
for (const [pattern, replacement] of PII_PATTERNS2) {
|
|
314
468
|
pattern.lastIndex = 0;
|
|
315
469
|
text = text.replace(pattern, replacement);
|
|
316
470
|
}
|
|
@@ -319,7 +473,7 @@ function redactPii(text) {
|
|
|
319
473
|
function sanitise(params) {
|
|
320
474
|
const result = {};
|
|
321
475
|
for (const [k, v] of Object.entries(params)) {
|
|
322
|
-
if (
|
|
476
|
+
if (SECRET_PATTERN2.test(k)) {
|
|
323
477
|
result[k] = "[REDACTED]";
|
|
324
478
|
} else {
|
|
325
479
|
result[k] = redactPii(String(v).slice(0, 500));
|
|
@@ -328,7 +482,7 @@ function sanitise(params) {
|
|
|
328
482
|
return result;
|
|
329
483
|
}
|
|
330
484
|
function hashValue(value) {
|
|
331
|
-
return (0,
|
|
485
|
+
return (0, import_crypto5.createHash)("sha256").update(JSON.stringify(value, null, 0)).digest("hex");
|
|
332
486
|
}
|
|
333
487
|
function recordEvent(client, toolName, taskId, prompt, args, start, status, errorCode, result) {
|
|
334
488
|
const params = {};
|
|
@@ -336,24 +490,50 @@ function recordEvent(client, toolName, taskId, prompt, args, start, status, erro
|
|
|
336
490
|
params[`arg${i}`] = arg;
|
|
337
491
|
});
|
|
338
492
|
const outputsHash = result != null ? hashValue(result) : "";
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
493
|
+
const tokenizer = client.tokenizer;
|
|
494
|
+
if (tokenizer) {
|
|
495
|
+
const pseudonymizedParams = tokenizer.pseudonymizeParams(params);
|
|
496
|
+
const pseudonymizedPrompt = prompt ? tokenizer.pseudonymizeText(prompt) : "";
|
|
497
|
+
const response = result != null ? tokenizer.pseudonymizeText(String(result).slice(0, RESPONSE_MAX_CHARS)) : "";
|
|
498
|
+
const event = AuditEventSchema.parse({
|
|
499
|
+
agent_id: client.agentId,
|
|
500
|
+
agent_version: client.agentVersion,
|
|
501
|
+
human_operator_id: client.humanOperatorId,
|
|
502
|
+
task_id: taskId,
|
|
503
|
+
session_id: client.sessionId,
|
|
504
|
+
tool_name: toolName,
|
|
505
|
+
tool_parameters: pseudonymizedParams,
|
|
506
|
+
prompt: pseudonymizedPrompt,
|
|
507
|
+
inputs_hash: hashValue(args),
|
|
508
|
+
outputs_hash: outputsHash,
|
|
509
|
+
response,
|
|
510
|
+
status,
|
|
511
|
+
error_code: errorCode,
|
|
512
|
+
duration_ms: Math.round(performance.now() - start),
|
|
513
|
+
encrypted_token_map: tokenizer.encryptTokenMap(),
|
|
514
|
+
pseudonymization_version: "1.0"
|
|
515
|
+
});
|
|
516
|
+
client.record(event);
|
|
517
|
+
} else {
|
|
518
|
+
const response = result != null ? redactPii(String(result).slice(0, RESPONSE_MAX_CHARS)) : "";
|
|
519
|
+
const event = AuditEventSchema.parse({
|
|
520
|
+
agent_id: client.agentId,
|
|
521
|
+
agent_version: client.agentVersion,
|
|
522
|
+
human_operator_id: client.humanOperatorId,
|
|
523
|
+
task_id: taskId,
|
|
524
|
+
session_id: client.sessionId,
|
|
525
|
+
tool_name: toolName,
|
|
526
|
+
tool_parameters: sanitise(params),
|
|
527
|
+
prompt,
|
|
528
|
+
inputs_hash: hashValue(args),
|
|
529
|
+
outputs_hash: outputsHash,
|
|
530
|
+
response,
|
|
531
|
+
status,
|
|
532
|
+
error_code: errorCode,
|
|
533
|
+
duration_ms: Math.round(performance.now() - start)
|
|
534
|
+
});
|
|
535
|
+
client.record(event);
|
|
536
|
+
}
|
|
357
537
|
}
|
|
358
538
|
function wrapFn(client, fn, toolName, taskId, prompt) {
|
|
359
539
|
const wrapped = function(...args) {
|
|
@@ -399,6 +579,7 @@ function auditTool(client, fnOrTaskId, options) {
|
|
|
399
579
|
0 && (module.exports = {
|
|
400
580
|
AuditClient,
|
|
401
581
|
AuditEventSchema,
|
|
582
|
+
SessionTokenizer,
|
|
402
583
|
auditTool,
|
|
403
584
|
computeEventHash,
|
|
404
585
|
createFileEmitter,
|
package/dist/index.d.cts
CHANGED
|
@@ -28,6 +28,8 @@ declare const AuditEventSchema: z.ZodObject<{
|
|
|
28
28
|
retry_count: z.ZodDefault<z.ZodNumber>;
|
|
29
29
|
prev_event_hash: z.ZodDefault<z.ZodString>;
|
|
30
30
|
signature: z.ZodDefault<z.ZodString>;
|
|
31
|
+
encrypted_token_map: z.ZodDefault<z.ZodString>;
|
|
32
|
+
pseudonymization_version: z.ZodDefault<z.ZodString>;
|
|
31
33
|
}, "strip", z.ZodTypeAny, {
|
|
32
34
|
event_id: string;
|
|
33
35
|
agent_id: string;
|
|
@@ -56,6 +58,8 @@ declare const AuditEventSchema: z.ZodObject<{
|
|
|
56
58
|
retry_count: number;
|
|
57
59
|
prev_event_hash: string;
|
|
58
60
|
signature: string;
|
|
61
|
+
encrypted_token_map: string;
|
|
62
|
+
pseudonymization_version: string;
|
|
59
63
|
}, {
|
|
60
64
|
agent_id: string;
|
|
61
65
|
agent_version: string;
|
|
@@ -84,6 +88,8 @@ declare const AuditEventSchema: z.ZodObject<{
|
|
|
84
88
|
retry_count?: number | undefined;
|
|
85
89
|
prev_event_hash?: string | undefined;
|
|
86
90
|
signature?: string | undefined;
|
|
91
|
+
encrypted_token_map?: string | undefined;
|
|
92
|
+
pseudonymization_version?: string | undefined;
|
|
87
93
|
}>;
|
|
88
94
|
type AuditEvent = z.infer<typeof AuditEventSchema>;
|
|
89
95
|
|
|
@@ -106,6 +112,47 @@ type EmitFn = (eventDict: Record<string, unknown>) => void;
|
|
|
106
112
|
declare function createHttpEmitter(apiUrl: string, apiKey: string, fallbackPath: string): EmitFn;
|
|
107
113
|
declare function createFileEmitter(fallbackPath: string): EmitFn;
|
|
108
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Zero-knowledge PII pseudonymization with encrypted token map.
|
|
117
|
+
*
|
|
118
|
+
* Replaces PII with deterministic HMAC-based pseudonyms and encrypts the
|
|
119
|
+
* original-to-pseudonym mapping with the customer's X25519 public key.
|
|
120
|
+
* Wytness never sees the real PII — only the customer can decrypt the map.
|
|
121
|
+
*/
|
|
122
|
+
declare class SessionTokenizer {
|
|
123
|
+
private _hmacSecret;
|
|
124
|
+
private _encryptionPub;
|
|
125
|
+
private _piiFields;
|
|
126
|
+
private _tokenMap;
|
|
127
|
+
private _reverseMap;
|
|
128
|
+
/**
|
|
129
|
+
* @param hmacSecret 32-byte secret for deterministic pseudonyms.
|
|
130
|
+
* @param encryptionPublicKey 32-byte X25519 public key for encrypting the token map.
|
|
131
|
+
* @param piiFields Parameter names whose values should be fully pseudonymized.
|
|
132
|
+
*/
|
|
133
|
+
constructor(hmacSecret: Uint8Array, encryptionPublicKey: Uint8Array, piiFields?: string[]);
|
|
134
|
+
get tokenMap(): Record<string, string>;
|
|
135
|
+
private addMapping;
|
|
136
|
+
pseudonymizeValue(value: string, fieldPath?: string): string;
|
|
137
|
+
pseudonymizeText(text: string): string;
|
|
138
|
+
pseudonymizeParams(params: Record<string, unknown>): Record<string, unknown>;
|
|
139
|
+
/**
|
|
140
|
+
* Encrypt the token map with the customer's X25519 public key.
|
|
141
|
+
*
|
|
142
|
+
* Format: base64([ephemeral_pub 32B][nonce 24B][ciphertext])
|
|
143
|
+
* Uses NaCl box (X25519 + XSalsa20-Poly1305).
|
|
144
|
+
*/
|
|
145
|
+
encryptTokenMap(): string;
|
|
146
|
+
/**
|
|
147
|
+
* Decrypt a token map using the customer's X25519 private key.
|
|
148
|
+
*
|
|
149
|
+
* @param encryptedB64 Base64-encoded encrypted blob from encryptTokenMap().
|
|
150
|
+
* @param privateKey 32-byte X25519 private (secret) key.
|
|
151
|
+
* @returns The token map (pseudonym → original).
|
|
152
|
+
*/
|
|
153
|
+
static decryptTokenMap(encryptedB64: string, privateKey: Uint8Array): Record<string, string>;
|
|
154
|
+
}
|
|
155
|
+
|
|
109
156
|
interface AuditClientOptions {
|
|
110
157
|
agentId: string;
|
|
111
158
|
agentVersion?: string;
|
|
@@ -115,6 +162,9 @@ interface AuditClientOptions {
|
|
|
115
162
|
fallbackLogPath?: string;
|
|
116
163
|
httpApiKey?: string;
|
|
117
164
|
httpEndpoint?: string;
|
|
165
|
+
piiHmacSecret?: string;
|
|
166
|
+
encryptionPublicKey?: string;
|
|
167
|
+
piiFields?: string[];
|
|
118
168
|
}
|
|
119
169
|
declare class AuditClient {
|
|
120
170
|
readonly agentId: string;
|
|
@@ -124,8 +174,10 @@ declare class AuditClient {
|
|
|
124
174
|
private _sessionId;
|
|
125
175
|
private _secretKey;
|
|
126
176
|
private _emit;
|
|
177
|
+
private _tokenizer;
|
|
127
178
|
constructor(options: AuditClientOptions);
|
|
128
179
|
get sessionId(): string;
|
|
180
|
+
get tokenizer(): SessionTokenizer | null;
|
|
129
181
|
/** Record an audit event. Never throws — errors are logged and swallowed. */
|
|
130
182
|
record(event: AuditEvent): void;
|
|
131
183
|
/**
|
|
@@ -157,4 +209,4 @@ interface AuditToolOptions {
|
|
|
157
209
|
*/
|
|
158
210
|
declare function auditTool(client: AuditClient, fnOrTaskId?: ((...args: unknown[]) => unknown) | string, options?: AuditToolOptions | string): any;
|
|
159
211
|
|
|
160
|
-
export { AuditClient, type AuditClientOptions, type AuditEvent, AuditEventSchema, auditTool, computeEventHash, createFileEmitter, createHttpEmitter, generateKeypair, hashValue, redactPii, signEvent, verifyChain, verifyEvent };
|
|
212
|
+
export { AuditClient, type AuditClientOptions, type AuditEvent, AuditEventSchema, SessionTokenizer, auditTool, computeEventHash, createFileEmitter, createHttpEmitter, generateKeypair, hashValue, redactPii, signEvent, verifyChain, verifyEvent };
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ declare const AuditEventSchema: z.ZodObject<{
|
|
|
28
28
|
retry_count: z.ZodDefault<z.ZodNumber>;
|
|
29
29
|
prev_event_hash: z.ZodDefault<z.ZodString>;
|
|
30
30
|
signature: z.ZodDefault<z.ZodString>;
|
|
31
|
+
encrypted_token_map: z.ZodDefault<z.ZodString>;
|
|
32
|
+
pseudonymization_version: z.ZodDefault<z.ZodString>;
|
|
31
33
|
}, "strip", z.ZodTypeAny, {
|
|
32
34
|
event_id: string;
|
|
33
35
|
agent_id: string;
|
|
@@ -56,6 +58,8 @@ declare const AuditEventSchema: z.ZodObject<{
|
|
|
56
58
|
retry_count: number;
|
|
57
59
|
prev_event_hash: string;
|
|
58
60
|
signature: string;
|
|
61
|
+
encrypted_token_map: string;
|
|
62
|
+
pseudonymization_version: string;
|
|
59
63
|
}, {
|
|
60
64
|
agent_id: string;
|
|
61
65
|
agent_version: string;
|
|
@@ -84,6 +88,8 @@ declare const AuditEventSchema: z.ZodObject<{
|
|
|
84
88
|
retry_count?: number | undefined;
|
|
85
89
|
prev_event_hash?: string | undefined;
|
|
86
90
|
signature?: string | undefined;
|
|
91
|
+
encrypted_token_map?: string | undefined;
|
|
92
|
+
pseudonymization_version?: string | undefined;
|
|
87
93
|
}>;
|
|
88
94
|
type AuditEvent = z.infer<typeof AuditEventSchema>;
|
|
89
95
|
|
|
@@ -106,6 +112,47 @@ type EmitFn = (eventDict: Record<string, unknown>) => void;
|
|
|
106
112
|
declare function createHttpEmitter(apiUrl: string, apiKey: string, fallbackPath: string): EmitFn;
|
|
107
113
|
declare function createFileEmitter(fallbackPath: string): EmitFn;
|
|
108
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Zero-knowledge PII pseudonymization with encrypted token map.
|
|
117
|
+
*
|
|
118
|
+
* Replaces PII with deterministic HMAC-based pseudonyms and encrypts the
|
|
119
|
+
* original-to-pseudonym mapping with the customer's X25519 public key.
|
|
120
|
+
* Wytness never sees the real PII — only the customer can decrypt the map.
|
|
121
|
+
*/
|
|
122
|
+
declare class SessionTokenizer {
|
|
123
|
+
private _hmacSecret;
|
|
124
|
+
private _encryptionPub;
|
|
125
|
+
private _piiFields;
|
|
126
|
+
private _tokenMap;
|
|
127
|
+
private _reverseMap;
|
|
128
|
+
/**
|
|
129
|
+
* @param hmacSecret 32-byte secret for deterministic pseudonyms.
|
|
130
|
+
* @param encryptionPublicKey 32-byte X25519 public key for encrypting the token map.
|
|
131
|
+
* @param piiFields Parameter names whose values should be fully pseudonymized.
|
|
132
|
+
*/
|
|
133
|
+
constructor(hmacSecret: Uint8Array, encryptionPublicKey: Uint8Array, piiFields?: string[]);
|
|
134
|
+
get tokenMap(): Record<string, string>;
|
|
135
|
+
private addMapping;
|
|
136
|
+
pseudonymizeValue(value: string, fieldPath?: string): string;
|
|
137
|
+
pseudonymizeText(text: string): string;
|
|
138
|
+
pseudonymizeParams(params: Record<string, unknown>): Record<string, unknown>;
|
|
139
|
+
/**
|
|
140
|
+
* Encrypt the token map with the customer's X25519 public key.
|
|
141
|
+
*
|
|
142
|
+
* Format: base64([ephemeral_pub 32B][nonce 24B][ciphertext])
|
|
143
|
+
* Uses NaCl box (X25519 + XSalsa20-Poly1305).
|
|
144
|
+
*/
|
|
145
|
+
encryptTokenMap(): string;
|
|
146
|
+
/**
|
|
147
|
+
* Decrypt a token map using the customer's X25519 private key.
|
|
148
|
+
*
|
|
149
|
+
* @param encryptedB64 Base64-encoded encrypted blob from encryptTokenMap().
|
|
150
|
+
* @param privateKey 32-byte X25519 private (secret) key.
|
|
151
|
+
* @returns The token map (pseudonym → original).
|
|
152
|
+
*/
|
|
153
|
+
static decryptTokenMap(encryptedB64: string, privateKey: Uint8Array): Record<string, string>;
|
|
154
|
+
}
|
|
155
|
+
|
|
109
156
|
interface AuditClientOptions {
|
|
110
157
|
agentId: string;
|
|
111
158
|
agentVersion?: string;
|
|
@@ -115,6 +162,9 @@ interface AuditClientOptions {
|
|
|
115
162
|
fallbackLogPath?: string;
|
|
116
163
|
httpApiKey?: string;
|
|
117
164
|
httpEndpoint?: string;
|
|
165
|
+
piiHmacSecret?: string;
|
|
166
|
+
encryptionPublicKey?: string;
|
|
167
|
+
piiFields?: string[];
|
|
118
168
|
}
|
|
119
169
|
declare class AuditClient {
|
|
120
170
|
readonly agentId: string;
|
|
@@ -124,8 +174,10 @@ declare class AuditClient {
|
|
|
124
174
|
private _sessionId;
|
|
125
175
|
private _secretKey;
|
|
126
176
|
private _emit;
|
|
177
|
+
private _tokenizer;
|
|
127
178
|
constructor(options: AuditClientOptions);
|
|
128
179
|
get sessionId(): string;
|
|
180
|
+
get tokenizer(): SessionTokenizer | null;
|
|
129
181
|
/** Record an audit event. Never throws — errors are logged and swallowed. */
|
|
130
182
|
record(event: AuditEvent): void;
|
|
131
183
|
/**
|
|
@@ -157,4 +209,4 @@ interface AuditToolOptions {
|
|
|
157
209
|
*/
|
|
158
210
|
declare function auditTool(client: AuditClient, fnOrTaskId?: ((...args: unknown[]) => unknown) | string, options?: AuditToolOptions | string): any;
|
|
159
211
|
|
|
160
|
-
export { AuditClient, type AuditClientOptions, type AuditEvent, AuditEventSchema, auditTool, computeEventHash, createFileEmitter, createHttpEmitter, generateKeypair, hashValue, redactPii, signEvent, verifyChain, verifyEvent };
|
|
212
|
+
export { AuditClient, type AuditClientOptions, type AuditEvent, AuditEventSchema, SessionTokenizer, auditTool, computeEventHash, createFileEmitter, createHttpEmitter, generateKeypair, hashValue, redactPii, signEvent, verifyChain, verifyEvent };
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,10 @@ var AuditEventSchema = z.object({
|
|
|
34
34
|
retry_count: z.number().int().default(0),
|
|
35
35
|
// Chain integrity
|
|
36
36
|
prev_event_hash: z.string().default(""),
|
|
37
|
-
signature: z.string().default("")
|
|
37
|
+
signature: z.string().default(""),
|
|
38
|
+
// Pseudonymization
|
|
39
|
+
encrypted_token_map: z.string().default(""),
|
|
40
|
+
pseudonymization_version: z.string().default("")
|
|
38
41
|
});
|
|
39
42
|
|
|
40
43
|
// src/signing.ts
|
|
@@ -112,7 +115,7 @@ async function sendOne(apiUrl, apiKey, body) {
|
|
|
112
115
|
body,
|
|
113
116
|
signal: AbortSignal.timeout(1e4)
|
|
114
117
|
});
|
|
115
|
-
if (resp.status !== 201) {
|
|
118
|
+
if (resp.status !== 201 && resp.status !== 202) {
|
|
116
119
|
throw new Error(`HTTP ${resp.status}`);
|
|
117
120
|
}
|
|
118
121
|
return true;
|
|
@@ -188,6 +191,147 @@ async function flushPending() {
|
|
|
188
191
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
189
192
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
190
193
|
import { dirname as dirname2 } from "path";
|
|
194
|
+
|
|
195
|
+
// src/tokenizer.ts
|
|
196
|
+
import { createHmac } from "crypto";
|
|
197
|
+
import nacl2 from "tweetnacl";
|
|
198
|
+
import { encodeBase64 as encodeBase642, decodeBase64 as decodeBase642 } from "tweetnacl-util";
|
|
199
|
+
var SECRET_PATTERN = /key|secret|token|password|passwd|pwd|credential|auth/i;
|
|
200
|
+
var PII_PATTERNS = [
|
|
201
|
+
[/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "EMAIL"],
|
|
202
|
+
[/\b\d{3}[-.\s]?\d{2}[-.\s]?\d{4}\b/g, "SSN"],
|
|
203
|
+
[/\b\d{3}[-.\s]?\d{3}[-.\s]?\d{3}\b/g, "TFN"],
|
|
204
|
+
[/\b(?:\d{4}[-\s]?){3}\d{4}\b/g, "CARD"],
|
|
205
|
+
[/\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g, "PHONE"]
|
|
206
|
+
];
|
|
207
|
+
function hmacPseudonym(value, secret, piiType = "PII") {
|
|
208
|
+
const digest = createHmac("sha256", secret).update(value, "utf-8").digest("hex");
|
|
209
|
+
return `${piiType}_${digest.slice(0, 8)}`;
|
|
210
|
+
}
|
|
211
|
+
var SessionTokenizer = class {
|
|
212
|
+
_hmacSecret;
|
|
213
|
+
_encryptionPub;
|
|
214
|
+
_piiFields;
|
|
215
|
+
_tokenMap = {};
|
|
216
|
+
// pseudonym → original
|
|
217
|
+
_reverseMap = {};
|
|
218
|
+
// original → pseudonym
|
|
219
|
+
/**
|
|
220
|
+
* @param hmacSecret 32-byte secret for deterministic pseudonyms.
|
|
221
|
+
* @param encryptionPublicKey 32-byte X25519 public key for encrypting the token map.
|
|
222
|
+
* @param piiFields Parameter names whose values should be fully pseudonymized.
|
|
223
|
+
*/
|
|
224
|
+
constructor(hmacSecret, encryptionPublicKey, piiFields = []) {
|
|
225
|
+
this._hmacSecret = hmacSecret;
|
|
226
|
+
this._encryptionPub = encryptionPublicKey;
|
|
227
|
+
this._piiFields = piiFields;
|
|
228
|
+
}
|
|
229
|
+
get tokenMap() {
|
|
230
|
+
return { ...this._tokenMap };
|
|
231
|
+
}
|
|
232
|
+
addMapping(original, pseudonym) {
|
|
233
|
+
this._tokenMap[pseudonym] = original;
|
|
234
|
+
this._reverseMap[original] = pseudonym;
|
|
235
|
+
}
|
|
236
|
+
pseudonymizeValue(value, fieldPath = "") {
|
|
237
|
+
if (!value || !value.trim())
|
|
238
|
+
return value;
|
|
239
|
+
if (this._piiFields.includes(fieldPath)) {
|
|
240
|
+
if (this._reverseMap[value])
|
|
241
|
+
return this._reverseMap[value];
|
|
242
|
+
const pseudonym = hmacPseudonym(value, this._hmacSecret);
|
|
243
|
+
this.addMapping(value, pseudonym);
|
|
244
|
+
return pseudonym;
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
pseudonymizeText(text) {
|
|
249
|
+
if (!text)
|
|
250
|
+
return text;
|
|
251
|
+
const known = Object.keys(this._reverseMap).sort(
|
|
252
|
+
(a, b) => b.length - a.length
|
|
253
|
+
);
|
|
254
|
+
for (const original of known) {
|
|
255
|
+
if (text.includes(original)) {
|
|
256
|
+
text = text.split(original).join(this._reverseMap[original]);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const [pattern, piiType] of PII_PATTERNS) {
|
|
260
|
+
pattern.lastIndex = 0;
|
|
261
|
+
text = text.replace(pattern, (matched) => {
|
|
262
|
+
if (this._reverseMap[matched])
|
|
263
|
+
return this._reverseMap[matched];
|
|
264
|
+
const pseudonym = hmacPseudonym(matched, this._hmacSecret, piiType);
|
|
265
|
+
this.addMapping(matched, pseudonym);
|
|
266
|
+
return pseudonym;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return text;
|
|
270
|
+
}
|
|
271
|
+
pseudonymizeParams(params) {
|
|
272
|
+
const result = {};
|
|
273
|
+
for (const [k, v] of Object.entries(params)) {
|
|
274
|
+
if (SECRET_PATTERN.test(k)) {
|
|
275
|
+
result[k] = "[REDACTED]";
|
|
276
|
+
} else if (this._piiFields.includes(k)) {
|
|
277
|
+
const val = String(v).slice(0, 500);
|
|
278
|
+
result[k] = this.pseudonymizeValue(val, k);
|
|
279
|
+
} else {
|
|
280
|
+
const val = String(v).slice(0, 500);
|
|
281
|
+
result[k] = this.pseudonymizeText(val);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Encrypt the token map with the customer's X25519 public key.
|
|
288
|
+
*
|
|
289
|
+
* Format: base64([ephemeral_pub 32B][nonce 24B][ciphertext])
|
|
290
|
+
* Uses NaCl box (X25519 + XSalsa20-Poly1305).
|
|
291
|
+
*/
|
|
292
|
+
encryptTokenMap() {
|
|
293
|
+
const plaintext = Buffer.from(
|
|
294
|
+
JSON.stringify(this._tokenMap),
|
|
295
|
+
"utf-8"
|
|
296
|
+
);
|
|
297
|
+
const ephemeral = nacl2.box.keyPair();
|
|
298
|
+
const nonce = nacl2.randomBytes(24);
|
|
299
|
+
const ciphertext = nacl2.box(
|
|
300
|
+
plaintext,
|
|
301
|
+
nonce,
|
|
302
|
+
this._encryptionPub,
|
|
303
|
+
ephemeral.secretKey
|
|
304
|
+
);
|
|
305
|
+
if (!ciphertext) {
|
|
306
|
+
throw new Error("Encryption failed");
|
|
307
|
+
}
|
|
308
|
+
const packed = new Uint8Array(32 + 24 + ciphertext.length);
|
|
309
|
+
packed.set(ephemeral.publicKey, 0);
|
|
310
|
+
packed.set(nonce, 32);
|
|
311
|
+
packed.set(ciphertext, 56);
|
|
312
|
+
return encodeBase642(packed);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Decrypt a token map using the customer's X25519 private key.
|
|
316
|
+
*
|
|
317
|
+
* @param encryptedB64 Base64-encoded encrypted blob from encryptTokenMap().
|
|
318
|
+
* @param privateKey 32-byte X25519 private (secret) key.
|
|
319
|
+
* @returns The token map (pseudonym → original).
|
|
320
|
+
*/
|
|
321
|
+
static decryptTokenMap(encryptedB64, privateKey) {
|
|
322
|
+
const packed = decodeBase642(encryptedB64);
|
|
323
|
+
const ephemeralPub = packed.slice(0, 32);
|
|
324
|
+
const nonce = packed.slice(32, 56);
|
|
325
|
+
const ciphertext = packed.slice(56);
|
|
326
|
+
const plaintext = nacl2.box.open(ciphertext, nonce, ephemeralPub, privateKey);
|
|
327
|
+
if (!plaintext) {
|
|
328
|
+
throw new Error("Decryption failed \u2014 wrong key or corrupted data");
|
|
329
|
+
}
|
|
330
|
+
return JSON.parse(Buffer.from(plaintext).toString("utf-8"));
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// src/client.ts
|
|
191
335
|
var AuditClient = class {
|
|
192
336
|
agentId;
|
|
193
337
|
agentVersion;
|
|
@@ -196,6 +340,7 @@ var AuditClient = class {
|
|
|
196
340
|
_sessionId;
|
|
197
341
|
_secretKey;
|
|
198
342
|
_emit;
|
|
343
|
+
_tokenizer = null;
|
|
199
344
|
constructor(options) {
|
|
200
345
|
this.agentId = options.agentId;
|
|
201
346
|
this.agentVersion = options.agentVersion ?? "0.1.0";
|
|
@@ -225,10 +370,18 @@ var AuditClient = class {
|
|
|
225
370
|
} else {
|
|
226
371
|
this._emit = createFileEmitter(fallback);
|
|
227
372
|
}
|
|
373
|
+
if (options.piiHmacSecret && options.encryptionPublicKey) {
|
|
374
|
+
const hmacBytes = new Uint8Array(Buffer.from(options.piiHmacSecret, "base64"));
|
|
375
|
+
const pubBytes = new Uint8Array(Buffer.from(options.encryptionPublicKey, "base64"));
|
|
376
|
+
this._tokenizer = new SessionTokenizer(hmacBytes, pubBytes, options.piiFields ?? []);
|
|
377
|
+
}
|
|
228
378
|
}
|
|
229
379
|
get sessionId() {
|
|
230
380
|
return this._sessionId;
|
|
231
381
|
}
|
|
382
|
+
get tokenizer() {
|
|
383
|
+
return this._tokenizer;
|
|
384
|
+
}
|
|
232
385
|
/** Record an audit event. Never throws — errors are logged and swallowed. */
|
|
233
386
|
record(event) {
|
|
234
387
|
try {
|
|
@@ -252,9 +405,9 @@ var AuditClient = class {
|
|
|
252
405
|
};
|
|
253
406
|
|
|
254
407
|
// src/decorator.ts
|
|
255
|
-
import { createHash as
|
|
256
|
-
var
|
|
257
|
-
var
|
|
408
|
+
import { createHash as createHash3 } from "crypto";
|
|
409
|
+
var SECRET_PATTERN2 = /key|secret|token|password|passwd|pwd|credential|auth/i;
|
|
410
|
+
var PII_PATTERNS2 = [
|
|
258
411
|
[/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "[EMAIL_REDACTED]"],
|
|
259
412
|
[/\b\d{3}[-.\s]?\d{2}[-.\s]?\d{4}\b/g, "[SSN_REDACTED]"],
|
|
260
413
|
[/\b\d{3}[-.\s]?\d{3}[-.\s]?\d{3}\b/g, "[TFN_REDACTED]"],
|
|
@@ -263,7 +416,7 @@ var PII_PATTERNS = [
|
|
|
263
416
|
];
|
|
264
417
|
var RESPONSE_MAX_CHARS = 5e3;
|
|
265
418
|
function redactPii(text) {
|
|
266
|
-
for (const [pattern, replacement] of
|
|
419
|
+
for (const [pattern, replacement] of PII_PATTERNS2) {
|
|
267
420
|
pattern.lastIndex = 0;
|
|
268
421
|
text = text.replace(pattern, replacement);
|
|
269
422
|
}
|
|
@@ -272,7 +425,7 @@ function redactPii(text) {
|
|
|
272
425
|
function sanitise(params) {
|
|
273
426
|
const result = {};
|
|
274
427
|
for (const [k, v] of Object.entries(params)) {
|
|
275
|
-
if (
|
|
428
|
+
if (SECRET_PATTERN2.test(k)) {
|
|
276
429
|
result[k] = "[REDACTED]";
|
|
277
430
|
} else {
|
|
278
431
|
result[k] = redactPii(String(v).slice(0, 500));
|
|
@@ -281,7 +434,7 @@ function sanitise(params) {
|
|
|
281
434
|
return result;
|
|
282
435
|
}
|
|
283
436
|
function hashValue(value) {
|
|
284
|
-
return
|
|
437
|
+
return createHash3("sha256").update(JSON.stringify(value, null, 0)).digest("hex");
|
|
285
438
|
}
|
|
286
439
|
function recordEvent(client, toolName, taskId, prompt, args, start, status, errorCode, result) {
|
|
287
440
|
const params = {};
|
|
@@ -289,24 +442,50 @@ function recordEvent(client, toolName, taskId, prompt, args, start, status, erro
|
|
|
289
442
|
params[`arg${i}`] = arg;
|
|
290
443
|
});
|
|
291
444
|
const outputsHash = result != null ? hashValue(result) : "";
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
445
|
+
const tokenizer = client.tokenizer;
|
|
446
|
+
if (tokenizer) {
|
|
447
|
+
const pseudonymizedParams = tokenizer.pseudonymizeParams(params);
|
|
448
|
+
const pseudonymizedPrompt = prompt ? tokenizer.pseudonymizeText(prompt) : "";
|
|
449
|
+
const response = result != null ? tokenizer.pseudonymizeText(String(result).slice(0, RESPONSE_MAX_CHARS)) : "";
|
|
450
|
+
const event = AuditEventSchema.parse({
|
|
451
|
+
agent_id: client.agentId,
|
|
452
|
+
agent_version: client.agentVersion,
|
|
453
|
+
human_operator_id: client.humanOperatorId,
|
|
454
|
+
task_id: taskId,
|
|
455
|
+
session_id: client.sessionId,
|
|
456
|
+
tool_name: toolName,
|
|
457
|
+
tool_parameters: pseudonymizedParams,
|
|
458
|
+
prompt: pseudonymizedPrompt,
|
|
459
|
+
inputs_hash: hashValue(args),
|
|
460
|
+
outputs_hash: outputsHash,
|
|
461
|
+
response,
|
|
462
|
+
status,
|
|
463
|
+
error_code: errorCode,
|
|
464
|
+
duration_ms: Math.round(performance.now() - start),
|
|
465
|
+
encrypted_token_map: tokenizer.encryptTokenMap(),
|
|
466
|
+
pseudonymization_version: "1.0"
|
|
467
|
+
});
|
|
468
|
+
client.record(event);
|
|
469
|
+
} else {
|
|
470
|
+
const response = result != null ? redactPii(String(result).slice(0, RESPONSE_MAX_CHARS)) : "";
|
|
471
|
+
const event = AuditEventSchema.parse({
|
|
472
|
+
agent_id: client.agentId,
|
|
473
|
+
agent_version: client.agentVersion,
|
|
474
|
+
human_operator_id: client.humanOperatorId,
|
|
475
|
+
task_id: taskId,
|
|
476
|
+
session_id: client.sessionId,
|
|
477
|
+
tool_name: toolName,
|
|
478
|
+
tool_parameters: sanitise(params),
|
|
479
|
+
prompt,
|
|
480
|
+
inputs_hash: hashValue(args),
|
|
481
|
+
outputs_hash: outputsHash,
|
|
482
|
+
response,
|
|
483
|
+
status,
|
|
484
|
+
error_code: errorCode,
|
|
485
|
+
duration_ms: Math.round(performance.now() - start)
|
|
486
|
+
});
|
|
487
|
+
client.record(event);
|
|
488
|
+
}
|
|
310
489
|
}
|
|
311
490
|
function wrapFn(client, fn, toolName, taskId, prompt) {
|
|
312
491
|
const wrapped = function(...args) {
|
|
@@ -351,6 +530,7 @@ function auditTool(client, fnOrTaskId, options) {
|
|
|
351
530
|
export {
|
|
352
531
|
AuditClient,
|
|
353
532
|
AuditEventSchema,
|
|
533
|
+
SessionTokenizer,
|
|
354
534
|
auditTool,
|
|
355
535
|
computeEventHash,
|
|
356
536
|
createFileEmitter,
|
package/package.json
CHANGED