@@ -55,54 +55,71 @@ function resolveClientIp (req) {
5555}
5656
5757/**
58- * Creates an Express middleware for IP whitelisting
58+ * Checks whether a browser Origin matches the host serving the mirror.
59+ * Non-browser clients (Electron clientonly, curl, node_helpers) send no Origin and are allowed.
60+ * @param {object } req - Incoming request object
61+ * @returns {boolean } True if the origin is same-host or absent
62+ */
63+ function isSameOrigin ( req ) {
64+ const origin = req . headers ?. origin ;
65+ if ( ! origin ) return true ;
66+
67+ const host = req . headers ?. host ;
68+ if ( ! host ) return false ;
69+
70+ try {
71+ return new URL ( origin ) . host === new URL ( `http://${ host } ` ) . host ;
72+ } catch {
73+ return false ;
74+ }
75+ }
76+
77+ /**
78+ * Determines why a request is denied, or null if it is allowed.
79+ * Enforces same-origin first (CSRF protection), then the optional IP whitelist.
80+ * @param {object } req - Incoming Express or Socket.IO request
81+ * @param {string[] } whitelist - Array of allowed IP addresses or CIDR ranges (empty = any IP)
82+ * @returns {string|null } A human-readable denial reason, or null when allowed
83+ */
84+ function accessDenialReason ( req , whitelist ) {
85+ // Strip control characters from the attacker-controlled Origin header before logging it
86+ if ( ! isSameOrigin ( req ) ) return `Origin ${ String ( req . headers ?. origin ) . replace ( / [ \r \n ] / g, "" ) } is not allowed` ;
87+
88+ if ( Array . isArray ( whitelist ) && whitelist . length > 0 ) {
89+ const clientIp = resolveClientIp ( req ) ;
90+ if ( ! isAllowed ( clientIp , whitelist ) ) return `IP ${ clientIp } is not allowed` ;
91+ }
92+
93+ return null ;
94+ }
95+
96+ /**
97+ * Creates an Express middleware enforcing same-origin and the IP whitelist.
5998 * @param {string[] } whitelist - Array of allowed IP addresses or CIDR ranges
6099 * @returns {import("express").RequestHandler } Express middleware function
61100 */
62101function ipAccessControl ( whitelist ) {
63- // Empty whitelist means allow all
64- if ( ! Array . isArray ( whitelist ) || whitelist . length === 0 ) {
65- return function ( req , res , next ) {
66- res . header ( "Access-Control-Allow-Origin" , "*" ) ;
67- next ( ) ;
68- } ;
69- }
70-
71102 return function ( req , res , next ) {
72- const clientIp = resolveClientIp ( req ) ;
103+ const reason = accessDenialReason ( req , whitelist ) ;
104+ if ( ! reason ) return next ( ) ;
73105
74- if ( isAllowed ( clientIp , whitelist ) ) {
75- res . header ( "Access-Control-Allow-Origin" , "*" ) ;
76- next ( ) ;
77- } else {
78- Log . warn ( `IP ${ clientIp } is not allowed to access the mirror` ) ;
79- res . status ( 403 ) . send ( "This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this." ) ;
80- }
106+ Log . warn ( `${ reason } to access the mirror` ) ;
107+ res . status ( 403 ) . send ( "This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this." ) ;
81108 } ;
82109}
83110
84111/**
85- * Creates a Socket.IO `allowRequest` handler that enforces the same IP whitelist as the HTTP middleware.
86- * This closes the gap where Socket.IO handshakes bypassed the Express-only `ipAccessControl` middleware.
112+ * Creates a Socket.IO `allowRequest` handler enforcing the same rules as the HTTP middleware.
87113 * @param {string[] } whitelist - Array of allowed IP addresses or CIDR ranges
88114 * @returns {(req: object, callback: (err: string | null, success: boolean) => void) => void } Socket.IO allowRequest handler
89115 */
90116function socketIpAccessControl ( whitelist ) {
91- // Empty whitelist means allow all
92- if ( ! Array . isArray ( whitelist ) || whitelist . length === 0 ) {
93- return function ( req , callback ) {
94- callback ( null , true ) ; // allow the connection
95- } ;
96- }
97-
98117 return function ( req , callback ) {
99- const clientIp = resolveClientIp ( req ) ;
100- if ( isAllowed ( clientIp , whitelist ) ) {
101- callback ( null , true ) ; // allow the connection
102- } else {
103- Log . warn ( `IP ${ clientIp } is not allowed to connect to the mirror socket` ) ;
104- callback ( "This device is not allowed to access your mirror." , false ) ;
105- }
118+ const reason = accessDenialReason ( req , whitelist ) ;
119+ if ( ! reason ) return callback ( null , true ) ;
120+
121+ Log . warn ( `${ reason } to connect to the mirror socket` ) ;
122+ callback ( "This device is not allowed to access your mirror." , false ) ;
106123 } ;
107124}
108125
0 commit comments