Learn to mitigate potential security issues by redacting sensitive data (like user credentials or secrets) from your logs using Pino logger for Node.js. Consider this a crucial part of your logging strategy.
In today's world where data breaches are increasingly common, secure logging is not just important - it's absolutely essential. One area that developers often overlook is the logging of sensitive data, like user credentials or secrets. Such data leaks can lead to serious security vulnerabilities and violations of GDPR compliance if you're not careful.
This article will guide you through redacting such sensitive data from your logs when using Pino, a performant and lightweight logger for Node.js, aiding your application in maintaining GDPR compliance and reducing the risk of privileging escalation.
Pino
When instantiating a pino instance, you usually start with a similar piece of code:
// You can find the Gist here: https://gist.github.com/Lp-Francois/3d1b36907de8283a8a3eedca31c9cfc3
// file:`example.js`
// install packages: `npm i pino-http --save`
'use strict'
const pino = require('pino-http')
const http = require('http')
const server = http.createServer(handle)
const logger = pino({
base: undefined, // removes pid and hostname from the logs
})
function handle (req, res) {
logger(req, res)
req.log.info('my request')
res.end('hello world')
}
server.listen(3000, () => console.log('[i] running on http://localhost:3000'))
and when running it, then sending requests to it, you can
# terminal window 1
$ node example.js
# terminal window 2
$ curl http://localhost:3000 -H "authorization: bearer my-super-secret" -H "Content-Type: application/json"
# logs in terminal 1:
[i] running on http://localhost:3000
{"level":30,"time":1711808000315,"req":{"id":1,"method":"GET","url":"/","headers":{"host":"localhost:3000","user-agent":"curl/8.4.0","accept":"*/*","authorization":"bearer my-super-secret","content-type":"application/json"},"remoteAddress":"::1","remotePort":63547},"msg":"my request"}
{"level":30,"time":1711808000322,"req":{"id":1,"method":"GET","url":"/","headers":{"host":"localhost:3000","user-agent":"curl/8.4.0","accept":"*/*","authorization":"bearer my-super-secret","content-type":"application/json"},"remoteAddress":"::1","remotePort":63547},"res":{"statusCode":200,"headers":{}},"responseTime":7,"msg":"request completed"}
Notice the authorization
header containing the secret in the logs 😢: ..."accept":"*/*","authorization":"bearer my-super-secret"...
Redaction
Pino has a "hidden" (hard to find) documentation on redacting secrets.
It is possible to pass to your pino logger an array containing paths to keys holding sensible data.
To remove our authorization
header from the req
object, we just have to append this to our config: redact: ['req.headers.authorization']
.
Here is the full example:
'use strict'
const pino = require('pino-http')
const http = require('http')
const server = http.createServer(handle)
const logger = pino({
base: undefined,
// ⚠️ The next line is the important one:
redact: [
'req.headers.authorization',
]
})
function handle (req, res) {
logger(req, res)
req.log.info('my request')
res.end('hello world')
}
server.listen(3000, () => console.log('running on http://localhost:3000'))
Outputing the following logs:
[i] running on http://localhost:3000
{"level":30,"time":1711824390585,"req":{"id":1,"method":"GET","url":"/","headers":{"host":"localhost:3000","user-agent":"curl/8.4.0","accept":"*/*","authorization":"[Redacted]","content-type":"application/json"},"remoteAddress":"::1","remotePort":58055},"msg":"my request"}
{"level":30,"time":1711824390594,"req":{"id":1,"method":"GET","url":"/","headers":{"host":"localhost:3000","user-agent":"curl/8.4.0","accept":"*/*","authorization":"[Redacted]","content-type":"application/json"},"remoteAddress":"::1","remotePort":58055},"res":{"statusCode":200,"headers":{}},"responseTime":9,"msg":"request completed"}
Notice the authorization
header now has a [Redacted]
value!
Note the keys are case sensitive! If you want to add to the redact list an uppercase header like req.headers.CREDENTIALS
be careful as it would ignore the lowercase credentials
header.
It is also an option to drop both value and key (redact.remove
), or change the value of the redacted field to something else with the censor
option (I'll let you read the documentation for that 😉).
I didn't find many resources online that covered this tip specifically, so I hope this insight proves valuable to you!
Happy safe logging with Pino!
Top comments (0)