Skip to content

Commit 87b4f34

Browse files
committed
Remove password from stringified outputs
Theres a security concern where if you're not careful and you include your client or pool instance in console.log or stack traces it might include the database password. To widen the pit of success I'm making that field non-enumerable. You can still get at it...it just wont show up "by accident" when you're logging things now. The backwards compatiblity impact of this is very small, but it is still technically somewhat an API change so...8.0.
1 parent 5cf8f5f commit 87b4f34

File tree

5 files changed

+108
-24
lines changed

5 files changed

+108
-24
lines changed

packages/pg-pool/index.js

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ const removeWhere = (list, predicate) => {
1212
}
1313

1414
class IdleItem {
15-
constructor (client, idleListener, timeoutId) {
15+
constructor(client, idleListener, timeoutId) {
1616
this.client = client
1717
this.idleListener = idleListener
1818
this.timeoutId = timeoutId
1919
}
2020
}
2121

2222
class PendingItem {
23-
constructor (callback) {
23+
constructor(callback) {
2424
this.callback = callback
2525
}
2626
}
2727

28-
function promisify (Promise, callback) {
28+
function promisify(Promise, callback) {
2929
if (callback) {
3030
return { callback: callback, result: undefined }
3131
}
@@ -41,8 +41,8 @@ function promisify (Promise, callback) {
4141
return { callback: cb, result: result }
4242
}
4343

44-
function makeIdleListener (pool, client) {
45-
return function idleListener (err) {
44+
function makeIdleListener(pool, client) {
45+
return function idleListener(err) {
4646
err.client = client
4747

4848
client.removeListener('error', idleListener)
@@ -57,9 +57,24 @@ function makeIdleListener (pool, client) {
5757
}
5858

5959
class Pool extends EventEmitter {
60-
constructor (options, Client) {
60+
constructor(options, Client) {
6161
super()
62-
this.options = Object.assign({}, options)
62+
63+
// "hiding" the password so it doesn't show up in stack traces
64+
// or if the client is console.logged
65+
const optionsWithPasswordHidden = Object.assign({}, options)
66+
let password = optionsWithPasswordHidden.password
67+
Object.defineProperty(optionsWithPasswordHidden, 'password', {
68+
enumerable: false,
69+
get() {
70+
return password
71+
},
72+
set(value) {
73+
password = value
74+
}
75+
})
76+
77+
this.options = optionsWithPasswordHidden
6378
this.options.max = this.options.max || this.options.poolSize || 10
6479
this.log = this.options.log || function () { }
6580
this.Client = this.options.Client || Client || require('pg').Client
@@ -77,11 +92,11 @@ class Pool extends EventEmitter {
7792
this.ended = false
7893
}
7994

80-
_isFull () {
95+
_isFull() {
8196
return this._clients.length >= this.options.max
8297
}
8398

84-
_pulseQueue () {
99+
_pulseQueue() {
85100
this.log('pulse queue')
86101
if (this.ended) {
87102
this.log('pulse queue ended')
@@ -124,7 +139,7 @@ class Pool extends EventEmitter {
124139
throw new Error('unexpected condition')
125140
}
126141

127-
_remove (client) {
142+
_remove(client) {
128143
const removed = removeWhere(
129144
this._idle,
130145
item => item.client === client
@@ -139,7 +154,7 @@ class Pool extends EventEmitter {
139154
this.emit('remove', client)
140155
}
141156

142-
connect (cb) {
157+
connect(cb) {
143158
if (this.ending) {
144159
const err = new Error('Cannot use a pool after calling end on the pool')
145160
return cb ? cb(err) : this.Promise.reject(err)
@@ -185,7 +200,7 @@ class Pool extends EventEmitter {
185200
return result
186201
}
187202

188-
newClient (pendingItem) {
203+
newClient(pendingItem) {
189204
const client = new this.Client(this.options)
190205
this._clients.push(client)
191206
const idleListener = makeIdleListener(this, client)
@@ -233,7 +248,7 @@ class Pool extends EventEmitter {
233248
}
234249

235250
// acquire a client for a pending work item
236-
_acquireClient (client, pendingItem, idleListener, isNew) {
251+
_acquireClient(client, pendingItem, idleListener, isNew) {
237252
if (isNew) {
238253
this.emit('connect', client)
239254
}
@@ -277,7 +292,7 @@ class Pool extends EventEmitter {
277292

278293
// release a client back to the poll, include an error
279294
// to remove it from the pool
280-
_release (client, idleListener, err) {
295+
_release(client, idleListener, err) {
281296
client.on('error', idleListener)
282297

283298
if (err || this.ending) {
@@ -299,7 +314,7 @@ class Pool extends EventEmitter {
299314
this._pulseQueue()
300315
}
301316

302-
query (text, values, cb) {
317+
query(text, values, cb) {
303318
// guard clause against passing a function as the first parameter
304319
if (typeof text === 'function') {
305320
const response = promisify(this.Promise, text)
@@ -352,7 +367,7 @@ class Pool extends EventEmitter {
352367
return response.result
353368
}
354369

355-
end (cb) {
370+
end(cb) {
356371
this.log('ending')
357372
if (this.ending) {
358373
const err = new Error('Called end on pool more than once')
@@ -365,15 +380,15 @@ class Pool extends EventEmitter {
365380
return promised.result
366381
}
367382

368-
get waitingCount () {
383+
get waitingCount() {
369384
return this._pendingQueue.length
370385
}
371386

372-
get idleCount () {
387+
get idleCount() {
373388
return this._idle.length
374389
}
375390

376-
get totalCount () {
391+
get totalCount() {
377392
return this._clients.length
378393
}
379394
}

packages/pg/lib/client.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,21 @@ var Client = function (config) {
3030
this.database = this.connectionParameters.database
3131
this.port = this.connectionParameters.port
3232
this.host = this.connectionParameters.host
33-
this.password = this.connectionParameters.password
33+
34+
// "hiding" the password so it doesn't show up in stack traces
35+
// or if the client is console.logged
36+
let password = this.connectionParameters.password
37+
Object.defineProperty(this, 'password', {
38+
enumerable: false,
39+
configurable: false,
40+
get() {
41+
return password
42+
},
43+
set(value) {
44+
password = value
45+
}
46+
})
47+
3448
this.replication = this.connectionParameters.replication
3549

3650
var c = config || {}

packages/pg/lib/connection-parameters.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,18 @@ var ConnectionParameters = function (config) {
5454
this.database = val('database', config)
5555
this.port = parseInt(val('port', config), 10)
5656
this.host = val('host', config)
57-
this.password = val('password', config)
57+
58+
// "hiding" the password so it doesn't show up in stack traces
59+
// or if the client is console.logged
60+
const password = val('password', config)
61+
Object.defineProperty(this, 'password', {
62+
enumerable: false,
63+
configurable: false,
64+
get() {
65+
return password
66+
}
67+
})
68+
5869
this.binary = val('binary', config)
5970
this.ssl = typeof config.ssl === 'undefined' ? useSsl() : config.ssl
6071
this.client_encoding = val('client_encoding', config)

packages/pg/lib/native/client.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,19 @@ var Client = module.exports = function (config) {
4343
// for the time being. TODO: deprecate all this jazz
4444
var cp = this.connectionParameters = new ConnectionParameters(config)
4545
this.user = cp.user
46-
this.password = cp.password
46+
47+
// "hiding" the password so it doesn't show up in stack traces
48+
// or if the client is console.logged
49+
let hiddenPassword = cp.password
50+
Object.defineProperty(this, 'password', {
51+
enumerable: false,
52+
get() {
53+
return hiddenPassword
54+
},
55+
set(value) {
56+
hiddenPassword = value
57+
}
58+
})
4759
this.database = cp.database
4860
this.host = cp.host
4961
this.port = cp.port
@@ -187,7 +199,7 @@ Client.prototype.query = function (config, values, callback) {
187199

188200
// we already returned an error,
189201
// just do nothing if query completes
190-
query.callback = () => {}
202+
query.callback = () => { }
191203

192204
// Remove from queue
193205
var index = this._queryQueue.indexOf(query)
@@ -280,7 +292,7 @@ Client.prototype._pulseQueryQueue = function (initialConnection) {
280292
// attempt to cancel an in-progress query
281293
Client.prototype.cancel = function (query) {
282294
if (this._activeQuery === query) {
283-
this.native.cancel(function () {})
295+
this.native.cancel(function () { })
284296
} else if (this._queryQueue.indexOf(query) !== -1) {
285297
this._queryQueue.splice(this._queryQueue.indexOf(query), 1)
286298
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
"use strict"
3+
const helper = require('./../test-helper')
4+
const assert = require('assert')
5+
const util = require('util')
6+
7+
const suite = new helper.Suite()
8+
9+
const password = 'FAIL THIS TEST'
10+
11+
suite.test('Password should not exist in toString() output', () => {
12+
const pool = new helper.pg.Pool({ password })
13+
const client = new helper.pg.Client({ password })
14+
assert(pool.toString().indexOf(password) === -1);
15+
assert(client.toString().indexOf(password) === -1);
16+
})
17+
18+
suite.test('Password should not exist in util.inspect output', () => {
19+
const pool = new helper.pg.Pool({ password })
20+
const client = new helper.pg.Client({ password })
21+
const depth = 20;
22+
assert(util.inspect(pool, { depth }).indexOf(password) === -1);
23+
assert(util.inspect(client, { depth }).indexOf(password) === -1);
24+
})
25+
26+
suite.test('Password should not exist in json.stringfy output', () => {
27+
const pool = new helper.pg.Pool({ password })
28+
const client = new helper.pg.Client({ password })
29+
const depth = 20;
30+
assert(JSON.stringify(pool).indexOf(password) === -1);
31+
assert(JSON.stringify(client).indexOf(password) === -1);
32+
})

0 commit comments

Comments
 (0)