@@ -9,6 +9,7 @@ const log = common.log('hooks:api');
99const _ = require ( 'lodash' ) ;
1010const utils = require ( './utils' ) ;
1111const rights = require ( '../../../api/utils/rights' ) ;
12+ const ssrfProtection = require ( './ssrf-protection' ) ;
1213
1314const FEATURE_NAME = 'hooks' ;
1415
@@ -232,7 +233,7 @@ const CheckEffectProperties = function(effect) {
232233 //todo: add more validation for effect types
233234 if ( effect ) {
234235 if ( effect . type === "HTTPEffect" ) {
235- rules . url = { 'required' : true , 'type' : 'URL' , 'regex' : '^(?!.*(?:localhost|127\\.0\\.0\\.1|\\[::1\\])).*(?:https?|ftp):\\/\\/(?:[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)+|\\[(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}\\])(?::\\d{1,5})?(?:\\/\\S*)?$' } ;
236+ rules . url = { 'required' : true , 'type' : 'URL' } ;
236237 rules . headers = { 'required' : false , 'type' : 'Object' } ;
237238 }
238239 }
@@ -273,7 +274,7 @@ plugins.register("/permissions/features", function(ob) {
273274plugins . register ( "/i/hook/save" , function ( ob ) {
274275 let paramsInstance = ob . params ;
275276
276- validateCreate ( ob . params , FEATURE_NAME , function ( params ) {
277+ validateCreate ( ob . params , FEATURE_NAME , async function ( params ) {
277278 let hookConfig = params . qstring . hook_config ;
278279 if ( ! hookConfig ) {
279280 common . returnMessage ( params , 400 , 'Invalid hookConfig' ) ;
@@ -290,9 +291,12 @@ plugins.register("/i/hook/save", function(ob) {
290291 return true ;
291292 }
292293
293- if ( hookConfig . effects && ! validateEffects ( hookConfig . effects ) ) {
294- common . returnMessage ( params , 400 , 'Invalid configuration for effects' ) ;
295- return true ;
294+ if ( hookConfig . effects ) {
295+ const effectValidation = await validateEffects ( hookConfig . effects ) ;
296+ if ( ! effectValidation . valid ) {
297+ common . returnMessage ( params , 400 , effectValidation . error || 'Invalid configuration for effects' ) ;
298+ return true ;
299+ }
296300 }
297301
298302 if ( hookConfig . _id ) {
@@ -367,20 +371,25 @@ plugins.register("/i/hook/save", function(ob) {
367371
368372/***
369373 * @param {array } effects - array of effects
370- * @returns {boolean } isValid - true if all effects are valid
371374 */
372- function validateEffects ( effects ) {
373- let isValid = true ;
375+ async function validateEffects ( effects ) {
374376 if ( effects ) {
375377 for ( let i = 0 ; i < effects . length ; i ++ ) {
376378 if ( ! ( common . validateArgs ( effects [ i ] . configuration , CheckEffectProperties ( effects [ i ] ) ) ) ) {
377- isValid = false ;
378- break ;
379+ return { valid : false , error : 'Invalid effect configuration' } ;
380+ }
381+
382+ // SSRF protection: validate HTTPEffect URLs with DNS resolution
383+ if ( effects [ i ] . type === "HTTPEffect" && effects [ i ] . configuration && effects [ i ] . configuration . url ) {
384+ const urlCheck = await ssrfProtection . isUrlSafe ( effects [ i ] . configuration . url ) ;
385+ if ( ! urlCheck . safe ) {
386+ return { valid : false , error : 'Unsafe URL in HTTPEffect: ' + urlCheck . error } ;
387+ }
379388 }
380389 }
381390 }
382391
383- return isValid ;
392+ return { valid : true , error : null } ;
384393
385394}
386395
@@ -636,9 +645,12 @@ plugins.register("/i/hook/test", function(ob) {
636645 }
637646
638647 // Null check for effects
639- if ( hookConfig . effects && ! validateEffects ( hookConfig . effects ) ) {
640- common . returnMessage ( params , 400 , 'Config invalid' ) ;
641- return ; // Add return to exit early
648+ if ( hookConfig . effects ) {
649+ const effectValidation = await validateEffects ( hookConfig . effects ) ;
650+ if ( ! effectValidation . valid ) {
651+ common . returnMessage ( params , 400 , effectValidation . error || 'Config invalid' ) ;
652+ return ; // Add return to exit early
653+ }
642654 }
643655
644656
0 commit comments