Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ function alladdrs (req, trust) {
// get addresses
var addrs = forwarded(req)

if (addrs[0] == null && isUnixSocket(req)) {
// Unix domain sockets / named pipes have no remoteAddress;
// treat them as loopback so configured trust can apply.
addrs[0] = '127.0.0.1'
}

if (!trust) {
// Return all addresses
return addrs
Expand All @@ -74,6 +80,40 @@ function alladdrs (req, trust) {
return addrs
}

/**
* Determine if the request arrived over a Unix domain socket
* or Windows named pipe.
*
* Node.js leaves `remoteAddress` unset for IPC connections. The
* documented signal is `server.address()` returning a string (the
* path). Some Node versions also expose `socket.address().path`.
*
* @param {Object} req
* @private
*/

function isUnixSocket (req) {
var sock = req.socket || req.connection

if (!sock) {
return false
}

var bind = sock.server && typeof sock.server.address === 'function'
? sock.server.address()
: undefined

if (typeof bind === 'string') {
return true
}

bind = typeof sock.address === 'function'
? sock.address()
: undefined

return !!(bind && typeof bind.path === 'string')
}

/**
* Compile argument into trust function.
*
Expand Down
138 changes: 138 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@

var assert = require('assert')
var deepEqual = require('deep-equal')
var fs = require('fs')
var http = require('http')
var os = require('os')
var path = require('path')
var proxyaddr = require('..')

describe('proxyaddr(req, trust)', function () {
Expand Down Expand Up @@ -380,6 +384,61 @@ describe('proxyaddr(req, trust)', function () {
})
assert.strictEqual(proxyaddr(req, '127.0.0.1'), undefined)
})

it('should not treat a TCP socket without remoteAddress as loopback', function () {
var req = createReq(undefined, {
'x-forwarded-for': '10.0.0.1'
})
req.connection.server = {
address: function () {
return { address: '0.0.0.0', family: 'IPv4', port: 3000 }
}
}
assert.strictEqual(proxyaddr(req, 'loopback'), undefined)
})
})

describe('when request is over a unix socket', function () {
it('should treat the socket as loopback', function () {
var req = createUnixReq()
assert.strictEqual(proxyaddr(req, 'loopback'), '127.0.0.1')
})

it('should honor X-Forwarded-For when loopback is trusted', function () {
var req = createUnixReq({
'x-forwarded-for': '8.8.8.8, 10.0.0.1'
})
assert.strictEqual(proxyaddr(req, 'loopback'), '10.0.0.1')
})

it('should honor further trusted hops after the socket', function () {
var req = createUnixReq({
'x-forwarded-for': '8.8.8.8, 10.0.0.1'
})
assert.strictEqual(proxyaddr(req, ['loopback', '10.0.0.0/8']), '8.8.8.8')
})

it('should not trust headers when loopback is not trusted', function () {
var req = createUnixReq({
'x-forwarded-for': '8.8.8.8'
})
assert.strictEqual(proxyaddr(req, []), '127.0.0.1')
})

it('should detect the socket via address().path', function () {
var req = {
connection: {
remoteAddress: undefined,
address: function () {
return { path: '/tmp/app.sock' }
}
},
headers: {
'x-forwarded-for': '8.8.8.8'
}
}
assert.strictEqual(proxyaddr(req, 'loopback'), '8.8.8.8')
})
})
})

Expand Down Expand Up @@ -437,6 +496,22 @@ describe('proxyaddr.all(req, [trust])', function () {
strictDeepEqual(proxyaddr.all(req, []), ['127.0.0.1'])
})
})

describe('when request is over a unix socket', function () {
it('should include loopback as the socket address', function () {
var req = createUnixReq({
'x-forwarded-for': '10.0.0.1'
})
strictDeepEqual(proxyaddr.all(req), ['127.0.0.1', '10.0.0.1'])
})

it('should stop at first untrusted after the socket', function () {
var req = createUnixReq({
'x-forwarded-for': '8.8.8.8, 10.0.0.1'
})
strictDeepEqual(proxyaddr.all(req, 'loopback'), ['127.0.0.1', '10.0.0.1'])
})
})
})

describe('proxyaddr.compile(trust)', function () {
Expand Down Expand Up @@ -627,6 +702,69 @@ function createReq (socketAddr, headers) {
}
}

function createUnixReq (headers) {
var socket = {
remoteAddress: undefined,
server: {
address: function () {
return '/tmp/app.sock'
}
}
}

return {
connection: socket,
socket: socket,
headers: headers || {}
}
}

describe('when listening on a unix socket', function () {
var socketPath = path.join(os.tmpdir(), 'proxy-addr-' + process.pid + '.sock')

afterEach(function () {
try {
fs.unlinkSync(socketPath)
} catch (e) { /* ignore */ }
})

it('should treat the live socket as loopback and honor X-Forwarded-For', function (done) {
if (process.platform === 'win32') {
return this.skip()
}

var server = http.createServer(function (req, res) {
res.end(JSON.stringify({
ip: proxyaddr(req, 'loopback'),
all: proxyaddr.all(req, 'loopback'),
none: proxyaddr(req, [])
}))
})

server.listen(socketPath, function () {
http.get({
headers: { 'X-Forwarded-For': '8.8.8.8, 10.0.0.1' },
path: '/',
socketPath: socketPath
}, function (res) {
var buf = ''
res.on('data', function (chunk) { buf += chunk })
res.on('end', function () {
server.close(function () {
var body = JSON.parse(buf)
assert.strictEqual(body.ip, '10.0.0.1')
strictDeepEqual(body.all, ['127.0.0.1', '10.0.0.1'])
assert.strictEqual(body.none, '127.0.0.1')
done()
})
})
}).on('error', function (err) {
server.close(function () { done(err) })
})
})
})
})

function strictDeepEqual (actual, expected, message) {
if (assert.deepStrictEqual) {
assert.deepStrictEqual(actual, expected, message)
Expand Down