@@ -481,7 +481,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
481481 // Validates the given create payload against Zod schema if any
482482 private validateCreateInputSchema ( model : string , data : any ) {
483483 const schema = this . policyUtils . getZodSchema ( model , 'create' ) ;
484- if ( schema ) {
484+ if ( schema && data ) {
485485 const parseResult = schema . safeParse ( data ) ;
486486 if ( ! parseResult . success ) {
487487 throw this . policyUtils . deniedByPolicy (
@@ -514,26 +514,29 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
514514
515515 args = this . policyUtils . clone ( args ) ;
516516
517- // do static input validation and check if post-create checks are needed
517+ // go through create items, statically check input to determine if post-create
518+ // check is needed, and also validate zod schema
518519 let needPostCreateCheck = false ;
519520 for ( const item of enumerate ( args . data ) ) {
521+ const validationResult = this . validateCreateInputSchema ( this . model , item ) ;
522+ if ( validationResult !== item ) {
523+ this . policyUtils . replace ( item , validationResult ) ;
524+ }
525+
520526 const inputCheck = this . policyUtils . checkInputGuard ( this . model , item , 'create' ) ;
521527 if ( inputCheck === false ) {
528+ // unconditionally deny
522529 throw this . policyUtils . deniedByPolicy (
523530 this . model ,
524531 'create' ,
525532 undefined ,
526533 CrudFailureReason . ACCESS_POLICY_VIOLATION
527534 ) ;
528535 } else if ( inputCheck === true ) {
529- const r = this . validateCreateInputSchema ( this . model , item ) ;
530- if ( r !== item ) {
531- this . policyUtils . replace ( item , r ) ;
532- }
536+ // unconditionally allow
533537 } else if ( inputCheck === undefined ) {
534538 // static policy check is not possible, need to do post-create check
535539 needPostCreateCheck = true ;
536- break ;
537540 }
538541 }
539542
@@ -808,7 +811,13 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
808811
809812 // check if the update actually writes to this model
810813 let thisModelUpdate = false ;
811- const updatePayload : any = ( args as any ) . data ?? args ;
814+ const updatePayload = ( args as any ) . data ?? args ;
815+
816+ const validatedPayload = this . validateUpdateInputSchema ( model , updatePayload ) ;
817+ if ( validatedPayload !== updatePayload ) {
818+ this . policyUtils . replace ( updatePayload , validatedPayload ) ;
819+ }
820+
812821 if ( updatePayload ) {
813822 for ( const key of Object . keys ( updatePayload ) ) {
814823 const field = resolveField ( this . modelMeta , model , key ) ;
@@ -879,6 +888,8 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
879888 ) ;
880889 }
881890
891+ args . data = this . validateUpdateInputSchema ( model , args . data ) ;
892+
882893 const updateGuard = this . policyUtils . getAuthGuard ( db , model , 'update' ) ;
883894 if ( this . policyUtils . isTrue ( updateGuard ) || this . policyUtils . isFalse ( updateGuard ) ) {
884895 // injects simple auth guard into where clause
@@ -939,7 +950,10 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
939950 await _registerPostUpdateCheck ( model , uniqueFilter ) ;
940951
941952 // convert upsert to update
942- context . parent . update = { where : args . where , data : args . update } ;
953+ context . parent . update = {
954+ where : args . where ,
955+ data : this . validateUpdateInputSchema ( model , args . update ) ,
956+ } ;
943957 delete context . parent . upsert ;
944958
945959 // continue visiting the new payload
@@ -1038,6 +1052,37 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
10381052 return { result, postWriteChecks } ;
10391053 }
10401054
1055+ // Validates the given update payload against Zod schema if any
1056+ private validateUpdateInputSchema ( model : string , data : any ) {
1057+ const schema = this . policyUtils . getZodSchema ( model , 'update' ) ;
1058+ if ( schema && data ) {
1059+ // update payload can contain non-literal fields, like:
1060+ // { x: { increment: 1 } }
1061+ // we should only validate literal fields
1062+
1063+ const literalData = Object . entries ( data ) . reduce < any > (
1064+ ( acc , [ k , v ] ) => ( { ...acc , ...( typeof v !== 'object' ? { [ k ] : v } : { } ) } ) ,
1065+ { }
1066+ ) ;
1067+
1068+ const parseResult = schema . safeParse ( literalData ) ;
1069+ if ( ! parseResult . success ) {
1070+ throw this . policyUtils . deniedByPolicy (
1071+ model ,
1072+ 'update' ,
1073+ `input failed validation: ${ fromZodError ( parseResult . error ) } ` ,
1074+ CrudFailureReason . DATA_VALIDATION_VIOLATION ,
1075+ parseResult . error
1076+ ) ;
1077+ }
1078+
1079+ // schema may have transformed field values, use it to overwrite the original data
1080+ return { ...data , ...parseResult . data } ;
1081+ } else {
1082+ return data ;
1083+ }
1084+ }
1085+
10411086 private isUnsafeMutate ( model : string , args : any ) {
10421087 if ( ! args ) {
10431088 return false ;
@@ -1072,6 +1117,8 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
10721117 args = this . policyUtils . clone ( args ) ;
10731118 this . policyUtils . injectAuthGuardAsWhere ( this . prisma , args , this . model , 'update' ) ;
10741119
1120+ args . data = this . validateUpdateInputSchema ( this . model , args . data ) ;
1121+
10751122 if ( this . policyUtils . hasAuthGuard ( this . model , 'postUpdate' ) || this . policyUtils . getZodSchema ( this . model ) ) {
10761123 // use a transaction to do post-update checks
10771124 const postWriteChecks : PostWriteCheckRecord [ ] = [ ] ;
0 commit comments