browserPublicProxy.test.ts 2.5 KB

1234567891011121314151617181920212223242526272829303132
  1. // @vitest-environment node
  2. import {afterEach,describe,expect,it,vi} from 'vitest'
  3. import {request} from 'node:http'
  4. import {connect} from 'node:net'
  5. import {BrowserPublicProxy,publicBrowserURL} from '../../src/runner/browser/publicProxy.js'
  6. const proxies:BrowserPublicProxy[]=[]
  7. async function fixture(){const authorize=vi.fn(async()=>{}),resolve=vi.fn(async()=>[{address:'10.2.3.4',family:4}]);const proxy=await BrowserPublicProxy.start(authorize,resolve);proxies.push(proxy);return {proxy,authorize,resolve}}
  8. function get(proxy:BrowserPublicProxy,target:string,authenticated=true){return new Promise<number>((resolve,reject)=>{const req=request(proxy.serverURL,{path:target,headers:authenticated?{'Proxy-Authorization':'Basic '+Buffer.from(`${proxy.username}:${proxy.password}`).toString('base64')}:{}} ,res=>{res.resume();res.on('end',()=>resolve(res.statusCode!))});req.on('error',reject);req.end()})}
  9. afterEach(async()=>{await Promise.all(proxies.splice(0).map(proxy=>proxy.close()))})
  10. describe('browser public network boundary',()=>{
  11. it('requires private credentials before resolving a destination',async()=>{
  12. const f=await fixture();expect(await get(f.proxy,'http://example.com/',false)).toBe(407);expect(f.resolve).not.toHaveBeenCalled();expect(f.authorize).not.toHaveBeenCalled()
  13. })
  14. it('rejects private IPs and public-looking names resolving to private destinations',async()=>{
  15. const f=await fixture()
  16. expect(await get(f.proxy,'http://127.0.0.1/')).toBe(403)
  17. expect(await get(f.proxy,'http://example.com/')).toBe(403)
  18. expect(f.resolve).toHaveBeenCalledWith('example.com')
  19. })
  20. it('rejects HTTPS tunnels to local destinations',async()=>{
  21. const f=await fixture(),url=new URL(f.proxy.serverURL)
  22. const output=await new Promise<string>((resolve,reject)=>{
  23. const socket=connect(Number(url.port),url.hostname,()=>socket.write(`CONNECT 127.0.0.1:443 HTTP/1.1\r\nHost: 127.0.0.1:443\r\nProxy-Authorization: Basic ${Buffer.from(`${f.proxy.username}:${f.proxy.password}`).toString('base64')}\r\n\r\n`))
  24. let text='';socket.on('data',chunk=>text+=chunk);socket.on('end',()=>resolve(text));socket.on('error',reject)
  25. })
  26. expect(output).toContain('403 Forbidden')
  27. })
  28. it('rejects non-web protocols, credential URLs and non-web ports',()=>{
  29. for(const value of ['file:///etc/passwd','ftp://example.com/','http://name:secret@example.com/','http://example.com:9222/'])expect(()=>publicBrowserURL(value)).toThrow()
  30. expect(publicBrowserURL('https://example.com/path').hostname).toBe('example.com')
  31. })
  32. })