VYPR
Critical severityNVD Advisory· Published Sep 27, 2021· Updated Aug 4, 2024

Prototype pollution in aurelia-path

CVE-2021-41097

Description

aurelia-path is part of the Aurelia platform and contains utilities for path manipulation. There is a prototype pollution vulnerability in aurelia-path before version 1.1.7. The vulnerability exposes Aurelia application that uses aurelia-path package to parse a string. The majority of this will be Aurelia applications that employ the aurelia-router package. An example is this could allow an attacker to change the prototype of base object class Object by tricking an application to parse the following URL: https://aurelia.io/blog/?__proto__[asdf]=asdf. The problem is patched in version 1.1.7.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
aurelia-pathnpm
< 1.1.71.1.7

Affected products

1

Patches

1
7c4e235433a4

fix: security issue gh closes #44

https://github.com/aurelia/pathbigoponJun 9, 2021via ghsa
3 files changed · +351 315
  • src/index.ts+7 4 renamed
    @@ -158,7 +158,7 @@ function buildParam(key: string, value: any, traditional?: boolean): Array<strin
     * @param traditional Boolean Use the old URI template standard (RFC6570)
     * @returns The generated query string, excluding leading '?'.
     */
    -export function buildQueryString(params: Object, traditional?: Boolean): string {
    +export function buildQueryString(params?: Object, traditional?: boolean): string {
       let pairs = [];
       let keys = Object.keys(params || {}).sort();
       for (let i = 0, len = keys.length; i < len; i++) {
    @@ -203,16 +203,19 @@ function processScalarParam(existedParam: Object, value: Object): Object {
     * @param keys Collection of keys related to this parameter.
     * @param value Parameter value to append.
     */
    -function parseComplexParam(queryParams: Object, keys: Object, value: any): void {
    +function parseComplexParam(queryParams: Object, keys: (string | number)[], value: any): void {
       let currentParams = queryParams;
       let keysLastIndex = keys.length - 1;
       for (let j = 0; j <= keysLastIndex; j++) {
    -    let key = keys[j] === '' ? currentParams.length : keys[j];
    +    let key = keys[j] === '' ? (currentParams as any).length : keys[j];
    +    if (key === '__proto__') {
    +      throw new Error('Prototype pollution detected.');
    +    }
         if (j < keysLastIndex) {
           // The value has to be an array or a false value
           // It can happen that the value is no array if the key was repeated with traditional style like `list=1&list[]=2`
           let prevValue = !currentParams[key] || typeof currentParams[key] === 'object' ? currentParams[key] : [currentParams[key]];
    -      currentParams = currentParams[key] = prevValue || (isNaN(keys[j + 1]) ? {} : []);
    +      currentParams = currentParams[key] = prevValue || (isNaN(keys[j + 1] as number) ? {} : []);
         } else {
           currentParams = currentParams[key] = value;
         }
    
  • test/path.spec.js+0 311 removed
    @@ -1,311 +0,0 @@
    -import { relativeToFile, join, parseQueryString, buildQueryString } from '../src/index';
    -
    -describe('relativeToFile', () => {
    -  it('can make a dot path relative to a simple file', () => {
    -    var file = 'some/file.html';
    -    var path = './other/module';
    -
    -    expect(relativeToFile(path, file)).toBe('some/other/module');
    -  });
    -
    -  it('can make a dot path relative to an absolute file', () => {
    -    var file = 'http://durandal.io/some/file.html';
    -    var path = './other/module';
    -
    -    expect(relativeToFile(path, file)).toBe('http://durandal.io/some/other/module');
    -  });
    -
    -  it('can make a double dot path relative to an absolute file', () => {
    -    var file = 'http://durandal.io/some/file.html';
    -    var path = '../other/module';
    -
    -    expect(relativeToFile(path, file)).toBe('http://durandal.io/other/module');
    -  });
    -
    -  it('returns path if null file provided', () => {
    -    var file = null;
    -    var path = 'module';
    -
    -    expect(relativeToFile(path, file)).toBe('module');
    -  });
    -
    -  it('returns path if empty file provided', () => {
    -    var file = '';
    -    var path = 'module';
    -
    -    expect(relativeToFile(path, file)).toBe('module');
    -  });
    -});
    -
    -describe('join', () => {
    -  it('can combine two simple paths', () => {
    -    var path1 = 'one';
    -    var path2 = 'two';
    -
    -    expect(join(path1, path2)).toBe('one/two');
    -  });
    -
    -  it('can combine an absolute path and a simple path', () => {
    -    var path1 = '/one';
    -    var path2 = 'two';
    -
    -    expect(join(path1, path2)).toBe('/one/two');
    -  });
    -
    -  it('can combine an absolute path and a simple path with slash', () => {
    -    var path1 = '/one';
    -    var path2 = '/two';
    -
    -    expect(join(path1, path2)).toBe('/one/two');
    -  });
    -
    -  it('can combine a single slash and a simple path', () => {
    -    var path1 = '/';
    -    var path2 = 'two';
    -
    -    expect(join(path1, path2)).toBe('/two');
    -  });
    -
    -  it('can combine a single slash and a simple path with slash', () => {
    -    var path1 = '/';
    -    var path2 = '/two';
    -
    -    expect(join(path1, path2)).toBe('/two');
    -  });
    -
    -  it('can combine an absolute path with protocol and a simple path', () => {
    -    var path1 = 'http://durandal.io';
    -    var path2 = 'two';
    -
    -    expect(join(path1, path2)).toBe('http://durandal.io/two');
    -  });
    -
    -  it('can combine an absolute path with protocol and a simple path with slash', () => {
    -    var path1 = 'http://durandal.io';
    -    var path2 = '/two';
    -
    -    expect(join(path1, path2)).toBe('http://durandal.io/two');
    -  });
    -
    -  it('can combine an absolute path and a simple path with a dot', () => {
    -    var path1 = 'http://durandal.io';
    -    var path2 = './two';
    -
    -    expect(join(path1, path2)).toBe('http://durandal.io/two');
    -  });
    -
    -  it('can combine a simple path and a relative path', () => {
    -    var path1 = 'one';
    -    var path2 = '../two';
    -
    -    expect(join(path1, path2)).toBe('two');
    -  });
    -
    -  it('can combine an absolute path and a relative path', () => {
    -    var path1 = 'http://durandal.io/somewhere';
    -    var path2 = '../two';
    -
    -    expect(join(path1, path2)).toBe('http://durandal.io/two');
    -  });
    -
    -  it('can combine a protocol independent path and a simple path', () => {
    -    var path1 = '//durandal.io';
    -    var path2 = 'two';
    -
    -    expect(join(path1, path2)).toBe('//durandal.io/two');
    -  });
    -
    -  it('can combine a protocol independent path and a simple path with slash', () => {
    -    var path1 = '//durandal.io';
    -    var path2 = '/two';
    -
    -    expect(join(path1, path2)).toBe('//durandal.io/two');
    -  });
    -
    -  it('can combine a protocol independent path and a simple path with a dot', () => {
    -    var path1 = '//durandal.io';
    -    var path2 = './two';
    -
    -    expect(join(path1, path2)).toBe('//durandal.io/two');
    -  });
    -
    -  it('can combine a protocol independent path and a relative path', () => {
    -    var path1 = '//durandal.io/somewhere';
    -    var path2 = '../two';
    -
    -    expect(join(path1, path2)).toBe('//durandal.io/two');
    -  });
    -
    -  it('can combine a complex path and a relative path', () => {
    -    var path1 = 'one/three';
    -    var path2 = '../two';
    -
    -    expect(join(path1, path2)).toBe('one/two');
    -  });
    -
    -  it('returns path2 if path1 null', () => {
    -    var path1 = null;
    -    var path2 = 'two';
    -
    -    expect(join(path1, path2)).toBe('two');
    -  });
    -
    -  it('returns path2 if path1 empty', () => {
    -    var path1 = '';
    -    var path2 = 'two';
    -
    -    expect(join(path1, path2)).toBe('two');
    -  });
    -
    -  it('returns path1 if path2 null', () => {
    -    var path1 = 'one';
    -    var path2 = null;
    -
    -    expect(join(path1, path2)).toBe('one');
    -  });
    -
    -  it('returns path1 if path2 empty', () => {
    -    var path1 = 'one';
    -    var path2 = '';
    -
    -    expect(join(path1, path2)).toBe('one');
    -  });
    -
    -  it('should retain leading .. in path1', () => {
    -    var path1 = '../one';
    -    var path2 = './two';
    -
    -    expect(join(path1, path2)).toBe('../one/two');
    -  });
    -
    -  it('should retain consecutive leading .. in path1', () => {
    -    var path1 = '../../one';
    -    var path2 = './two';
    -
    -    expect(join(path1, path2)).toBe('../../one/two');
    -  });
    -
    -  it('should handle .. in path1 and path2', () => {
    -    var path1 = '../../one';
    -    var path2 = '../two';
    -
    -    expect(join(path1, path2)).toBe('../../two');
    -  });
    -
    -  it('should merge .. in path1 and path2', () => {
    -    var path1 = '../../one';
    -    var path2 = '../../two';
    -
    -    expect(join(path1, path2)).toBe('../../../two');
    -  });
    -
    -  it('should retain consecutive leading .. but not other .. in path1', () => {
    -    var path1 = '../../one/../three';
    -    var path2 = './two';
    -
    -    expect(join(path1, path2)).toBe('../../three/two');
    -  });
    -
    -  it('should respect a trailing slash', () => {
    -    var path1 = 'one/';
    -    var path2 = 'two/';
    -
    -    expect(join(path1, path2)).toBe('one/two/');
    -  });
    -
    -  it('should respect file:/// protocol with three slashes (empty host)', () => {
    -    var path1 = 'file:///one';
    -    var path2 = '/two';
    -
    -    expect(join(path1, path2)).toBe('file:///one/two');
    -  });
    -
    -  it('should respect file:// protocol with two slashes (host given)', () => {
    -    var path1 = 'file://localhost:8080';
    -    var path2 = '/two';
    -
    -    expect(join(path1, path2)).toBe('file://localhost:8080/two');
    -  });
    -
    -  it('should allow scheme-relative URL that uses colons in the path', () => {
    -    var path1 = '//localhost/one:/';
    -    var path2 = '/two';
    -
    -    expect(join(path1, path2)).toBe('//localhost/one:/two');
    -  });
    -
    -  it('should not add more than two leading slashes to http:// protocol', () => {
    -    var path1 = 'http:///';
    -    var path2 = '/two';
    -
    -    expect(join(path1, path2)).toBe('http://two');
    -  });
    -});
    -
    -describe('query strings', () => {
    -  it('should build query strings', () => {
    -    let gen = buildQueryString;
    -
    -    expect(gen()).toBe('');
    -    expect(gen(null)).toBe('');
    -    expect(gen({})).toBe('');
    -    expect(gen({ a: null })).toBe('');
    -
    -    expect(gen({ '': 'a' })).toBe('=a');
    -    expect(gen({ a: 'b' })).toBe('a=b');
    -    expect(gen({ a: 'b', c: 'd' })).toBe('a=b&c=d');
    -    expect(gen({ a: 'b', c: 'd' }, true)).toBe('a=b&c=d');
    -    expect(gen({ a: 'b', c: null })).toBe('a=b');
    -    expect(gen({ a: 'b', c: null }, true)).toBe('a=b');
    -
    -    expect(gen({ a: ['b', 'c'] })).toBe('a%5B%5D=b&a%5B%5D=c');
    -    expect(gen({ a: ['b', 'c'] }, true)).toBe('a=b&a=c');
    -    expect(gen({ '&': ['b', 'c'] })).toBe('%26%5B%5D=b&%26%5B%5D=c');
    -    expect(gen({ '&': ['b', 'c'] }, true)).toBe('%26=b&%26=c');
    -
    -    expect(gen({ a: '&' })).toBe('a=%26');
    -    expect(gen({ '&': 'a' })).toBe('%26=a');
    -    expect(gen({ a: true })).toBe('a=true');
    -    expect(gen({ '$test': true })).toBe('$test=true');
    -
    -    expect(gen({ obj: { a: 5, b: "str", c: false } })).toBe('obj%5Ba%5D=5&obj%5Bb%5D=str&obj%5Bc%5D=false');
    -    expect(gen({ obj: { a: 5, b: "str", c: false } }, true)).toBe('obj=%5Bobject%20Object%5D');
    -    expect(gen({ obj:{ a: 5, b: undefined}})).toBe('obj%5Ba%5D=5');
    -
    -    expect(gen({a: {b: ['c','d', ['f', 'g']]}})).toBe('a%5Bb%5D%5B%5D=c&a%5Bb%5D%5B%5D=d&a%5Bb%5D%5B2%5D%5B%5D=f&a%5Bb%5D%5B2%5D%5B%5D=g');
    -    expect(gen({a: {b: ['c','d', ['f', 'g']]}}, true)).toBe('a=%5Bobject%20Object%5D');
    -    expect(gen({a: ['c','d', ['f', 'g']]}, true)).toBe('a=c&a=d&a=f%2Cg');
    -    expect(gen({a: ['c','d', {f: 'g'}]}, true)).toBe('a=c&a=d&a=%5Bobject%20Object%5D');
    -  });
    -
    -  it('should parse query strings', () => {
    -    let parse = parseQueryString;
    -
    -    expect(parse('')).toEqual({});
    -    expect(parse('=')).toEqual({});
    -    expect(parse('&')).toEqual({});
    -    expect(parse('?')).toEqual({});
    -
    -    expect(parse('a')).toEqual({ a: true });
    -    expect(parse('a&b')).toEqual({ a: true, b: true });
    -    expect(parse('a=')).toEqual({ a: '' });
    -    expect(parse('a=&b=')).toEqual({ a: '', b: '' });
    -
    -    expect(parse('a=b')).toEqual({ a: 'b' });
    -    expect(parse('a=b&c=d')).toEqual({ a: 'b', c: 'd' });
    -    expect(parse('a=b&&c=d')).toEqual({ a: 'b', c: 'd' });
    -    expect(parse('a=b&a=c')).toEqual({ a: ['b', 'c'] });
    -
    -    expect(parse('a=b&c=d=')).toEqual({ a: 'b', c: 'd' });
    -    expect(parse('a=b&c=d==')).toEqual({ a: 'b', c: 'd' });
    -
    -    expect(parse('a=%26')).toEqual({ a: '&' });
    -    expect(parse('%26=a')).toEqual({ '&': 'a' });
    -    expect(parse('%26[]=b&%26[]=c')).toEqual({ '&': ['b', 'c'] });
    -
    -    expect(parse('a[b]=c&a[d]=e')).toEqual({a: {b: 'c', d: 'e'}});
    -    expect(parse('a[b][c][d]=e')).toEqual({a: {b: {c: {d: 'e'}}}});
    -    expect(parse('a[b][]=c&a[b][]=d&a[b][2][]=f&a[b][2][]=g')).toEqual({a: {b: ['c','d', ['f', 'g']]}});
    -    expect(parse('a[0]=b')).toEqual({a: ['b']});
    -  });
    -});
    
  • test/path.spec.ts+344 0 added
    @@ -0,0 +1,344 @@
    +import { relativeToFile, join, parseQueryString, buildQueryString } from '../src/index';
    +import assert from 'assert';
    +
    +const expect = (input: any) => {
    +  return {
    +    toBe: (output: any) => {
    +      assert.strictEqual(input, output);
    +    },
    +    toEqual: (output: any) => {
    +      assert.deepStrictEqual(input, output)
    +    }
    +  }
    +}
    +
    +describe('relativeToFile', () => {
    +  it('can make a dot path relative to a simple file', () => {
    +    const file = 'some/file.html';
    +    const path = './other/module';
    +
    +    expect(relativeToFile(path, file)).toBe('some/other/module');
    +  });
    +
    +  it('can make a dot path relative to an absolute file', () => {
    +    const file = 'http://durandal.io/some/file.html';
    +    const path = './other/module';
    +
    +    expect(relativeToFile(path, file)).toBe('http://durandal.io/some/other/module');
    +  });
    +
    +  it('can make a double dot path relative to an absolute file', () => {
    +    const file = 'http://durandal.io/some/file.html';
    +    const path = '../other/module';
    +
    +    expect(relativeToFile(path, file)).toBe('http://durandal.io/other/module');
    +  });
    +
    +  it('returns path if null file provided', () => {
    +    const file = null;
    +    const path = 'module';
    +
    +    expect(relativeToFile(path, file)).toBe('module');
    +  });
    +
    +  it('returns path if empty file provided', () => {
    +    const file = '';
    +    const path = 'module';
    +
    +    expect(relativeToFile(path, file)).toBe('module');
    +  });
    +});
    +
    +describe('join', () => {
    +  it('can combine two simple paths', () => {
    +    const path1 = 'one';
    +    const path2 = 'two';
    +
    +    expect(join(path1, path2)).toBe('one/two');
    +  });
    +
    +  it('can combine an absolute path and a simple path', () => {
    +    const path1 = '/one';
    +    const path2 = 'two';
    +
    +    expect(join(path1, path2)).toBe('/one/two');
    +  });
    +
    +  it('can combine an absolute path and a simple path with slash', () => {
    +    const path1 = '/one';
    +    const path2 = '/two';
    +
    +    expect(join(path1, path2)).toBe('/one/two');
    +  });
    +
    +  it('can combine a single slash and a simple path', () => {
    +    const path1 = '/';
    +    const path2 = 'two';
    +
    +    expect(join(path1, path2)).toBe('/two');
    +  });
    +
    +  it('can combine a single slash and a simple path with slash', () => {
    +    const path1 = '/';
    +    const path2 = '/two';
    +
    +    expect(join(path1, path2)).toBe('/two');
    +  });
    +
    +  it('can combine an absolute path with protocol and a simple path', () => {
    +    const path1 = 'http://durandal.io';
    +    const path2 = 'two';
    +
    +    expect(join(path1, path2)).toBe('http://durandal.io/two');
    +  });
    +
    +  it('can combine an absolute path with protocol and a simple path with slash', () => {
    +    const path1 = 'http://durandal.io';
    +    const path2 = '/two';
    +
    +    expect(join(path1, path2)).toBe('http://durandal.io/two');
    +  });
    +
    +  it('can combine an absolute path and a simple path with a dot', () => {
    +    const path1 = 'http://durandal.io';
    +    const path2 = './two';
    +
    +    expect(join(path1, path2)).toBe('http://durandal.io/two');
    +  });
    +
    +  it('can combine a simple path and a relative path', () => {
    +    const path1 = 'one';
    +    const path2 = '../two';
    +
    +    expect(join(path1, path2)).toBe('two');
    +  });
    +
    +  it('can combine an absolute path and a relative path', () => {
    +    const path1 = 'http://durandal.io/somewhere';
    +    const path2 = '../two';
    +
    +    expect(join(path1, path2)).toBe('http://durandal.io/two');
    +  });
    +
    +  it('can combine a protocol independent path and a simple path', () => {
    +    const path1 = '//durandal.io';
    +    const path2 = 'two';
    +
    +    expect(join(path1, path2)).toBe('//durandal.io/two');
    +  });
    +
    +  it('can combine a protocol independent path and a simple path with slash', () => {
    +    const path1 = '//durandal.io';
    +    const path2 = '/two';
    +
    +    expect(join(path1, path2)).toBe('//durandal.io/two');
    +  });
    +
    +  it('can combine a protocol independent path and a simple path with a dot', () => {
    +    const path1 = '//durandal.io';
    +    const path2 = './two';
    +
    +    expect(join(path1, path2)).toBe('//durandal.io/two');
    +  });
    +
    +  it('can combine a protocol independent path and a relative path', () => {
    +    const path1 = '//durandal.io/somewhere';
    +    const path2 = '../two';
    +
    +    expect(join(path1, path2)).toBe('//durandal.io/two');
    +  });
    +
    +  it('can combine a complex path and a relative path', () => {
    +    const path1 = 'one/three';
    +    const path2 = '../two';
    +
    +    expect(join(path1, path2)).toBe('one/two');
    +  });
    +
    +  it('returns path2 if path1 null', () => {
    +    const path1 = null;
    +    const path2 = 'two';
    +
    +    expect(join(path1, path2)).toBe('two');
    +  });
    +
    +  it('returns path2 if path1 empty', () => {
    +    const path1 = '';
    +    const path2 = 'two';
    +
    +    expect(join(path1, path2)).toBe('two');
    +  });
    +
    +  it('returns path1 if path2 null', () => {
    +    const path1 = 'one';
    +    const path2 = null;
    +
    +    expect(join(path1, path2)).toBe('one');
    +  });
    +
    +  it('returns path1 if path2 empty', () => {
    +    const path1 = 'one';
    +    const path2 = '';
    +
    +    expect(join(path1, path2)).toBe('one');
    +  });
    +
    +  it('should retain leading .. in path1', () => {
    +    const path1 = '../one';
    +    const path2 = './two';
    +
    +    expect(join(path1, path2)).toBe('../one/two');
    +  });
    +
    +  it('should retain consecutive leading .. in path1', () => {
    +    const path1 = '../../one';
    +    const path2 = './two';
    +
    +    expect(join(path1, path2)).toBe('../../one/two');
    +  });
    +
    +  it('should handle .. in path1 and path2', () => {
    +    const path1 = '../../one';
    +    const path2 = '../two';
    +
    +    expect(join(path1, path2)).toBe('../../two');
    +  });
    +
    +  it('should merge .. in path1 and path2', () => {
    +    const path1 = '../../one';
    +    const path2 = '../../two';
    +
    +    expect(join(path1, path2)).toBe('../../../two');
    +  });
    +
    +  it('should retain consecutive leading .. but not other .. in path1', () => {
    +    const path1 = '../../one/../three';
    +    const path2 = './two';
    +
    +    expect(join(path1, path2)).toBe('../../three/two');
    +  });
    +
    +  it('should respect a trailing slash', () => {
    +    const path1 = 'one/';
    +    const path2 = 'two/';
    +
    +    expect(join(path1, path2)).toBe('one/two/');
    +  });
    +
    +  it('should respect file:/// protocol with three slashes (empty host)', () => {
    +    const path1 = 'file:///one';
    +    const path2 = '/two';
    +
    +    expect(join(path1, path2)).toBe('file:///one/two');
    +  });
    +
    +  it('should respect file:// protocol with two slashes (host given)', () => {
    +    const path1 = 'file://localhost:8080';
    +    const path2 = '/two';
    +
    +    expect(join(path1, path2)).toBe('file://localhost:8080/two');
    +  });
    +
    +  it('should allow scheme-relative URL that uses colons in the path', () => {
    +    const path1 = '//localhost/one:/';
    +    const path2 = '/two';
    +
    +    expect(join(path1, path2)).toBe('//localhost/one:/two');
    +  });
    +
    +  it('should not add more than two leading slashes to http:// protocol', () => {
    +    const path1 = 'http:///';
    +    const path2 = '/two';
    +
    +    expect(join(path1, path2)).toBe('http://two');
    +  });
    +});
    +
    +describe('query strings', () => {
    +  interface IBuildTestCase {
    +    input: any;
    +    traditional?: boolean;
    +    output: string;
    +  }
    +  const testCases: IBuildTestCase[] = [
    +    { input: undefined, output: '' },
    +    { input: null, output: '' },
    +    { input: {}, output: '' },
    +    { input: { a: null }, output: '' },
    +    { input: { '': 'a' }, output: '=a' },
    +    { input: { a: 'b' }, output: 'a=b' },
    +    { input: { a: 'b', c: 'd' }, output: 'a=b&c=d' },
    +    { input: { a: 'b', c: 'd' }, traditional: true, output: 'a=b&c=d' },
    +    { input: { a: 'b', c: null }, output: 'a=b' },
    +    { input: { a: 'b', c: null }, traditional: true, output: 'a=b' },
    +    { input: { a: ['b', 'c'] }, output: 'a%5B%5D=b&a%5B%5D=c' },
    +    { input: { a: ['b', 'c'] }, traditional: true, output: 'a=b&a=c' },
    +    { input: { '&': ['b', 'c'] }, output: '%26%5B%5D=b&%26%5B%5D=c' },
    +    { input: { '&': ['b', 'c'] }, traditional: true, output: '%26=b&%26=c' },
    +
    +    { input: { a: '&' }, output: 'a=%26' },
    +    { input: { '&': 'a' }, output: '%26=a' },
    +    { input: { a: true }, output: 'a=true' },
    +    { input: { '$test': true }, output: '$test=true' },
    +
    +    { input: { obj: { a: 5, b: "str", c: false } }, output: 'obj%5Ba%5D=5&obj%5Bb%5D=str&obj%5Bc%5D=false' },
    +    { input: { obj: { a: 5, b: "str", c: false } }, traditional: true, output: 'obj=%5Bobject%20Object%5D' },
    +    { input: { obj: { a: 5, b: undefined } }, output: 'obj%5Ba%5D=5' },
    +
    +    { input: { a: { b: ['c', 'd', ['f', 'g']] } }, output: 'a%5Bb%5D%5B%5D=c&a%5Bb%5D%5B%5D=d&a%5Bb%5D%5B2%5D%5B%5D=f&a%5Bb%5D%5B2%5D%5B%5D=g' },
    +    { input: { a: { b: ['c', 'd', ['f', 'g']] } }, traditional: true, output: 'a=%5Bobject%20Object%5D' },
    +    { input: { a: ['c', 'd', ['f', 'g']] }, traditional: true, output: 'a=c&a=d&a=f%2Cg' },
    +    { input: { a: ['c', 'd', { f: 'g' }] }, traditional: true, output: 'a=c&a=d&a=%5Bobject%20Object%5D' },
    +  ];
    +
    +  testCases.forEach(({ input, output, traditional }) => {
    +    it(`builds ${input instanceof Object ? JSON.stringify(input) : input} to "${output}"`, () => {
    +      expect(buildQueryString(input, traditional)).toBe(output);
    +    });
    +  });
    +
    +  interface IParseTestCase {
    +    input: string;
    +    output: any;
    +  }
    +
    +  const parseTestCases: IParseTestCase[] = [
    +    { input: '', output: {} },
    +    { input: '=', output: {} },
    +    { input: '&', output: {} },
    +    { input: '?', output: {} },
    +
    +    { input: 'a', output: { a: true } },
    +    { input: 'a&b', output: { a: true, b: true } },
    +    { input: 'a=', output: { a: '' } },
    +    { input: 'a=&b=', output: { a: '', b: '' } },
    +
    +    { input: 'a=b', output: { a: 'b' } },
    +    { input: 'a=b&c=d', output: { a: 'b', c: 'd' } },
    +    { input: 'a=b&&c=d', output: { a: 'b', c: 'd' } },
    +    { input: 'a=b&a=c', output: { a: ['b', 'c'] } },
    +
    +    { input: 'a=b&c=d=', output: { a: 'b', c: 'd' } },
    +    { input: 'a=b&c=d==', output: { a: 'b', c: 'd' } },
    +
    +    { input: 'a=%26', output: { a: '&' } },
    +    { input: '%26=a', output: { '&': 'a' } },
    +    { input: '%26[]=b&%26[]=c', output: { '&': ['b', 'c'] } },
    +
    +    { input: 'a[b]=c&a[d]=e', output: { a: { b: 'c', d: 'e' } } },
    +    { input: 'a[b][c][d]=e', output: { a: { b: { c: { d: 'e' } } } } },
    +    { input: 'a[b][]=c&a[b][]=d&a[b][2][]=f&a[b][2][]=g', output: { a: { b: ['c', 'd', ['f', 'g']] } } },
    +    { input: 'a[0]=b', output: { a: ['b'] } },
    +  ];
    +
    +  parseTestCases.forEach(({ input, output }) => {
    +    it(`parses ${input} to "${JSON.stringify(output)}"`, () => {
    +      expect(parseQueryString(input)).toEqual(output);
    +    });
    +  });
    +
    +  it('does not pollute prototype', () => {
    +    const path1 = '__proto__[asdf]=asdf';
    +    assert.throws(() => parseQueryString(path1), 'Prototype pollution detected');
    +  });
    +});
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.