* tmp: add sigsci

* add sigsci.js

* add sigsci.js

* feat: add sigsci to middleware

* convert sigsci to esmodule

* add sigsci-agent to docker-compose.tmpl
This commit is contained in:
Mike Surowiec 2022-05-12 11:15:17 -05:00 коммит произвёл GitHub
Родитель b0641c98af
Коммит ed8eda1f60
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 811 добавлений и 1 удалений

Просмотреть файл

@ -13,7 +13,7 @@ module.exports = {
babelOptions: { configFile: './.babelrc' },
sourceType: 'module',
},
ignorePatterns: ['tmp/*', '!/.*', '/.next/', 'script/bookmarklets/*'],
ignorePatterns: ['tmp/*', '!/.*', '/.next/', 'script/bookmarklets/*', 'lib/sigsci.js'],
rules: {
'import/no-extraneous-dependencies': ['error', { packageDir: '.' }],
},

Просмотреть файл

@ -19,6 +19,7 @@ services:
HEROKU_PRODUCTION_APP: true
PORT: 4000
DD_AGENT_HOST: datadog-agent
SIGSCI_RPC_ADDRESS: sigsci-agent:8000
depends_on:
- datadog-agent
restart: always
@ -31,3 +32,12 @@ services:
DD_API_KEY: ${DD_API_KEY}
DD_AGENT_HOST: datadog-agent
DD_HISTOGRAM_PERCENTILES: 0.99 0.95 0.50
sigsci-agent:
image: signalsciences/sigsci-agent
ports:
- '8000:8000'
environment:
SIGSCI_RPC_ADDRESS: 0.0.0.0:8000
SIGSCI_ACCESSKEY: ${SIGSCI_ACCESSKEYID}
SIGSCI_SECRETACCESSKEY: ${SIGSCI_SECRETACCESSKEY}

681
lib/sigsci.js Normal file
Просмотреть файл

@ -0,0 +1,681 @@
/*
* NodeJS Module
*
* Copyright (c) 2019-2020 Signal Sciences Corp.
*
* Proprietary and Confidential - Do Not Distribute
*
*/
/* jslint node: true */
'use strict'
/* jshint bitwise: true, curly: true, eqeqeq: true */
/* jshint freeze: true, funcscope: true, futurehostile: true */
/* jshint latedef: true, noarg: true, nocomma: true, nonbsp: true */
/* jshint nonew: true, notypeof: true, singleGroups: true */
/* jshint undef: true, unused: true */
/* jshint asi:true */
import Session from 'msgpack5rpc'
import net from 'node:net'
import util from 'node:util'
import stream from 'node:stream'
// default parameters
var defaultOptions = {
// path specifies the UDS to connect to the agent
path: '/var/run/sigsci.sock',
// maxPostSize - if a POST body is larger than maxPostSize
// the post body is NOT sent to the agent.
maxPostSize: 100000,
// socketTime - if the agent does not respond in this number of
// milliseconds, "fail open" and allow the request to pass
socketTimeout: 100 /* milliseconds */,
// HTTP methods that can contain a body. Unlikely this needs to be
// changed.
bodyMethods: {
POST: true,
PUT: true,
PATCH: true,
},
// TK
anomalySize: 524288,
// TK
anomalyDuration: 1000 /* milliseconds */,
// Enable debug log
debug: false,
// Inspect additional content types of body: ['text/plain','text/html']
expectedContentTypes: [],
// log function to use
log: function (msg) {
console.log(util.format('SIGSCI %s', msg))
},
}
// Utility functinon to merge two objects into another.
// Used for setting default values.
// from http://stackoverflow.com/a/8625261
var merge = function () {
var obj = {}
var i = 0
var il = arguments.length
var key
for (; i < il; i++) {
for (key in arguments[i]) {
if (arguments[i].hasOwnProperty(key)) {
obj[key] = arguments[i][key]
}
}
}
return obj
}
// rawHeadersToPairs converts a nodejs raw header list
// to a list of pairs expected in the protocol.
var rawHeadersToPairs = function (raw) {
var out = []
var n = raw.length
for (var i = 0; i < n; i += 2) {
out.push([raw[i], raw[i + 1]])
}
return out
}
var headersToPairs = function (raw) {
var out = []
for (var key in raw) {
out.push([key, raw[key]])
}
return out
}
var getRequestHeaders = function (req) {
// modern
if (req.rawHeaders) {
return rawHeadersToPairs(req.rawHeaders)
}
// old 0.10.X series
return headersToPairs(req.headers)
}
var getPost = function (req, maxSize, bodyMethods, expectedContentTypes) {
// can this method even have a body?
if (bodyMethods[req.method] !== true) {
return false
}
var contentLength = parseInt(req.headers['content-length'])
// does content-length not exist or not make sense?
if (isNaN(contentLength) || contentLength <= 0) {
return false
}
// too big?
if (contentLength >= maxSize) {
return false
}
// something the agent can decode?
return isValidContentType(req, expectedContentTypes)
}
var isValidContentType = function (req, expectedContentTypes) {
var contentType = ('' + req.headers['content-type']).toLowerCase()
if (
contentType.indexOf('application/x-www-form-urlencoded') !== -1 ||
contentType.startsWith('multipart/form-data') ||
contentType.startsWith('application/graphql') ||
contentType.indexOf('json') !== -1 ||
contentType.indexOf('javascript') !== -1 ||
contentType.indexOf('xml') !== -1
) {
return true
}
for (var i = 0; i < expectedContentTypes.length; i++) {
if (contentType.startsWith(expectedContentTypes[i])) {
return true
}
}
if (req.rawHeaders) {
var headers = req.rawHeaders
for (var i = 0, count = 0; i < headers.length; i += 2) {
if (headers[i].toLowerCase() === 'content-type') {
if (++count > 1) {
return true
}
}
}
}
return false
}
var isNotSpace = function (header) {
return header !== ''
}
var isBlocking = function (responseCode) {
return responseCode >= 300 && responseCode <= 599
}
var isRedirect = function (responseCode) {
return responseCode >= 300 && responseCode <= 399
}
var splitHeader = function (line) {
var keyVal = line.split(':')
if (keyVal.length < 2) {
return [keyVal[0].trim(), '']
} else {
return [keyVal[0].trim(), keyVal.splice(1).join(':').trim()]
}
}
var getResponseHeaders = function (res) {
return (res._header || '').split('\r\n').filter(isNotSpace).map(splitHeader)
}
var getRpcHeader = function (rpcResponse, header) {
var headers = rpcResponse.RequestHeaders
for (var i = 0; i < headers.length; i++) {
var entry = headers[i]
if (header === entry[0]) {
return entry[1]
}
}
return null
}
var readPostBody = function (req, cb) {
// POST - async read
var postBody = []
var fnOnData = function (chunk) {
// append the current chunk of data to the fullBody variable
postBody.push(chunk)
}
var fnOnEnd = function () {
setImmediate(function () {
// now we need to "push back" the postbody into a stream that
// so the raw application can continue to function no matter
// what
// First remove the listeners we already set up
req.removeListener('data', fnOnData)
req.removeListener('end', fnOnEnd)
// make new stream, copy it over into current request obj
var s = new stream.Readable()
s._read = function noop() {}
for (var attr in s) {
req[attr] = s[attr]
}
// push in new body and EOF marker
postBody = Buffer.concat(postBody)
req.push(postBody)
req.push(null)
cb(postBody.toString())
})
}
req.on('data', fnOnData)
req.on('end', fnOnEnd)
}
const wafCode = {
WAF_CONNECT_ERROR: 'waf-connect-error',
WAF_CONNECT_TIMEOUT: 'waf-connect-timeout',
WAF_FAIL_OPEN: 'waf-fail-open',
WAF_OK: 'waf-ok',
WAF_BLOCKING: 'waf-blocking',
WAF_UNKNOWN: 'waf-unknown',
}
function Sigsci(userOptions) {
this.options = merge(defaultOptions, userOptions)
// Determine if we are UDS or TCP
//
// The default is to use UDS, so 'path' is set, and 'port' is unset.
//
// For TCP:
// 'port' must be specified
// 'host' is optional and defaults to 'localhost'
//
// For UDS:
// 'path' must be specified
//
// So:
// If 'port' is set after merge, then we are TCP, and
// delete the 'path' property to prevent node.js confusion.
//
// https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener
//
if ('port' in this.options) {
delete this.options.path
}
}
Sigsci.prototype.express = function () {
var self = this
return function (req, res, next) {
res.on('finish', function () {
onAfterResponse(req, res, self.options)
})
middleware(req, res, self.options, function (wafResponse) {
var wafSignalCode = wafResponse.wafCode
var rpcResponse = wafResponse.response
if (shouldContinue(wafSignalCode)) {
next()
return
} else if (wafSignalCode == wafCode.WAF_BLOCKING) {
handleNativeBlocking(res, rpcResponse)
return
}
return
})
}
}
Sigsci.prototype.wrap = function (next) {
var self = this
return function (req, res) {
res.on('finish', function () {
onAfterResponse(req, res, self.options)
})
middleware(req, res, self.options, function (wafResponse) {
var wafSignalCode = wafResponse.wafCode
var rpcResponse = wafResponse.response
if (shouldContinue(wafSignalCode)) {
next(req, res)
return
} else if (wafSignalCode == wafCode.WAF_BLOCKING) {
handleNativeBlocking(res, rpcResponse)
return
}
return
})
}
}
function handleNativeBlocking(res, rpcResponse) {
var responseCode = rpcResponse.WAFResponse
if (isRedirect(responseCode)) {
var redirectHeader = getRpcHeader(rpcResponse, 'X-Sigsci-Redirect')
if (redirectHeader) {
res.setHeader('Location', redirectHeader)
}
res.statusCode = responseCode
res.end('redirect')
} else {
res.writeHead(responseCode, { 'Content-Type': 'text/plain' })
res.end('not acceptable')
}
}
// this is to be used for HAPI 14
Sigsci.prototype.hapi = function () {
var self = this
return function (request, reply) {
var req = request.raw.req
var res = request.raw.res
middleware(req, res, self.options, function (wafResponse) {
var wafSignalCode = wafResponse.wafCode
var rpcResponse = wafResponse.response
if (shouldContinue(wafSignalCode)) {
reply.continue()
return
} else if (wafSignalCode == wafCode.WAF_BLOCKING) {
var responseCode = rpcResponse.WAFResponse
if (isRedirect(responseCode)) {
var redirectHeader = getRpcHeader(rpcResponse, 'X-Sigsci-Redirect')
reply(responseCode).code(responseCode).header('Location', redirectHeader)
} else {
reply(rpcResponse.WAFResponse).code(rpcResponse.WAFResponse)
}
}
return
})
}
}
Sigsci.prototype.hapi18 = function () {
return this.hapi17()
}
// this can be used for HAPI 17 and 18
Sigsci.prototype.hapi17 = function () {
var self = this
return function (request, reply) {
var req = request.raw.req
var res = request.raw.res
return new Promise(function (resolve) {
middleware(req, res, self.options, function (wafResponse) {
var wafSignalCode = wafResponse.wafCode
var rpcResponse = wafResponse.response
if (shouldContinue(wafSignalCode)) {
resolve(reply.continue)
return
} else if (wafSignalCode == wafCode.WAF_BLOCKING) {
var responseCode = rpcResponse.WAFResponse
if (isRedirect(responseCode)) {
var redirectHeader = getRpcHeader(rpcResponse, 'X-Sigsci-Redirect')
resolve(
reply
.response(responseCode)
.code(responseCode)
.header('Location', redirectHeader)
.takeover()
)
} else {
resolve(reply.response(responseCode).code(responseCode).takeover())
}
return
}
return
})
})
}
}
Sigsci.prototype.koa = function () {
var self = this
return function (ctx, next) {
var req = ctx.req
var res = ctx.res
return new Promise(function (resolve) {
middleware(req, res, self.options, function (wafResponse) {
res.on('finish', function () {
onAfterResponse(req, res, self.options)
})
var wafSignalCode = wafResponse.wafCode
var rpcResponse = wafResponse.response
if (shouldContinue(wafSignalCode)) {
resolve(next())
return
} else if (wafSignalCode == wafCode.WAF_BLOCKING) {
resolve(handleNativeBlocking(res, rpcResponse))
return
}
return
})
})
}
}
Sigsci.prototype.hapi17Ending = function () {
return this.hapiEnding()
}
Sigsci.prototype.hapi18Ending = function () {
return this.hapiEnding()
}
Sigsci.prototype.hapiEnding = function () {
var self = this
return function (request) {
onAfterResponse(request.raw.req, request.raw.res, self.options)
}
}
function shouldContinue(wafSignalCode) {
return (
wafSignalCode == wafCode.WAF_CONNECT_ERROR ||
wafSignalCode == wafCode.WAF_CONNECT_TIMEOUT ||
wafSignalCode == wafCode.WAF_FAIL_OPEN ||
wafSignalCode == wafCode.WAF_OK ||
wafSignalCode == wafCode.WAF_UNKNOWN
)
}
var makePre = function (req, postBody) {
var now = Date.now()
var sock = req.socket
var scheme = 'http'
var tlsProtocol = ''
var tlsCipher = ''
if (typeof sock.getCipher === 'function') {
scheme = 'https'
var cipherStuff = sock.getCipher()
if (cipherStuff !== null) {
tlsProtocol = cipherStuff.version
tlsCipher = cipherStuff.name
}
}
return {
ModuleVersion: 'sigsci-module-nodejs ' + module.version,
ServerVersion: 'nodejs ' + process.version,
ServerFlavor: '',
ServerName: req.headers.host, // TBD vs. require('os').hostname(); ? why include at all
Timestamp: Math.floor(req._sigsciRequestStart / 1000),
NowMillis: now,
RemoteAddr: req.connection.remoteAddress,
Method: req.method,
Scheme: scheme,
URI: req.url,
Protocol: req.httpVersion,
TLSProtocol: tlsProtocol,
TLSCipher: tlsCipher,
HeadersIn: getRequestHeaders(req),
PostBody: postBody,
}
}
var middleware = function (req, res, options, processWafResponse) {
req._sigsciRequestStart = Date.now()
req._sigsciBytesWritten = req.socket.bytesWritten
// GET or other method without body
if (!getPost(req, options.maxPostSize, options.bodyMethods, options.expectedContentTypes)) {
preRequest(req, '', options, processWafResponse)
return
}
readPostBody(req, function (postBody) {
preRequest(req, postBody, options, processWafResponse)
return
})
}
var preRequest = function (req, postBody, options, processWafResponse) {
var client = new net.Socket()
client.setTimeout(options.socketTimeout)
client.connect(options, function () {
req._sigsciSession = new Session()
req._sigsciSession.attach(client, client)
req._sigsciClient = client
var callback = function (err, rpcResponse) {
var wafResponse = onPre(req, err, options, rpcResponse) // this is resolved
processWafResponse(wafResponse)
}
req._sigsciSession.request('RPC.PreRequest', [makePre(req, postBody)], callback)
})
client.on('error', function (err) {
options.log(util.format('PreRequest connection error ' + JSON.stringify(err)))
client.destroy() // kill client after server's response
processWafResponse(new WAFResponse(wafCode.WAF_CONNECT_ERROR))
})
client.on('timeout', function (err) {
// err is typically undefined here since its a timeout
// need to touch it to prevent lint error
err = null
options.log(util.format('PreRequest timeout after %d ms', Date.now() - req._sigsciRequestStart))
client.destroy() // kill client after server's response
processWafResponse(new WAFResponse(wafCode.WAF_CONNECT_TIMEOUT))
})
}
var onPre = function (req, err, options, rpcResponse) {
req._sigsciClient.destroy()
if (err) {
// fail open.
options.log(util.format('onPre error: %s', err))
return new WAFResponse(wafCode.WAF_FAIL_OPEN)
}
// save agent response since we'll use it later.
req.SigSciAgent = rpcResponse
var responseCode = rpcResponse.WAFResponse
if (responseCode == 200) {
return new WAFResponse(wafCode.WAF_OK, rpcResponse)
}
if (isBlocking(responseCode)) {
return new WAFResponse(wafCode.WAF_BLOCKING, rpcResponse)
}
return new WAFResponse(wafCode.WAF_UNKNOWN, rpcResponse)
}
var onAfterResponse = function (req, res, options) {
var obj
var rpcResponse = req.SigSciAgent
if (!rpcResponse) {
// something bad happened
return
}
var duration = Date.now() - req._sigsciRequestStart
if (duration < 0) {
duration = 0
}
var headers = getResponseHeaders(res)
var contentLength = -1
for (var i = 0; i < headers.length; i++) {
if (headers[i][0].toLowerCase() === 'content-length') {
contentLength = parseInt(headers[i][1])
}
}
if (contentLength === -1 && req.socket && req.socket.bytesWritten) {
contentLength = req.socket.bytesWritten - req._sigsciBytesWritten
}
if (options.debug) {
options.log(
util.format('after,%s,%s,%s', req._sigsciRequestStart, Date.now(), rpcResponse.RequestID)
)
}
if (rpcResponse.RequestID) {
obj = {
WAFResponse: rpcResponse.WAFResponse,
RequestID: rpcResponse.RequestID,
ResponseCode: res.statusCode,
ResponseMillis: duration,
ResponseSize: contentLength,
HeadersOut: getResponseHeaders(res),
}
send(req, res, 'RPC.UpdateRequest', obj, options, onUpdateResponse, null)
return
}
// full post response
if (
res.statusCode >= 300 ||
duration > options.anomalyDuration ||
contentLength > options.anomalySize
) {
obj = makePre(req, '')
obj.WAFResponse = rpcResponse.WAFResponse
obj.ResponseCode = res.statusCode
obj.ResponseMillis = duration
obj.ResponseSize = contentLength
obj.HeadersOut = getResponseHeaders(res)
// do update or post request
send(req, res, 'RPC.PostRequest', obj, options, onPostResponse, null)
}
//
// no update or post request --> nothing to do
//
}
// onUpdateResponse is triggered after a RPC.UpdateRequest
var onUpdateResponse = function (options, err /* , rpcResponse */) {
if (err !== null && err !== undefined) {
options.log(util.format('RPC.UpdateResponse error: %s', err))
}
}
// onPostResponse is triggered after a RPC.PostRequest
var onPostResponse = function (options, err /* , rpcResponse */) {
if (err !== null && err !== undefined) {
options.log(util.format('RPC.PostResponse error: %s', err))
}
}
var send = function (req, res, method, obj, options, callback, onerror) {
req._sigsciPostRequestStart = Date.now()
var client = new net.Socket()
var log = options.log
var debug = options.debug
var destroyCallback = function (err) {
if (!client.destroyed) {
client.destroy()
}
if (callback) {
callback(options, err)
}
}
client.setTimeout(options.socketTimeout)
client.connect(options, function () {
var session = new Session()
session.attach(client, client)
session.request(method, [obj], destroyCallback)
})
client.on('error', function (err) {
log(util.format('Update/PostRequest connection error: %s', err.message))
client.destroy() // kill client after server's response
if (onerror) {
onerror(req, res)
}
})
client.on('timeout', function (err) {
var duration = Date.now() - req._sigsciPostRequestStart
if (debug) {
var rpcResponse = req.SigSciAgent
var requestId = ''
if (rpcResponse) {
requestId = rpcResponse.RequestID
}
log(
util.format(
'send,%s,%s,%s,%s',
req._sigsciRequestStart,
Date.now(),
requestId,
req._sigsciPostRequestStart
)
)
}
log(util.format('Update/PostRequest timeout after %d ms', duration))
client.destroy() // kill client after server's response
})
}
function WAFResponse(wafCode, response) {
this.wafCode = wafCode
this.response = response
}
export default Sigsci

Просмотреть файл

@ -2,6 +2,8 @@ import fs from 'fs'
import path from 'path'
import express from 'express'
import Sigsci from '../lib/sigsci.js'
import instrument from '../lib/instrument-middleware.js'
import haltOnDroppedConnection from './halt-on-dropped-connection.js'
import abort from './abort.js'
@ -129,6 +131,19 @@ export default function (app) {
app.use(datadog)
}
if (process.env.SIGSCI_RPC_ADDRESS) {
// Fastly Signal Sciences is a module that intercepts Express requests,
// and sends them to the Signal Science agent over TCP. That agent might
// then deem the request blockable and exits the request there.
// More information about the module here
// https://docs.fastly.com/signalsciences/install-guides/other-modules/nodejs-module/
const sigsci = new Sigsci({
host: process.env.SIGSCI_RPC_ADDRESS.split(':')[0],
port: process.env.SIGSCI_RPC_ADDRESS.split(':')[1],
})
app.use(sigsci.express())
}
// Must appear before static assets and all other requests
// otherwise we won't be able to benefit from that functionality
// for static assets as well.

103
package-lock.json сгенерированный
Просмотреть файл

@ -55,6 +55,7 @@
"mdast-util-from-markdown": "^1.2.0",
"mdast-util-to-string": "^3.1.0",
"morgan": "^1.10.0",
"msgpack5rpc": "^1.1.0",
"next": "^11.1.3",
"parse5": "^6.0.1",
"port-used": "^2.0.8",
@ -14698,6 +14699,56 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/msgpack5": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-3.6.1.tgz",
"integrity": "sha512-VoY2AaoowHZLLKyEb5FRzuhdSzXn5quGjcMKJOJHJPxp9baYZx5t6jiHUhp5aNRlqqlt+5GXQGovMLNKsrm1hg==",
"dependencies": {
"bl": "^1.2.1",
"inherits": "^2.0.3",
"readable-stream": "^2.3.3",
"safe-buffer": "^5.1.1"
}
},
"node_modules/msgpack5/node_modules/bl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
"integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==",
"dependencies": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
}
},
"node_modules/msgpack5/node_modules/readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/msgpack5/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/msgpack5rpc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/msgpack5rpc/-/msgpack5rpc-1.1.0.tgz",
"integrity": "sha1-9Leqf4sgsxez2PbYuLLCQ29xbpA=",
"dependencies": {
"msgpack5": "^3.3.0"
}
},
"node_modules/nanoid": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
@ -33214,6 +33265,58 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"msgpack5": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-3.6.1.tgz",
"integrity": "sha512-VoY2AaoowHZLLKyEb5FRzuhdSzXn5quGjcMKJOJHJPxp9baYZx5t6jiHUhp5aNRlqqlt+5GXQGovMLNKsrm1hg==",
"requires": {
"bl": "^1.2.1",
"inherits": "^2.0.3",
"readable-stream": "^2.3.3",
"safe-buffer": "^5.1.1"
},
"dependencies": {
"bl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
"integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==",
"requires": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"msgpack5rpc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/msgpack5rpc/-/msgpack5rpc-1.1.0.tgz",
"integrity": "sha1-9Leqf4sgsxez2PbYuLLCQ29xbpA=",
"requires": {
"msgpack5": "^3.3.0"
}
},
"nanoid": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",

Просмотреть файл

@ -57,6 +57,7 @@
"mdast-util-from-markdown": "^1.2.0",
"mdast-util-to-string": "^3.1.0",
"morgan": "^1.10.0",
"msgpack5rpc": "^1.1.0",
"next": "^11.1.3",
"parse5": "^6.0.1",
"port-used": "^2.0.8",