@kya-os/mcp-i 1.11.1 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,430 +0,0 @@
1
- /**
2
- * Audit Logging System for MCP-I Runtime
3
- *
4
- * TODO: Update AuditLogger to implement IAuditLogger from @kya-os/mcp-i-core/runtime/audit-logger.
5
- * Currently using Node.js-specific implementation. This will allow the interface to be used
6
- * across all platform implementations while maintaining platform-specific crypto implementations.
7
- *
8
- * Handles audit record generation and logging with frozen format
9
- * according to requirements 5.4, 5.5, and 6.7 (configurable retention).
10
- *
11
- * ## Privacy Guarantees
12
- *
13
- * This system is designed with privacy-first principles:
14
- *
15
- * **NEVER Logged:**
16
- * - Request/response bodies (only SHA-256 hashes)
17
- * - Secrets (private keys, API keys, tokens, nonces)
18
- * - PII (names, emails, addresses, phone numbers)
19
- * - Key material (only key IDs are logged)
20
- *
21
- * **ALWAYS Logged:**
22
- * - Metadata only (DIDs, key IDs, timestamps)
23
- * - SHA-256 hashes (not plaintext data)
24
- * - Session IDs (for correlation, not sensitive)
25
- * - Verification results (yes/no only)
26
- * - Scope identifiers (application-level)
27
- *
28
- * ## Frozen Format
29
- *
30
- * The audit.v1 format is **frozen** and will not change:
31
- *
32
- * ```
33
- * audit.v1 ts=<unix> session=<id> audience=<host> did=<did> kid=<kid> reqHash=<sha256:..> resHash=<sha256:..> verified=yes|no scope=<scopeId|->
34
- * ```
35
- *
36
- * ## Event-Driven Rotation
37
- *
38
- * This implementation uses **event-driven rotation** (not timers):
39
- * - Rotation checks happen on each `logAuditRecord()` call
40
- * - No setInterval/setTimeout (works in Cloudflare Workers)
41
- * - No cleanup needed (no timers to clear)
42
- * - Predictable behavior (rotation on activity)
43
- *
44
- * @module audit
45
- * @see {@link https://github.com/kya-os/xmcp-i/docs/concepts/audit-logging.md | Audit Logging Documentation}
46
- */
47
- import type { AuditRecord } from "@kya-os/mcp/types" with { "resolution-mode": "import" };
48
- import { SessionContext } from "@kya-os/contracts/handshake";
49
- import { AgentIdentity } from "./identity";
50
- /**
51
- * Audit log rotation strategy
52
- */
53
- export type AuditRotationStrategy = "size" | "time" | "count" | "custom";
54
- /**
55
- * Audit rotation context passed to hooks
56
- */
57
- export interface AuditRotationContext {
58
- strategy: AuditRotationStrategy;
59
- trigger: string;
60
- recordsLogged: number;
61
- timestamp: number;
62
- }
63
- /**
64
- * Audit log rotation hooks
65
- */
66
- export interface AuditRotationHooks {
67
- /**
68
- * Called when audit log should be rotated
69
- * @param context - Rotation context with metadata
70
- * @returns Promise that resolves when rotation is complete
71
- */
72
- onRotation?: (context: AuditRotationContext) => Promise<void>;
73
- /**
74
- * Called when audit log reaches size limit
75
- * @param sizeBytes - Current size in bytes
76
- * @param limit - Size limit that was exceeded
77
- */
78
- onSizeLimit?: (sizeBytes: number, limit: number) => Promise<void>;
79
- /**
80
- * Called on time-based rotation (e.g., daily, hourly)
81
- * @param interval - Rotation interval that triggered (e.g., "daily", "hourly")
82
- */
83
- onTimeBased?: (interval: string) => Promise<void>;
84
- /**
85
- * Called when record count reaches threshold
86
- * @param count - Number of records logged
87
- * @param threshold - Count threshold that was exceeded
88
- */
89
- onCountThreshold?: (count: number, threshold: number) => Promise<void>;
90
- }
91
- /**
92
- * Audit logging configuration
93
- *
94
- * @example Basic configuration
95
- * ```typescript
96
- * const config: AuditConfig = {
97
- * enabled: true,
98
- * logFunction: console.log,
99
- * includePayloads: false, // ALWAYS false for privacy
100
- * };
101
- * ```
102
- *
103
- * @example With rotation
104
- * ```typescript
105
- * const config: AuditConfig = {
106
- * enabled: true,
107
- * rotation: {
108
- * strategy: 'size',
109
- * sizeLimit: 10 * 1024 * 1024, // 10 MB
110
- * hooks: {
111
- * onRotation: async (context) => {
112
- * // Archive old log file
113
- * },
114
- * },
115
- * },
116
- * };
117
- * ```
118
- */
119
- export interface AuditConfig {
120
- /**
121
- * Enable audit logging (default: true)
122
- */
123
- enabled?: boolean;
124
- /**
125
- * Custom log function (default: console.log)
126
- *
127
- * @param record - The formatted audit line (frozen format)
128
- *
129
- * @example File-based logging
130
- * ```typescript
131
- * logFunction: (record) => {
132
- * fs.appendFileSync('/var/log/audit.log', record + '\n');
133
- * }
134
- * ```
135
- */
136
- logFunction?: (record: string) => void;
137
- /**
138
- * Include payloads in logs (default: false)
139
- *
140
- * **WARNING:** This should ALWAYS be false. The frozen format ensures
141
- * that even if set to true, no request/response bodies will be logged.
142
- * This flag exists for compatibility but has no effect on the frozen format.
143
- *
144
- * @deprecated This flag has no effect due to frozen format privacy guarantees
145
- */
146
- includePayloads?: boolean;
147
- /**
148
- * Log rotation configuration (event-driven, no timers)
149
- */
150
- rotation?: {
151
- /**
152
- * Rotation strategy
153
- * - 'size': Rotate when log reaches size limit (checked on each call)
154
- * - 'time': Rotate based on elapsed time (checked on each call)
155
- * - 'count': Rotate after N records
156
- * - 'custom': Custom rotation logic via hooks only
157
- */
158
- strategy?: AuditRotationStrategy;
159
- /**
160
- * Size limit in bytes (for size-based rotation)
161
- *
162
- * @example 10 MB limit
163
- * ```typescript
164
- * sizeLimit: 10 * 1024 * 1024
165
- * ```
166
- */
167
- sizeLimit?: number;
168
- /**
169
- * Time interval in milliseconds (for time-based rotation)
170
- * Rotation is checked on each logAuditRecord() call, not via timer.
171
- *
172
- * @example Daily rotation
173
- * ```typescript
174
- * timeInterval: 24 * 60 * 60 * 1000
175
- * ```
176
- */
177
- timeInterval?: number;
178
- /**
179
- * Record count threshold (for count-based rotation)
180
- *
181
- * @example Rotate after 10k records
182
- * ```typescript
183
- * countThreshold: 10000
184
- * ```
185
- */
186
- countThreshold?: number;
187
- /**
188
- * Custom rotation hooks
189
- */
190
- hooks?: AuditRotationHooks;
191
- };
192
- }
193
- /**
194
- * Audit context for logging
195
- *
196
- * Contains all metadata needed to generate an audit record.
197
- *
198
- * **Privacy Note:** Only metadata is extracted from these objects.
199
- * The identity's private key, session's nonce, and other sensitive
200
- * fields are NEVER included in the audit log.
201
- *
202
- * @example
203
- * ```typescript
204
- * const context: AuditContext = {
205
- * identity: {
206
- * did: 'did:web:example.com:agents:agent-1',
207
- * keyId: 'key-20241014-abc123',
208
- * // ... private key NOT logged
209
- * },
210
- * session: {
211
- * sessionId: 'sess_abc123',
212
- * audience: 'example.com',
213
- * // ... nonce NOT logged
214
- * },
215
- * requestHash: 'sha256:1234567890abcdef...',
216
- * responseHash: 'sha256:fedcba0987654321...',
217
- * verified: 'yes',
218
- * scopeId: 'orders.create',
219
- * };
220
- * ```
221
- */
222
- export interface AuditContext {
223
- /**
224
- * Agent identity
225
- *
226
- * Only `did` and `keyId` are logged. Private key is NEVER logged.
227
- */
228
- identity: AgentIdentity;
229
- /**
230
- * Session context
231
- *
232
- * Only `sessionId` and `audience` are logged. Nonce is NEVER logged.
233
- */
234
- session: SessionContext;
235
- /**
236
- * Request hash (SHA-256 with `sha256:` prefix)
237
- *
238
- * @example 'sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
239
- */
240
- requestHash: string;
241
- /**
242
- * Response hash (SHA-256 with `sha256:` prefix)
243
- *
244
- * @example 'sha256:fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321'
245
- */
246
- responseHash: string;
247
- /**
248
- * Verification result
249
- *
250
- * - 'yes': Proof was verified successfully
251
- * - 'no': Proof verification failed
252
- */
253
- verified: "yes" | "no";
254
- /**
255
- * Optional scope identifier
256
- *
257
- * Application-level scope (e.g., 'orders.create', 'users.read').
258
- * If not provided, '-' is used in the audit log.
259
- *
260
- * @example 'orders.create'
261
- */
262
- scopeId?: string;
263
- }
264
- /**
265
- * Event context for logging events that bypass session deduplication
266
- *
267
- * Used for consent events where multiple events occur in the same session.
268
- * Unlike AuditContext, this allows multiple events per session.
269
- */
270
- export interface AuditEventContext {
271
- /**
272
- * Event type identifier
273
- *
274
- * @example "consent:page_viewed", "consent:approved", "runtime:initialized"
275
- */
276
- eventType: string;
277
- /**
278
- * Agent identity
279
- *
280
- * Only `did` and `keyId` are logged. Private key is NEVER logged.
281
- */
282
- identity: AgentIdentity;
283
- /**
284
- * Session context
285
- *
286
- * Only `sessionId` and `audience` are logged. Nonce is NEVER logged.
287
- */
288
- session: SessionContext;
289
- /**
290
- * Optional event-specific data
291
- *
292
- * Used for generating event hash. Not logged directly.
293
- */
294
- eventData?: Record<string, any>;
295
- }
296
- /**
297
- * Audit logger class with event-driven rotation support
298
- *
299
- * Privacy Guarantees:
300
- * - NEVER logs request/response bodies (only SHA-256 hashes)
301
- * - NEVER logs secrets (private keys, API keys, tokens, nonces)
302
- * - NEVER logs PII (personal information)
303
- * - Only logs metadata: DIDs, key IDs, timestamps, hashes, session IDs
304
- * - Frozen format: audit.v1 with fixed field order
305
- *
306
- * Rotation Strategy:
307
- * - Event-driven (no timers) - rotation checks on each logAuditRecord() call
308
- * - Works in all environments (Node.js, Cloudflare Workers, Vercel Edge)
309
- * - No cleanup needed (no timers to clear)
310
- */
311
- export declare class AuditLogger {
312
- private config;
313
- private sessionAuditLog;
314
- private totalRecordsLogged;
315
- private currentLogSize;
316
- private lastRotationTime;
317
- private destroyed;
318
- constructor(config?: AuditConfig);
319
- /**
320
- * Emit audit record on first call per session
321
- * Requirements: 5.4, 5.5
322
- *
323
- * This method:
324
- * 1. Checks if logger is destroyed
325
- * 2. Logs audit record (first call per session)
326
- * 3. Checks if rotation is needed (event-driven)
327
- * 4. Triggers rotation hooks if threshold met
328
- */
329
- logAuditRecord(context: AuditContext): Promise<void>;
330
- /**
331
- * Log an event using the frozen audit format WITHOUT session deduplication.
332
- *
333
- * Unlike logAuditRecord(), this method ALWAYS logs the event, regardless
334
- * of whether an event has already been logged for this session. This is
335
- * necessary for consent events where multiple events occur in the same session.
336
- *
337
- * The event still uses the frozen audit.v1 format for consistency, but
338
- * bypasses the "once per session" constraint.
339
- *
340
- * @param context - Event context including eventType, identity, session, and eventData
341
- */
342
- logEvent(context: AuditEventContext): Promise<void>;
343
- /**
344
- * Generate deterministic hash for event
345
- */
346
- private hashEvent;
347
- /**
348
- * Format audit record as frozen audit line
349
- * Format: audit.v1 ts=<unix> session=<id> audience=<host> did=<did> kid=<kid> reqHash=<sha256:..> resHash=<sha256:..> verified=yes|no scope=<scopeId|->
350
- */
351
- private formatAuditLine;
352
- /**
353
- * Check if rotation is needed and trigger if necessary (event-driven)
354
- *
355
- * This method is called on each logAuditRecord() call.
356
- * No timers are used - rotation is checked based on current state.
357
- */
358
- private checkRotation;
359
- /**
360
- * Rotate audit log now (manually triggered)
361
- *
362
- * This method:
363
- * 1. Calls onRotation hook (for user to archive/rotate log file)
364
- * 2. Resets rotation counters (size, time, count)
365
- *
366
- * @param trigger - Reason for rotation (e.g., "manual", "size-limit")
367
- */
368
- rotateNow(trigger?: string): Promise<void>;
369
- /**
370
- * Destroy audit logger and cleanup resources
371
- *
372
- * This method:
373
- * 1. Clears session tracking (memory)
374
- * 2. Resets statistics
375
- * 3. Marks logger as destroyed
376
- *
377
- * Note: No timers to clear (event-driven rotation)
378
- */
379
- destroy(): void;
380
- /**
381
- * Clear session audit log (useful for testing)
382
- */
383
- clearSessionLog(): void;
384
- /**
385
- * Get audit statistics
386
- */
387
- getStats(): {
388
- enabled: boolean;
389
- sessionsLogged: number;
390
- includePayloads: boolean;
391
- totalRecordsLogged: number;
392
- currentLogSize: number;
393
- lastRotationTime: number;
394
- };
395
- /**
396
- * Update configuration
397
- */
398
- updateConfig(config: Partial<AuditConfig>): void;
399
- }
400
- /**
401
- * Key rotation audit logging
402
- */
403
- export interface KeyRotationAuditContext {
404
- identity: AgentIdentity;
405
- oldKeyId: string;
406
- newKeyId: string;
407
- mode: "dev" | "prod";
408
- delegated: "yes" | "no";
409
- force: "yes" | "no";
410
- }
411
- /**
412
- * Log key rotation audit record
413
- * Format: keys.rotate.v1 ts=<unix> did=<did> oldKid=<kid> newKid=<kid> mode=dev|prod delegated=yes|no force=yes|no
414
- */
415
- export declare function logKeyRotationAudit(context: KeyRotationAuditContext, logFunction?: (record: string) => void): void;
416
- /**
417
- * Default audit logger instance
418
- */
419
- export declare const defaultAuditLogger: AuditLogger;
420
- /**
421
- * Utility functions
422
- */
423
- /**
424
- * Parse audit line back to record (for testing/analysis)
425
- */
426
- export declare function parseAuditLine(line: string): AuditRecord | null;
427
- /**
428
- * Validate audit record format
429
- */
430
- export declare function validateAuditRecord(record: any): record is AuditRecord;