@@ -89,9 +89,9 @@ export function validatePathSegment(
8989 const pathTraversalPatterns = [
9090 '..' ,
9191 './' ,
92- '.\\.' , // Windows path traversal
93- '%2e%2e' , // URL encoded ..
94- '%252e%252e' , // Double URL encoded ..
92+ '.\\.' ,
93+ '%2e%2e' ,
94+ '%252e%252e' ,
9595 '..%2f' ,
9696 '..%5c' ,
9797 '%2e%2e%2f' ,
@@ -391,7 +391,6 @@ export function validateHostname(
391391
392392 const lowerHostname = hostname . toLowerCase ( )
393393
394- // Block localhost
395394 if ( lowerHostname === 'localhost' ) {
396395 logger . warn ( 'Hostname is localhost' , { paramName } )
397396 return {
@@ -400,7 +399,6 @@ export function validateHostname(
400399 }
401400 }
402401
403- // Use ipaddr.js to check if hostname is an IP and if it's private/reserved
404402 if ( ipaddr . isValid ( lowerHostname ) ) {
405403 if ( isPrivateOrReservedIP ( lowerHostname ) ) {
406404 logger . warn ( 'Hostname matches blocked IP range' , {
@@ -414,7 +412,6 @@ export function validateHostname(
414412 }
415413 }
416414
417- // Basic hostname format validation
418415 const hostnamePattern =
419416 / ^ [ a - z 0 - 9 ] ( [ a - z 0 - 9 - ] { 0 , 61 } [ a - z 0 - 9 ] ) ? ( \. [ a - z 0 - 9 ] ( [ a - z 0 - 9 - ] { 0 , 61 } [ a - z 0 - 9 ] ) ? ) * $ / i
420417
@@ -460,10 +457,7 @@ export function validateFileExtension(
460457 }
461458 }
462459
463- // Remove leading dot if present
464460 const ext = extension . startsWith ( '.' ) ? extension . slice ( 1 ) : extension
465-
466- // Normalize to lowercase
467461 const normalizedExt = ext . toLowerCase ( )
468462
469463 if ( ! allowedExtensions . map ( ( e ) => e . toLowerCase ( ) ) . includes ( normalizedExt ) ) {
@@ -515,7 +509,6 @@ export function validateMicrosoftGraphId(
515509 }
516510 }
517511
518- // Check for path traversal patterns (../)
519512 const pathTraversalPatterns = [
520513 '../' ,
521514 '..\\' ,
@@ -525,7 +518,7 @@ export function validateMicrosoftGraphId(
525518 '%2e%2e%5c' ,
526519 '%2e%2e\\' ,
527520 '..%5c' ,
528- '%252e%252e%252f' , // double encoded
521+ '%252e%252e%252f' ,
529522 ]
530523
531524 const lowerValue = value . toLowerCase ( )
@@ -542,7 +535,6 @@ export function validateMicrosoftGraphId(
542535 }
543536 }
544537
545- // Check for control characters and null bytes
546538 if ( / [ \x00 - \x1f \x7f ] / . test ( value ) || value . includes ( '%00' ) ) {
547539 logger . warn ( 'Control characters in Microsoft Graph ID' , { paramName } )
548540 return {
@@ -551,16 +543,13 @@ export function validateMicrosoftGraphId(
551543 }
552544 }
553545
554- // Check for newlines (which could be used for header injection)
555546 if ( value . includes ( '\n' ) || value . includes ( '\r' ) ) {
556547 return {
557548 isValid : false ,
558549 error : `${ paramName } contains invalid newline characters` ,
559550 }
560551 }
561552
562- // Microsoft Graph IDs can contain many characters, but not suspicious patterns
563- // We've blocked path traversal, so allow the rest
564553 return { isValid : true , sanitized : value }
565554}
566555
@@ -583,7 +572,6 @@ export function validateJiraCloudId(
583572 value : string | null | undefined ,
584573 paramName = 'cloudId'
585574) : ValidationResult {
586- // Jira cloud IDs are alphanumeric with hyphens (UUID-like)
587575 return validatePathSegment ( value , {
588576 paramName,
589577 allowHyphens : true ,
@@ -612,7 +600,6 @@ export function validateJiraIssueKey(
612600 value : string | null | undefined ,
613601 paramName = 'issueKey'
614602) : ValidationResult {
615- // Jira issue keys: letters, numbers, hyphens (PROJECT-123 format)
616603 return validatePathSegment ( value , {
617604 paramName,
618605 allowHyphens : true ,
@@ -653,7 +640,6 @@ export function validateExternalUrl(
653640 }
654641 }
655642
656- // Must be a valid URL
657643 let parsedUrl : URL
658644 try {
659645 parsedUrl = new URL ( url )
@@ -667,7 +653,8 @@ export function validateExternalUrl(
667653 const protocol = parsedUrl . protocol
668654 const hostname = parsedUrl . hostname . toLowerCase ( )
669655
670- const cleanHostname = hostname . startsWith ( '[' ) && hostname . endsWith ( ']' ) ? hostname . slice ( 1 , - 1 ) : hostname
656+ const cleanHostname =
657+ hostname . startsWith ( '[' ) && hostname . endsWith ( ']' ) ? hostname . slice ( 1 , - 1 ) : hostname
671658
672659 let isLocalhost = cleanHostname === 'localhost'
673660 if ( ipaddr . isValid ( cleanHostname ) ) {
@@ -677,15 +664,13 @@ export function validateExternalUrl(
677664 }
678665 }
679666
680- // Require HTTPS except for localhost development
681667 if ( protocol !== 'https:' && ! ( protocol === 'http:' && isLocalhost ) ) {
682668 return {
683669 isValid : false ,
684670 error : `${ paramName } must use https:// protocol` ,
685671 }
686672 }
687673
688- // Block private/reserved IPs while allowing loopback addresses for local development.
689674 if ( ! isLocalhost && ipaddr . isValid ( cleanHostname ) ) {
690675 if ( isPrivateOrReservedIP ( cleanHostname ) ) {
691676 return {
0 commit comments