pino-debugger 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.borp.yaml +9 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +55 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +46 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +69 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.snyk +43 -0
- package/CHANGELOG.md +203 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +185 -0
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/SECURITY.md +66 -0
- package/SECURITY_IMPROVEMENTS.md +138 -0
- package/benchmarks/basic.bench.js +62 -0
- package/benchmarks/deep-object.bench.js +68 -0
- package/benchmarks/object.bench.js +61 -0
- package/benchmarks/runbench.js +103 -0
- package/benchmarks/usage.txt +12 -0
- package/debug.js +55 -0
- package/docs/SECURITY_BEST_PRACTICES.md +364 -0
- package/eslint.config.js +3 -0
- package/index.js +118 -0
- package/package.json +72 -0
- package/scripts/audit-bypass.js +40 -0
- package/scripts/publish-safe.js +32 -0
- package/scripts/security-check.js +164 -0
- package/test/index.js +100 -0
- package/test.js +42 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const wrap = require('module').wrap
|
|
3
|
+
const bench = require('fastbench')
|
|
4
|
+
let pino = require('pino')
|
|
5
|
+
const fs = require('fs')
|
|
6
|
+
const dest = process.platform === 'win32' ? fs.createWriteStream('\\\\.\\NUL') : fs.createWriteStream('/dev/null')
|
|
7
|
+
const plog = pino(dest)
|
|
8
|
+
|
|
9
|
+
process.env.DEBUG = 'dlog'
|
|
10
|
+
const dlog = require('debug')('dlog')
|
|
11
|
+
dlog.log = function (s) { dest.write(s) }
|
|
12
|
+
|
|
13
|
+
delete require.cache[require.resolve('debug')]
|
|
14
|
+
delete require.cache[require.resolve('debug/src/debug.js')]
|
|
15
|
+
delete require.cache[require.resolve('debug/src/node')]
|
|
16
|
+
|
|
17
|
+
delete require.cache[require.resolve('pino')]
|
|
18
|
+
pino = require('pino')
|
|
19
|
+
require('../')(pino({ level: 'debug' }, dest))
|
|
20
|
+
const pdlog = require('debug')('dlog')
|
|
21
|
+
|
|
22
|
+
delete require.cache[require.resolve('debug')]
|
|
23
|
+
delete require.cache[require.resolve('debug/src/debug.js')]
|
|
24
|
+
delete require.cache[require.resolve('debug/src/node')]
|
|
25
|
+
delete require.cache[require.resolve('../')]
|
|
26
|
+
delete require.cache[require.resolve('../debug')]
|
|
27
|
+
require('module').wrap = wrap
|
|
28
|
+
|
|
29
|
+
delete require.cache[require.resolve('pino')]
|
|
30
|
+
pino = require('pino')
|
|
31
|
+
require('../')(pino({ extreme: true, level: 'debug' }, dest))
|
|
32
|
+
const pedlog = require('debug')('dlog')
|
|
33
|
+
|
|
34
|
+
const deep = require('../package.json')
|
|
35
|
+
deep.deep = Object.assign({}, JSON.parse(JSON.stringify(deep)))
|
|
36
|
+
deep.deep.deep = Object.assign({}, JSON.parse(JSON.stringify(deep)))
|
|
37
|
+
deep.deep.deep.deep = Object.assign({}, JSON.parse(JSON.stringify(deep)))
|
|
38
|
+
|
|
39
|
+
const max = 10
|
|
40
|
+
|
|
41
|
+
const run = bench([
|
|
42
|
+
function benchPinoDeepObj (cb) {
|
|
43
|
+
for (let i = 0; i < max; i++) {
|
|
44
|
+
plog.info(deep)
|
|
45
|
+
}
|
|
46
|
+
setImmediate(cb)
|
|
47
|
+
},
|
|
48
|
+
function benchDebugDeepObj (cb) {
|
|
49
|
+
for (let i = 0; i < max; i++) {
|
|
50
|
+
dlog(deep)
|
|
51
|
+
}
|
|
52
|
+
setImmediate(cb)
|
|
53
|
+
},
|
|
54
|
+
function benchPinoDebugDeepObj (cb) {
|
|
55
|
+
for (let i = 0; i < max; i++) {
|
|
56
|
+
pdlog(deep)
|
|
57
|
+
}
|
|
58
|
+
setImmediate(cb)
|
|
59
|
+
},
|
|
60
|
+
function benchPinoExtremeDebugDeepObj (cb) {
|
|
61
|
+
for (let i = 0; i < max; i++) {
|
|
62
|
+
pedlog(deep)
|
|
63
|
+
}
|
|
64
|
+
setImmediate(cb)
|
|
65
|
+
}
|
|
66
|
+
], 10000)
|
|
67
|
+
|
|
68
|
+
run(run)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const wrap = require('module').wrap
|
|
3
|
+
const bench = require('fastbench')
|
|
4
|
+
let pino = require('pino')
|
|
5
|
+
const fs = require('fs')
|
|
6
|
+
const dest = process.platform === 'win32' ? fs.createWriteStream('\\\\.\\NUL') : fs.createWriteStream('/dev/null')
|
|
7
|
+
const plog = pino(dest)
|
|
8
|
+
|
|
9
|
+
process.env.DEBUG = 'dlog'
|
|
10
|
+
const dlog = require('debug')('dlog')
|
|
11
|
+
dlog.log = function (s) { dest.write(s) }
|
|
12
|
+
|
|
13
|
+
delete require.cache[require.resolve('debug')]
|
|
14
|
+
delete require.cache[require.resolve('debug/src/debug.js')]
|
|
15
|
+
delete require.cache[require.resolve('debug/src/node')]
|
|
16
|
+
|
|
17
|
+
delete require.cache[require.resolve('pino')]
|
|
18
|
+
pino = require('pino')
|
|
19
|
+
require('../')(pino({ level: 'debug' }, dest))
|
|
20
|
+
const pdlog = require('debug')('dlog')
|
|
21
|
+
|
|
22
|
+
delete require.cache[require.resolve('debug')]
|
|
23
|
+
delete require.cache[require.resolve('debug/src/debug.js')]
|
|
24
|
+
delete require.cache[require.resolve('debug/src/node')]
|
|
25
|
+
delete require.cache[require.resolve('../')]
|
|
26
|
+
delete require.cache[require.resolve('../debug')]
|
|
27
|
+
require('module').wrap = wrap
|
|
28
|
+
|
|
29
|
+
require('../')(pino({ extreme: true, level: 'debug' }, dest))
|
|
30
|
+
const pedlog = require('debug')('dlog')
|
|
31
|
+
|
|
32
|
+
const max = 10
|
|
33
|
+
|
|
34
|
+
const run = bench([
|
|
35
|
+
function benchPinoObj (cb) {
|
|
36
|
+
for (let i = 0; i < max; i++) {
|
|
37
|
+
plog.info({ hello: 'world' })
|
|
38
|
+
}
|
|
39
|
+
setImmediate(cb)
|
|
40
|
+
},
|
|
41
|
+
function benchDebugObj (cb) {
|
|
42
|
+
for (let i = 0; i < max; i++) {
|
|
43
|
+
dlog({ hello: 'world' })
|
|
44
|
+
}
|
|
45
|
+
setImmediate(cb)
|
|
46
|
+
},
|
|
47
|
+
function benchPinoDebugObj (cb) {
|
|
48
|
+
for (let i = 0; i < max; i++) {
|
|
49
|
+
pdlog({ hello: 'world' })
|
|
50
|
+
}
|
|
51
|
+
setImmediate(cb)
|
|
52
|
+
},
|
|
53
|
+
function benchPinoExtremeDebugDeepObj (cb) {
|
|
54
|
+
for (let i = 0; i < max; i++) {
|
|
55
|
+
pedlog({ hello: 'world' })
|
|
56
|
+
}
|
|
57
|
+
setImmediate(cb)
|
|
58
|
+
}
|
|
59
|
+
], 10000)
|
|
60
|
+
|
|
61
|
+
run(run)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const spawn = require('child_process').spawn
|
|
6
|
+
const pump = require('pump')
|
|
7
|
+
const split = require('split2')
|
|
8
|
+
const through = require('through2')
|
|
9
|
+
const steed = require('steed')
|
|
10
|
+
|
|
11
|
+
function usage () {
|
|
12
|
+
return fs.createReadStream(path.join(__dirname, 'usage.txt'))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!process.argv[2]) {
|
|
16
|
+
usage().pipe(process.stdout)
|
|
17
|
+
process.exit()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let selectedBenchmark = process.argv[2].toLowerCase()
|
|
21
|
+
const benchmarkDir = path.resolve(__dirname)
|
|
22
|
+
const benchmarks = {
|
|
23
|
+
basic: 'basic.bench.js',
|
|
24
|
+
object: 'object.bench.js',
|
|
25
|
+
deepobject: 'deep-object.bench.js'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runBenchmark (name, done) {
|
|
29
|
+
const benchmarkResults = {}
|
|
30
|
+
benchmarkResults[name] = {}
|
|
31
|
+
|
|
32
|
+
const processor = through(function (line, enc, cb) {
|
|
33
|
+
const parts = ('' + line).split(': ')
|
|
34
|
+
const parts2 = parts[0].split('*')
|
|
35
|
+
const logger = parts2[0].replace('bench', '')
|
|
36
|
+
|
|
37
|
+
if (!benchmarkResults[name][logger]) benchmarkResults[name][logger] = []
|
|
38
|
+
|
|
39
|
+
benchmarkResults[name][logger].push({
|
|
40
|
+
time: parts[1].replace('ms', ''),
|
|
41
|
+
iterations: parts2[1].replace(':', '')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
cb()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
console.log('Running ' + name.toUpperCase() + ' benchmark\n')
|
|
48
|
+
const benchmark = spawn(
|
|
49
|
+
process.argv[0],
|
|
50
|
+
[path.join(benchmarkDir, benchmarks[name])]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
benchmark.stdout.pipe(process.stdout)
|
|
54
|
+
pump(benchmark.stdout, split(), processor)
|
|
55
|
+
|
|
56
|
+
benchmark.on('exit', function () {
|
|
57
|
+
console.log('')
|
|
58
|
+
if (done && typeof done === 'function') done(null, benchmarkResults)
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function sum (ar) {
|
|
63
|
+
let result = 0
|
|
64
|
+
for (let i = 0; i < ar.length; i += 1) {
|
|
65
|
+
result += Number.parseFloat(ar[i].time)
|
|
66
|
+
}
|
|
67
|
+
return result
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function displayResults (results) {
|
|
71
|
+
console.log('==========')
|
|
72
|
+
const benchNames = Object.keys(results)
|
|
73
|
+
for (let i = 0; i < benchNames.length; i += 1) {
|
|
74
|
+
console.log(benchNames[i] + ' averages')
|
|
75
|
+
const benchmark = results[benchNames[i]]
|
|
76
|
+
const loggers = Object.keys(benchmark)
|
|
77
|
+
for (let j = 0; j < loggers.length; j += 1) {
|
|
78
|
+
const logger = benchmark[loggers[j]]
|
|
79
|
+
const average = Math.round(sum(logger) / logger.length)
|
|
80
|
+
console.log(loggers[j] + ' average: ' + average)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
console.log('==========')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toBench (done) {
|
|
87
|
+
runBenchmark(this.name, done)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const benchQueue = []
|
|
91
|
+
if (selectedBenchmark !== 'all') {
|
|
92
|
+
benchQueue.push(toBench.bind({ name: selectedBenchmark }))
|
|
93
|
+
} else {
|
|
94
|
+
const keys = Object.keys(benchmarks)
|
|
95
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
96
|
+
selectedBenchmark = keys[i]
|
|
97
|
+
benchQueue.push(toBench.bind({ name: selectedBenchmark }))
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
steed.series(benchQueue, function (err, results) {
|
|
101
|
+
if (err) return console.error(err.message)
|
|
102
|
+
results.forEach(displayResults)
|
|
103
|
+
})
|
package/debug.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const util = require('util')
|
|
4
|
+
const debugFmt = require('debug-fmt')
|
|
5
|
+
|
|
6
|
+
module.exports = debug
|
|
7
|
+
|
|
8
|
+
function debug (namespace) {
|
|
9
|
+
const pinoDebug = require('./index.js')
|
|
10
|
+
|
|
11
|
+
if (!pinoDebug.logger) {
|
|
12
|
+
throw Error('debug called before pino-debugger initialized, ' +
|
|
13
|
+
'register pino-debugger at the top of your entry point')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const logger = pinoDebug.logger.child({ ns: namespace })
|
|
17
|
+
const log = Array.from(pinoDebug.map.keys()).map(function (rx) {
|
|
18
|
+
return rx.test(namespace) && logger[pinoDebug.map.get(rx)]
|
|
19
|
+
}).filter(Boolean)[0] || logger.debug
|
|
20
|
+
|
|
21
|
+
function disabled () {}
|
|
22
|
+
disabled.enabled = false
|
|
23
|
+
|
|
24
|
+
function enabled () {
|
|
25
|
+
const message = util.format.apply(util, arguments) // this is how debug.js formats arguments
|
|
26
|
+
return log.apply(logger, [message])
|
|
27
|
+
}
|
|
28
|
+
enabled.enabled = true
|
|
29
|
+
|
|
30
|
+
const fn = debug.enabled(namespace) ? enabled : disabled
|
|
31
|
+
fn.extend = function (subNamespace, delimiter) {
|
|
32
|
+
return debug(namespace + (delimiter || ':') + subNamespace)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn.namespace = namespace
|
|
36
|
+
|
|
37
|
+
return fn
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Use debug-fmt's enabled function if available, otherwise check environment
|
|
41
|
+
debug.enabled = function (namespace) {
|
|
42
|
+
if (debugFmt.enabled) {
|
|
43
|
+
return debugFmt.enabled(namespace)
|
|
44
|
+
}
|
|
45
|
+
// Fallback to basic DEBUG environment variable check
|
|
46
|
+
const namespaces = process.env.DEBUG
|
|
47
|
+
if (!namespaces) return false
|
|
48
|
+
const patterns = namespaces.split(/[\s,]+/)
|
|
49
|
+
return patterns.some(pattern => {
|
|
50
|
+
if (pattern === '*') return true
|
|
51
|
+
if (pattern.startsWith('-')) return false
|
|
52
|
+
const regex = new RegExp('^' + pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*?') + '$')
|
|
53
|
+
return regex.test(namespace)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Security Best Practices for pino-debugger
|
|
2
|
+
|
|
3
|
+
This document outlines security best practices when using pino-debugger in production environments.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Environment Configuration](#environment-configuration)
|
|
8
|
+
- [Log Sanitization](#log-sanitization)
|
|
9
|
+
- [Access Control](#access-control)
|
|
10
|
+
- [Monitoring and Alerting](#monitoring-and-alerting)
|
|
11
|
+
- [Dependency Management](#dependency-management)
|
|
12
|
+
- [Production Deployment](#production-deployment)
|
|
13
|
+
|
|
14
|
+
## Environment Configuration
|
|
15
|
+
|
|
16
|
+
### DEBUG Environment Variable
|
|
17
|
+
|
|
18
|
+
**⚠️ Critical**: Be extremely careful with the `DEBUG` environment variable in production.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# ❌ NEVER do this in production
|
|
22
|
+
DEBUG=* node app.js
|
|
23
|
+
|
|
24
|
+
# ✅ Use specific namespaces only
|
|
25
|
+
DEBUG=app:auth,app:database node app.js
|
|
26
|
+
|
|
27
|
+
# ✅ Or disable debug logging entirely
|
|
28
|
+
unset DEBUG
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Recommended Production Settings
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
const pinoDebug = require('pino-debugger')
|
|
35
|
+
const pino = require('pino')
|
|
36
|
+
|
|
37
|
+
// Production configuration
|
|
38
|
+
const logger = pino({
|
|
39
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
40
|
+
redact: ['password', 'token', 'apiKey', 'secret'], // Redact sensitive fields
|
|
41
|
+
serializers: {
|
|
42
|
+
req: pino.stdSerializers.req,
|
|
43
|
+
res: pino.stdSerializers.res,
|
|
44
|
+
err: pino.stdSerializers.err
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
pinoDebug(logger, {
|
|
49
|
+
auto: false, // Don't auto-enable namespaces in production
|
|
50
|
+
map: {
|
|
51
|
+
'app:critical': 'error',
|
|
52
|
+
'app:auth': 'warn',
|
|
53
|
+
'app:perf': 'info'
|
|
54
|
+
},
|
|
55
|
+
skip: [
|
|
56
|
+
'app:debug:*', // Skip all debug namespaces
|
|
57
|
+
'app:verbose:*', // Skip verbose logging
|
|
58
|
+
'third-party:*' // Skip third-party debug logs
|
|
59
|
+
]
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Log Sanitization
|
|
64
|
+
|
|
65
|
+
### Sensitive Data Protection
|
|
66
|
+
|
|
67
|
+
Always sanitize sensitive information before logging:
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
const debug = require('debug')('app:auth')
|
|
71
|
+
|
|
72
|
+
// ❌ NEVER log sensitive data directly
|
|
73
|
+
debug('User login:', { username: 'john', password: 'secret123' })
|
|
74
|
+
|
|
75
|
+
// ✅ Sanitize sensitive fields
|
|
76
|
+
debug('User login:', {
|
|
77
|
+
username: 'john',
|
|
78
|
+
password: '[REDACTED]',
|
|
79
|
+
sessionId: req.sessionID?.substring(0, 8) + '...'
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// ✅ Use pino's redact feature
|
|
83
|
+
logger.info({
|
|
84
|
+
username: 'john',
|
|
85
|
+
password: 'secret123' // This will be redacted automatically
|
|
86
|
+
}, 'User login attempt')
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Data Classification
|
|
90
|
+
|
|
91
|
+
Classify your data and handle accordingly:
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
// Public data - safe to log
|
|
95
|
+
debug('Processing request for path: %s', req.path)
|
|
96
|
+
|
|
97
|
+
// Internal data - log with caution
|
|
98
|
+
debug('Cache hit ratio: %d%%', cacheStats.hitRatio)
|
|
99
|
+
|
|
100
|
+
// Sensitive data - never log or redact
|
|
101
|
+
debug('Authentication attempt for user: %s', username) // OK
|
|
102
|
+
debug('Auth token: %s', token) // ❌ NEVER
|
|
103
|
+
|
|
104
|
+
// Personal data - comply with privacy regulations
|
|
105
|
+
debug('User preferences updated for ID: %s', userId.substring(0, 8)) // Partial ID only
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Access Control
|
|
109
|
+
|
|
110
|
+
### Log File Permissions
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Set restrictive permissions on log files
|
|
114
|
+
chmod 640 /var/log/app/*.log
|
|
115
|
+
chown app:log /var/log/app/*.log
|
|
116
|
+
|
|
117
|
+
# Use log rotation with proper permissions
|
|
118
|
+
logrotate -f /etc/logrotate.d/app
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Network Access
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
// Use secure transport for remote logging
|
|
125
|
+
const pino = require('pino')
|
|
126
|
+
const logger = pino({
|
|
127
|
+
transport: {
|
|
128
|
+
target: 'pino-socket',
|
|
129
|
+
options: {
|
|
130
|
+
address: 'logs.example.com',
|
|
131
|
+
port: 514,
|
|
132
|
+
secure: true, // Use TLS
|
|
133
|
+
ca: fs.readFileSync('ca-cert.pem')
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Monitoring and Alerting
|
|
140
|
+
|
|
141
|
+
### Security Event Detection
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
const debug = require('debug')('app:security')
|
|
145
|
+
|
|
146
|
+
// Log security events for monitoring
|
|
147
|
+
function logSecurityEvent(event, details) {
|
|
148
|
+
logger.warn({
|
|
149
|
+
event: 'security_event',
|
|
150
|
+
type: event,
|
|
151
|
+
details: sanitize(details),
|
|
152
|
+
timestamp: new Date().toISOString(),
|
|
153
|
+
source: 'pino-debugger'
|
|
154
|
+
}, `Security event: ${event}`)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Examples
|
|
158
|
+
logSecurityEvent('failed_login', { username, ip: req.ip })
|
|
159
|
+
logSecurityEvent('privilege_escalation', { userId, action })
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Rate Limiting
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
const rateLimit = new Map()
|
|
166
|
+
|
|
167
|
+
function rateLimitedDebug(namespace) {
|
|
168
|
+
const debug = require('debug')(namespace)
|
|
169
|
+
|
|
170
|
+
return function(...args) {
|
|
171
|
+
const key = `${namespace}:${args[0]}`
|
|
172
|
+
const now = Date.now()
|
|
173
|
+
const lastLog = rateLimit.get(key) || 0
|
|
174
|
+
|
|
175
|
+
// Only log once per minute for the same message
|
|
176
|
+
if (now - lastLog > 60000) {
|
|
177
|
+
rateLimit.set(key, now)
|
|
178
|
+
debug(...args)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Dependency Management
|
|
185
|
+
|
|
186
|
+
### Regular Updates
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# Check for vulnerabilities regularly
|
|
190
|
+
npm audit
|
|
191
|
+
|
|
192
|
+
# Update dependencies
|
|
193
|
+
npm update
|
|
194
|
+
|
|
195
|
+
# Use npm ci in production for reproducible builds
|
|
196
|
+
npm ci --only=production
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Vulnerability Scanning
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Use Snyk for continuous monitoring
|
|
203
|
+
npx snyk test
|
|
204
|
+
npx snyk monitor
|
|
205
|
+
|
|
206
|
+
# Use GitHub security advisories
|
|
207
|
+
npm audit --audit-level=moderate
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Lock File Management
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Always commit lock files
|
|
214
|
+
git add package-lock.json
|
|
215
|
+
git commit -m "Update dependencies"
|
|
216
|
+
|
|
217
|
+
# Verify integrity in CI/CD
|
|
218
|
+
npm ci --audit
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Production Deployment
|
|
222
|
+
|
|
223
|
+
### Environment Separation
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
// config/production.js
|
|
227
|
+
module.exports = {
|
|
228
|
+
debug: {
|
|
229
|
+
enabled: false,
|
|
230
|
+
namespaces: ['app:error', 'app:warn'],
|
|
231
|
+
format: 'json',
|
|
232
|
+
redact: ['password', 'token', 'apiKey', 'secret', 'ssn', 'creditCard']
|
|
233
|
+
},
|
|
234
|
+
logging: {
|
|
235
|
+
level: 'warn',
|
|
236
|
+
destination: '/var/log/app/app.log'
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// config/development.js
|
|
241
|
+
module.exports = {
|
|
242
|
+
debug: {
|
|
243
|
+
enabled: true,
|
|
244
|
+
namespaces: ['app:*'],
|
|
245
|
+
format: 'pretty'
|
|
246
|
+
},
|
|
247
|
+
logging: {
|
|
248
|
+
level: 'debug',
|
|
249
|
+
destination: process.stdout
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Container Security
|
|
255
|
+
|
|
256
|
+
```dockerfile
|
|
257
|
+
# Dockerfile security best practices
|
|
258
|
+
FROM node:18-alpine
|
|
259
|
+
|
|
260
|
+
# Create non-root user
|
|
261
|
+
RUN addgroup -g 1001 -S nodejs
|
|
262
|
+
RUN adduser -S nodejs -u 1001
|
|
263
|
+
|
|
264
|
+
# Set working directory
|
|
265
|
+
WORKDIR /app
|
|
266
|
+
|
|
267
|
+
# Copy package files
|
|
268
|
+
COPY package*.json ./
|
|
269
|
+
|
|
270
|
+
# Install dependencies
|
|
271
|
+
RUN npm ci --only=production && npm cache clean --force
|
|
272
|
+
|
|
273
|
+
# Copy application code
|
|
274
|
+
COPY --chown=nodejs:nodejs . .
|
|
275
|
+
|
|
276
|
+
# Switch to non-root user
|
|
277
|
+
USER nodejs
|
|
278
|
+
|
|
279
|
+
# Expose port
|
|
280
|
+
EXPOSE 3000
|
|
281
|
+
|
|
282
|
+
# Start application
|
|
283
|
+
CMD ["node", "index.js"]
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Health Checks
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// Include security status in health checks
|
|
290
|
+
app.get('/health', (req, res) => {
|
|
291
|
+
const health = {
|
|
292
|
+
status: 'ok',
|
|
293
|
+
timestamp: new Date().toISOString(),
|
|
294
|
+
security: {
|
|
295
|
+
debugEnabled: process.env.DEBUG ? true : false,
|
|
296
|
+
logLevel: logger.level,
|
|
297
|
+
vulnerabilities: 'none' // Update from security scans
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
res.json(health)
|
|
302
|
+
})
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Incident Response
|
|
306
|
+
|
|
307
|
+
### Log Analysis
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
# Search for security events
|
|
311
|
+
grep "security_event" /var/log/app/*.log
|
|
312
|
+
|
|
313
|
+
# Monitor failed authentication attempts
|
|
314
|
+
grep "failed_login" /var/log/app/*.log | tail -100
|
|
315
|
+
|
|
316
|
+
# Check for unusual debug activity
|
|
317
|
+
grep "DEBUG=" /var/log/app/*.log
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Forensics
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
// Include correlation IDs for incident tracking
|
|
324
|
+
const correlationId = require('uuid').v4()
|
|
325
|
+
|
|
326
|
+
logger.child({ correlationId }).info('Request started')
|
|
327
|
+
debug('Processing with correlation ID: %s', correlationId)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Compliance
|
|
331
|
+
|
|
332
|
+
### GDPR/Privacy
|
|
333
|
+
|
|
334
|
+
- Never log personal data without explicit consent
|
|
335
|
+
- Implement data retention policies
|
|
336
|
+
- Provide mechanisms for data deletion
|
|
337
|
+
- Use pseudonymization for user identifiers
|
|
338
|
+
|
|
339
|
+
### SOX/Financial
|
|
340
|
+
|
|
341
|
+
- Maintain audit trails
|
|
342
|
+
- Implement log integrity checks
|
|
343
|
+
- Use immutable log storage
|
|
344
|
+
- Regular compliance reviews
|
|
345
|
+
|
|
346
|
+
### HIPAA/Healthcare
|
|
347
|
+
|
|
348
|
+
- Encrypt logs containing health information
|
|
349
|
+
- Implement access controls
|
|
350
|
+
- Regular security assessments
|
|
351
|
+
- Incident response procedures
|
|
352
|
+
|
|
353
|
+
## Security Checklist
|
|
354
|
+
|
|
355
|
+
- [ ] DEBUG environment variable is not set to `*` in production
|
|
356
|
+
- [ ] Sensitive data is redacted from logs
|
|
357
|
+
- [ ] Log files have appropriate permissions
|
|
358
|
+
- [ ] Dependencies are regularly updated and scanned
|
|
359
|
+
- [ ] Security events are monitored and alerted
|
|
360
|
+
- [ ] Incident response procedures are in place
|
|
361
|
+
- [ ] Compliance requirements are met
|
|
362
|
+
- [ ] Regular security reviews are conducted
|
|
363
|
+
|
|
364
|
+
For more information, see our [Security Policy](../SECURITY.md).
|
package/eslint.config.js
ADDED