Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

unexpected-mitm

Package Overview
Dependencies
Maintainers
5
Versions
108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

unexpected-mitm - npm Package Compare versions

Comparing version 10.2.2 to 11.0.0

lib/consumeReadableStream.js

650

lib/unexpectedMitm.js
/* global setImmediate, process, after, console */
var messy = require('messy'),
consumeReadableStream = require('./consumeReadableStream'),
createMitm = require('mitm-papandreou'),
createSerializedRequestHandler = require('./createSerializedRequestHandler'),
_ = require('underscore'),
http = require('http'),
https = require('https'),
formatHeaderObj = require('./formatHeaderObj'),
fs = require('fs'),
path = require('path'),
stream = require('stream'),
urlModule = require('url'),
memoizeSync = require('memoizesync'),

@@ -16,26 +17,4 @@ callsite = require('callsite'),

var isNodeZeroTen = !!process.version.match(/v0.10/);
// fallback to an inlined version of 0.12+ path.isAbsolute() for 0.10 compat
var pathIsAbsolute = path.isAbsolute || function (path) {
var len = path.length;
if (len === 0) {
return false;
}
var code = path.charCodeAt(0);
if (code === 47/*/*/ || code === 92/*\*/) {
return true;
} else if ((code >= 65/*A*/ && code <= 90/*Z*/) ||
(code >= 97/*a*/ && code <= 122/*z*/)) {
// Possible device root
var UnexpectedMitmMocker = require('./UnexpectedMitmMocker');
if (len > 2 && path.charCodeAt(1) === 58/*:*/) {
code = path.charCodeAt(2);
if (code === 47/*/*/ || code === 92/*\*/) {
return true;
}
}
}
return false;
};
function checkEnvFlag(varName) {

@@ -45,6 +24,2 @@ return process.env[varName] === 'true';

function isRegExp(obj) {
return Object.prototype.toString.call(obj) === '[object RegExp]';
}
function isTextualBody(message, content) {

@@ -54,10 +29,2 @@ return message.hasTextualContentType && bufferCanBeInterpretedAsUtf8(content);

function formatHeaderObj(headerObj) {
var result = {};
Object.keys(headerObj).forEach(function (headerName) {
result[messy.formatHeaderName(headerName)] = headerObj[headerName];
});
return result;
}
function bufferCanBeInterpretedAsUtf8(buffer) {

@@ -97,9 +64,2 @@ // Hack: Since Buffer.prototype.toString('utf-8') is very forgiving, convert the buffer to a string

function trimHeadersLower(message) {
delete message.headers.valuesByName['content-length'];
delete message.headers.valuesByName['transfer-encoding'];
delete message.headers.valuesByName.connection;
delete message.headers.valuesByName.date;
}
function trimMessage(message) {

@@ -152,18 +112,2 @@ if (typeof message.body !== 'undefined') {

function trimMockResponse(mockResponse) {
var responseProperties = {};
responseProperties.statusCode = mockResponse.statusCode;
responseProperties.statusMessage = mockResponse.statusMessage;
responseProperties.protocolName = mockResponse.protocolName;
responseProperties.protocolVersion = mockResponse.protocolVersion;
responseProperties.headers = formatHeaderObj(mockResponse.headers.valuesByName);
if (mockResponse.body) {
// read out the messy decoded body
responseProperties.body = mockResponse.body;
}
return responseProperties;
}
function trimRecordedExchange(recordedExchange) {

@@ -176,194 +120,2 @@ return {

function createSerializedRequestHandler(onRequest) {
var activeRequest = false,
requestQueue = [];
function processNextRequest() {
function cleanUpAndProceed() {
if (activeRequest) {
activeRequest = false;
setImmediate(processNextRequest);
}
}
while (requestQueue.length > 0 && !activeRequest) {
activeRequest = true;
var reqAndRes = requestQueue.shift(),
req = reqAndRes[0],
res = reqAndRes[1],
resEnd = res.end;
res.end = function () {
resEnd.apply(this, arguments);
cleanUpAndProceed();
};
// This happens upon an error, so we need to make sure that we catch that case also:
res.on('close', cleanUpAndProceed);
onRequest(req, res);
}
}
return function (req, res) {
requestQueue.push([req, res]);
processNextRequest();
};
}
function resolveExpectedRequestProperties(expectedRequestProperties) {
if (typeof expectedRequestProperties === 'string') {
expectedRequestProperties = { url: expectedRequestProperties };
} else if (expectedRequestProperties && typeof expectedRequestProperties === 'object') {
expectedRequestProperties = _.extend({}, expectedRequestProperties);
}
if (expectedRequestProperties) {
if (typeof expectedRequestProperties.url === 'string') {
var matchMethod = expectedRequestProperties.url.match(/^([A-Z]+) ([\s\S]*)$/);
if (matchMethod) {
expectedRequestProperties.method = expectedRequestProperties.method || matchMethod[1];
expectedRequestProperties.url = matchMethod[2];
}
}
} else {
expectedRequestProperties = {};
}
if (/^https?:\/\//.test(expectedRequestProperties.url)) {
var urlObj = urlModule.parse(expectedRequestProperties.url);
expectedRequestProperties.headers = expectedRequestProperties.headers || {};
if (Object.keys(expectedRequestProperties.headers).every(function (key) {
return key.toLowerCase() !== 'host';
})) {
expectedRequestProperties.headers.host = urlObj.host;
}
expectedRequestProperties.host = expectedRequestProperties.host || urlObj.hostname;
if (urlObj.port && typeof expectedRequestProperties.port === 'undefined') {
expectedRequestProperties.port = parseInt(urlObj.port, 10);
}
if (urlObj.protocol === 'https:' && typeof expectedRequestProperties.encrypted === 'undefined') {
expectedRequestProperties.encrypted = true;
}
expectedRequestProperties.url = urlObj.path;
}
var expectedRequestBody = expectedRequestProperties.body;
if (Array.isArray(expectedRequestBody) || (expectedRequestBody && typeof expectedRequestBody === 'object' && !isRegExp(expectedRequestBody) && (typeof Buffer === 'undefined' || !Buffer.isBuffer(expectedRequestBody)))) {
// in the case of a streamed request body and skip asserting the body
if (typeof expectedRequestBody.pipe === 'function') {
throw new Error('unexpected-mitm: a stream cannot be used to verify the request body, please specify the buffer instead.');
}
expectedRequestProperties.headers = expectedRequestProperties.headers || {};
if (Object.keys(expectedRequestProperties.headers).every(function (key) {
return key.toLowerCase() !== 'content-type';
})) {
expectedRequestProperties.headers['Content-Type'] = 'application/json';
}
}
return expectedRequestProperties;
}
function createMockResponse(responseProperties) {
var mockResponse = new messy.HttpResponse(responseProperties);
mockResponse.statusCode = mockResponse.statusCode || 200;
mockResponse.protocolName = mockResponse.protocolName || 'HTTP';
mockResponse.protocolVersion = mockResponse.protocolVersion || '1.1';
mockResponse.statusMessage = mockResponse.statusMessage || http.STATUS_CODES[mockResponse.statusCode];
return mockResponse;
}
function calculateBodyByteCount(chunk) {
var trailerIdx = findHeaderSeparator(chunk);
if (trailerIdx !== -1) {
return chunk.slice(trailerIdx + 4).length;
}
return 0;
}
function findHeaderSeparator(chunk) {
return chunk.toString().indexOf('\r\n\r\n');
}
function isTrailerChunk(chunk) {
return chunk.slice(-5).toString() === '0\r\n\r\n';
}
function attachConnectionHook(connection, callback) {
var bodyByteCount = 0;
var contentLengthByteCount = 0;
var hasContentLength = false;
var rawChunks = [];
var sawHeaders = false;
var _write = connection._write;
/*
* wrap low level write to gather response data and return raw data
* to callback when entire response is written
*/
connection._write = function (chunk, encoding, cb) {
chunk = Buffer.isBuffer(chunk) ? chunk : new Buffer(chunk);
rawChunks.push(chunk);
if (!sawHeaders) {
var chunkString = chunk.toString();
var match;
match = chunkString.match(/^Transfer-Encoding: /m);
if (match) {
sawHeaders = true;
}
match = chunkString.match(/^Content-Length: (\d+)/m);
if (match) {
// handle the current chunk containing body bytes
bodyByteCount = calculateBodyByteCount(chunk);
contentLengthByteCount = +match[1];
hasContentLength = true;
sawHeaders = true;
}
} else {
bodyByteCount += chunk.length;
}
if ((isTrailerChunk(chunk) && !hasContentLength) ||
(hasContentLength && bodyByteCount === contentLengthByteCount)) {
/*
* explicitly execute the callback returning raw data here
* to ensure it is run before issuing the final write which
* will complete the request on the wire
*/
callback(null, Buffer.concat(rawChunks));
}
_write.call(connection, chunk, encoding, cb);
};
}
function attachResponseHooks(res, callback) {
var hasEnded = false; // keep track of whether res.end() has been called
attachConnectionHook(res.connection, function (err, rawBuffer) {
if (hasEnded) {
callback(null, rawBuffer);
}
});
['end', 'destroy'].forEach(function (methodName) {
var orig = res[methodName];
res[methodName] = function (chunk, encoding) {
if (methodName === 'end') {
hasEnded = true;
}
var returnValue = orig.apply(this, arguments);
if (methodName === 'destroy') {
hasEnded = true;
callback(null);
}
// unconditionally return value given we do not wrap write
return returnValue;
};
});
}
function determineCallsite(stack) {

@@ -431,3 +183,4 @@ // discard the first frame

expect = expect.child()
.use(require('unexpected-messy'));
.use(require('unexpected-messy'))
.use(require('./mockerAssertions'));

@@ -547,56 +300,2 @@ expectForRendering.addType({

function consumeReadableStream(readableStream, options) {
options = options || {};
var skipConcat = !!options.skipConcat;
return expect.promise(function (resolve, reject) {
var chunks = [];
readableStream.on('data', function (chunk) {
chunks.push(chunk);
}).on('end', function (chunk) {
resolve({ body: skipConcat ? chunks : Buffer.concat(chunks) });
}).on('error', function (err) {
resolve({ body: skipConcat ? chunks : Buffer.concat(chunks), error: err });
});
});
}
function getMockResponse(responseProperties) {
var mockResponse;
var mockResponseError;
if (Object.prototype.toString.call(responseProperties) === '[object Error]') {
mockResponseError = responseProperties;
} else {
mockResponse = createMockResponse(responseProperties);
}
return expect.promise(function () {
if (!mockResponseError && mockResponse && mockResponse.body && typeof mockResponse.body.pipe === 'function') {
return consumeReadableStream(mockResponse.body).then(function (result) {
if (result.error) {
mockResponseError = result.error;
}
if (result.body) {
mockResponse.unchunkedBody = result.body;
}
});
}
}).then(function () {
if (mockResponse && !mockResponseError && (Array.isArray(mockResponse.body) || (mockResponse.body && typeof mockResponse.body === 'object' && (typeof Buffer === 'undefined' || !Buffer.isBuffer(mockResponse.body))))) {
if (!mockResponse.headers.has('Content-Type')) {
mockResponse.headers.set('Content-Type', 'application/json');
}
}
return [ mockResponse, mockResponseError ];
});
}
function observeResponse(res) {
return expect.promise(function (resolve, reject) {
attachResponseHooks(res, function (err, rawBuffer) {
resolve(rawBuffer);
});
});
}
function performRequest(requestResult) {

@@ -611,3 +310,3 @@ return expect.promise(function (resolve, reject) {

}, requestResult.metadata)).on('response', function (response) {
consumeReadableStream(response).caught(reject).then(function (result) {
consumeReadableStream(response).catch(reject).then(function (result) {
if (result.error) {

@@ -725,3 +424,3 @@ // TODO: Consider adding support for recording this (the upstream response erroring out while we're recording it)

recordedExchanges.push(recordedExchange);
consumeReadableStream(req).caught(reject).then(function (result) {
consumeReadableStream(req).catch(reject).then(function (result) {
if (result.error) {

@@ -772,3 +471,3 @@ // TODO: Consider adding support for recording this (the request erroring out while we're recording it)

});
}).caught(function (err) {
}).catch(function (err) {
recordedExchange.response = err;

@@ -782,3 +481,3 @@ clientSocket.emit('error', err);

return expect.shift();
}).caught(reject).then(function (value) {
}).catch(reject).then(function (value) {
recordedExchanges = recordedExchanges.map(trimRecordedExchange);

@@ -828,3 +527,3 @@ if (recordedExchanges.length === 1) {

if (!pathIsAbsolute(testFile)) {
if (!path.isAbsolute(testFile)) {
testFile = path.join(path.dirname(writeCallsite.fileName), testFile);

@@ -874,7 +573,7 @@ }

})
.exportAssertion('<any> with http mocked out [and verified] [with extra info] [allowing modification] <array|object> <assertion>', function (expect, subject, requestDescriptions) { // ...
expect.errorMode = 'nested';
var mitm = createMitm();
.exportAssertion('<any> with http mocked out [and verified] [with extra info] <array|object> <assertion>', function (expect, subject, requestDescriptions) { // ...
expect.errorMode = 'default';
var shouldBeVerified = checkEnvFlag('UNEXPECTED_MITM_VERIFY') || expect.flags['and verified'];
var shouldReturnExtraInfo = expect.flags['with extra info'];
var shouldReturnExtraInfo = expect.flags['with extra info'] || shouldBeVerified;
expect.flags['with extra info'] = shouldReturnExtraInfo;

@@ -887,8 +586,6 @@ if (!Array.isArray(requestDescriptions)) {

}
} else if (!expect.flags['allowing modification']) {
// duplicate descriptions to allow array consumption
} else {
requestDescriptions = requestDescriptions.slice(0);
}
var nextRequestDescriptionIndex = 0;

@@ -901,315 +598,15 @@ var verifyBlocks = requestDescriptions.map(function (description) {

var __lastError;
var mocker = new UnexpectedMitmMocker({
requestDescriptions: requestDescriptions
});
// Keep track of the http/https agents that we have seen
// during the test so we can clean up afterwards:
var seenAgents = [];
var assertionPromise = expect.promise(function (resolve, reject) {
var httpConversation = new messy.HttpConversation(),
httpConversationSatisfySpec = {exchanges: []};
__lastError = null;
mitm.on('request', createSerializedRequestHandler(function (req, res) {
if (!res.connection) {
// I've observed some cases where keep-alive is
// being used and we end up with an extra "phantom
// request event" even though only requeest is being
// issued. Seems like a bug in mitm.
// It goes without saying that we should try to get
// rid of this. Hopefully something will happen
// on https://github.com/moll/node-mitm/pull/36
return;
}
var clientSocket = req.connection._mitm.client;
var clientSocketOptions = req.connection._mitm.opts;
if (typeof clientSocketOptions.port === 'string') {
// The port could have been defined as a string in a 3rdparty library doing the http(s) call, and that seems to be valid use of the http(s) module
clientSocketOptions = _.defaults({
port: parseInt(clientSocketOptions.port, 10)
}, clientSocketOptions);
}
var agent = clientSocketOptions.agent || (res.connection.encrypted ? https : http).globalAgent;
if (seenAgents.indexOf(agent) === -1) {
seenAgents.push(agent);
}
var currentDescription = requestDescriptions[nextRequestDescriptionIndex];
nextRequestDescriptionIndex += 1;
var hasRequestDescription = !!currentDescription,
metadata =
_.defaults(
{ encrypted: Boolean(res.connection.encrypted) },
_.pick(clientSocketOptions, messy.HttpRequest.metadataPropertyNames),
_.pick(clientSocketOptions && clientSocketOptions.agent && clientSocketOptions.agent.options, messy.HttpRequest.metadataPropertyNames)
),
requestDescription = currentDescription,
requestProperties,
responseProperties = requestDescription && requestDescription.response,
expectedRequestProperties;
expect.promise(function () {
expectedRequestProperties = resolveExpectedRequestProperties(requestDescription && requestDescription.request);
}).then(function () {
return consumeReadableStream(req, {skipConcat: true});
}).then(function (result) {
if (result.error) {
// TODO: Consider adding support for recording this (the request erroring out while we're consuming it)
throw result.error;
}
requestProperties = _.extend({
method: req.method,
path: req.url,
protocolName: 'HTTP',
protocolVersion: req.httpVersion,
headers: req.headers,
unchunkedBody: Buffer.concat(result.body)
}, metadata);
if (!hasRequestDescription) {
// there was no mock so arrange "<no response>"
assertMockResponse(null);
/*
* We wish to cause the generation of a diff
* so we arrange to enter our 'success' path
* but ensure the delegated assertion fully
* completes by signalling an error condition.
* Given we later fail on "to satisfy" we pass
* a null fulfilment value.
*
* Note the use of the single reject/resolve
* behaviour of promises: while the delegated
* assertion is reject()ed, we have already
* resolve()d thus the rejection of the former
* is effectively ignored and we proceed with
* our output.
*/
// cancel the delegated assertion
clientSocket.emit('error', new Error('unexpected-mitm: Saw unexpected requests.'));
// continue with current assertion
resolve([null, httpConversation, httpConversationSatisfySpec]);
return [null, null];
}
if (typeof responseProperties === 'function') {
// reset the readable req stream state
stream.Readable.call(req);
// read stream data from the buffered chunks
req._read = function () {
this.push(result.body.shift() || null);
};
if (isNodeZeroTen) {
/*
* As is mentioned in the streams documentation this
* call can be issued to kick some of the machinery
* and is apparently done within the standard lib.
*/
req.read(0);
}
// call response function inside a promise to catch exceptions
return expect.promise(function () {
responseProperties(req, res);
}).then(function () {
return [null, null];
}).caught(function (err) {
// ensure delivery of the caught error to the underlying socket
return [null, err];
});
} else {
return getMockResponse(responseProperties);
}
}).spread(function (mockResponse, mockResponseError) {
if (!(mockResponse || mockResponseError)) {
return;
}
var originalErrorMode = expect.errorMode;
expect.errorMode = 'default';
return expect.promise(function () {
var assertionMockRequest = new messy.HttpRequest(requestProperties);
trimHeadersLower(assertionMockRequest);
var assertionExchange = new messy.HttpExchange({request: assertionMockRequest});
if (mockResponse) {
assertionExchange.response = mockResponse;
} else if (mockResponseError) {
assertionExchange.response = mockResponseError;
}
return expect(assertionExchange, 'to satisfy', {request: expectedRequestProperties});
}).then(function () {
expect.errorMode = originalErrorMode;
// continue thus respond
deliverMockResponse(mockResponse, mockResponseError);
});
}).caught(function (e) {
/*
* Given an error occurs, the deferred assertion
* will still be pending and must be completed. We
* do this by signalling the error on the socket.
*/
// record the error
__lastError = e;
// cancel the delegated assertion
try {
clientSocket.emit('error', e);
} finally {
/*
* If an something was thrown trying to signal
* an error we have little choice but to try to
* reject the assertion. This is only safe with
* Unexpected 10.15.x and above.
*/
reject(e);
}
});
function assertMockResponse(mockResponse, mockResponseError) {
var mockRequest = new messy.HttpRequest(requestProperties);
var httpExchange = new messy.HttpExchange({request: mockRequest});
if (mockResponse) {
httpExchange.response = mockResponse;
} else if (mockResponseError) {
httpExchange.response = mockResponseError;
}
httpConversation.exchanges.push(httpExchange);
// only attempt request validation with a mock
if (hasRequestDescription) {
httpConversationSatisfySpec.exchanges.push({request: expectedRequestProperties});
}
/*
* We explicitly do not complete the promise
* when the last request for which we have a
* mock is seen.
*
* Instead simply record any exchanges that
* passed through and defer the resolution or
* rejection to the handler functions attached
* to the delegated assertion execution below.
*/
}
function deliverMockResponse(mockResponse, mockResponseError) {
setImmediate(function () {
var nonEmptyMockResponse = false;
if (mockResponse) {
res.statusCode = mockResponse.statusCode;
mockResponse.headers.getNames().forEach(function (headerName) {
mockResponse.headers.getAll(headerName).forEach(function (value) {
nonEmptyMockResponse = true;
res.setHeader(headerName, value);
});
});
var unchunkedBody = mockResponse.unchunkedBody;
if (typeof unchunkedBody !== 'undefined' && unchunkedBody.length > 0) {
nonEmptyMockResponse = true;
res.write(unchunkedBody);
} else if (nonEmptyMockResponse) {
res.writeHead(res.statusCode || 200);
}
}
if (mockResponseError) {
setImmediate(function () {
clientSocket.emit('error', mockResponseError);
assertMockResponse(mockResponse, mockResponseError);
});
} else {
res.end();
}
});
}
/*
* hook final write callback to record raw data and immediately
* assert request so it occurs prior to request completion
*/
observeResponse(res).then(function (rawBuffer) {
var mockResponse = createMockResponse(rawBuffer);
assertMockResponse(mockResponse);
});
}));
expect.promise(function () {
var assertionPromise = expect.promise(function () {
return mocker.mock(function () {
return expect.shift();
}).then(function (fulfilmentValue) {
/*
* The deletgated assertion issuing requests may have
* completed successfully (i.e. silent error handling)
* but a request failed our checks. Thus go to reject.
*/
if (__lastError) {
throw __lastError;
}
/*
* Handle the case of specified but unprocessed mocks.
* If there were none remaining immediately complete.
*
* Where the driving assertion resolves we must check
* if any mocks still exist. If so, we add them to the
* set of expected exchanges and resolve the promise.
*/
var hasRemainingRequestDescriptions = nextRequestDescriptionIndex < requestDescriptions.length;
if (hasRemainingRequestDescriptions) {
// exhaust remaining mocks using a promises chain
(function nextItem() {
var remainingDescription = requestDescriptions[nextRequestDescriptionIndex];
nextRequestDescriptionIndex += 1;
if (remainingDescription) {
var expectedRequestProperties;
return expect.promise(function () {
expectedRequestProperties = resolveExpectedRequestProperties(remainingDescription.request);
}).then(function () {
return getMockResponse(remainingDescription.response);
}).spread(function (mockResponse, mockResponseError) {
httpConversationSatisfySpec.exchanges.push({
request: expectedRequestProperties,
response: mockResponseError || trimMockResponse(mockResponse)
});
return nextItem();
}).caught(reject);
} else {
resolve([fulfilmentValue, httpConversation, httpConversationSatisfySpec]);
}
})();
} else {
resolve([fulfilmentValue, httpConversation, httpConversationSatisfySpec]);
}
}).caught(function (e) {
if (__lastError) {
reject(__lastError);
} else {
reject(e);
}
});
}).spread(function (fulfilmentValue, httpConversation, httpConversationSatisfySpec) {
expect.errorMode = 'default';
return expect(httpConversation, 'to satisfy', httpConversationSatisfySpec).then(function () {
if (shouldBeVerified || shouldReturnExtraInfo) {
return [fulfilmentValue, httpConversation, httpConversationSatisfySpec];
} else {
return fulfilmentValue;
}
});
}).finally(function () {
seenAgents.forEach(function (agent) {
if (agent.freeSockets) {
Object.keys(agent.freeSockets).forEach(function (key) {
agent.freeSockets[key] = [];
});
}
});
mitm.disable();
}).then(function () {
return expect(mocker, 'to be complete [with extra info]');
});
if (shouldBeVerified) {
expect.errorMode = 'default';
var verifier = createVerifier(expect, verifyBlocks);

@@ -1230,3 +627,4 @@

});
}
};
{
"name": "unexpected-mitm",
"version": "10.2.2",
"version": "11.0.0",
"description": "Unexpected plugin for the mitm library",

@@ -27,28 +27,29 @@ "author": "Andreas Lind <andreaslindpetersen@gmail.com>",

"devDependencies": {
"body-parser": "1.15.0",
"coveralls": "2.11.2",
"eslint": "2.13.1",
"eslint-config-onelint": "1.2.0",
"express": "4.13.4",
"mocha": "2.2.5",
"nyc": "6.4.4",
"pem": "1.7.2",
"request": "2.53.0",
"semver": "5.0.1",
"sinon": "1.17.2",
"socketerrors-papandreou": "0.2.0-patch2",
"unexpected": "10.27.0",
"body-parser": "^1.18.2",
"coveralls": "^3.0.0",
"eslint": "^4.19.1",
"eslint-config-onelint": "^3.0.0",
"express": "^4.16.3",
"mocha": "^2.2.5",
"nyc": "^11.6.0",
"pem": "^1.12.5",
"request": "^2.53.0",
"semver": "^5.0.1",
"sinon": "^4.5.0",
"socketerrors-papandreou": "^0.2.0-patch2",
"unexpected": "^10.27.0",
"unexpected-documentation-site-generator": "^4.0.0",
"unexpected-express": "9.0.0",
"unexpected-http": "6.0.0",
"unexpected-express": "^10.0.0",
"unexpected-http": "^6.0.0",
"unexpected-markdown": "^1.4.0",
"unexpected-sinon": "10.1.1"
"unexpected-sinon": "^10.1.1"
},
"dependencies": {
"callsite": "1.0.0",
"detect-indent": "3.0.0",
"memoizesync": "0.5.0",
"callsite": "^1.0.0",
"createerror": "1.1.0",
"detect-indent": "^5.0.0",
"memoizesync": "^1.1.1",
"messy": "^6.16.0",
"mitm-papandreou": "1.3.3-patch4",
"underscore": "1.7.0",
"mitm-papandreou": "^1.3.3-patch4",
"underscore": "^1.8.3",
"unexpected-messy": "^7.2.0"

@@ -55,0 +56,0 @@ },

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc