@@ -52,6 +52,21 @@ interface RouterOptions {
5252 embedFn ?: EmbedFn ;
5353}
5454
55+ // Securite : assainir les entrees FTS5 MATCH (retirer les operateurs speciaux)
56+ function sanitizeFts5 ( input : string ) : string {
57+ // Retirer les caracteres speciaux FTS5 : AND, OR, NOT, *, NEAR, ^, "
58+ // Garder uniquement les mots alphanumeriques
59+ return input
60+ . replace ( / [ * " ^ ( ) { } [ \] ] / g, '' )
61+ . replace ( / \b ( A N D | O R | N O T | N E A R ) \b / gi, '' )
62+ . trim ( ) ;
63+ }
64+
65+ // Securite : echapper les caracteres speciaux LIKE (% et _)
66+ function escapeLike ( input : string ) : string {
67+ return input . replace ( / [ % _ ] / g, '\\$&' ) ;
68+ }
69+
5570export function createMemoriesRouter ( db : DatabaseType , options : RouterOptions = { } ) : Router {
5671 const router = Router ( ) ;
5772
@@ -280,14 +295,21 @@ export function createMemoriesRouter(db: DatabaseType, options: RouterOptions =
280295 return ;
281296 }
282297
298+ // Securite : assainir l'entree FTS5 pour eviter les injections
299+ const sanitized = sanitizeFts5 ( q ) ;
300+ if ( ! sanitized ) {
301+ res . json ( { data : [ ] } ) ;
302+ return ;
303+ }
304+
283305 const rows = db . prepare ( `
284306 SELECT m.* FROM memories m
285307 WHERE m.id IN (
286308 SELECT rowid FROM memory_content_fts WHERE memory_content_fts MATCH ?
287309 )
288310 AND m.deleted_at IS NULL
289311 ORDER BY m.created_at DESC
290- ` ) . all ( q ) as MemoryRow [ ] ;
312+ ` ) . all ( sanitized ) as MemoryRow [ ] ;
291313
292314 res . json ( { data : rows . map ( parseMemory ) } ) ;
293315 } ) ;
@@ -526,10 +548,10 @@ export function createMemoriesRouter(db: DatabaseType, options: RouterOptions =
526548 if ( tagsFilter ) {
527549 const tags = tagsFilter . split ( ',' ) . map ( t => t . trim ( ) ) . filter ( Boolean ) ;
528550 if ( tags . length > 0 ) {
529- const tagConditions = tags . map ( ( ) => ' tags LIKE ?' ) . join ( ' OR ' ) ;
551+ const tagConditions = tags . map ( ( ) => " tags LIKE ? ESCAPE '\\'" ) . join ( ' OR ' ) ;
530552 whereClauses . push ( `(${ tagConditions } )` ) ;
531553 for ( const tag of tags ) {
532- whereParams . push ( `%${ tag } %` ) ;
554+ whereParams . push ( `%${ escapeLike ( tag ) } %` ) ;
533555 }
534556 }
535557 }
@@ -695,10 +717,10 @@ export function createMemoriesRouter(db: DatabaseType, options: RouterOptions =
695717 if ( tagsFilter ) {
696718 const tags = tagsFilter . split ( ',' ) . map ( t => t . trim ( ) ) . filter ( Boolean ) ;
697719 if ( tags . length > 0 ) {
698- const tagConditions = tags . map ( ( ) => ' tags LIKE ?' ) . join ( ' OR ' ) ;
720+ const tagConditions = tags . map ( ( ) => " tags LIKE ? ESCAPE '\\'" ) . join ( ' OR ' ) ;
699721 whereClauses . push ( `(${ tagConditions } )` ) ;
700722 for ( const tag of tags ) {
701- whereParams . push ( `%${ tag } %` ) ;
723+ whereParams . push ( `%${ escapeLike ( tag ) } %` ) ;
702724 }
703725 }
704726 }
@@ -860,7 +882,7 @@ export function createMemoriesRouter(db: DatabaseType, options: RouterOptions =
860882
861883 // PUT /api/tags/:tag - renommer un tag dans toutes les memoires
862884 router . put ( '/tags/:tag' , ( req : Request , res : Response ) => {
863- const { tag } = req . params ;
885+ const tag = req . params . tag as string ;
864886 const { new_name } = req . body ;
865887
866888 if ( ! new_name || typeof new_name !== 'string' || ! new_name . trim ( ) ) {
@@ -874,8 +896,8 @@ export function createMemoriesRouter(db: DatabaseType, options: RouterOptions =
874896
875897 // Trouver toutes les memoires actives contenant ce tag
876898 const rows = db . prepare (
877- ' SELECT id, content_hash, tags FROM memories WHERE deleted_at IS NULL AND tags LIKE ?'
878- ) . all ( `%${ tag } %` ) as { id : number ; content_hash : string ; tags : string } [ ] ;
899+ " SELECT id, content_hash, tags FROM memories WHERE deleted_at IS NULL AND tags LIKE ? ESCAPE '\\'"
900+ ) . all ( `%${ escapeLike ( tag ) } %` ) as { id : number ; content_hash : string ; tags : string } [ ] ;
879901
880902 let updated = 0 ;
881903
@@ -908,14 +930,14 @@ export function createMemoriesRouter(db: DatabaseType, options: RouterOptions =
908930
909931 // DELETE /api/tags/:tag - retirer un tag de toutes les memoires
910932 router . delete ( '/tags/:tag' , ( req : Request , res : Response ) => {
911- const { tag } = req . params ;
933+ const tag = req . params . tag as string ;
912934 const now = Date . now ( ) / 1000 ;
913935 const nowIso = new Date ( ) . toISOString ( ) ;
914936
915937 // Trouver toutes les memoires actives contenant ce tag
916938 const rows = db . prepare (
917- ' SELECT id, content_hash, tags FROM memories WHERE deleted_at IS NULL AND tags LIKE ?'
918- ) . all ( `%${ tag } %` ) as { id : number ; content_hash : string ; tags : string } [ ] ;
939+ " SELECT id, content_hash, tags FROM memories WHERE deleted_at IS NULL AND tags LIKE ? ESCAPE '\\'"
940+ ) . all ( `%${ escapeLike ( tag ) } %` ) as { id : number ; content_hash : string ; tags : string } [ ] ;
919941
920942 let updated = 0 ;
921943
@@ -962,8 +984,8 @@ export function createMemoriesRouter(db: DatabaseType, options: RouterOptions =
962984 const nowIso = new Date ( ) . toISOString ( ) ;
963985
964986 // Construire la condition LIKE pour trouver les memoires avec au moins un tag source
965- const likeConditions = sources . map ( ( ) => ' tags LIKE ?' ) . join ( ' OR ' ) ;
966- const likeParams = sources . map ( ( s : string ) => `%${ s } %` ) ;
987+ const likeConditions = sources . map ( ( ) => " tags LIKE ? ESCAPE '\\'" ) . join ( ' OR ' ) ;
988+ const likeParams = sources . map ( ( s : string ) => `%${ escapeLike ( s ) } %` ) ;
967989
968990 const rows = db . prepare (
969991 `SELECT id, content_hash, tags FROM memories WHERE deleted_at IS NULL AND (${ likeConditions } )`
@@ -973,7 +995,7 @@ export function createMemoriesRouter(db: DatabaseType, options: RouterOptions =
973995
974996 const mergeAll = db . transaction ( ( ) => {
975997 for ( const row of rows ) {
976- let currentTags = row . tags . split ( ',' ) . map ( t => t . trim ( ) ) . filter ( Boolean ) ;
998+ const currentTags = row . tags . split ( ',' ) . map ( t => t . trim ( ) ) . filter ( Boolean ) ;
977999 let modified = false ;
9781000
9791001 // Retirer tous les tags sources
0 commit comments