@@ -20,24 +20,36 @@ function LocalBinary(){
2020 this . baseRetries = 9 ;
2121 this . sourceURL = null ;
2222 this . downloadErrorMessage = null ;
23+ /*
24+ * Per-instance binary-download signalling. Historically these three fields were
25+ * carried on process.env (BINARY_DOWNLOAD_FALLBACK_ENABLED / _ERROR_MESSAGE /
26+ * _SOURCE_URL), which is a process-global mutable store: a failure on one Local
27+ * instance bled into every other instance in the same process, and an attacker
28+ * who could set the env before boot could force this instance to download from
29+ * an arbitrary host. Keep the state on the instance instead. The owning Local
30+ * object shares ONE downloadState object across the LocalBinary instances it
31+ * recreates during a retry loop, so the fallback URL is still cached within a
32+ * single Local instance without leaking across sibling instances.
33+ */
34+ this . downloadState = { fallbackEnabled : false , errorMessage : null , sourceURL : null } ;
2335
2436 this . getSourceUrlSync = function ( conf , retries ) {
2537 /* Request for an endpoint to download the local binary from Rails no more than twice with 5 retries each */
2638 if ( ! [ 4 , 9 ] . includes ( retries ) && this . sourceURL != null ) {
2739 return this . sourceURL ;
2840 }
2941
30- if ( process . env . BINARY_DOWNLOAD_SOURCE_URL !== undefined && process . env . BINARY_DOWNLOAD_FALLBACK_ENABLED == 'true' && this . parentRetries != 4 ) {
42+ if ( this . downloadState . sourceURL != null && this . downloadState . fallbackEnabled && this . parentRetries != 4 ) {
3143 /* This is triggered from Local.js if there's an error executing the downloaded binary */
32- return process . env . BINARY_DOWNLOAD_SOURCE_URL ;
44+ return this . downloadState . sourceURL ;
3345 }
3446
3547 let cmd , opts ;
3648 cmd = 'node' ;
3749 opts = [ path . join ( __dirname , 'fetchDownloadSourceUrl.js' ) , this . key , this . bsHost ] ;
3850
39- if ( retries == 4 || ( process . env . BINARY_DOWNLOAD_FALLBACK_ENABLED == 'true' && this . parentRetries == 4 ) ) {
40- opts . push ( true , this . downloadErrorMessage || process . env . BINARY_DOWNLOAD_ERROR_MESSAGE ) ;
51+ if ( retries == 4 || ( this . downloadState . fallbackEnabled && this . parentRetries == 4 ) ) {
52+ opts . push ( true , this . downloadErrorMessage || this . downloadState . errorMessage ) ;
4153 } else {
4254 opts . push ( false , null ) ;
4355 }
@@ -56,7 +68,7 @@ function LocalBinary(){
5668 const obj = childProcess . spawnSync ( cmd , opts , { env : env } ) ;
5769 if ( obj . stdout . length > 0 ) {
5870 this . sourceURL = obj . stdout . toString ( ) . replace ( / \n + $ / , '' ) ;
59- process . env . BINARY_DOWNLOAD_SOURCE_URL = this . sourceURL ;
71+ this . downloadState . sourceURL = this . sourceURL ;
6072 return this . sourceURL ;
6173 } else if ( obj . stderr . length > 0 ) {
6274 let output = Buffer . from ( JSON . parse ( JSON . stringify ( obj . stderr ) ) . data ) . toString ( ) ;
@@ -70,23 +82,23 @@ function LocalBinary(){
7082 return callback ( null , this . sourceURL ) ;
7183 }
7284
73- if ( process . env . BINARY_DOWNLOAD_SOURCE_URL !== undefined && process . env . BINARY_DOWNLOAD_FALLBACK_ENABLED == 'true' && this . parentRetries != 4 ) {
85+ if ( this . downloadState . sourceURL != null && this . downloadState . fallbackEnabled && this . parentRetries != 4 ) {
7486 /* This is triggered from Local.js if there's an error executing the downloaded binary */
75- return callback ( null , process . env . BINARY_DOWNLOAD_SOURCE_URL ) ;
87+ return callback ( null , this . downloadState . sourceURL ) ;
7688 }
7789
7890 let downloadFallback = false ;
7991 let downloadErrorMessage = null ;
8092
81- if ( retries == 4 || ( process . env . BINARY_DOWNLOAD_FALLBACK_ENABLED == 'true' && this . parentRetries == 4 ) ) {
93+ if ( retries == 4 || ( this . downloadState . fallbackEnabled && this . parentRetries == 4 ) ) {
8294 downloadFallback = true ;
83- downloadErrorMessage = this . downloadErrorMessage || process . env . BINARY_DOWNLOAD_ERROR_MESSAGE ;
95+ downloadErrorMessage = this . downloadErrorMessage || this . downloadState . errorMessage ;
8496 }
8597
8698 fetchDownloadSourceUrlAsync ( this . key , this . bsHost , downloadFallback , downloadErrorMessage , conf . proxyHost , conf . proxyPort , conf . useCaCertificate , ( err , sourceURL ) => {
8799 if ( err ) return callback ( err ) ;
88100 this . sourceURL = sourceURL ;
89- process . env . BINARY_DOWNLOAD_SOURCE_URL = sourceURL ;
101+ this . downloadState . sourceURL = sourceURL ;
90102 callback ( null , sourceURL ) ;
91103 } ) ;
92104 } ;
0 commit comments