22 lines
1.8 KiB
TypeScript
22 lines
1.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import {test,mock} from 'node:test';
|
|
import {EventEmitter} from 'node:events';
|
|
import {Readable} from 'node:stream';
|
|
let connections=0,pinned='',code=200;
|
|
mock.module('node:dns/promises',{namedExports:{lookup:async(host:string)=>[{address:host.startsWith('private')?'127.0.0.1':'1.1.1.1',family:4}]}});
|
|
mock.module('node:https',{namedExports:{request:(_url:URL,options:{lookup:(h:string,o:object,cb:(e:unknown,ip:string)=>void)=>void},callback:(r:unknown)=>void)=>{
|
|
connections++;options.lookup('unused',{},(_e,ip)=>{pinned=ip;});
|
|
const request=new EventEmitter() as EventEmitter&{end:()=>void};request.end=()=>queueMicrotask(()=>{const response=Readable.from(['<html>Book</html>']) as Readable&{statusCode:number;headers:object};response.statusCode=code;response.headers={'content-type':'text/html',location:code===302?'http://127.0.0.1/private':undefined};callback(response);});return request;
|
|
}}});
|
|
const {requestPublic}=await import('../lib/http.ts');
|
|
test('outbound transport rejects unsafe URLs and private DNS before creating a socket',async()=>{
|
|
for(const url of ['http://127.0.0.1/','http://[::1]/','file:///etc/passwd','https://private.provider.org/'])await assert.rejects(requestPublic(url));
|
|
assert.equal(connections,0);
|
|
});
|
|
test('a public DNS address is pinned into the socket lookup',async()=>{
|
|
const response=await requestPublic('https://public.provider.org/book');assert.equal(pinned,'1.1.1.1');assert.equal(response.code,200);assert.equal(connections,1);
|
|
});
|
|
test('a redirect is returned to the caller without following it past the trust boundary',async()=>{
|
|
code=302;const before=connections,response=await requestPublic('https://public.provider.org/redirect');assert.equal(response.code,302);assert.equal(response.headers.location,'http://127.0.0.1/private');assert.equal(connections,before+1);
|
|
});
|