@@ -21,6 +21,16 @@ import {
2121 CapacityAnalysis ,
2222 WorkloadPredictions
2323} from '../types/graph.js' ;
24+ import {
25+ sanitizeString ,
26+ sanitizeNodeId ,
27+ sanitizeMetadata ,
28+ sanitizeNodeType ,
29+ sanitizeNodeStatus ,
30+ sanitizePriority ,
31+ validateBulkOperation ,
32+ validateMemoryUsage
33+ } from '../utils/sanitizer.js' ;
2434
2535export interface PaginationInfo {
2636 total_count : number ;
@@ -403,14 +413,23 @@ export class GraphService {
403413
404414 async createNode ( args : CreateNodeArgs ) {
405415 return this . withSession ( async ( session ) => {
406- const {
407- title = 'Untitled Node' ,
408- description = '' ,
409- type = 'TASK' ,
410- status = 'PROPOSED' ,
411- contributor_ids = [ ] ,
412- metadata = { }
413- } = args ;
416+ // Validate memory usage before processing
417+ validateMemoryUsage ( args , 10 ) ; // 10MB limit
418+
419+ // Sanitize and validate all inputs
420+ const title = sanitizeString ( args . title || 'Untitled Node' , 500 ) ;
421+ const description = sanitizeString ( args . description || '' , 2000 ) ;
422+ const type = sanitizeNodeType ( args . type || 'TASK' ) ;
423+ const status = sanitizeNodeStatus ( args . status || 'PROPOSED' ) ;
424+ const contributor_ids = Array . isArray ( args . contributor_ids ) ?
425+ args . contributor_ids . map ( id => sanitizeNodeId ( id ) ) . slice ( 0 , 50 ) : // Limit contributors
426+ [ ] ;
427+ const metadata = sanitizeMetadata ( args . metadata || { } ) ;
428+
429+ // Validate bulk operation if multiple contributors
430+ if ( contributor_ids . length > 0 ) {
431+ validateBulkOperation ( contributor_ids . length , 50 ) ;
432+ }
414433
415434 const id = `node_${ Date . now ( ) } _${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } ` ;
416435 const now = new Date ( ) . toISOString ( ) ;
@@ -474,40 +493,50 @@ export class GraphService {
474493
475494 async updateNode ( args : UpdateNodeArgs ) {
476495 return this . withSession ( async ( session ) => {
496+ // Validate memory usage before processing
497+ validateMemoryUsage ( args , 10 ) ; // 10MB limit
498+
477499 const { node_id, ...updates } = args ;
478500
479501 if ( ! node_id ) {
480502 return {
481503 content : [ {
482504 type : 'text' ,
483- text : 'node_id is required for updating a node'
505+ text : JSON . stringify ( { error : 'node_id is required for updating a node' } )
484506 } ] ,
485507 isError : true
486508 } ;
487509 }
488510
511+ // Sanitize node ID
512+ const sanitizedNodeId = sanitizeNodeId ( node_id ) ;
513+
489514 // Build the SET clause dynamically based on provided fields
490515 const setClause : string [ ] = [ 'n.updatedAt = $now' ] ;
491516 const params : Neo4jParams = {
492- node_id,
517+ node_id : sanitizedNodeId ,
493518 now : new Date ( ) . toISOString ( )
494519 } ;
495520
496521 if ( updates . title !== undefined ) {
497522 setClause . push ( 'n.title = $title' ) ;
498- params . title = updates . title ;
523+ params . title = sanitizeString ( updates . title , 500 ) ;
499524 }
500525 if ( updates . description !== undefined ) {
501526 setClause . push ( 'n.description = $description' ) ;
502- params . description = updates . description ;
527+ params . description = sanitizeString ( updates . description , 2000 ) ;
503528 }
504529 if ( updates . status !== undefined ) {
505530 setClause . push ( 'n.status = $status' ) ;
506- params . status = updates . status ;
531+ params . status = sanitizeNodeStatus ( updates . status ) ;
532+ }
533+ if ( updates . type !== undefined ) {
534+ setClause . push ( 'n.type = $type' ) ;
535+ params . type = sanitizeNodeType ( updates . type ) ;
507536 }
508537 if ( updates . metadata !== undefined ) {
509538 setClause . push ( 'n.metadata = $metadata' ) ;
510- params . metadata = JSON . stringify ( updates . metadata ) ;
539+ params . metadata = JSON . stringify ( sanitizeMetadata ( updates . metadata ) ) ;
511540 }
512541
513542 const query = `
@@ -717,9 +746,12 @@ export class GraphService {
717746 return this . withSession ( async ( session ) => {
718747 const { node_id, relationships_limit = 20 , relationships_offset = 0 } = args ;
719748
720- // Ensure all numeric values are properly converted to integers
721- const relationshipsLimitInt = typeof relationships_limit === 'number' ? Math . floor ( relationships_limit ) : parseInt ( String ( relationships_limit ) , 10 ) || 20 ;
722- const relationshipsOffsetInt = typeof relationships_offset === 'number' ? Math . floor ( relationships_offset ) : parseInt ( String ( relationships_offset ) , 10 ) || 0 ;
749+ // Sanitize node ID input
750+ const sanitizedNodeId = sanitizeNodeId ( node_id ) ;
751+
752+ // Ensure all numeric values are properly converted to integers with limits
753+ const relationshipsLimitInt = Math . min ( Math . max ( 1 , Math . floor ( Number ( relationships_limit ) || 20 ) ) , 100 ) ; // Max 100 relationships
754+ const relationshipsOffsetInt = Math . max ( 0 , Math . floor ( Number ( relationships_offset ) || 0 ) ) ;
723755
724756 // First get the node and basic info
725757 const nodeQuery = `
@@ -746,11 +778,11 @@ export class GraphService {
746778 LIMIT $relationships_limit
747779 ` ;
748780
749- // Execute queries
750- const nodeResult = await session . run ( nodeQuery , { node_id } ) ;
751- const relCountResult = await session . run ( relCountQuery , { node_id } ) ;
781+ // Execute queries with sanitized node ID
782+ const nodeResult = await session . run ( nodeQuery , { node_id : sanitizedNodeId } ) ;
783+ const relCountResult = await session . run ( relCountQuery , { node_id : sanitizedNodeId } ) ;
752784 const relationshipsResult = await session . run ( relationshipsQuery , {
753- node_id,
785+ node_id : sanitizedNodeId ,
754786 relationships_offset : int ( relationshipsOffsetInt ) ,
755787 relationships_limit : int ( relationshipsLimitInt )
756788 } ) ;
@@ -761,7 +793,7 @@ export class GraphService {
761793 type : 'text' ,
762794 text : JSON . stringify ( {
763795 error : 'Node not found' ,
764- node_id
796+ node_id : sanitizedNodeId
765797 } , null , 2 )
766798 } ] ,
767799 isError : true
@@ -986,22 +1018,34 @@ export class GraphService {
9861018 recalculate_computed = true
9871019 } = args ;
9881020
1021+ // Sanitize node ID
1022+ const sanitizedNodeId = sanitizeNodeId ( node_id ) ;
1023+
9891024 const updates : string [ ] = [ ] ;
990- const params : Neo4jParams = { node_id } ;
1025+ const params : Neo4jParams = { node_id : sanitizedNodeId } ;
9911026
9921027 if ( priority_executive !== undefined ) {
993- updates . push ( 'n.priorityExec = $priority_executive' ) ;
994- params . priority_executive = Math . max ( 0 , Math . min ( 1 , priority_executive ) ) ;
1028+ const sanitizedPriority = sanitizePriority ( priority_executive ) ;
1029+ if ( sanitizedPriority !== null ) {
1030+ updates . push ( 'n.priorityExec = $priority_executive' ) ;
1031+ params . priority_executive = sanitizedPriority ;
1032+ }
9951033 }
9961034
9971035 if ( priority_individual !== undefined ) {
998- updates . push ( 'n.priorityIndiv = $priority_individual' ) ;
999- params . priority_individual = Math . max ( 0 , Math . min ( 1 , priority_individual ) ) ;
1036+ const sanitizedPriority = sanitizePriority ( priority_individual ) ;
1037+ if ( sanitizedPriority !== null ) {
1038+ updates . push ( 'n.priorityIndiv = $priority_individual' ) ;
1039+ params . priority_individual = sanitizedPriority ;
1040+ }
10001041 }
10011042
10021043 if ( priority_community !== undefined ) {
1003- updates . push ( 'n.priorityComm = $priority_community' ) ;
1004- params . priority_community = Math . max ( 0 , Math . min ( 1 , priority_community ) ) ;
1044+ const sanitizedPriority = sanitizePriority ( priority_community ) ;
1045+ if ( sanitizedPriority !== null ) {
1046+ updates . push ( 'n.priorityComm = $priority_community' ) ;
1047+ params . priority_community = sanitizedPriority ;
1048+ }
10051049 }
10061050
10071051 if ( recalculate_computed ) {
0 commit comments