Socket
Socket
Sign inDemoInstall

mockttp

Package Overview
Dependencies
Maintainers
1
Versions
125
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mockttp - npm Package Compare versions

Comparing version 3.5.1 to 3.6.0

4

custom-typings/node-type-extensions.d.ts

@@ -19,2 +19,6 @@ // There's a few places where we attach extra data to some node objects during

// Extra metadata attached to a TLS socket, taken from the client hello and
// preceeding tunneling steps.
__tlsMetadata?: {}; // Can't ref Mockttp real type here
// Normally only defined on TLSSocket, but useful to explicitly include here

@@ -21,0 +25,0 @@ // Undefined on plain HTTP, 'true' on TLSSocket.

@@ -71,2 +71,3 @@ "use strict";

timingEvents: Json!
tlsMetadata: Json!
}

@@ -82,2 +83,3 @@

timingEvents: Json!
tlsMetadata: Json!
}

@@ -84,0 +86,0 @@

@@ -46,2 +46,12 @@ "use strict";

message.tags = [];
if (event === null || event === void 0 ? void 0 : event.startsWith('tls-')) {
// TLS passthrough & error events should have raw JSON socket metadata:
if (message.tlsMetadata) {
message.tlsMetadata = JSON.parse(message.tlsMetadata);
}
else {
// For old servers, just use empty metadata:
message.tlsMetadata = {};
}
}
}

@@ -335,2 +345,3 @@ function normalizeWebSocketMessage(message) {

timingEvents
${this.schema.asOptionalField('TlsPassthroughEvent', 'tlsMetadata')}
}

@@ -348,2 +359,3 @@ }`,

timingEvents
${this.schema.asOptionalField('TlsPassthroughEvent', 'tlsMetadata')}
}

@@ -359,2 +371,3 @@ }`,

${this.schema.asOptionalField(['TlsHandshakeFailure', 'TlsRequest'], 'timingEvents')}
${this.schema.asOptionalField(['TlsHandshakeFailure', 'TlsRequest'], 'tlsMetadata')}
}

@@ -361,0 +374,0 @@ }`,

28

dist/server/http-combo-server.js

@@ -150,5 +150,3 @@ "use strict";

});
if ((_b = options.https.tlsPassthrough) === null || _b === void 0 ? void 0 : _b.length) {
passThroughMatchingTls(tlsServer, options.https.tlsPassthrough, tlsPassthroughListener);
}
analyzeAndMaybePassThroughTls(tlsServer, (_b = options.https.tlsPassthrough) !== null && _b !== void 0 ? _b : [], tlsPassthroughListener);
server = httpolyglot.createServer(tlsServer, requestListener);

@@ -167,2 +165,3 @@ }

server.on('secureConnection', (socket) => {
var _a;
const parentSocket = (0, socket_util_1.getParentSocket)(socket);

@@ -174,2 +173,5 @@ if (parentSocket) {

copyTimingDetails(parentSocket, socket);
// With TLS metadata, we only propagate directly from parent sockets, not through
// CONNECT etc - we only want it if the final hop is TLS, previous values don't matter.
(_a = socket.__tlsMetadata) !== null && _a !== void 0 ? _a : (socket.__tlsMetadata = parentSocket.__tlsMetadata);
}

@@ -274,7 +276,7 @@ else if (!socket.__timingInfo) {

/**
* Takes a tls passthrough list, and reconfigures a given TLS server so that all
* matching requests are passed to the given passthrough listener, instead of
* beginning a full TLS handshake.
* Takes a tls passthrough list (may be empty), and reconfigures a given TLS server so that all
* client hellos are parsed, matching requests are passed to the given passthrough listener (without
* continuing setup) and client hello metadata is attached to all sockets.
*/
function passThroughMatchingTls(server, passthroughList, listener) {
function analyzeAndMaybePassThroughTls(server, passthroughList, passthroughListener) {
const hostnames = passthroughList.map(({ hostname }) => hostname);

@@ -289,8 +291,16 @@ const tlsConnectionListener = server.listeners('connection')[0];

const sniHostname = helloData.serverName;
socket.__tlsMetadata = {
sniHostname,
connectHostname,
connectPort,
clientAlpn: helloData.alpnProtocols,
ja3Fingerprint: (0, read_tls_client_hello_1.calculateJa3FromFingerprintData)(helloData.fingerprintData)
};
if (connectHostname && hostnames.includes(connectHostname)) {
listener(socket, connectHostname, connectPort ? parseInt(connectPort, 10) : undefined);
const upstreamPort = connectPort ? parseInt(connectPort, 10) : undefined;
passthroughListener(socket, connectHostname, upstreamPort);
return; // Do not continue with TLS
}
else if (sniHostname && hostnames.includes(sniHostname)) {
listener(socket, sniHostname); // Can't guess the port - it's not included in SNI
passthroughListener(socket, sniHostname); // Can't guess the port - not included in SNI
return; // Do not continue with TLS

@@ -297,0 +307,0 @@ }

@@ -54,3 +54,11 @@ /// <reference types="node" />

timingEvents: TlsTimingEvents;
tlsMetadata: TlsSocketMetadata;
}
export interface TlsSocketMetadata {
sniHostname?: string;
connectHostname?: string;
connectPort?: string;
clientAlpn?: string[];
ja3Fingerprint?: string;
}
export interface TlsPassthroughEvent extends TlsConnectionEvent {

@@ -57,0 +65,0 @@ id: string;

@@ -156,14 +156,18 @@ "use strict";

function buildSocketEventData(socket) {
var _a, _b, _c;
var _a, _b, _c, _d;
const timingInfo = socket.__timingInfo ||
((_a = socket._parent) === null || _a === void 0 ? void 0 : _a.__timingInfo) ||
buildSocketTimingInfo();
// Attached in passThroughMatchingTls TLS sniffing logic in http-combo-server:
const tlsMetadata = socket.__tlsMetadata ||
((_b = socket._parent) === null || _b === void 0 ? void 0 : _b.__tlsMetadata) ||
{};
return {
hostname: socket.servername,
// These only work because of oncertcb monkeypatch above
// These only work because of oncertcb monkeypatch in http-combo-server:
remoteIpAddress: socket.remoteAddress || // Normal case
((_b = socket._parent) === null || _b === void 0 ? void 0 : _b.remoteAddress) || // Pre-certCB error, e.g. timeout
((_c = socket._parent) === null || _c === void 0 ? void 0 : _c.remoteAddress) || // Pre-certCB error, e.g. timeout
socket.initialRemoteAddress,
remotePort: socket.remotePort ||
((_c = socket._parent) === null || _c === void 0 ? void 0 : _c.remotePort) ||
((_d = socket._parent) === null || _d === void 0 ? void 0 : _d.remotePort) ||
socket.initialRemotePort,

@@ -176,3 +180,4 @@ tags: [],

handshakeTimestamp: timingInfo.tlsConnectedTimestamp
}
},
tlsMetadata
};

@@ -179,0 +184,0 @@ }

@@ -16,2 +16,17 @@ /// <reference types="node" />

keyLength?: number;
/**
* The countryName that will be used in the certificate for incoming TLS
* connections.
*/
countryName?: string;
/**
* The localityName that will be used in the certificate for incoming TLS
* connections.
*/
localityName?: string;
/**
* The organizationName that will be used in the certificate for incoming TLS
* connections.
*/
organizationName?: string;
}

@@ -47,5 +62,6 @@ export declare type PEM = string | string[] | Buffer | Buffer[];

private caKey;
private options;
private certCache;
constructor(caKey: PEM, caCert: PEM, keyLength: number);
constructor(options: CertDataOptions);
generateCertificate(domain: string): GeneratedCertificate;
}

@@ -100,7 +100,3 @@ "use strict";

fs.readFile(options.certPath, 'utf8')
]).then(([keyContents, certContents]) => ({
key: keyContents,
cert: certContents,
keyLength: options.keyLength
}));
]).then(([keyContents, certContents]) => (Object.assign(Object.assign({}, _.omit(options, ['keyPath', 'certPath'])), { key: keyContents, cert: certContents })));
}

@@ -110,3 +106,3 @@ else {

}
return new CA(certOptions.key, certOptions.cert, certOptions.keyLength || 2048);
return new CA(certOptions);
});

@@ -122,6 +118,8 @@ }

class CA {
constructor(caKey, caCert, keyLength) {
this.caKey = pki.privateKeyFromPem(caKey.toString('utf8'));
this.caCert = pki.certificateFromPem(caCert.toString('utf8'));
constructor(options) {
this.caKey = pki.privateKeyFromPem(options.key.toString());
this.caCert = pki.certificateFromPem(options.cert.toString());
this.certCache = {};
this.options = options !== null && options !== void 0 ? options : {};
const keyLength = options.keyLength || 2048;
if (!KEY_PAIR || KEY_PAIR.length < keyLength) {

@@ -133,2 +131,3 @@ // If we have no key, or not a long enough one, generate one.

generateCertificate(domain) {
var _a, _b, _c, _d, _e, _f;
// TODO: Expire domains from the cache? Based on their actual expiry?

@@ -164,5 +163,5 @@ if (this.certCache[domain])

: [{ name: 'commonName', value: domain }]),
{ name: 'countryName', value: 'XX' },
{ name: 'localityName', value: 'Unknown' },
{ name: 'organizationName', value: 'Mockttp Cert - DO NOT TRUST' }
{ name: 'countryName', value: (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.countryName) !== null && _b !== void 0 ? _b : 'XX' },
{ name: 'localityName', value: (_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.localityName) !== null && _d !== void 0 ? _d : 'Unknown' },
{ name: 'organizationName', value: (_f = (_e = this.options) === null || _e === void 0 ? void 0 : _e.organizationName) !== null && _f !== void 0 ? _f : 'Mockttp Cert - DO NOT TRUST' }
]);

@@ -169,0 +168,0 @@ cert.setIssuer(this.caCert.subject.attributes);

{
"name": "mockttp",
"version": "3.5.1",
"version": "3.6.0",
"description": "Mock HTTP server for testing HTTP clients and stubbing webservices",

@@ -5,0 +5,0 @@ "exports": {

@@ -69,2 +69,3 @@ import gql from "graphql-tag";

timingEvents: Json!
tlsMetadata: Json!
}

@@ -80,2 +81,3 @@

timingEvents: Json!
tlsMetadata: Json!
}

@@ -82,0 +84,0 @@

@@ -10,4 +10,2 @@ import _ = require('lodash');

import type { Serialized } from '../serialization/serialization';
import { AdminQuery } from './admin-query';

@@ -51,2 +49,12 @@ import { SchemaIntrospector } from './schema-introspection';

if (!message.tags) message.tags = [];
if (event?.startsWith('tls-')) {
// TLS passthrough & error events should have raw JSON socket metadata:
if (message.tlsMetadata) {
message.tlsMetadata = JSON.parse(message.tlsMetadata);
} else {
// For old servers, just use empty metadata:
message.tlsMetadata = {};
}
}
}

@@ -369,2 +377,3 @@

timingEvents
${this.schema.asOptionalField('TlsPassthroughEvent', 'tlsMetadata')}
}

@@ -382,2 +391,3 @@ }`,

timingEvents
${this.schema.asOptionalField('TlsPassthroughEvent', 'tlsMetadata')}
}

@@ -393,2 +403,3 @@ }`,

${this.schema.asOptionalField(['TlsHandshakeFailure', 'TlsRequest'], 'timingEvents')}
${this.schema.asOptionalField(['TlsHandshakeFailure', 'TlsRequest'], 'tlsMetadata')}
}

@@ -395,0 +406,0 @@ }`,

@@ -10,3 +10,7 @@ import _ = require('lodash');

import httpolyglot = require('@httptoolkit/httpolyglot');
import { NonTlsError, readTlsClientHello } from 'read-tls-client-hello';
import {
calculateJa3FromFingerprintData,
NonTlsError,
readTlsClientHello
} from 'read-tls-client-hello';

@@ -175,9 +179,7 @@ import { TlsHandshakeFailure } from '../types';

if (options.https.tlsPassthrough?.length) {
passThroughMatchingTls(
tlsServer,
options.https.tlsPassthrough,
tlsPassthroughListener
);
}
analyzeAndMaybePassThroughTls(
tlsServer,
options.https.tlsPassthrough ?? [],
tlsPassthroughListener
);

@@ -206,2 +208,5 @@ server = httpolyglot.createServer(tlsServer, requestListener);

copyTimingDetails(parentSocket, socket);
// With TLS metadata, we only propagate directly from parent sockets, not through
// CONNECT etc - we only want it if the final hop is TLS, previous values don't matter.
socket.__tlsMetadata ??= parentSocket.__tlsMetadata;
} else if (!socket.__timingInfo) {

@@ -334,10 +339,10 @@ socket.__timingInfo = buildSocketTimingInfo();

/**
* Takes a tls passthrough list, and reconfigures a given TLS server so that all
* matching requests are passed to the given passthrough listener, instead of
* beginning a full TLS handshake.
* Takes a tls passthrough list (may be empty), and reconfigures a given TLS server so that all
* client hellos are parsed, matching requests are passed to the given passthrough listener (without
* continuing setup) and client hello metadata is attached to all sockets.
*/
function passThroughMatchingTls(
function analyzeAndMaybePassThroughTls(
server: tls.Server,
passthroughList: Required<MockttpHttpsOptions>['tlsPassthrough'],
listener: (socket: net.Socket, address: string, port?: number) => void
passthroughListener: (socket: net.Socket, address: string, port?: number) => void
) {

@@ -355,7 +360,16 @@ const hostnames = passthroughList.map(({ hostname }) => hostname);

socket.__tlsMetadata = {
sniHostname,
connectHostname,
connectPort,
clientAlpn: helloData.alpnProtocols,
ja3Fingerprint: calculateJa3FromFingerprintData(helloData.fingerprintData)
};
if (connectHostname && hostnames.includes(connectHostname)) {
listener(socket, connectHostname, connectPort ? parseInt(connectPort, 10) : undefined)
const upstreamPort = connectPort ? parseInt(connectPort, 10) : undefined;
passthroughListener(socket, connectHostname, upstreamPort);
return; // Do not continue with TLS
} else if (sniHostname && hostnames.includes(sniHostname)) {
listener(socket, sniHostname); // Can't guess the port - it's not included in SNI
passthroughListener(socket, sniHostname); // Can't guess the port - not included in SNI
return; // Do not continue with TLS

@@ -362,0 +376,0 @@ }

@@ -72,4 +72,13 @@ import stream = require('stream');

timingEvents: TlsTimingEvents;
tlsMetadata: TlsSocketMetadata;
}
export interface TlsSocketMetadata {
sniHostname?: string;
connectHostname?: string;
connectPort?: string;
clientAlpn?: string[];
ja3Fingerprint?: string;
}
export interface TlsPassthroughEvent extends TlsConnectionEvent {

@@ -76,0 +85,0 @@ id: string;

@@ -161,5 +161,10 @@ import * as _ from 'lodash';

// Attached in passThroughMatchingTls TLS sniffing logic in http-combo-server:
const tlsMetadata = socket.__tlsMetadata ||
socket._parent?.__tlsMetadata ||
{};
return {
hostname: socket.servername,
// These only work because of oncertcb monkeypatch above
// These only work because of oncertcb monkeypatch in http-combo-server:
remoteIpAddress: socket.remoteAddress || // Normal case

@@ -177,3 +182,4 @@ socket._parent?.remoteAddress || // Pre-certCB error, e.g. timeout

handshakeTimestamp: timingInfo.tlsConnectedTimestamp
}
},
tlsMetadata
};

@@ -180,0 +186,0 @@ }

@@ -26,2 +26,20 @@ import * as _ from 'lodash';

keyLength?: number;
/**
* The countryName that will be used in the certificate for incoming TLS
* connections.
*/
countryName?: string;
/**
* The localityName that will be used in the certificate for incoming TLS
* connections.
*/
localityName?: string;
/**
* The organizationName that will be used in the certificate for incoming TLS
* connections.
*/
organizationName?: string;
}

@@ -131,5 +149,5 @@

]).then(([ keyContents, certContents ]) => ({
..._.omit(options, ['keyPath', 'certPath']),
key: keyContents,
cert: certContents,
keyLength: options.keyLength
cert: certContents
}));

@@ -141,3 +159,3 @@ }

return new CA(certOptions.key, certOptions.cert, certOptions.keyLength || 2048);
return new CA(certOptions);
}

@@ -159,14 +177,14 @@

private caKey: forge.pki.PrivateKey;
private options: CertDataOptions;
private certCache: { [domain: string]: GeneratedCertificate };
constructor(
caKey: PEM,
caCert: PEM,
keyLength: number
) {
this.caKey = pki.privateKeyFromPem(caKey.toString('utf8'));
this.caCert = pki.certificateFromPem(caCert.toString('utf8'));
constructor(options: CertDataOptions) {
this.caKey = pki.privateKeyFromPem(options.key.toString());
this.caCert = pki.certificateFromPem(options.cert.toString());
this.certCache = {};
this.options = options ?? {};
const keyLength = options.keyLength || 2048;
if (!KEY_PAIR || KEY_PAIR.length < keyLength) {

@@ -221,5 +239,5 @@ // If we have no key, or not a long enough one, generate one.

),
{ name: 'countryName', value: 'XX' }, // ISO-3166-1 alpha-2 'unknown country' code
{ name: 'localityName', value: 'Unknown' },
{ name: 'organizationName', value: 'Mockttp Cert - DO NOT TRUST' }
{ name: 'countryName', value: this.options?.countryName ?? 'XX' }, // ISO-3166-1 alpha-2 'unknown country' code
{ name: 'localityName', value: this.options?.localityName ?? 'Unknown' },
{ name: 'organizationName', value: this.options?.organizationName ?? 'Mockttp Cert - DO NOT TRUST' }
]);

@@ -226,0 +244,0 @@ cert.setIssuer(this.caCert.subject.attributes);

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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