@@ -260,11 +260,66 @@ export type SecurityEvent = {
260260const securityLog : SecurityEvent [ ] = [ ] ;
261261const MAX_LOG_SIZE = 1000 ;
262262
263+ import { db , auditLogs } from "@query/db" ;
264+
265+ const flushQueue : Omit < SecurityEvent , 'timestamp' > [ ] = [ ] ;
266+ const FLUSH_INTERVAL = 5000 ;
267+ const MAX_BATCH_SIZE = 50 ;
268+ let flushTimer : NodeJS . Timeout | null = null ;
269+
270+ async function flushLogs ( ) {
271+ if ( flushQueue . length === 0 ) return ;
272+
273+ const batch = flushQueue . splice ( 0 , MAX_BATCH_SIZE ) ;
274+
275+ if ( ! db ) {
276+ console . warn ( `[Security] DB unavailable, dropping ${ batch . length } logs` ) ;
277+ return ;
278+ }
279+
280+ try {
281+ const values = batch . map ( event => {
282+ const safeDetails = event . details ? event . details . replace ( / ( p a s s w o r d | t o k e n | s e c r e t ) = [ ^ & ] * / gi, '$1=***' ) : undefined ;
283+ const severity = event . type === 'injection_attempt' ? 'critical' :
284+ event . type === 'auth_failure' ? 'warn' : 'info' ;
285+
286+ return {
287+ action : event . type ,
288+ userId : event . identifier . startsWith ( 'user:' ) ? event . identifier . split ( ':' ) [ 1 ] : null ,
289+ resourceId : event . identifier ,
290+ metadata : { details : safeDetails } ,
291+ severity,
292+ } ;
293+ } ) ;
294+
295+ await db . insert ( auditLogs ) . values ( values ) ;
296+ } catch ( err ) {
297+ console . error ( `[Security] Failed to flush ${ batch . length } logs:` , err ) ;
298+ // In a real system, might want to re-queue, but for now we drop to avoid endless growth
299+ }
300+ }
301+
302+ // Start flush timer
303+ if ( ! flushTimer ) {
304+ flushTimer = setInterval ( ( ) => {
305+ void flushLogs ( ) ;
306+ } , FLUSH_INTERVAL ) ;
307+ }
308+
263309export function logSecurityEvent ( event : Omit < SecurityEvent , 'timestamp' > ) {
310+ // Keep in-memory for immediate/short-term checks
264311 securityLog . push ( { ...event , timestamp : Date . now ( ) } ) ;
265312 if ( securityLog . length > MAX_LOG_SIZE ) {
266313 securityLog . shift ( ) ;
267314 }
315+
316+ // Queue for DB persistence
317+ flushQueue . push ( event ) ;
318+
319+ // Instant flush if critical or queue full
320+ if ( event . type === 'injection_attempt' || flushQueue . length >= MAX_BATCH_SIZE ) {
321+ void flushLogs ( ) ;
322+ }
268323}
269324
270325export function getRecentSecurityEvents ( minutes : number = 60 ) : SecurityEvent [ ] {
0 commit comments