VYPR
Low severityNVD Advisory· Published Jun 12, 2026

CVE-2026-53724

CVE-2026-53724

Description

Parse Server file upload extension blocklist can be bypassed by appending a trailing dot to a filename, leading to stored XSS.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Parse Server file upload extension blocklist can be bypassed by appending a trailing dot to a filename, leading to stored XSS.

Vulnerability

Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Prior to versions 8.6.79 and 9.9.1-alpha.4, the default file upload extension blocklist can be bypassed by appending a trailing dot to a filename whose extension would otherwise be blocked (e.g. poc.svg.). The trailing dot causes the extension parser to extract an empty string, which short-circuits the blocklist check, and the attacker-controlled Content-Type is forwarded to the storage adapter unchanged. [1][3]

Exploitation

An attacker can upload a file with a name like poc.svg. (trailing dot). The extension parser extracts an empty extension, bypassing the blocklist. The attacker provides a Content-Type such as image/svg+xml, which is passed unchanged to the storage adapter that persists and serves that Content-Type (e.g., S3 or GCS). A victim who opens the file URL then receives the file with an active content type, enabling stored XSS. No authentication is required if file upload is publicly accessible; the attack requires only the ability to upload files with a crafted filename. [1][3]

Impact

Successful exploitation allows an attacker to achieve stored cross-site scripting (XSS) when a victim opens the uploaded file URL. The impact is information disclosure or session hijacking depending on the victim's context. The default GridFS adapter is not affected because it sets X-Content-Type-Options: nosniff on responses. [1][3]

Mitigation

Parse Server has patched this issue in versions 8.6.79 and 9.9.1-alpha.4. The fix treats a filename ending in a dot as extensionless and falls back to validating the Content-Type subtype against the configured extension blocklist. Workarounds include configuring the storage adapter or CDN to derive Content-Type from the filename extension instead of using the stored Content-Type, or replacing the default blocklist with an explicit allowlist of needed file extensions. This vulnerability is not listed on the CISA KEV as of the publication date. [1][3]

AI Insight generated on Jun 12, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

2
9e992797ebd4

fix: Stored XSS via trailing-dot filename bypassing file upload extension blocklist ([GHSA-7wqv-xjf3-x35v](https://github.com/parse-community/parse-server/security/advisories/GHSA-7wqv-xjf3-x35v)) (#10490)

https://github.com/parse-community/parse-serverManuelJun 1, 2026via body-scan-shorthand
5 files changed · +219 10
  • spec/Utils.spec.js+21 0 modified
    @@ -212,4 +212,25 @@ describe('Utils', () => {
           expect(error.message).toBe('Detailed error message');
         });
       });
    +
    +  describe('getFileExtension', () => {
    +    const cases = [
    +      ['file.txt', 'txt'],
    +      ['file.tar.gz', 'gz'],
    +      ['.hidden', 'hidden'],
    +      ['file.', ''],
    +      ['file..', ''],
    +      ['file', ''],
    +      ['', ''],
    +      [null, ''],
    +      [undefined, ''],
    +      ['poc.svg.', ''],
    +      ['archive.tar.gz.', ''],
    +    ];
    +    for (const [input, expected] of cases) {
    +      it(`returns ${JSON.stringify(expected)} for ${JSON.stringify(input)}`, () => {
    +        expect(Utils.getFileExtension(input)).toBe(expected);
    +      });
    +    }
    +  });
     });
    
  • spec/vulnerabilities.spec.js+164 0 modified
    @@ -2832,6 +2832,170 @@ describe('Vulnerabilities', () => {
           });
         });
     
    +    describe('(GHSA-7wqv-xjf3-x35v) Stored XSS via trailing-dot filename bypassing file extension blocklist', () => {
    +      const headers = {
    +        'X-Parse-Application-Id': 'test',
    +        'X-Parse-REST-API-Key': 'rest',
    +      };
    +
    +      beforeEach(async () => {
    +        await reconfigureServer({
    +          fileUpload: {
    +            enableForPublic: true,
    +          },
    +        });
    +      });
    +
    +      it('blocks trailing-dot SVG filename with dangerous _ContentType on JSON-body upload', async () => {
    +        const svgContent = Buffer.from(
    +          '<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>'
    +        ).toString('base64');
    +        await expectAsync(
    +          request({
    +            method: 'POST',
    +            url: 'http://localhost:8378/1/files/poc.svg.',
    +            body: JSON.stringify({
    +              _ApplicationId: 'test',
    +              _JavaScriptKey: 'test',
    +              _ContentType: 'image/svg+xml',
    +              base64: svgContent,
    +            }),
    +          }).catch(e => {
    +            throw new Error(e.data.error);
    +          })
    +        ).toBeRejectedWith(jasmine.objectContaining({
    +          message: jasmine.stringMatching(/File upload of extension .+ is disabled/),
    +        }));
    +      });
    +
    +      it('blocks trailing-dot SVG filename with dangerous Content-Type on binary upload', async () => {
    +        await expectAsync(
    +          request({
    +            method: 'POST',
    +            headers: {
    +              ...headers,
    +              'Content-Type': 'image/svg+xml',
    +            },
    +            url: 'http://localhost:8378/1/files/poc.svg.',
    +            body: '<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>',
    +          }).catch(e => {
    +            throw new Error(e.data.error);
    +          })
    +        ).toBeRejectedWith(jasmine.objectContaining({
    +          message: jasmine.stringMatching(/File upload of extension .+ is disabled/),
    +        }));
    +      });
    +
    +      it('blocks filename with mixed trailing dots and whitespace', async () => {
    +        for (const filename of ['poc.svg..', 'poc.svg. ', 'poc.svg . ']) {
    +          await expectAsync(
    +            request({
    +              method: 'POST',
    +              headers: {
    +                ...headers,
    +                'Content-Type': 'image/svg+xml',
    +              },
    +              url: `http://localhost:8378/1/files/${encodeURIComponent(filename)}`,
    +              body: '<svg/>',
    +            }).catch(e => {
    +              throw new Error(e.data.error);
    +            })
    +          ).toBeRejectedWith(jasmine.objectContaining({
    +            message: jasmine.stringMatching(/File upload of extension .+ is disabled/),
    +          }));
    +        }
    +      });
    +
    +      it('still allows trailing-dot filename with allowed Content-Type', async () => {
    +        const adapter = Config.get('test').filesController.adapter;
    +        const spy = spyOn(adapter, 'createFile').and.callThrough();
    +        const response = await request({
    +          method: 'POST',
    +          url: 'http://localhost:8378/1/files/notes.txt.',
    +          body: JSON.stringify({
    +            _ApplicationId: 'test',
    +            _JavaScriptKey: 'test',
    +            _ContentType: 'text/plain',
    +            base64: Buffer.from('hello').toString('base64'),
    +          }),
    +          headers,
    +        });
    +        expect(response.status).toBe(201);
    +        expect(spy).toHaveBeenCalled();
    +      });
    +
    +      it('FilesController treats trailing-dot filename as extensionless when appending derived extension via master key upload', async () => {
    +        await reconfigureServer({
    +          fileUpload: {
    +            enableForPublic: true,
    +          },
    +          preserveFileName: true,
    +        });
    +        const adapter = Config.get('test').filesController.adapter;
    +        const spy = spyOn(adapter, 'createFile').and.callThrough();
    +        const response = await request({
    +          method: 'POST',
    +          url: 'http://localhost:8378/1/files/poc.svg.',
    +          headers: {
    +            'X-Parse-Application-Id': 'test',
    +            'X-Parse-Master-Key': 'test',
    +            'Content-Type': 'image/svg+xml',
    +          },
    +          body: '<svg/>',
    +        });
    +        expect(response.status).toBe(201);
    +        expect(spy).toHaveBeenCalled();
    +        const filenameArg = spy.calls.mostRecent().args[0];
    +        const contentTypeArg = spy.calls.mostRecent().args[2];
    +        expect(filenameArg).toBe('poc.svg.svg');
    +        expect(contentTypeArg).toBe('image/svg+xml');
    +      });
    +
    +      it('allows trailing-dot filename when no Content-Type is supplied (no XSS path)', async () => {
    +        // Trailing-dot filename with no caller-supplied Content-Type: the
    +        // blocklist gate skips because no extension can be determined, but no
    +        // attacker-controlled Content-Type reaches the storage adapter — only
    +        // the SDK's benign default — so no stored XSS is possible.
    +        const adapter = Config.get('test').filesController.adapter;
    +        const spy = spyOn(adapter, 'createFile').and.callThrough();
    +        const response = await request({
    +          method: 'POST',
    +          headers: {
    +            'X-Parse-Application-Id': 'test',
    +            'X-Parse-REST-API-Key': 'rest',
    +          },
    +          url: 'http://localhost:8378/1/files/poc.svg.',
    +          body: '<svg/>',
    +        });
    +        expect(response.status).toBe(201);
    +        expect(spy).toHaveBeenCalled();
    +        const contentTypeArg = spy.calls.mostRecent().args[2];
    +        expect(contentTypeArg).not.toMatch(/svg|html|xml|xhtml|xslt|mathml/i);
    +      });
    +
    +      it('falls back to raw Content-Type when Content-Type is malformed (no slash)', async () => {
    +        // Exercises the last-resort branch: when both the filename has no usable
    +        // extension AND the Content-Type lacks a "/" subtype to parse, the raw
    +        // Content-Type is used as the extension so a malformed header that
    +        // matches a blocked pattern still trips the blocklist.
    +        await expectAsync(
    +          request({
    +            method: 'POST',
    +            headers: {
    +              ...headers,
    +              'Content-Type': 'svg',
    +            },
    +            url: 'http://localhost:8378/1/files/poc',
    +            body: '<svg/>',
    +          }).catch(e => {
    +            throw new Error(e.data.error);
    +          })
    +        ).toBeRejectedWith(jasmine.objectContaining({
    +          message: jasmine.stringMatching(/File upload of extension svg is disabled/),
    +        }));
    +      });
    +    });
    +
         describe('(GHSA-9ccr-fpp6-78qf) Schema poisoning via __proto__ bypassing requestKeywordDenylist and addField CLP', () => {
           const headers = {
             'Content-Type': 'application/json',
    
  • src/Controllers/FilesController.js+5 4 modified
    @@ -2,8 +2,8 @@
     import { randomHexString } from '../cryptoUtils';
     import AdaptableController from './AdaptableController';
     import { validateFilename, FilesAdapter } from '../Adapters/Files/FilesAdapter';
    -import path from 'path';
     const Parse = require('parse').Parse;
    +const Utils = require('../Utils');
     
     const legacyFilesRegex = new RegExp(
       '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-.*'
    @@ -15,12 +15,13 @@ export class FilesController extends AdaptableController {
       }
     
       async createFile(config, filename, data, contentType, options) {
    -    const extname = path.extname(filename);
    -
    +    const extname = Utils.getFileExtension(filename);
         const hasExtension = extname.length > 0;
         const mime = (await import('mime')).default
         if (!hasExtension && contentType && mime.getExtension(contentType)) {
    -      filename = filename + '.' + mime.getExtension(contentType);
    +      // Avoid producing a doubled dot when the filename already ends in one
    +      const separator = filename.endsWith('.') ? '' : '.';
    +      filename = filename + separator + mime.getExtension(contentType);
         } else if (hasExtension) {
           contentType = mime.getType(filename) || contentType;
         }
    
  • src/Routers/FilesRouter.js+12 6 modified
    @@ -197,14 +197,20 @@ export class FilesRouter {
               }
             });
           };
    -      let extension = contentType;
    -      if (filename && filename.includes('.')) {
    -        extension = filename.substring(filename.lastIndexOf('.') + 1);
    -      } else if (contentType && contentType.includes('/')) {
    -        extension = contentType.split('/')[1];
    -      }
    +      let extension = Utils.getFileExtension(filename);
           // Strip MIME parameters (e.g. ";charset=utf-8") and whitespace
           extension = extension?.split(';')[0]?.replace(/\s+/g, '');
    +      // If the filename has no usable extension (no dot, trailing dot, or
    +      // whitespace-only suffix), fall back to the Content-Type subtype — same
    +      // as a dotless filename.
    +      if (!extension && contentType && contentType.includes('/')) {
    +        extension = contentType.split('/')[1]?.split(';')[0]?.replace(/\s+/g, '');
    +      }
    +      // Last resort for malformed inputs (e.g. Content-Type without a slash):
    +      // use the raw Content-Type so the existing rejection path still fires.
    +      if (!extension && contentType) {
    +        extension = contentType.split(';')[0]?.replace(/\s+/g, '');
    +      }
     
           if (extension && !isValidExtension(extension)) {
             next(
    
  • src/Utils.js+17 0 modified
    @@ -478,6 +478,23 @@ class Utils {
         }
         return current;
       }
    +
    +  /**
    +   * Returns the file extension as the substring after the last dot in the
    +   * filename. A trailing dot or a filename without a dot yields an empty
    +   * string. Callers apply any further normalization (whitespace, MIME
    +   * parameters, etc.) for their use case — this is a pure parser, not a
    +   * policy.
    +   *
    +   * @param {string} filename
    +   * @returns {string} the extension, or `''` if none
    +   */
    +  static getFileExtension(filename) {
    +    if (!filename || !filename.includes('.')) {
    +      return '';
    +    }
    +    return filename.substring(filename.lastIndexOf('.') + 1);
    +  }
     }
     
     module.exports = Utils;
    
66484ce8fdd8

fix: Stored XSS via trailing-dot filename bypassing file upload extension blocklist ([GHSA-7wqv-xjf3-x35v](https://github.com/parse-community/parse-server/security/advisories/GHSA-7wqv-xjf3-x35v)) (#10489)

https://github.com/parse-community/parse-serverManuelJun 1, 2026via body-scan-shorthand
5 files changed · +223 10
  • spec/Utils.spec.js+21 0 modified
    @@ -447,4 +447,25 @@ describe('Utils', () => {
           expect(Utils.isObject(true)).toBe(false);
         });
       });
    +
    +  describe('getFileExtension', () => {
    +    const cases = [
    +      ['file.txt', 'txt'],
    +      ['file.tar.gz', 'gz'],
    +      ['.hidden', 'hidden'],
    +      ['file.', ''],
    +      ['file..', ''],
    +      ['file', ''],
    +      ['', ''],
    +      [null, ''],
    +      [undefined, ''],
    +      ['poc.svg.', ''],
    +      ['archive.tar.gz.', ''],
    +    ];
    +    for (const [input, expected] of cases) {
    +      it(`returns ${JSON.stringify(expected)} for ${JSON.stringify(input)}`, () => {
    +        expect(Utils.getFileExtension(input)).toBe(expected);
    +      });
    +    }
    +  });
     });
    
  • spec/vulnerabilities.spec.js+168 0 modified
    @@ -1749,6 +1749,174 @@ describe('Vulnerabilities', () => {
         });
       });
     
    +  describe('(GHSA-7wqv-xjf3-x35v) Stored XSS via trailing-dot filename bypassing file extension blocklist', () => {
    +    const headers = {
    +      'X-Parse-Application-Id': 'test',
    +      'X-Parse-REST-API-Key': 'rest',
    +    };
    +
    +    beforeEach(async () => {
    +      await reconfigureServer({
    +        fileUpload: {
    +          enableForPublic: true,
    +        },
    +      });
    +    });
    +
    +    it('blocks trailing-dot SVG filename with dangerous _ContentType on JSON-body upload', async () => {
    +      const svgContent = Buffer.from(
    +        '<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>'
    +      ).toString('base64');
    +      // No X-Parse-Application-Id header — must be in JSON body to trigger
    +      // _ContentType extraction via the fileViaJSON middleware path.
    +      await expectAsync(
    +        request({
    +          method: 'POST',
    +          url: 'http://localhost:8378/1/files/poc.svg.',
    +          body: JSON.stringify({
    +            _ApplicationId: 'test',
    +            _JavaScriptKey: 'test',
    +            _ContentType: 'image/svg+xml',
    +            base64: svgContent,
    +          }),
    +        }).catch(e => {
    +          throw new Error(e.data.error);
    +        })
    +      ).toBeRejectedWith(jasmine.objectContaining({
    +        message: jasmine.stringMatching(/File upload of extension .+ is disabled/),
    +      }));
    +    });
    +
    +    it('blocks trailing-dot SVG filename with dangerous Content-Type on binary upload', async () => {
    +      await expectAsync(
    +        request({
    +          method: 'POST',
    +          headers: {
    +            ...headers,
    +            'Content-Type': 'image/svg+xml',
    +          },
    +          url: 'http://localhost:8378/1/files/poc.svg.',
    +          body: '<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>',
    +        }).catch(e => {
    +          throw new Error(e.data.error);
    +        })
    +      ).toBeRejectedWith(jasmine.objectContaining({
    +        message: jasmine.stringMatching(/File upload of extension .+ is disabled/),
    +      }));
    +    });
    +
    +    it('blocks filename with mixed trailing dots and whitespace', async () => {
    +      for (const filename of ['poc.svg..', 'poc.svg. ', 'poc.svg . ']) {
    +        await expectAsync(
    +          request({
    +            method: 'POST',
    +            headers: {
    +              ...headers,
    +              'Content-Type': 'image/svg+xml',
    +            },
    +            url: `http://localhost:8378/1/files/${encodeURIComponent(filename)}`,
    +            body: '<svg/>',
    +          }).catch(e => {
    +            throw new Error(e.data.error);
    +          })
    +        ).toBeRejectedWith(jasmine.objectContaining({
    +          message: jasmine.stringMatching(/File upload of extension .+ is disabled/),
    +        }));
    +      }
    +    });
    +
    +    it('still allows trailing-dot filename with allowed Content-Type', async () => {
    +      const adapter = Config.get('test').filesController.adapter;
    +      const spy = spyOn(adapter, 'createFile').and.callThrough();
    +      const response = await request({
    +        method: 'POST',
    +        url: 'http://localhost:8378/1/files/notes.txt.',
    +        body: JSON.stringify({
    +          _ApplicationId: 'test',
    +          _JavaScriptKey: 'test',
    +          _ContentType: 'text/plain',
    +          base64: Buffer.from('hello').toString('base64'),
    +        }),
    +        headers,
    +      });
    +      expect(response.status).toBe(201);
    +      expect(spy).toHaveBeenCalled();
    +    });
    +
    +    it('FilesController treats trailing-dot filename as extensionless when appending derived extension via master key upload', async () => {
    +      await reconfigureServer({
    +        fileUpload: {
    +          enableForPublic: true,
    +        },
    +        preserveFileName: true,
    +      });
    +      const adapter = Config.get('test').filesController.adapter;
    +      const spy = spyOn(adapter, 'createFile').and.callThrough();
    +      const response = await request({
    +        method: 'POST',
    +        url: 'http://localhost:8378/1/files/poc.svg.',
    +        headers: {
    +          'X-Parse-Application-Id': 'test',
    +          'X-Parse-Master-Key': 'test',
    +          'Content-Type': 'image/svg+xml',
    +        },
    +        body: '<svg/>',
    +      });
    +      expect(response.status).toBe(201);
    +      expect(spy).toHaveBeenCalled();
    +      const filenameArg = spy.calls.mostRecent().args[0];
    +      const contentTypeArg = spy.calls.mostRecent().args[2];
    +      // Trailing-dot filename is treated as extensionless: derived extension appended without doubling the dot
    +      expect(filenameArg).toBe('poc.svg.svg');
    +      // Caller-supplied Content-Type is preserved on the extensionless path
    +      expect(contentTypeArg).toBe('image/svg+xml');
    +    });
    +
    +    it('allows trailing-dot filename when no Content-Type is supplied (no XSS path)', async () => {
    +      // Trailing-dot filename with no caller-supplied Content-Type: the
    +      // blocklist gate skips because no extension can be determined, but no
    +      // attacker-controlled Content-Type reaches the storage adapter — only
    +      // the SDK's benign default — so no stored XSS is possible.
    +      const adapter = Config.get('test').filesController.adapter;
    +      const spy = spyOn(adapter, 'createFile').and.callThrough();
    +      const response = await request({
    +        method: 'POST',
    +        headers: {
    +          'X-Parse-Application-Id': 'test',
    +          'X-Parse-REST-API-Key': 'rest',
    +        },
    +        url: 'http://localhost:8378/1/files/poc.svg.',
    +        body: '<svg/>',
    +      });
    +      expect(response.status).toBe(201);
    +      expect(spy).toHaveBeenCalled();
    +      const contentTypeArg = spy.calls.mostRecent().args[2];
    +      expect(contentTypeArg).not.toMatch(/svg|html|xml|xhtml|xslt|mathml/i);
    +    });
    +
    +    it('falls back to raw Content-Type when Content-Type is malformed (no slash)', async () => {
    +      // Exercises the last-resort branch: when both the filename has no usable
    +      // extension AND the Content-Type lacks a "/" subtype to parse, the raw
    +      // Content-Type is used as the extension so a malformed header that
    +      // matches a blocked pattern still trips the blocklist.
    +      await expectAsync(
    +        request({
    +          method: 'POST',
    +          headers: {
    +            ...headers,
    +            'Content-Type': 'svg',
    +          },
    +          url: 'http://localhost:8378/1/files/poc',
    +          body: '<svg/>',
    +        }).catch(e => {
    +          throw new Error(e.data.error);
    +        })
    +      ).toBeRejectedWith(jasmine.objectContaining({
    +        message: jasmine.stringMatching(/File upload of extension svg is disabled/),
    +      }));
    +    });
    +  });
    +
       describe('(GHSA-q3vj-96h2-gwvg) SQL Injection via Increment amount on nested Object field', () => {
         const headers = {
           'Content-Type': 'application/json',
    
  • src/Controllers/FilesController.js+5 4 modified
    @@ -2,8 +2,8 @@
     import { randomHexString } from '../cryptoUtils';
     import AdaptableController from './AdaptableController';
     import { validateFilename, FilesAdapter } from '../Adapters/Files/FilesAdapter';
    -import path from 'path';
     const Parse = require('parse/node').Parse;
    +const Utils = require('../Utils');
     
     const legacyFilesRegex = new RegExp(
       '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-.*'
    @@ -15,12 +15,13 @@ export class FilesController extends AdaptableController {
       }
     
       async createFile(config, filename, data, contentType, options) {
    -    const extname = path.extname(filename);
    -
    +    const extname = Utils.getFileExtension(filename);
         const hasExtension = extname.length > 0;
         const mime = (await import('mime')).default
         if (!hasExtension && contentType && mime.getExtension(contentType)) {
    -      filename = filename + '.' + mime.getExtension(contentType);
    +      // Avoid producing a doubled dot when the filename already ends in one
    +      const separator = filename.endsWith('.') ? '' : '.';
    +      filename = filename + separator + mime.getExtension(contentType);
         } else if (hasExtension) {
           contentType = mime.getType(filename) || contentType;
         }
    
  • src/Routers/FilesRouter.js+12 6 modified
    @@ -423,14 +423,20 @@ export class FilesRouter {
               }
             });
           };
    -      let extension = contentType;
    -      if (filename && filename.includes('.')) {
    -        extension = filename.substring(filename.lastIndexOf('.') + 1);
    -      } else if (contentType && contentType.includes('/')) {
    -        extension = contentType.split('/')[1];
    -      }
    +      let extension = Utils.getFileExtension(filename);
           // Strip MIME parameters (e.g. ";charset=utf-8") and whitespace
           extension = extension?.split(';')[0]?.replace(/\s+/g, '');
    +      // If the filename has no usable extension (no dot, trailing dot, or
    +      // whitespace-only suffix), fall back to the Content-Type subtype — same
    +      // as a dotless filename.
    +      if (!extension && contentType && contentType.includes('/')) {
    +        extension = contentType.split('/')[1]?.split(';')[0]?.replace(/\s+/g, '');
    +      }
    +      // Last resort for malformed inputs (e.g. Content-Type without a slash):
    +      // use the raw Content-Type so the existing rejection path still fires.
    +      if (!extension && contentType) {
    +        extension = contentType.split(';')[0]?.replace(/\s+/g, '');
    +      }
     
           if (extension && !isValidExtension(extension)) {
             next(
    
  • src/Utils.js+17 0 modified
    @@ -576,6 +576,23 @@ class Utils {
             return Math.floor(num);
         }
       }
    +
    +  /**
    +   * Returns the file extension as the substring after the last dot in the
    +   * filename. A trailing dot or a filename without a dot yields an empty
    +   * string. Callers apply any further normalization (whitespace, MIME
    +   * parameters, etc.) for their use case — this is a pure parser, not a
    +   * policy.
    +   *
    +   * @param {string} filename
    +   * @returns {string} the extension, or `''` if none
    +   */
    +  static getFileExtension(filename) {
    +    if (!filename || !filename.includes('.')) {
    +      return '';
    +    }
    +    return filename.substring(filename.lastIndexOf('.') + 1);
    +  }
     }
     
     module.exports = Utils;
    

Vulnerability mechanics

Synthesis attempt was rejected by the grounding validator. Re-run pending.

References

3

News mentions

0

No linked articles in our index yet.