@wytness/sdk 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,9 +30,11 @@ await sendEmail("team@company.com", "Report", "Weekly summary");
30
30
 
31
31
  ## Features
32
32
 
33
+ - **Response capture** — AI agent responses are automatically captured, truncated to 5K chars, and PII-redacted
34
+ - **PII redaction** — emails, SSN, TFN, credit cards, and phone numbers are automatically redacted from responses and parameter values
33
35
  - **Cryptographic signing** — every event is signed with Ed25519 (tweetnacl)
34
36
  - **Hash chaining** — tamper-evident chain of events per agent session
35
- - **Automatic redaction** — secrets in parameters are automatically redacted
37
+ - **Automatic secret redaction** — secrets in parameter names are automatically redacted
36
38
  - **HTTP transport** — stream events to api.wytness.dev over HTTPS
37
39
  - **File fallback** — events are saved locally if the API is unreachable
38
40
 
@@ -66,7 +68,7 @@ const client = new AuditClient({
66
68
 
67
69
  ### `auditTool(client, fn, options)`
68
70
 
69
- Wraps a function to automatically log audit events. Captures function name, parameters, execution duration, success/failure status, cryptographic signature, and hash chain link.
71
+ Wraps a function to automatically log audit events. Captures function name, parameters, **response text** (truncated to 5K chars, PII-redacted), execution duration, success/failure status, cryptographic signature, and hash chain link.
70
72
 
71
73
  ### `client.sessionId`
72
74
 
@@ -114,6 +116,7 @@ import {
114
116
  AuditClientOptions,
115
117
  auditTool,
116
118
  hashValue,
119
+ redactPii,
117
120
  AuditEvent,
118
121
  AuditEventSchema,
119
122
  generateKeypair,
package/dist/index.cjs CHANGED
@@ -38,6 +38,7 @@ __export(src_exports, {
38
38
  createHttpEmitter: () => createHttpEmitter,
39
39
  generateKeypair: () => generateKeypair,
40
40
  hashValue: () => hashValue,
41
+ redactPii: () => redactPii,
41
42
  signEvent: () => signEvent,
42
43
  verifyChain: () => verifyChain,
43
44
  verifyEvent: () => verifyEvent
@@ -72,6 +73,7 @@ var AuditEventSchema = import_zod.z.object({
72
73
  inputs_source: import_zod.z.string().default(""),
73
74
  outputs_hash: import_zod.z.string().default(""),
74
75
  outputs_destination: import_zod.z.string().default(""),
76
+ response: import_zod.z.string().default(""),
75
77
  // Layer 5: Outcome
76
78
  status: import_zod.z.string().default("success"),
77
79
  error_code: import_zod.z.string().nullable().default(null),
@@ -299,21 +301,42 @@ var AuditClient = class {
299
301
  // src/decorator.ts
300
302
  var import_crypto4 = require("crypto");
301
303
  var SECRET_PATTERN = /key|secret|token|password|passwd|pwd|credential|auth/i;
304
+ var PII_PATTERNS = [
305
+ [/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "[EMAIL_REDACTED]"],
306
+ [/\b\d{3}[-.\s]?\d{2}[-.\s]?\d{4}\b/g, "[SSN_REDACTED]"],
307
+ [/\b\d{3}[-.\s]?\d{3}[-.\s]?\d{3}\b/g, "[TFN_REDACTED]"],
308
+ [/\b(?:\d{4}[-\s]?){3}\d{4}\b/g, "[CARD_REDACTED]"],
309
+ [/\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g, "[PHONE_REDACTED]"]
310
+ ];
311
+ var RESPONSE_MAX_CHARS = 5e3;
312
+ function redactPii(text) {
313
+ for (const [pattern, replacement] of PII_PATTERNS) {
314
+ pattern.lastIndex = 0;
315
+ text = text.replace(pattern, replacement);
316
+ }
317
+ return text;
318
+ }
302
319
  function sanitise(params) {
303
320
  const result = {};
304
321
  for (const [k, v] of Object.entries(params)) {
305
- result[k] = SECRET_PATTERN.test(k) ? "[REDACTED]" : String(v).slice(0, 500);
322
+ if (SECRET_PATTERN.test(k)) {
323
+ result[k] = "[REDACTED]";
324
+ } else {
325
+ result[k] = redactPii(String(v).slice(0, 500));
326
+ }
306
327
  }
307
328
  return result;
308
329
  }
309
330
  function hashValue(value) {
310
331
  return (0, import_crypto4.createHash)("sha256").update(JSON.stringify(value, null, 0)).digest("hex");
311
332
  }
312
- function recordEvent(client, toolName, taskId, args, start, status, errorCode, result) {
333
+ function recordEvent(client, toolName, taskId, prompt, args, start, status, errorCode, result) {
313
334
  const params = {};
314
335
  args.forEach((arg, i) => {
315
336
  params[`arg${i}`] = arg;
316
337
  });
338
+ const outputsHash = result != null ? hashValue(result) : "";
339
+ const response = result != null ? redactPii(String(result).slice(0, RESPONSE_MAX_CHARS)) : "";
317
340
  const event = AuditEventSchema.parse({
318
341
  agent_id: client.agentId,
319
342
  agent_version: client.agentVersion,
@@ -322,34 +345,36 @@ function recordEvent(client, toolName, taskId, args, start, status, errorCode, r
322
345
  session_id: client.sessionId,
323
346
  tool_name: toolName,
324
347
  tool_parameters: sanitise(params),
348
+ prompt,
325
349
  inputs_hash: hashValue(args),
326
- outputs_hash: result != null ? hashValue(result) : "",
350
+ outputs_hash: outputsHash,
351
+ response,
327
352
  status,
328
353
  error_code: errorCode,
329
354
  duration_ms: Math.round(performance.now() - start)
330
355
  });
331
356
  client.record(event);
332
357
  }
333
- function wrapFn(client, fn, toolName, taskId) {
358
+ function wrapFn(client, fn, toolName, taskId, prompt) {
334
359
  const wrapped = function(...args) {
335
360
  const start = performance.now();
336
361
  let result;
337
362
  try {
338
363
  result = fn.apply(this, args);
339
364
  } catch (e) {
340
- recordEvent(client, toolName, taskId, args, start, "failure", e.constructor.name, null);
365
+ recordEvent(client, toolName, taskId, prompt, args, start, "failure", e.constructor.name, null);
341
366
  throw e;
342
367
  }
343
368
  if (result && typeof result.then === "function") {
344
369
  return result.then((resolved) => {
345
- recordEvent(client, toolName, taskId, args, start, "success", null, resolved);
370
+ recordEvent(client, toolName, taskId, prompt, args, start, "success", null, resolved);
346
371
  return resolved;
347
372
  }).catch((e) => {
348
- recordEvent(client, toolName, taskId, args, start, "failure", e.constructor.name, null);
373
+ recordEvent(client, toolName, taskId, prompt, args, start, "failure", e.constructor.name, null);
349
374
  throw e;
350
375
  });
351
376
  }
352
- recordEvent(client, toolName, taskId, args, start, "success", null, result);
377
+ recordEvent(client, toolName, taskId, prompt, args, start, "success", null, result);
353
378
  return result;
354
379
  };
355
380
  Object.defineProperty(wrapped, "name", { value: toolName });
@@ -361,12 +386,13 @@ function auditTool(client, fnOrTaskId, options) {
361
386
  const opts = typeof options === "object" ? options : {};
362
387
  const toolName = opts.toolName ?? fn.name ?? "anonymous";
363
388
  const taskId2 = opts.taskId ?? "default";
364
- return wrapFn(client, fn, toolName, taskId2);
389
+ const prompt = opts.prompt ?? "";
390
+ return wrapFn(client, fn, toolName, taskId2, prompt);
365
391
  }
366
392
  const taskId = typeof fnOrTaskId === "string" ? fnOrTaskId : "default";
367
393
  return function(fn, fnName) {
368
394
  const toolName = fnName ?? fn.name ?? "anonymous";
369
- return wrapFn(client, fn, toolName, taskId);
395
+ return wrapFn(client, fn, toolName, taskId, "");
370
396
  };
371
397
  }
372
398
  // Annotate the CommonJS export names for ESM import in node:
@@ -379,6 +405,7 @@ function auditTool(client, fnOrTaskId, options) {
379
405
  createHttpEmitter,
380
406
  generateKeypair,
381
407
  hashValue,
408
+ redactPii,
382
409
  signEvent,
383
410
  verifyChain,
384
411
  verifyEvent
package/dist/index.d.cts CHANGED
@@ -21,6 +21,7 @@ declare const AuditEventSchema: z.ZodObject<{
21
21
  inputs_source: z.ZodDefault<z.ZodString>;
22
22
  outputs_hash: z.ZodDefault<z.ZodString>;
23
23
  outputs_destination: z.ZodDefault<z.ZodString>;
24
+ response: z.ZodDefault<z.ZodString>;
24
25
  status: z.ZodDefault<z.ZodString>;
25
26
  error_code: z.ZodDefault<z.ZodNullable<z.ZodString>>;
26
27
  duration_ms: z.ZodDefault<z.ZodNumber>;
@@ -49,6 +50,7 @@ declare const AuditEventSchema: z.ZodObject<{
49
50
  inputs_source: string;
50
51
  outputs_hash: string;
51
52
  outputs_destination: string;
53
+ response: string;
52
54
  error_code: string | null;
53
55
  duration_ms: number;
54
56
  retry_count: number;
@@ -76,6 +78,7 @@ declare const AuditEventSchema: z.ZodObject<{
76
78
  inputs_source?: string | undefined;
77
79
  outputs_hash?: string | undefined;
78
80
  outputs_destination?: string | undefined;
81
+ response?: string | undefined;
79
82
  error_code?: string | null | undefined;
80
83
  duration_ms?: number | undefined;
81
84
  retry_count?: number | undefined;
@@ -133,10 +136,12 @@ declare class AuditClient {
133
136
  flush(): Promise<void>;
134
137
  }
135
138
 
139
+ declare function redactPii(text: string): string;
136
140
  declare function hashValue(value: unknown): string;
137
141
  interface AuditToolOptions {
138
142
  toolName?: string;
139
143
  taskId?: string;
144
+ prompt?: string;
140
145
  }
141
146
  /**
142
147
  * Wrap a function with audit logging. Works with both sync and async functions.
@@ -152,4 +157,4 @@ interface AuditToolOptions {
152
157
  */
153
158
  declare function auditTool(client: AuditClient, fnOrTaskId?: ((...args: unknown[]) => unknown) | string, options?: AuditToolOptions | string): any;
154
159
 
155
- export { AuditClient, type AuditClientOptions, type AuditEvent, AuditEventSchema, auditTool, computeEventHash, createFileEmitter, createHttpEmitter, generateKeypair, hashValue, signEvent, verifyChain, verifyEvent };
160
+ export { AuditClient, type AuditClientOptions, type AuditEvent, AuditEventSchema, auditTool, computeEventHash, createFileEmitter, createHttpEmitter, generateKeypair, hashValue, redactPii, signEvent, verifyChain, verifyEvent };
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ declare const AuditEventSchema: z.ZodObject<{
21
21
  inputs_source: z.ZodDefault<z.ZodString>;
22
22
  outputs_hash: z.ZodDefault<z.ZodString>;
23
23
  outputs_destination: z.ZodDefault<z.ZodString>;
24
+ response: z.ZodDefault<z.ZodString>;
24
25
  status: z.ZodDefault<z.ZodString>;
25
26
  error_code: z.ZodDefault<z.ZodNullable<z.ZodString>>;
26
27
  duration_ms: z.ZodDefault<z.ZodNumber>;
@@ -49,6 +50,7 @@ declare const AuditEventSchema: z.ZodObject<{
49
50
  inputs_source: string;
50
51
  outputs_hash: string;
51
52
  outputs_destination: string;
53
+ response: string;
52
54
  error_code: string | null;
53
55
  duration_ms: number;
54
56
  retry_count: number;
@@ -76,6 +78,7 @@ declare const AuditEventSchema: z.ZodObject<{
76
78
  inputs_source?: string | undefined;
77
79
  outputs_hash?: string | undefined;
78
80
  outputs_destination?: string | undefined;
81
+ response?: string | undefined;
79
82
  error_code?: string | null | undefined;
80
83
  duration_ms?: number | undefined;
81
84
  retry_count?: number | undefined;
@@ -133,10 +136,12 @@ declare class AuditClient {
133
136
  flush(): Promise<void>;
134
137
  }
135
138
 
139
+ declare function redactPii(text: string): string;
136
140
  declare function hashValue(value: unknown): string;
137
141
  interface AuditToolOptions {
138
142
  toolName?: string;
139
143
  taskId?: string;
144
+ prompt?: string;
140
145
  }
141
146
  /**
142
147
  * Wrap a function with audit logging. Works with both sync and async functions.
@@ -152,4 +157,4 @@ interface AuditToolOptions {
152
157
  */
153
158
  declare function auditTool(client: AuditClient, fnOrTaskId?: ((...args: unknown[]) => unknown) | string, options?: AuditToolOptions | string): any;
154
159
 
155
- export { AuditClient, type AuditClientOptions, type AuditEvent, AuditEventSchema, auditTool, computeEventHash, createFileEmitter, createHttpEmitter, generateKeypair, hashValue, signEvent, verifyChain, verifyEvent };
160
+ export { AuditClient, type AuditClientOptions, type AuditEvent, AuditEventSchema, auditTool, computeEventHash, createFileEmitter, createHttpEmitter, generateKeypair, hashValue, redactPii, signEvent, verifyChain, verifyEvent };
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ var AuditEventSchema = z.object({
26
26
  inputs_source: z.string().default(""),
27
27
  outputs_hash: z.string().default(""),
28
28
  outputs_destination: z.string().default(""),
29
+ response: z.string().default(""),
29
30
  // Layer 5: Outcome
30
31
  status: z.string().default("success"),
31
32
  error_code: z.string().nullable().default(null),
@@ -253,21 +254,42 @@ var AuditClient = class {
253
254
  // src/decorator.ts
254
255
  import { createHash as createHash2 } from "crypto";
255
256
  var SECRET_PATTERN = /key|secret|token|password|passwd|pwd|credential|auth/i;
257
+ var PII_PATTERNS = [
258
+ [/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "[EMAIL_REDACTED]"],
259
+ [/\b\d{3}[-.\s]?\d{2}[-.\s]?\d{4}\b/g, "[SSN_REDACTED]"],
260
+ [/\b\d{3}[-.\s]?\d{3}[-.\s]?\d{3}\b/g, "[TFN_REDACTED]"],
261
+ [/\b(?:\d{4}[-\s]?){3}\d{4}\b/g, "[CARD_REDACTED]"],
262
+ [/\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g, "[PHONE_REDACTED]"]
263
+ ];
264
+ var RESPONSE_MAX_CHARS = 5e3;
265
+ function redactPii(text) {
266
+ for (const [pattern, replacement] of PII_PATTERNS) {
267
+ pattern.lastIndex = 0;
268
+ text = text.replace(pattern, replacement);
269
+ }
270
+ return text;
271
+ }
256
272
  function sanitise(params) {
257
273
  const result = {};
258
274
  for (const [k, v] of Object.entries(params)) {
259
- result[k] = SECRET_PATTERN.test(k) ? "[REDACTED]" : String(v).slice(0, 500);
275
+ if (SECRET_PATTERN.test(k)) {
276
+ result[k] = "[REDACTED]";
277
+ } else {
278
+ result[k] = redactPii(String(v).slice(0, 500));
279
+ }
260
280
  }
261
281
  return result;
262
282
  }
263
283
  function hashValue(value) {
264
284
  return createHash2("sha256").update(JSON.stringify(value, null, 0)).digest("hex");
265
285
  }
266
- function recordEvent(client, toolName, taskId, args, start, status, errorCode, result) {
286
+ function recordEvent(client, toolName, taskId, prompt, args, start, status, errorCode, result) {
267
287
  const params = {};
268
288
  args.forEach((arg, i) => {
269
289
  params[`arg${i}`] = arg;
270
290
  });
291
+ const outputsHash = result != null ? hashValue(result) : "";
292
+ const response = result != null ? redactPii(String(result).slice(0, RESPONSE_MAX_CHARS)) : "";
271
293
  const event = AuditEventSchema.parse({
272
294
  agent_id: client.agentId,
273
295
  agent_version: client.agentVersion,
@@ -276,34 +298,36 @@ function recordEvent(client, toolName, taskId, args, start, status, errorCode, r
276
298
  session_id: client.sessionId,
277
299
  tool_name: toolName,
278
300
  tool_parameters: sanitise(params),
301
+ prompt,
279
302
  inputs_hash: hashValue(args),
280
- outputs_hash: result != null ? hashValue(result) : "",
303
+ outputs_hash: outputsHash,
304
+ response,
281
305
  status,
282
306
  error_code: errorCode,
283
307
  duration_ms: Math.round(performance.now() - start)
284
308
  });
285
309
  client.record(event);
286
310
  }
287
- function wrapFn(client, fn, toolName, taskId) {
311
+ function wrapFn(client, fn, toolName, taskId, prompt) {
288
312
  const wrapped = function(...args) {
289
313
  const start = performance.now();
290
314
  let result;
291
315
  try {
292
316
  result = fn.apply(this, args);
293
317
  } catch (e) {
294
- recordEvent(client, toolName, taskId, args, start, "failure", e.constructor.name, null);
318
+ recordEvent(client, toolName, taskId, prompt, args, start, "failure", e.constructor.name, null);
295
319
  throw e;
296
320
  }
297
321
  if (result && typeof result.then === "function") {
298
322
  return result.then((resolved) => {
299
- recordEvent(client, toolName, taskId, args, start, "success", null, resolved);
323
+ recordEvent(client, toolName, taskId, prompt, args, start, "success", null, resolved);
300
324
  return resolved;
301
325
  }).catch((e) => {
302
- recordEvent(client, toolName, taskId, args, start, "failure", e.constructor.name, null);
326
+ recordEvent(client, toolName, taskId, prompt, args, start, "failure", e.constructor.name, null);
303
327
  throw e;
304
328
  });
305
329
  }
306
- recordEvent(client, toolName, taskId, args, start, "success", null, result);
330
+ recordEvent(client, toolName, taskId, prompt, args, start, "success", null, result);
307
331
  return result;
308
332
  };
309
333
  Object.defineProperty(wrapped, "name", { value: toolName });
@@ -315,12 +339,13 @@ function auditTool(client, fnOrTaskId, options) {
315
339
  const opts = typeof options === "object" ? options : {};
316
340
  const toolName = opts.toolName ?? fn.name ?? "anonymous";
317
341
  const taskId2 = opts.taskId ?? "default";
318
- return wrapFn(client, fn, toolName, taskId2);
342
+ const prompt = opts.prompt ?? "";
343
+ return wrapFn(client, fn, toolName, taskId2, prompt);
319
344
  }
320
345
  const taskId = typeof fnOrTaskId === "string" ? fnOrTaskId : "default";
321
346
  return function(fn, fnName) {
322
347
  const toolName = fnName ?? fn.name ?? "anonymous";
323
- return wrapFn(client, fn, toolName, taskId);
348
+ return wrapFn(client, fn, toolName, taskId, "");
324
349
  };
325
350
  }
326
351
  export {
@@ -332,6 +357,7 @@ export {
332
357
  createHttpEmitter,
333
358
  generateKeypair,
334
359
  hashValue,
360
+ redactPii,
335
361
  signEvent,
336
362
  verifyChain,
337
363
  verifyEvent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wytness/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript SDK for Wytness — audit logging for AI agents with cryptographic signing and chain integrity",
5
5
  "license": "MIT",
6
6
  "author": "Wytness <hello@wytness.dev>",