ai-shield-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/audit/logger.d.ts +40 -0
  2. package/dist/audit/logger.d.ts.map +1 -0
  3. package/dist/audit/logger.js +100 -0
  4. package/dist/audit/logger.js.map +1 -0
  5. package/dist/audit/types.d.ts +12 -0
  6. package/dist/audit/types.d.ts.map +1 -0
  7. package/dist/audit/types.js +3 -0
  8. package/dist/audit/types.js.map +1 -0
  9. package/dist/cache/lru.d.ts +27 -0
  10. package/dist/cache/lru.d.ts.map +1 -0
  11. package/dist/cache/lru.js +74 -0
  12. package/dist/cache/lru.js.map +1 -0
  13. package/dist/cost/anomaly.d.ts +10 -0
  14. package/dist/cost/anomaly.d.ts.map +1 -0
  15. package/dist/cost/anomaly.js +42 -0
  16. package/dist/cost/anomaly.js.map +1 -0
  17. package/dist/cost/pricing.d.ts +7 -0
  18. package/dist/cost/pricing.d.ts.map +1 -0
  19. package/dist/cost/pricing.js +51 -0
  20. package/dist/cost/pricing.js.map +1 -0
  21. package/dist/cost/tracker.d.ts +24 -0
  22. package/dist/cost/tracker.d.ts.map +1 -0
  23. package/dist/cost/tracker.js +136 -0
  24. package/dist/cost/tracker.js.map +1 -0
  25. package/dist/index.d.ts +18 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +59 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/policy/engine.d.ts +36 -0
  30. package/dist/policy/engine.d.ts.map +1 -0
  31. package/dist/policy/engine.js +127 -0
  32. package/dist/policy/engine.js.map +1 -0
  33. package/dist/policy/tools.d.ts +25 -0
  34. package/dist/policy/tools.d.ts.map +1 -0
  35. package/dist/policy/tools.js +158 -0
  36. package/dist/policy/tools.js.map +1 -0
  37. package/dist/scanner/canary.d.ts +9 -0
  38. package/dist/scanner/canary.d.ts.map +1 -0
  39. package/dist/scanner/canary.js +19 -0
  40. package/dist/scanner/canary.js.map +1 -0
  41. package/dist/scanner/chain.d.ts +17 -0
  42. package/dist/scanner/chain.d.ts.map +1 -0
  43. package/dist/scanner/chain.js +69 -0
  44. package/dist/scanner/chain.js.map +1 -0
  45. package/dist/scanner/heuristic.d.ts +28 -0
  46. package/dist/scanner/heuristic.d.ts.map +1 -0
  47. package/dist/scanner/heuristic.js +375 -0
  48. package/dist/scanner/heuristic.js.map +1 -0
  49. package/dist/scanner/pii.d.ts +17 -0
  50. package/dist/scanner/pii.d.ts.map +1 -0
  51. package/dist/scanner/pii.js +255 -0
  52. package/dist/scanner/pii.js.map +1 -0
  53. package/dist/shield.d.ts +31 -0
  54. package/dist/shield.d.ts.map +1 -0
  55. package/dist/shield.js +184 -0
  56. package/dist/shield.js.map +1 -0
  57. package/dist/types.d.ts +182 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +6 -0
  60. package/dist/types.js.map +1 -0
  61. package/package.json +27 -0
  62. package/src/audit/logger.ts +135 -0
  63. package/src/audit/schema.sql +51 -0
  64. package/src/audit/types.ts +16 -0
  65. package/src/cache/lru.ts +93 -0
  66. package/src/cost/anomaly.ts +57 -0
  67. package/src/cost/pricing.ts +58 -0
  68. package/src/cost/tracker.ts +182 -0
  69. package/src/index.ts +91 -0
  70. package/src/policy/engine.ts +163 -0
  71. package/src/policy/tools.ts +189 -0
  72. package/src/scanner/canary.ts +30 -0
  73. package/src/scanner/chain.ts +88 -0
  74. package/src/scanner/heuristic.ts +427 -0
  75. package/src/scanner/pii.ts +313 -0
  76. package/src/shield.ts +228 -0
  77. package/src/types.ts +242 -0
  78. package/tsconfig.json +8 -0
@@ -0,0 +1,313 @@
1
+ import type {
2
+ Scanner,
3
+ ScannerResult,
4
+ ScanContext,
5
+ Violation,
6
+ PIIEntity,
7
+ PIIType,
8
+ PIIAction,
9
+ PIIConfig,
10
+ } from "../types.js";
11
+
12
+ // ============================================================
13
+ // PII Scanner — German/EU-first PII Detection
14
+ // Supports: IBAN, Steuernr, Kreditkarte, Email, Phone, IP
15
+ // Modes: block / mask / tokenize
16
+ // ============================================================
17
+
18
+ interface PIIPattern {
19
+ type: PIIType;
20
+ pattern: RegExp;
21
+ validator?: (value: string) => boolean;
22
+ baseConfidence: number;
23
+ }
24
+
25
+ // --- German & International PII Patterns ---
26
+ const PII_PATTERNS: PIIPattern[] = [
27
+ // IBAN: DE + 2 check digits + 18 digits (with optional spaces/dashes)
28
+ {
29
+ type: "iban",
30
+ pattern: /\b[A-Z]{2}\s?\d{2}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\s?\d{2,4}\b/g,
31
+ validator: validateIBAN,
32
+ baseConfidence: 0.95,
33
+ },
34
+
35
+ // Credit card: 4 groups of 4 digits (Luhn-validated)
36
+ {
37
+ type: "credit_card",
38
+ pattern: /\b(?:\d{4}[\s-]?){3}\d{4}\b/g,
39
+ validator: validateLuhn,
40
+ baseConfidence: 0.95,
41
+ },
42
+
43
+ // German tax ID (Steuerliche Identifikationsnummer): 11 digits
44
+ {
45
+ type: "german_tax_id",
46
+ pattern: /\b\d{2}\s?\d{3}\s?\d{3}\s?\d{3}\b/g,
47
+ validator: validateGermanTaxId,
48
+ baseConfidence: 0.70,
49
+ },
50
+
51
+ // German social security number: 2 digits + 6 digits + letter + 3 digits
52
+ {
53
+ type: "german_social_security",
54
+ pattern: /\b\d{2}\s?\d{6}\s?[A-Z]\s?\d{3}\b/g,
55
+ baseConfidence: 0.75,
56
+ },
57
+
58
+ // Email
59
+ {
60
+ type: "email",
61
+ pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
62
+ baseConfidence: 0.95,
63
+ },
64
+
65
+ // Phone: German formats (+49, 0xxx) and international
66
+ {
67
+ type: "phone",
68
+ pattern:
69
+ /(?<!\d)(?:\+\d{1,3}|00\d{1,3}|0)\s?[\s\-/]?\(?\d{2,5}\)?[\s\-/]?\d{3,8}[\s\-/]?\d{0,5}\b/g,
70
+ validator: validatePhone,
71
+ baseConfidence: 0.80,
72
+ },
73
+
74
+ // IP addresses (v4)
75
+ {
76
+ type: "ip_address",
77
+ pattern:
78
+ /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g,
79
+ validator: validateIPNotPrivate,
80
+ baseConfidence: 0.85,
81
+ },
82
+
83
+ // URLs with embedded credentials
84
+ {
85
+ type: "url_with_credentials",
86
+ pattern: /https?:\/\/[^:\s]+:[^@\s]+@[^\s]+/g,
87
+ baseConfidence: 0.95,
88
+ },
89
+ ];
90
+
91
+ // --- Validators ---
92
+
93
+ function validateIBAN(raw: string): boolean {
94
+ const cleaned = raw.replace(/\s|-/g, "");
95
+ if (cleaned.length < 15 || cleaned.length > 34) return false;
96
+
97
+ // Move first 4 chars to end, convert letters to numbers
98
+ const rearranged = cleaned.substring(4) + cleaned.substring(0, 4);
99
+ const numeric = rearranged.replace(/[A-Z]/g, (c) =>
100
+ String(c.charCodeAt(0) - 55),
101
+ );
102
+
103
+ // Modulo 97 check (handle large numbers via chunking)
104
+ let remainder = numeric;
105
+ while (remainder.length > 2) {
106
+ const chunk = remainder.substring(0, 9);
107
+ remainder =
108
+ String(parseInt(chunk, 10) % 97) + remainder.substring(chunk.length);
109
+ }
110
+ return parseInt(remainder, 10) % 97 === 1;
111
+ }
112
+
113
+ function validateLuhn(raw: string): boolean {
114
+ const digits = raw.replace(/\D/g, "");
115
+ if (digits.length < 13 || digits.length > 19) return false;
116
+
117
+ let sum = 0;
118
+ for (let i = digits.length - 1; i >= 0; i--) {
119
+ let digit = parseInt(digits[i]!, 10);
120
+ if ((digits.length - i) % 2 === 0) {
121
+ digit *= 2;
122
+ if (digit > 9) digit -= 9;
123
+ }
124
+ sum += digit;
125
+ }
126
+ return sum % 10 === 0;
127
+ }
128
+
129
+ function validateGermanTaxId(raw: string): boolean {
130
+ const digits = raw.replace(/\s/g, "");
131
+ if (digits.length !== 11) return false;
132
+ if (!/^\d+$/.test(digits)) return false;
133
+ // First digit cannot be 0
134
+ if (digits[0] === "0") return false;
135
+ return true;
136
+ }
137
+
138
+ function validatePhone(raw: string): boolean {
139
+ const digits = raw.replace(/\D/g, "");
140
+ // Must be at least 7 digits, max 15
141
+ return digits.length >= 7 && digits.length <= 15;
142
+ }
143
+
144
+ function validateIPNotPrivate(raw: string): boolean {
145
+ const parts = raw.split(".").map(Number);
146
+ // Skip private/loopback ranges (not really PII)
147
+ if (parts[0] === 10) return false;
148
+ if (parts[0] === 172 && parts[1]! >= 16 && parts[1]! <= 31) return false;
149
+ if (parts[0] === 192 && parts[1] === 168) return false;
150
+ if (parts[0] === 127) return false;
151
+ return true;
152
+ }
153
+
154
+ // --- Masking ---
155
+
156
+ function maskValue(type: PIIType, value: string): string {
157
+ switch (type) {
158
+ case "email": {
159
+ const atIdx = value.indexOf("@");
160
+ if (atIdx <= 1) return "[EMAIL]";
161
+ return value[0] + "***@" + value.substring(atIdx + 1);
162
+ }
163
+ case "phone":
164
+ return value.substring(0, 4) + "****" + value.substring(value.length - 2);
165
+ case "iban":
166
+ return value.substring(0, 4) + " **** **** ****";
167
+ case "credit_card":
168
+ return "**** **** **** " + value.replace(/\D/g, "").substring(12);
169
+ default:
170
+ return `[${type.toUpperCase()}]`;
171
+ }
172
+ }
173
+
174
+ // --- PII Scanner Class ---
175
+
176
+ export class PIIScanner implements Scanner {
177
+ readonly name = "pii";
178
+ private patterns: PIIPattern[];
179
+ private action: PIIAction;
180
+ private typeOverrides: Partial<Record<PIIType, PIIAction>>;
181
+ private allowedTypes: Set<PIIType>;
182
+
183
+ constructor(config: PIIConfig = {}) {
184
+ this.patterns = PII_PATTERNS;
185
+ this.action = config.action ?? "mask";
186
+ this.typeOverrides = config.types ?? {};
187
+ this.allowedTypes = new Set(config.allowedTypes ?? []);
188
+ }
189
+
190
+ async scan(input: string, _context: ScanContext): Promise<ScannerResult> {
191
+ const start = performance.now();
192
+ const entities = this.detect(input);
193
+ const violations: Violation[] = [];
194
+
195
+ // Filter out allowed types
196
+ const activeEntities = entities.filter(
197
+ (e) => !this.allowedTypes.has(e.type),
198
+ );
199
+
200
+ if (activeEntities.length === 0) {
201
+ return {
202
+ decision: "allow",
203
+ violations: [],
204
+ sanitized: input,
205
+ durationMs: performance.now() - start,
206
+ };
207
+ }
208
+
209
+ // Build violations
210
+ let shouldBlock = false;
211
+ for (const entity of activeEntities) {
212
+ const action = this.typeOverrides[entity.type] ?? this.action;
213
+ if (action === "block") shouldBlock = true;
214
+
215
+ violations.push({
216
+ type: "pii_detected",
217
+ scanner: this.name,
218
+ score: entity.confidence,
219
+ threshold: 0,
220
+ message: `${entity.type} detected`,
221
+ detail: `Found ${entity.type} at position ${entity.start}-${entity.end} (action: ${action})`,
222
+ });
223
+ }
224
+
225
+ // Apply masking if needed
226
+ let sanitized = input;
227
+ const effectiveAction = shouldBlock ? "block" : this.action;
228
+ if (effectiveAction === "mask" || effectiveAction === "tokenize") {
229
+ sanitized = this.applyMasking(input, activeEntities);
230
+ }
231
+
232
+ const decision = shouldBlock ? "block" : "warn";
233
+
234
+ return {
235
+ decision,
236
+ violations,
237
+ sanitized,
238
+ durationMs: performance.now() - start,
239
+ };
240
+ }
241
+
242
+ /** Detect all PII entities in text */
243
+ detect(text: string): PIIEntity[] {
244
+ const raw: PIIEntity[] = [];
245
+
246
+ for (const piiPattern of this.patterns) {
247
+ // Create fresh regex for each scan (stateful with /g flag)
248
+ const regex = new RegExp(piiPattern.pattern.source, piiPattern.pattern.flags);
249
+ let match: RegExpExecArray | null;
250
+
251
+ while ((match = regex.exec(text)) !== null) {
252
+ const value = match[0];
253
+ const cleaned = value.replace(/[\s-]/g, "");
254
+
255
+ // Run validator if present
256
+ if (piiPattern.validator && !piiPattern.validator(cleaned)) {
257
+ continue;
258
+ }
259
+
260
+ raw.push({
261
+ type: piiPattern.type,
262
+ value,
263
+ start: match.index,
264
+ end: match.index + value.length,
265
+ confidence: piiPattern.baseConfidence,
266
+ });
267
+ }
268
+ }
269
+
270
+ return this.deduplicateOverlaps(raw);
271
+ }
272
+
273
+ /** Remove overlapping detections — keep the more specific (higher confidence) match */
274
+ private deduplicateOverlaps(entities: PIIEntity[]): PIIEntity[] {
275
+ if (entities.length <= 1) return entities;
276
+
277
+ // Sort by start position, then by span length descending (longer = more specific)
278
+ const sorted = [...entities].sort((a, b) =>
279
+ a.start !== b.start ? a.start - b.start : (b.end - b.start) - (a.end - a.start),
280
+ );
281
+
282
+ const kept: PIIEntity[] = [];
283
+ for (const entity of sorted) {
284
+ // Check if this entity overlaps with any already-kept entity
285
+ const overlaps = kept.some(
286
+ (k) => entity.start < k.end && entity.end > k.start,
287
+ );
288
+ if (!overlaps) {
289
+ kept.push(entity);
290
+ }
291
+ // If it overlaps, the already-kept entity wins (it appeared first in pattern order = more specific)
292
+ }
293
+
294
+ return kept;
295
+ }
296
+
297
+ /** Mask detected PII in text */
298
+ private applyMasking(text: string, entities: PIIEntity[]): string {
299
+ // Sort by position descending to preserve offsets
300
+ const sorted = [...entities].sort((a, b) => b.start - a.start);
301
+ let masked = text;
302
+
303
+ for (const entity of sorted) {
304
+ const replacement = maskValue(entity.type, entity.value);
305
+ masked =
306
+ masked.substring(0, entity.start) +
307
+ replacement +
308
+ masked.substring(entity.end);
309
+ }
310
+
311
+ return masked;
312
+ }
313
+ }
package/src/shield.ts ADDED
@@ -0,0 +1,228 @@
1
+ import type { ShieldConfig, ScanResult, ScanContext, ToolPolicy } from "./types.js";
2
+ import { ScannerChain } from "./scanner/chain.js";
3
+ import { HeuristicScanner } from "./scanner/heuristic.js";
4
+ import { PIIScanner } from "./scanner/pii.js";
5
+ import { ToolPolicyScanner } from "./policy/tools.js";
6
+ import { PolicyEngine } from "./policy/engine.js";
7
+ import { CostTracker } from "./cost/tracker.js";
8
+ import { AuditLogger, ConsoleAuditStore } from "./audit/logger.js";
9
+ import type { AuditStore } from "./audit/types.js";
10
+ import { ScanLRUCache } from "./cache/lru.js";
11
+
12
+ // ============================================================
13
+ // AIShield — Main class, single entry point
14
+ // ============================================================
15
+
16
+ export class AIShield {
17
+ private chain: ScannerChain;
18
+ private policyEngine: PolicyEngine;
19
+ private costTracker: CostTracker | null;
20
+ private auditLogger: AuditLogger | null;
21
+ private scanCache: ScanLRUCache<ScanResult> | null;
22
+ private config: ShieldConfig;
23
+
24
+ constructor(config: ShieldConfig = {}) {
25
+ this.config = config;
26
+ this.policyEngine = new PolicyEngine(config.preset ?? "public_website");
27
+ this.chain = new ScannerChain({ earlyExit: true });
28
+
29
+ // Build scanner chain based on config
30
+ this.setupScanners(config);
31
+
32
+ // Cost tracker (optional, needs Redis for distributed use)
33
+ this.costTracker = config.cost?.enabled !== false && config.cost?.budgets
34
+ ? new CostTracker(config.cost.budgets)
35
+ : null;
36
+
37
+ // Audit logger (optional)
38
+ this.auditLogger = this.setupAudit(config);
39
+
40
+ // Scan cache (enabled when cache config is provided)
41
+ this.scanCache = config.cache && config.cache.enabled !== false
42
+ ? new ScanLRUCache<ScanResult>({
43
+ maxSize: config.cache.maxSize,
44
+ ttlMs: config.cache.ttlMs,
45
+ })
46
+ : null;
47
+ }
48
+
49
+ /** Scan input text — the main API */
50
+ async scan(input: string, context: ScanContext = {}): Promise<ScanResult> {
51
+ // Apply preset if not set in context
52
+ if (!context.preset) {
53
+ context.preset = this.config.preset ?? "public_website";
54
+ }
55
+
56
+ // Check cache
57
+ if (this.scanCache) {
58
+ const cacheKey = this.buildCacheKey(input, context);
59
+ const cached = this.scanCache.get(cacheKey);
60
+ if (cached) {
61
+ return { ...cached, meta: { ...cached.meta, cached: true } };
62
+ }
63
+ }
64
+
65
+ const result = await this.chain.run(input, context);
66
+
67
+ // Store in cache
68
+ if (this.scanCache) {
69
+ const cacheKey = this.buildCacheKey(input, context);
70
+ this.scanCache.set(cacheKey, result);
71
+ }
72
+
73
+ // Log to audit if enabled
74
+ if (this.auditLogger) {
75
+ void this.auditLogger.log(input, result, context);
76
+ }
77
+
78
+ return result;
79
+ }
80
+
81
+ /** Check cost budget before making an LLM call */
82
+ async checkBudget(
83
+ entityId: string,
84
+ model: string,
85
+ estimatedInputTokens: number,
86
+ estimatedOutputTokens?: number,
87
+ ) {
88
+ if (!this.costTracker) {
89
+ return { allowed: true, currentSpend: 0, remainingBudget: Infinity };
90
+ }
91
+ return this.costTracker.checkBudget(
92
+ entityId,
93
+ model,
94
+ estimatedInputTokens,
95
+ estimatedOutputTokens,
96
+ );
97
+ }
98
+
99
+ /** Record cost after receiving LLM response */
100
+ async recordCost(
101
+ entityId: string,
102
+ model: string,
103
+ inputTokens: number,
104
+ outputTokens: number,
105
+ ) {
106
+ if (!this.costTracker) return null;
107
+ return this.costTracker.recordCost(entityId, model, inputTokens, outputTokens);
108
+ }
109
+
110
+ /** Get current spend for an entity */
111
+ async getCurrentSpend(entityId: string): Promise<number> {
112
+ if (!this.costTracker) return 0;
113
+ return this.costTracker.getCurrentSpend(entityId);
114
+ }
115
+
116
+ /** Get the policy engine */
117
+ getPolicy(): PolicyEngine {
118
+ return this.policyEngine;
119
+ }
120
+
121
+ /** Clear the scan cache */
122
+ clearCache(): void {
123
+ this.scanCache?.clear();
124
+ }
125
+
126
+ /** Get cache stats */
127
+ get cacheSize(): number {
128
+ return this.scanCache?.size ?? 0;
129
+ }
130
+
131
+ /** Graceful shutdown */
132
+ async close(): Promise<void> {
133
+ this.scanCache?.clear();
134
+ if (this.auditLogger) {
135
+ await this.auditLogger.close();
136
+ }
137
+ }
138
+
139
+ // --- Private setup ---
140
+
141
+ private buildCacheKey(input: string, context: ScanContext): string {
142
+ // Include preset + tool names in key since they affect scan results
143
+ const parts = [context.preset ?? "default"];
144
+ if (context.tools?.length) {
145
+ parts.push(context.tools.map((t) => t.name).sort().join(","));
146
+ }
147
+ parts.push(input);
148
+ return parts.join("\x00");
149
+ }
150
+
151
+ private setupScanners(config: ShieldConfig): void {
152
+ // 1. Heuristic injection scanner (always on unless explicitly disabled)
153
+ if (config.injection?.enabled !== false) {
154
+ const preset = this.policyEngine.getPreset();
155
+ this.chain.add(
156
+ new HeuristicScanner({
157
+ strictness: config.injection?.strictness ?? "medium",
158
+ threshold: config.injection?.threshold ?? preset.injection.threshold,
159
+ customPatterns: config.injection?.customPatterns?.map((pattern, i) => ({
160
+ id: `CUSTOM-${i + 1}`,
161
+ category: "instruction_override" as const,
162
+ pattern,
163
+ weight: 0.25,
164
+ description: `Custom pattern #${i + 1}`,
165
+ })),
166
+ }),
167
+ );
168
+ }
169
+
170
+ // 2. PII scanner
171
+ if (config.pii?.enabled !== false) {
172
+ this.chain.add(
173
+ new PIIScanner({
174
+ action: config.pii?.action ?? this.policyEngine.getPIIAction(),
175
+ locale: config.pii?.locale,
176
+ types: config.pii?.types,
177
+ allowedTypes: config.pii?.allowedTypes,
178
+ }),
179
+ );
180
+ }
181
+
182
+ // 3. Tool policy scanner
183
+ if (config.tools?.enabled !== false && config.tools?.policies) {
184
+ const toolPolicy: ToolPolicy = {
185
+ permissions: config.tools.policies,
186
+ global: {
187
+ dangerousPatterns:
188
+ config.tools.globalDangerousPatterns ??
189
+ this.policyEngine.getDangerousToolPatterns(),
190
+ maxToolChainDepth:
191
+ config.tools.maxToolChainDepth ??
192
+ this.policyEngine.getMaxToolChainDepth(),
193
+ },
194
+ };
195
+ this.chain.add(
196
+ new ToolPolicyScanner(toolPolicy, config.tools.manifestPins),
197
+ );
198
+ }
199
+ }
200
+
201
+ private setupAudit(config: ShieldConfig): AuditLogger | null {
202
+ if (config.audit?.enabled === false) return null;
203
+
204
+ let store: AuditStore;
205
+ switch (config.audit?.store) {
206
+ case "console":
207
+ store = new ConsoleAuditStore();
208
+ break;
209
+ case "postgresql":
210
+ // PostgreSQL store would be imported separately to keep core lightweight
211
+ // For now, fall through to console
212
+ store = new ConsoleAuditStore();
213
+ break;
214
+ case "memory":
215
+ default:
216
+ // If no store configured and audit not explicitly enabled, skip
217
+ if (!config.audit?.store && config.audit?.enabled !== true) return null;
218
+ store = new ConsoleAuditStore();
219
+ break;
220
+ }
221
+
222
+ return new AuditLogger({
223
+ store,
224
+ batchSize: config.audit?.batchSize,
225
+ flushIntervalMs: config.audit?.flushIntervalMs,
226
+ });
227
+ }
228
+ }