Highly configurable auditing of HTTP transactions

brom is a configurable CLI for recording HTTP transactions and improving security practices, designed for use in local environments and CI tools. Get your headers in order before deployment.

Getting Started

brom is available as an npm package, and must be installed globally.

npm install -g brom

Navigate to the root directory of your project and execute brom, followed by the path to your server file and the port which it runs on.

brom server/index.js 3000

brom will start proxying your application on another port (defaults to 9999) and launch a gui in your default browser. At this point you can navigate to localhost:9999 in another browser window and start interacting with your site.

Interactive Auditing

brom proxies your server, recording HTTP transactions as they stream through, and injecting a script into HTML documents to record third-party AJAX calls (on by default).

Automated Auditing

When called with an --automated flag, brom traverses Express application structures to identify all registered routes and their supported methods, sending dumb requests to each one. Great for check-ins and (with a bit of configuration) build processes.

Rules

Rules are the core of brom's auditing functionality. They are simple tests that are run against every HTTP transaction.

Here's a built-in rule:

{
  id: 'no-x-powered-by', // rule name (unique)
  header: 'x-powered-by', // where to display in UI
  when: (headers, type) => type === 'response', // when to run this rule
  expect: headers => !headers['x-powered-by'], // pass/fail statement
  fail: {
    message: `The X-Powered-By header advertises unnecessary 
              details about your server, and should not be sent.`,
    flags: ['severe']
  }
  pass: {
    flags: []
  }
}

While brom comes with smart defaults for assessing header practices, the security needs of your application are unique. brom provides a rule syntax to allow you to create your own rules. Simply create a folder in the root of your project called brom.rules.js that contains an array of objects with the above format. In addition to adding your own rules, you can also choose to ignore specific rules by listing their IDs in the configuration file.

Continuous Integration

brom offers a stricter version of its automated mode for use in CI workflows. When called with a --ci flag, brom will run automated analysis on the specified server.

brom server/index.js --ci

By default, it will not stop your build. To set conditions for stopping the build, you'll need to create a "halt-on" property on the configuration, with its value set to an array with the IDs of all rules you want to allow to stop the build.

{
  "halt-on": [
    "include-csp",
    "include-x-xss-protection"
  ]
}

When brom finishes the audit, any routes that triggered a warning from a rule included in the configuration will be listed in the console, and the build will fail.

Logging

If you'd like to keep records of your auditing sessions with, brom can save audit results as JSON logs in a directory of your choice. To configure this, either call brom with the --write-dir=[YOUR DIRECTORY] flag or add the following entry to your configuration file.

{
  "defaults": {
    "write-dir": "[PATH TO DIRECTORY]"
  }
}

You can revisit your brom logs by calling it with the --read-file=[FILE PATH] flag. This will open the client and allow you to view all of the data that was saved from that session.

Configuration

While most of brom's features can be triggered with flags, you can simplify the process by providing a brom.config.json file in your project's directory. brom needs, at minimum, to know what local port to proxy to, and with many settings it also needs a path to your server. If you'd rather just type brom, provide the following properties in the config file.

{
  "server": "demo/server.js",
  "port": 3000
}

All flags can be preconfigured here as well. In a "defaults" key provide settings matching the longform of any flag. These settings will always be overridden by flags and arguments passed directly to the CLI.

{
  "server": "demo/server.js",
  "port": 3000,
  "defaults": {
    "automated": true,
    "https": true,
    "preserve-caching": true,
    "write-dir": "brom-history"
  }
}

brom uses two lightweight servers to operate (a proxy for inspecting transactions, and a server for aggregating results from multiple sources). These operate on ports 9999 and 7913 respectively, but you can move them where you like.

{
  "server": "demo/server.js",
  "port": 3000,
  "proxy-port": 3001,
  "results-port": 3002
}

Some rules may not be relevant to your application. To turn these off, create an "ignore-rules" key in your configuration and list the IDs of the rules you'd like to turn off.

{
  "server": "demo/server.js",
  "port": 3000,
  "ignore-rules": [
    "no-x-powered-by"
  ]
}

HTTPS

brom proxies your application, so if your server runs in HTTPS, brom will need to as well. Because all three servers run off your localhost, a single SSL key and certificate pair is all that's necessary to enable this. Your key and certificate can stay in their usual position, but you'll need to tell brom where to find them. To point brom to your .key and .crt file, add a "https" property to the configuration file, with paths to the key and certificate. Then, all it takes to run in HTTPS mode is to call brom with a --https flag.

{  
  "https": {
    "key": "../application/server.key",
    "cert":"../application/server.crt"
  }
}

API

While brom is primarily designed as an auditing tool, it can also integrate into your application as an API. brom contains a series of header parsers written to spec, and you can access them by requiring brom into your code.

const parse = require('brom');

parse({
  'Content-Security-Policy': "default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com",
  'Feature-Policy': "vibrate 'none'; geolocation 'none'",
  'Cookie': 'hello=world; foo=bar;',
  'Set-Cookie': 'a=b; Path=/; HttpOnly;',
  'Content-Type': 'text/html; charset=utf-8',
  'Content-Length': '7913'
});

// {
//   contentSecurityPolicy: {
//     'default-src': ['\'self\''],
//     'img-src': ['*'],
//     'media-src': ['media1.com', 'media2.com'],
//     'script-src': ['userscripts.example.com']
//   },
//   cookies: [
//     { name: 'hello', value: 'world' },
//     { name: 'foo', value: 'bar' }
//   ],
//   featurePolicy: {
//     vibrate: ['\'none\''],
//     geolocation: ['\'none\'']
//   },
//   setCookie: [
//     {
//       name: 'a',
//       value: 'b',
//       Path: '/',
//       'Max-Age': '0',
//       Secure: true,
//       HttpOnly: true
//     }
//   ],
//   headers: {
//     'Content-Type': 'text/html; charset=utf-8',
//     'Content-Length': '7913'
//   }
// }

When called on its own, it will parse an entire header set, but you can also parse individual headers:

brom.csp("default-src 'self'; img-src *; script-src userscripts.example.com");

// {
//   'default-src': ['\'self\''],
//   'img-src': ['*'],
//   'script-src': ['userscripts.example.com']
// }

Full documentation of all parsers can be found in brom's npm readme.