Socket
Socket
Sign inDemoInstall

toxy

Package Overview
Dependencies
29
Maintainers
1
Versions
25
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    toxy

Hackable HTTP proxy to simulate server failure scenarios and unexpected conditions


Version published
Maintainers
1
Created

Readme

Source

toxy Build Status Code Climate NPM Stability

toxy is a hackable HTTP proxy to simulate failure scenarios and unexpected conditions.

It was mainly designed for fuzz/evil testing purposes, becoming particulary useful to cover fault tolerant and resiliency capabilities of a system, tipically in service-oriented distributed architectures, where toxy may act as intermediate proxy among services.

toxy allows you to plug in poisons, optionally filtered by rules, which basically can intercept and alter the HTTP flow as you want, performing multiple evil actions in the middle of that process, such as limiting the bandwidth, delaying TCP packets, injecting network jitter latency or replying with a custom error or status code.

Runs in node.js/io.js. Compatible with connect/express. Built on top of rocky, a full-featured, middleware-oriented HTTP/S proxy.

Requires node.js +0.12 or io.js +1.6

Contents

Features

  • Full-featured HTTP/S proxy (backed by http-proxy)
  • Hackable and elegant programmatic API (inspired on connect/express)
  • Featured built-in router with nested configuration
  • Hierarchical poisioning and rules based filtering
  • Hierarchical middleware layer (global and route-specific)
  • Easily augmentable via middleware (based on connect/express middleware)
  • Built-in poisons (bandwidth, error, abort, latency, slow read...)
  • Rule-based poisoning (probabilistic, HTTP method, headers, body...)
  • Support third-party poisons and rules
  • Built-in balancer and traffic intercept via middleware
  • Inherits the API and features from rocky
  • Compatible with connect/express (and most of their middleware)
  • Runs as standalone HTTP proxy

Introduction

Why toxy?

There're some other similar solutions to toxy in the market, but most of them don't provide a proper programmatic control and are not easy to hack, configure and/or extend. Additionally, most of the those solutions are based only on the TCP stack only instead of providing more features to the scope of HTTP applicacion level protocol, like toxy does.

toxy provides a powerful hacking-driven and extensible solution with a convenient low-level interface and extensible programmatic control, serveds with a simple and fluent API and the power, simplicity and fun of node.js.

Concepts

toxy introduces two main core directives worth knowing before using it:

Poisons are the specific logic to infect an incoming or outgoing HTTP flow (e.g: injecting a latency, replying with an error). HTTP flow can be poisoned by one or multiple poisons, and you can plug in poisons at global or route level.

Rules are a kind of validation filters that are applied to the whole global HTTP flow or to a concrete poison, in order to determine if one or multiple poisons should be enabled or not to infect the HTTP traffic (e.g: match headers, query params, method...).

How it works

↓   ( Incoming request )  ↓
↓           |||           ↓
↓     ----------------    ↓
↓     |  Toxy Router |    ↓ --> Match a route based on the incoming request
↓     ----------------    ↓
↓           |||           ↓
↓     ----------------    ↓
↓     |  Exec Rules  |    ↓ --> Apply configured rules for the request
↓     ----------------    ↓
↓          |||            ↓
↓     ----------------    ↓
↓     | Exec Poisons |    ↓ --> If all rules passed, poisoning the HTTP flow
↓     ----------------    ↓
↓        /       \        ↓
↓        \       /        ↓
↓   -------------------   ↓
↓   | HTTP dispatcher |   ↓ --> Proxy the HTTP traffic for both poisoned or not
↓   -------------------   ↓

Usage

Installation

npm install toxy

Examples

See the examples directory for more use cases

Basic poisioning
var toxy = require('toxy')
var poisons = toxy.poisons
var rules = toxy.rules

var proxy = toxy()

proxy
  .forward('http://httpbin.org')

proxy
  .poison(poisons.latency({ jitter: 500 }))
  .rule(rules.random(50))
  .poison(poisons.bandwidth({ bps: 1024 }))
  .withRule(rules.method('GET'))

proxy.get('/*')
proxy.listen(3000)

Poisons

Poisons host specific logic which intercepts and mutates, wraps, modify and/or cancel an HTTP transaction in the proxy server. Poisons can be applied to incoming or outgoing, or even both traffic flows.

Poisons can be composed and reused for different HTTP scenarios. They are executed in FIFO order and asynchronously.

Built-in poisons

Latency

Name: latency

Infects the HTTP flow injecting a latency jitter in the response

Arguments:

  • options object
    • *jitter+ number - Jitter value in miliseconds
    • max number - Random jitter maximum value
    • min number - Random jitter minimum value
toxy.poison(toxy.poisons.latency({ jitter: 1000 }))
// Or alternatively using a random value
toxy.poison(toxy.poisons.latency({ max: 1000, min: 100 }))
Inject response

Name: inject

Injects a custom response, intercepting the request before sending it to the target server. Useful to inject errors originated in the server.

Arguments:

  • options object
    • *code+ number - Response HTTP status code
    • headers object - Optional headers to send
    • body mixed - Optional body data to send
    • encoding string - Body encoding. Default to utf8
toxy.poison(toxy.poisons.inject({
  code: 503,
  body: '{"error": "toxy injected error"}',
  headers: {'Content-Type': 'application/json'}
}))
Bandwidth

Name: bandwidth

Limits the amount of bytes sent over the network in outgoing HTTP traffic for a specific threshold time frame.

Arguments:

  • options object
    • *bps+ number - Bytes per seconds
    • threshold number - Threshold time frame in miliseconds
toxy.poison(toxy.poisons.bandwidth({ bps: 1024 }))
Rate limit

Name: rateLimit

Limits the amount of requests received by the proxy in a specific threshold time frame. Designed to test API limits. Exposes the X-RateLimit-* headers.

Limits are stored in-memory, meaning they are volalite and therfore flushed on every server stop.

Arguments:

  • options object
    • *limit+ number - Total amount of request
    • threshold number - Limit threshold time frame in miliseconds.
    • message string - Optional error message when limit reached.
    • code number - HTTP status code when limit reached. Default to 429.
toxy.poison(toxy.poisons.rateLimit({ limit: 10, threshold: 1000 }))
Slow read

Name: slowRead

Reads incoming payload data packets slowly. Only valid for non-GET request.

Arguments:

  • options object
    • chunk number - Packet chunk size in bytes. Default to 1024
    • threshold number - Limit threshold time frame in miliseconds. Default to 1000
toxy.poison(toxy.poisons.slowRead({ chunk: 2048, threshold: 1000 }))
Slow open

Name: slowOpen

Delays the HTTP connection ready state.

Arguments:

  • options object
    • delay number - Delay connection in miliseconds. Default to 1000
toxy.poison(toxy.poisons.slowOpen({ delay: 2000 }))
Slow close

Name: slowClose

Delays the HTTP connection close signal.

Arguments:

  • options object
    • delay number - Delay time in miliseconds. Default to 1000
toxy.poison(toxy.poisons.slowClose({ delay: 2000 }))
Throttle

Name: throttle

Restricts the amount of packets sent over the network in a specific threshold time frame.Arguments:

  • options object
    • *chunk+ number - Packet chunk size in bytes. Default to 1024
    • threshold object - Limit threshold time frame in miliseconds. Default to 1000
toxy.poison(toxy.poisons.slowRead({ chunk: 2048, threshold: 1000 }))
Abort connection

Name: abort

Aborts the TCP connection, optionally with a custom error. From the low-level perspective, this will destroy the socket on the server, operating only at TCP level without sending any specific HTTP application level data.

Arguments:

  • miliseconds number - Optional socket destroy delay in miliseconds
toxy.poison(toxy.poisons.abort())
Timeout

Name: timeout

Defines a response timeout. Useful when forward to potentially slow servers.

Arguments:

  • miliseconds number - Timeout limit in miliseconds
toxy.poison(toxy.poisons.timeout(5000))

How to write poisons

Poisons are implemented as standalone middleware (like in connect/express).

Here's a simple example of a server latency poison:

function latency(delay) {
  /**
   * We name the function since toxy uses it as identifier to get/disable/remove it in the future
   */
  return function latency(req, res, next) {
    var timeout = setTimeout(clean, delay)
    req.once('close', onClose)

    function onClose() {
      clearTimeout(timeout)
      next('client connection closed')
    }

    function clean() {
      req.removeListener('close', onClose)
      next()
    }
  }
}

// Register and enable the poison
toxy
  .get('/foo')
  .poison(latency(2000))

For featured real example, take a look to the built-in poisons implementation.

Rules

Rules are simple validation filters which inspect an HTTP request and determine, given a certain rules (e.g: method, headers, query params), if the HTTP transaction should be poisoned or not.

Rules are useful to compose, decouple and reuse logic among different scenarios of poisoning. You can also define globally applied rules or nested poison-scope rules only.

Rules are executed in FIFO order. Their evaluation logic is equivalent to Array#every() in JavaScript: all the rules must pass in order to proceed with the poisoning.

Built-in rules

Probability

Enables the rule by a random probabilistic. Useful for random poisioning.

Arguments:

  • percentage number - Percentage of filtering. Default 50
var rule = toxy.rules.probability(85)
toxy.rule(rule)
Method

Filters by HTTP method.

Arguments:

  • method string|array - Method or methods to filter.
var method = toxy.rules.method(['GET', 'POST'])
toxy.rule(method)
Headers

Filter by certain headers.

Arguments:

  • headers object - Headers to match by key-value pair. value can be a string, regexp, boolean or function(headerValue, headerName) => boolean
var matchHeaders = {
  'content-type': /^application/\json/i,
  'server': true, // meaning it should be present,
  'accept': function (value, key) {
    return value.indexOf('text') !== -1
  }
}

var rule = toxy.rules.headers(matchHeaders)
toxy.rule(rule)
Content Type

Filters by content type header. It should be present

Arguments:

  • value string|regexp - Header value to match.
var rule = toxy.rules.contentType('application/json')
toxy.rule(rule)
Body

Match incoming body payload data by string, regexp or custom filter function

Arguments:

  • match string|regexp|function - Body content to match
  • limit string - Optional. Body limit in human size. E.g: 5mb
  • encoding string - Body encoding. Default to utf8
  • length number - Body length. Default taken from Content-Length header
var rule = toxy.rules.body('"hello":"world"')
toxy.rule(rule)

// Or using a filter function returning a boolean
var rule = toxy.rules.body(function (body) {
  return body.indexOf('hello') !== -1
})
toxy.rule(rule)

How to write rules

Rules are simple middleware functions that resolve asyncronously with a boolean value to determine if a given HTTP transaction should be ignored when poisoning.

Your rule must resolve with a boolean param calling the next(err, shouldIgnore) function in the middleware, passing a true value if the rule has not matches and should not apply the poisioning, and therefore continuing with the next middleware stack.

Here's an example of a simple rule matching the HTTP method to determine if:

function method(matchMethod) {
  /**
   * We name the function since it's used by toxy to identify the rule to get/disable/remove it in the future
   */
  return function method(req, res, next) {
    var shouldIgnore = req.method !== matchMethod
    next(null, shouldIgnore)
  }
}

// Register and enable the rule
toxy
  .get('/foo')
  .rule(method('GET'))
  .poison(/* ... */)

For featured real examples, take a look to the built-in rules implementation

Programmatic API

toxy API is completely built on top the rocky API. In other words, you can use any of the methods, features and middleware layer natively provided by rocky.

toxy([ options ])

Create a new toxy proxy.

For supported options, please see rocky documentation

var toxy = require('toxy')

toxy({ forward: 'http://server.net', timeout: 30000 })

toxy
  .get('/foo')
  .poison(toxy.poisons.latency(1000))
  .withRule(toxy.rules.contentType('json'))
  .forward('http://foo.server')

toxy
  .post('/bar')
  .poison(toxy.poisons.bandwidth({ bps: 1024 }))
  .withRule(toxy.rules.probability(50))
  .forward('http://bar.server')

toxy.all('/*')
toxy#get(path, [ middleware... ])

Return: ToxyRoute

toxy#post(path, [ middleware... ])

Return: ToxyRoute

toxy#put(path, [ middleware... ])

Return: ToxyRoute

toxy#patch(path, [ middleware... ])

Return: ToxyRoute

toxy#delete(path, [ middleware... ])

Return: ToxyRoute

toxy#head(path, [ middleware... ])

Return: ToxyRoute

toxy#all(path, [ middleware... ])

Return: ToxyRoute

toxy#forward(url)
toxy#balance(urls)
toxy#replay(url)
toxy#use(middleware)
toxy#useResponse(middleware)
toxy#useReplay(middleware)
toxy#poison(poison)

Alias: usePoison

toxy#rule(rule)

Alias: useRule

toxy#withRule(rule)

Aliases: poisonRule, poisonFilter

toxy#enable(poison)
toxy#disable(poison)
toxy#remove(poison)

Return: boolean

toxy#isEnabled(poison)

Return: boolean

toxy#disableAll()

Alias: disablePoisons

toxy#poisons()

Return: array<Directive> Alias: getPoisons

toxy#flush()

Alias: flushPoisons

toxy#enableRule(rule)
toxy#disableRule(rule)
toxy#removeRule(rule)

Return: boolean

toxy#disableRules()
toxy#isRuleEnabled(rule)

Return: boolean

toxy#rules()

Return: array<Directive> Alias: getRules

toxy#flushRules()

ToxyRoute

Toxy route has, indeed, the same interface as Toxy global interface, but further actions you perform againts the API will only be applicable at route-level. In other words: good news, you already know the API.

This example will probably clarify possible doubts:

var toxy = require('toxy')
var proxy = toxy()

// Now using the global API
proxy
  .poison(toxy.poisons.bandwidth({ bps: 1024 }))
  .rule(toxy.rules.method('GET'))

// Now create a route
var route = proxy.get('/foo')

// Now using the ToxyRoute interface
route
  .poison(toxy.poisons.bandwidth({ bps: 512 }))
  .rule(toxy.rules.contentType('json'))

Directive(middlewareFn)

A convenient wrapper internally used for poisons and rules.

Normally you don't need to know this interface, but for hacking purposes or more low-level actions might be useful.

Directive#enable()

Return: boolean

Directive#disable()

Return: boolean

Directive#isEnabled()

Return: boolean

Directive#rule(rule)

Alias: filter

Directive#handler()

Return: function(req, res, next)

License

MIT - Tomas Aparicio

Keywords

FAQs

Last updated on 05 Aug 2015

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc