@@ -1856,6 +1856,211 @@ describe('AuthData Security Tests', () => {
18561856 expect ( error . code ) . toBeDefined ( ) ;
18571857 }
18581858 } ) ;
1859+
1860+ it ( 'should reject provider names with prototype pollution attempts' , async ( ) => {
1861+ mockFetch ( mockGpgamesLogin ( ) ) ;
1862+ const user = await Parse . User . logInWith ( 'gpgames' , {
1863+ authData : { id : MOCK_USER_ID , code : 'C1' } ,
1864+ } ) ;
1865+ const sessionToken = user . getSessionToken ( ) ;
1866+
1867+ // Try to use constructor as provider name (enumerable by default)
1868+ try {
1869+ await user . save (
1870+ { authData : { constructor : { id : 'test' } } } ,
1871+ { sessionToken }
1872+ ) ;
1873+ // If it doesn't throw, verify that constructor was not stored as malicious data
1874+ await user . fetch ( { sessionToken } ) ;
1875+ const authData = user . get ( 'authData' ) ;
1876+ // Constructor should not be in authData with our test data
1877+ // (either rejected or not configured, or stored but not as our test object)
1878+ if ( authData && authData . constructor ) {
1879+ const isOurTestData = typeof authData . constructor === 'object' &&
1880+ authData . constructor . id === 'test' ;
1881+ expect ( isOurTestData ) . toBe ( false ) ;
1882+ }
1883+ } catch ( error ) {
1884+ // Should reject with INVALID_KEY_NAME (either from validateAuthData or diffAuthData)
1885+ // If provider is not configured, may throw UNSUPPORTED_SERVICE, which is also acceptable
1886+ expect ( [ Parse . Error . INVALID_KEY_NAME , Parse . Error . UNSUPPORTED_SERVICE ] ) . toContain ( error . code ) ;
1887+ if ( error . code === Parse . Error . INVALID_KEY_NAME ) {
1888+ expect ( error . message ) . toContain ( 'Invalid provider name' ) ;
1889+ }
1890+ }
1891+
1892+ // Try to use prototype as provider name
1893+ try {
1894+ await user . save (
1895+ { authData : { prototype : { id : 'test' } } } ,
1896+ { sessionToken }
1897+ ) ;
1898+ // If it doesn't throw, verify that prototype was not stored as malicious data
1899+ await user . fetch ( { sessionToken } ) ;
1900+ const authData = user . get ( 'authData' ) ;
1901+ // Prototype should not be in authData with our test data
1902+ // (either rejected or not configured, or stored but not as our test object)
1903+ if ( authData && authData . prototype ) {
1904+ const isOurTestData = typeof authData . prototype === 'object' &&
1905+ authData . prototype . id === 'test' ;
1906+ expect ( isOurTestData ) . toBe ( false ) ;
1907+ }
1908+ } catch ( error ) {
1909+ // Should reject with INVALID_KEY_NAME (either from validateAuthData or diffAuthData)
1910+ // If provider is not configured, may throw UNSUPPORTED_SERVICE, which is also acceptable
1911+ expect ( [ Parse . Error . INVALID_KEY_NAME , Parse . Error . UNSUPPORTED_SERVICE ] ) . toContain ( error . code ) ;
1912+ if ( error . code === Parse . Error . INVALID_KEY_NAME ) {
1913+ expect ( error . message ) . toContain ( 'Invalid provider name' ) ;
1914+ }
1915+ }
1916+
1917+ // Note: __proto__ is not enumerable by default in Object.keys(),
1918+ // so it won't be processed by diffAuthData. However, if it were
1919+ // enumerable (via Object.defineProperty with enumerable: true),
1920+ // it would be rejected by our validation. This is acceptable because
1921+ // normal object literals don't have enumerable __proto__.
1922+ } ) ;
1923+
1924+ it ( 'should reject provider names with NoSQL injection attempts' , async ( ) => {
1925+ mockFetch ( mockGpgamesLogin ( ) ) ;
1926+ const user = await Parse . User . logInWith ( 'gpgames' , {
1927+ authData : { id : MOCK_USER_ID , code : 'C1' } ,
1928+ } ) ;
1929+ const sessionToken = user . getSessionToken ( ) ;
1930+
1931+ // Try to use dots in provider name (NoSQL injection)
1932+ const maliciousProviders = [
1933+ 'gpgames.id' ,
1934+ 'gpgames.access_token' ,
1935+ 'provider.name.field' ,
1936+ 'test.test' ,
1937+ ] ;
1938+
1939+ for ( const maliciousProvider of maliciousProviders ) {
1940+ try {
1941+ await user . save (
1942+ { authData : { [ maliciousProvider ] : { id : 'test' } } } ,
1943+ { sessionToken }
1944+ ) ;
1945+ fail ( `Should have rejected provider name with dots: ${ maliciousProvider } ` ) ;
1946+ } catch ( error ) {
1947+ expect ( error . code ) . toBe ( Parse . Error . INVALID_KEY_NAME ) ;
1948+ expect ( error . message ) . toContain ( 'Invalid provider name' ) ;
1949+ }
1950+ }
1951+ } ) ;
1952+
1953+ it ( 'should reject provider names with special characters' , async ( ) => {
1954+ mockFetch ( mockGpgamesLogin ( ) ) ;
1955+ const user = await Parse . User . logInWith ( 'gpgames' , {
1956+ authData : { id : MOCK_USER_ID , code : 'C1' } ,
1957+ } ) ;
1958+ const sessionToken = user . getSessionToken ( ) ;
1959+
1960+ // Try various special characters
1961+ const maliciousProviders = [
1962+ 'provider-name' , // hyphen
1963+ 'provider name' , // space
1964+ 'provider@name' , // @
1965+ 'provider#name' , // #
1966+ 'provider$name' , // $
1967+ 'provider[name]' , // brackets
1968+ 'provider{name}' , // braces
1969+ 'provider/name' , // slash
1970+ 'provider\\name' , // backslash
1971+ 'provider.name' , // dot
1972+ '123provider' , // starts with number
1973+ '' , // empty string
1974+ ] ;
1975+
1976+ for ( const maliciousProvider of maliciousProviders ) {
1977+ try {
1978+ await user . save (
1979+ { authData : { [ maliciousProvider ] : { id : 'test' } } } ,
1980+ { sessionToken }
1981+ ) ;
1982+ fail ( `Should have rejected invalid provider name: ${ maliciousProvider } ` ) ;
1983+ } catch ( error ) {
1984+ expect ( error . code ) . toBe ( Parse . Error . INVALID_KEY_NAME ) ;
1985+ expect ( error . message ) . toContain ( 'Invalid provider name' ) ;
1986+ }
1987+ }
1988+ } ) ;
1989+
1990+ it ( 'should accept valid provider names' , async ( ) => {
1991+ mockFetch ( mockGpgamesLogin ( ) ) ;
1992+ const user = await Parse . User . logInWith ( 'gpgames' , {
1993+ authData : { id : MOCK_USER_ID , code : 'C1' } ,
1994+ } ) ;
1995+ const sessionToken = user . getSessionToken ( ) ;
1996+
1997+ // Valid provider names should work
1998+ const validProviders = [
1999+ 'gpgames' ,
2000+ 'instagram' ,
2001+ 'provider_name' ,
2002+ 'providerName' ,
2003+ 'ProviderName' ,
2004+ 'provider123' ,
2005+ 'p' ,
2006+ 'provider_name_123' ,
2007+ ] ;
2008+
2009+ for ( const validProvider of validProviders ) {
2010+ // Skip if provider is not configured (will fail with different error)
2011+ if ( validProvider === 'gpgames' || validProvider === 'instagram' ) {
2012+ continue ;
2013+ }
2014+
2015+ try {
2016+ await user . save (
2017+ { authData : { [ validProvider ] : { id : 'test' } } } ,
2018+ { sessionToken }
2019+ ) ;
2020+ // Should not throw INVALID_KEY_NAME error
2021+ // May throw UNSUPPORTED_SERVICE if provider not configured, which is expected
2022+ } catch ( error ) {
2023+ // Should not be INVALID_KEY_NAME for valid provider names
2024+ expect ( error . code ) . not . toBe ( Parse . Error . INVALID_KEY_NAME ) ;
2025+ }
2026+ }
2027+ } ) ;
2028+
2029+ it ( 'should prevent injection when linking multiple providers' , async ( ) => {
2030+ mockFetch ( mockGpgamesLogin ( ) ) ;
2031+ const user = await Parse . User . logInWith ( 'gpgames' , {
2032+ authData : { id : MOCK_USER_ID , code : 'C1' } ,
2033+ } ) ;
2034+ const sessionToken = user . getSessionToken ( ) ;
2035+
2036+ // Try to link malicious provider name
2037+ // Use constructor which should be enumerable
2038+ try {
2039+ await user . save (
2040+ {
2041+ authData : {
2042+ constructor : { id : 'test' } ,
2043+ } ,
2044+ } ,
2045+ { sessionToken }
2046+ ) ;
2047+ // If it doesn't throw, verify that constructor was not stored
2048+ await user . fetch ( { sessionToken } ) ;
2049+ const authData = user . get ( 'authData' ) ;
2050+ // Constructor should not be in authData (either rejected or not configured)
2051+ const hasConstructor = authData && authData . constructor &&
2052+ typeof authData . constructor === 'object' &&
2053+ authData . constructor . id === 'test' ;
2054+ expect ( hasConstructor ) . toBe ( false ) ;
2055+ } catch ( error ) {
2056+ // Should reject with INVALID_KEY_NAME (either from validateAuthData or diffAuthData)
2057+ // If provider is not configured, may throw UNSUPPORTED_SERVICE, which is also acceptable
2058+ expect ( [ Parse . Error . INVALID_KEY_NAME , Parse . Error . UNSUPPORTED_SERVICE ] ) . toContain ( error . code ) ;
2059+ if ( error . code === Parse . Error . INVALID_KEY_NAME ) {
2060+ expect ( error . message ) . toContain ( 'Invalid provider name' ) ;
2061+ }
2062+ }
2063+ } ) ;
18592064 } ) ;
18602065} ) ;
18612066
0 commit comments