Repository: axios/axios
Pull request #6543 (2024-08-13 18:03:53)

fix(sec): CVE-2024-39338 (#6539)
Address CVE-2024-39338 correctly following https://github.com/axios/axios/issues/6463#issuecomment-2286538061

JosielHernandezDev: I´m waiting for this fix 🥹 
jasonsaayman: awesome thanks i was about to PR the same change
vinialexandre: Can someone please merge this for the love of God???
jasonsaayman: > Can someone please merge this for the love of God???

Thanks, I don't think it's appropriate to cuss on the PR, this is open source I don't get paid to do this work. If you want priority on a issue i can suggest signing up to the Platinum tier on open collective
vinialexandre: I wasn’t cussing, just asking someone to merge it so users can have the issue resolved. But sure, carry on with your sermon, buddy.
jasonsaayman: @levpachmanov pls can you add this on ln 45 in test/unit/regression/SNYK-JS-AXIOS-7361793.js

```
assert.strictEqual(error.message, 'Invalid URL: /' + userId);
```
levpachmanov: @jasonsaayman fixed, sorry for that
jasonsaayman: > @jasonsaayman fixed, sorry for that

all good 🙌 

Patch:
diff --git a/lib/adapters/http.js b/lib/adapters/http.js
index 7c982fa..d5ad9d1 100755
--- a/lib/adapters/http.js
+++ b/lib/adapters/http.js
@@ -229,7 +229,7 @@ export default isHttpAdapterSupported && function httpAdapter(config) {
 
     // Parse url
     const fullPath = buildFullPath(config.baseURL, config.url);
-    const parsed = new URL(fullPath, 'http://localhost');
+    const parsed = new URL(fullPath, utils.hasBrowserEnv ? platform.origin : undefined);
     const protocol = parsed.protocol || supportedProtocols[0];
 
     if (protocol === 'data:') {
diff --git a/lib/helpers/isAbsoluteURL.js b/lib/helpers/isAbsoluteURL.js
index 41f2785..4747a45 100644
--- a/lib/helpers/isAbsoluteURL.js
+++ b/lib/helpers/isAbsoluteURL.js
@@ -8,8 +8,8 @@
  * @returns {boolean} True if the specified URL is absolute, otherwise false
  */
 export default function isAbsoluteURL(url) {
-  // A URL is considered absolute if it begins with "<scheme>://".
+  // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
   // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
   // by any combination of letters, digits, plus, period, or hyphen.
-  return /^([a-z][a-z\d+\-.]*:)\/\//i.test(url);
+  return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url);
 }


Test patch:
diff --git a/test/specs/helpers/isAbsoluteURL.spec.js b/test/specs/helpers/isAbsoluteURL.spec.js
index cf8a4ce..872f5ef 100644
--- a/test/specs/helpers/isAbsoluteURL.spec.js
+++ b/test/specs/helpers/isAbsoluteURL.spec.js
@@ -12,8 +12,8 @@ describe('helpers::isAbsoluteURL', function () {
     expect(isAbsoluteURL('!valid://example.com/')).toBe(false);
   });
 
-  it('should return false if URL is protocol-relative', function () {
-    expect(isAbsoluteURL('//example.com/')).toBe(false);
+  it('should return true if URL is protocol-relative', function () {
+    expect(isAbsoluteURL('//example.com/')).toBe(true);
   });
 
   it('should return false if URL is relative', function () {
diff --git a/test/unit/regression/SNYK-JS-AXIOS-7361793.js b/test/unit/regression/SNYK-JS-AXIOS-7361793.js
index 247e3ea..2d8b205 100644
--- a/test/unit/regression/SNYK-JS-AXIOS-7361793.js
+++ b/test/unit/regression/SNYK-JS-AXIOS-7361793.js
@@ -4,6 +4,9 @@
 import axios from '../../../index.js';
 import http from 'http';
 import assert from 'assert';
+import utils from '../../../lib/utils.js';
+import platform from '../../../lib/platform/index.js';
+
 
 const GOOD_PORT = 4666;
 const BAD_PORT = 4667;
@@ -27,7 +30,7 @@ describe('Server-Side Request Forgery (SSRF)', () => {
         badServer.close();
     });
 
-    it('should not fetch bad server', async () => {
+    it('should not fetch in server-side mode', async () => {
         const ssrfAxios = axios.create({
             baseURL: 'http://localhost:' + String(GOOD_PORT),
         });
@@ -36,10 +39,43 @@ describe('Server-Side Request Forgery (SSRF)', () => {
         // Malicious payload is as below.
         const userId = '/localhost:' + String(BAD_PORT);
 
-        const response = await ssrfAxios.get(`/${userId}`);
-        assert.strictEqual(response.data, 'good');
-        assert.strictEqual(response.config.baseURL, 'http://localhost:' + String(GOOD_PORT));
-        assert.strictEqual(response.config.url, '//localhost:' + String(BAD_PORT));
-        assert.strictEqual(response.request.res.responseUrl, 'http://localhost:' + String(GOOD_PORT) + '/localhost:' + String(BAD_PORT));
+        try {
+            await ssrfAxios.get(`/${userId}`);
+        } catch (error) {
+            assert.ok(error.message.startsWith('Invalid URL'));
+            return;
+        }
+        assert.fail('Expected an error to be thrown');
+    });
+
+    describe('should fetch in client-side mode', () => {
+        let hasBrowserEnv, origin;
+
+        before(() => {
+            hasBrowserEnv = utils.hasBrowserEnv;
+            origin = platform.origin;
+            utils.hasBrowserEnv = true;
+            platform.origin = 'http://localhost:' + String(GOOD_PORT);
+        });
+        after(() => {
+            utils.hasBrowserEnv = hasBrowserEnv;
+            platform.origin = origin;
+        });
+        it('should fetch in client-side mode', async () => {
+            utils.hasBrowserEnv = true;
+            const ssrfAxios = axios.create({
+                baseURL: 'http://localhost:' + String(GOOD_PORT),
+            });
+
+            // Good payload would be `userId = '12345'`
+            // Malicious payload is as below.
+            const userId = '/localhost:' + String(BAD_PORT);
+
+            const response = await ssrfAxios.get(`/${userId}`);
+            assert.strictEqual(response.data, 'bad');
+            assert.strictEqual(response.config.baseURL, 'http://localhost:' + String(GOOD_PORT));
+            assert.strictEqual(response.config.url, '//localhost:' + String(BAD_PORT));
+            assert.strictEqual(response.request.res.responseUrl, 'http://localhost:' + String(BAD_PORT) + '/');
+        });
     });
 });