Update: I no longer work at WhiteHat and neither does this team. Hire them!

I work for WhiteHat Security and I lead a team called The Shire. Our team primarily builds web applications in Ruby on Rails and we're based in downtown Pittsburgh.

We're hiring a couple more developers to join our outstanding team. I'm fortunate enough to work with some of the best people in Pittsburgh. Bright, curious, hilarious, and they're good hackers1, too! We're interested in talking to programmers of all levels. Don't shy away if you are interested but have limited experience. We want to hire great people who put in the effort, and you are hard enough to find as it is.

It's difficult to write a "please come work with me" essay so here's my plan: I'm going to tell you about our team culture, then about what we're looking for in a candidate. The method I'm using is to post our internal team culture document verbatim, and our internal job position document. They're both on our team wiki, and editable by anyone on the team.

If you read all this and you'd like to work with us please email me to talk about it. You can reach me at work with the following obfuscated email address:

 1name    = %w(Casey West)
 2company = %q(WhiteHat Security)
 3email   = [
 4  name.join('.'),
 5  [
 6    company.tr(' ','')[0..10], # We go to 11. \o/
 7    'com'
 8  ].join('.')
11puts email

You can also find links to me on social media on the left if you'd like to chat casually.

Shire Culture

The Shire operates under two guiding principles: trust and transparency. As a member of the team you are trusted to use the best ideas to do the most important work. As a team we agree to be transparent in our work, our ideas, and our struggles.

This means we are all accountable for the success of our work as a team. It doesn't matter what functional role you have, which skills you bring to the team when you start, or where you are located. If there's work that needs to be done we take responsibility for it, complete it, and ship it as a team.

If we succeed in our goals it's because we did it together. If we fail it's because we all failed together. Thankfully, failure is not terminal. We learn from it, adapt, and do better going forward.

This brings us to The Shire Motto: If something is broken we fix it.

We don't put a ticket on another team's queue and wait around or complain. We do all the work we can to fix whatever is broken. We accept external roadblocks only when we are physically or administratively blocked, and we try to avoid those blockers in the future, too.

The Shire has adopted this attitude. Doing so has meant we've accumulated a wealth of knowledge about much of the WhiteHat Architecture. Everything from the hardware we run on to the way we document our APIs.

We are not afraid of solving hard problems correctly, and we're not afraid to spend the time getting things right. We’re not afraid to make good change anywhere in the company with whatever the right tool for the job is: technology, knowledge, experience, culture, or even a good hearty laugh.

With this understanding of our Shire culture it's clear to see what your role as a team member is: Get the job done. Do whatever it takes, and do it well.

We work at WhiteHat to meet customer needs with positive business outcomes.

Here are some things we don't work at WhiteHat to do:

  • Write beautiful code.
  • Test everything.
  • Make it pixel perfect.
  • Handle every edge case.
  • Document every process.

When we are doing well we get some of these for free, which is a wonderful thing, but they are not our primary objective.

We don't work beyond our physical limits. Each team member should work to maintain their health and happiness, and the team should hold each other accountable for that. If someone is burning out we should encourage them to rest. Take care of yourself and each other.

What does this mean for you, for each team member? Lets consider it by functional team, since we already understand we're all in this together, anyway.


You are the expert on the software we create. You know where it is, how it's organized, how it's tested, and how it's deployed. If a problem can be solved by writing some code you know how to do that and you will. If you don't know how—no problem—you will soon. You aren't afraid of a challenge. You're courageous!

You know how to test what the team has created. You collaborate with other team members regularly to share knowledge and ideas, and to work together to find the best way to do the work because, after all, the team is all responsible for it.

When you're concerned there's a problem you raise the issue openly, trust the team to respond, and get to work fixing it with the team.

You help others always, especially lending support in areas of your expertise. You take care to teach others what you know so we all learn and grow.

You know what to do next because you follow the team's progress. If ever you don't know you will soon because you'll ask. Nobody has to give you a detailed task list, though, because you're self motivated and will dig into the next unsolved problem.

Developers: See Hackers

Testers: See Hackers

Product Management and Design: See Hackers

Scrum Master: See Hackers

Interns: See Hackers

These are the expectations the team has of every member. Regardless of your title or tenure this is your charge.

Everyone on The Shire is a hacker, so happy hacking!

Shire Hacker Job Description

About Us

WhiteHat Security helps prevent website attacks by providing the most complete Web security solution. We keep our customers safe in some of the most heavily regulated and attacked industries out there.

At WhiteHat we code and have fun. We're hacker culture1. We're focused on delivering quality code every day and we do that by taking risks, staying flexible, and moving fast. Our work is challenging and our team is passionate about it. We're creative and dedicated.

Position Summary

Do you want to be challenged by your job and overcome those challenges with awesome people? If you do we want you on the team!

We want full stack, modern web application developers: polyglots who use the right tool for the job. As a team member you will work on products that run our business. We need you to passionately champion great ideas, collaborate on a distributed team, and exercise unencumbered intellectual curiosity so we can ship fantastic software together.

Desired Skills and Experience

A qualified candidate will:

  • Have experience writing applications that are secure and have to scale.
  • Have experience with responsible development practices: writing unit and functional tests as part of development; writing maintainable, well structured code thoughtfully.
  • Be comfortable working collaboratively with distributed teams.
  • Have some exposure to the full stack: *nix, devops, automated systems, application development, javascript, CSS, and HTML5.
  • Be fearless: you know how to find the answer and you're not afraid to take a risk and try things that might not work.
  • Be a scientist: collect data and make informed decisions whenever possible.

An ideal candidate will also:

  • Love beautiful code: elegant code is often the result of elegant solutions.
  • Have extensive hands-on experience with web application frameworks such as Ruby on Rails, Django, or Express.
  • Contribute to open source projects and participate in technical communities.
  • Understand distributed architectures such as data sharding, service oriented architecture (SOA), and load balancing, and know when to use them.

This position will likely be filled at our headquarters in Santa Clara, or our downtown Pittsburgh office. Remote work may be an option for an exceptional candidate with remote work experience outside the Bay Area or Pittsburgh. Please apply if you are an exceptional candidate!

Thank you for considering WhiteHat Security. When you apply please include your GitHub user ID and tell us which open source project really excites you right now, and why.

WhiteHat Security is an equal employment opportunity company.

  1. "The act of engaging in activities (such as programming or other media) in a spirit of playfulness and exploration is termed hacking. However the defining characteristic of a hacker is not the activities performed themselves (e.g. programming), but the manner in which it is done: Hacking entails some form of excellence, for example exploring the limits of what is possible, thereby doing something exciting and meaningful." — Hacker (programming subculture), Wikipedia 2 3

I love developing applications with the peek gem. It's designed for development or staging environments; there are a few tricks if you don't want it on production or test environments.

This gem puts a little bar on your pages which gives you all sorts of helpful information about the request you just made.

peek screenshot

As you can see there is information about the branch you're on, performance metrics, database queries, caches, shared memory, and job queues in there. Read the documentation for peek on GitHub and find the plugins that work best for you. They're excellent.

The trouble is I definitely don't want to install Peek on production, or in my test environments, and the peek usage instructions would, indeed, require me to do so for a few key reasons: initializers, routes, assets, and views. Using peek requires a few steps of setup in your application. Each of the areas I enumerated requires peek to be a loaded gem in the environment you're running on.

My first attempt to work around this involved wrapping calls with a defined? test. For example, in config/routes.rb:

1mount Peek::Railtie => '/peek' if defined?(Peek)

To style and script peek bar you need some CSS and JavaScript assets as well, which are loaded through the asset pipeline. Problem is, if the gem isn't in your bundle those loads fail, and so does pre-compilation if you attempt it.

Finally, your views should be checking the peek_enabled? method, which is defined by peek in your ApplicationController class. So, for example, when rendering the peek bar itself you should be doing this:

2  = render 'peek/bar' if peek_enabled?

Again, if peek isn't in your Bundle, such as in the production environment, this render will fail.

I'm not particularly enthusiastic about this situation, so here is my guide to using peek responsibly. There are enough moving parts I decided to use the Table of Contents.


Peek must be loaded into our environment. I'm loading it into my :development environment only, along with a list of Peek plugins I want to use.

1group :development do
2  # Peek Bar
3  gem 'peek'
4  gem 'peek-git'
5  gem 'peek-gc'
6  gem 'peek-performance_bar'
7  gem 'peek-pg'

PeekBar Class

This class is meant to answer two questions:

  1. Is Peek available for use? This question is asked when we start and configure the application.
  2. Should peek be enabled for this request? This question is asked during requests. Specifically during view rendering for any view that incorporates peek, which is most likely a layout.


I put this class in lib/peek_bar.rb.

 1class PeekBar
 2  def self.available?
 3    !defined?(Peek).nil?
 4  end
 6  def self.enabled?(current_user)
 7    return false unless available?
 9    # The default test.
10    %w(development staging).include? Rails.env
11  end

Here you can see we've encapsulated the defined? test into PeekBar.available?. We use it in PeekBar.enabled? as the first test.

If peek is available, and we're in development or the current_user likes nerdy toys we enable peek.


I use rspec for testing, so my spec is in spec/lib/peek_bar_spec.rb.

 1require 'rails_helper'
 3RSpec.describe PeekBar do
 4  subject { described_class }
 6  context 'without Peek' do
 7    before(:each) do
 8      Object.send(:remove_const, :Peek) if defined?(Peek)
 9    end
11    it "isn't available" do
12      expect(subject.available?).to be(false)
13    end
15    it "isn't enabled" do
16      expect(subject.enabled?(anything)).to be(false)
17    end
18  end
20  context 'with Peek' do
21    before(:each) { Peek = Class.new }
22    after(:each) { Object.send(:remove_const, :Peek) }
24    it 'is available' do
25      expect(subject.available?).to be(true)
26    end
28    it 'is enabled' do
29      allow(Rails.env).to receive(:development?).and_return(true)
30      expect(subject.enabled?(anything)).to be(true)
31    end
32  end

In order to ensure this test is isolated I take extra precautions to ensure the Peek constant isn't available to my tests unless I explicitly allow it.


By default Rails doesn't load ruby code from the lib/ directory, so lets be sure to tell it to. In config/application.rb add this:

1config.autoload_paths += %W(
2  #{config.root}/lib

PeekBarHelper Module

As I said earlier peek implements a peek_enabled? method and suggests if you want to customize it define the method in your ApplicationController class. I recommend doing that in a helper class instead.

We need to know if peek should be enabled and to verify that we also need to know if it's available. With the PeekBar class this is simple.


In app/helpers/peek_bar_helper.rb.

1module PeekBarHelper
2  # Typical Peek Integration
3  def peek_enabled?
4    PeekBar.enabled?(current_user)
5  end


Since we're programming responsibly here's a test for the helper. In spec/helpers/peek_bar_helper_spec.rb.

 1require 'rails_helper'
 3RSpec.describe PeekBarHelper, type: :helper do
 4  before(:each) do
 5    allow(helper).to receive(:current_user).and_return(anything)
 6  end
 8  it 'is not enabled' do
 9    allow(PeekBar).to receive(:enabled?).and_return(false)
10    expect(helper.peek_enabled?).to be(false)
11  end
13  it 'is enabled' do
14    allow(PeekBar).to receive(:enabled?).and_return(true)
15    expect(helper.peek_enabled?).to be(true)
16  end

Peek Initialization

The peek documentation explains how to set up config/initializers/peek.rb to enable the plugins you've selected. We need to first test for the availability of peek like this:

1if PeekBar.available?
2  Peek.into Peek::Views::Git
3  Peek.into Peek::Views::GC
4  Peek.into Peek::Views::PerformanceBar
5  Peek.into Peek::Views::PG


We don't want to load or pre-compile the peek assets unless we intend to use them. My recommended approach is to wrap their inclusion in a new set of CSS and JavaScript files. I use the asset pipeline, so that's how I'll be including the gem's assets.

CSS and JavaScript Definition

CSS is in app/assets/stylesheets/peek_bar.scss.

 1//= require peek
 2//= require peek/views/performance_bar
 4// Peek bar on bottom of page.
 5#peek {
 6  position: fixed;
 7  bottom:   0;
 8  left:     0;
 9  right:    0;
10  z-index:  999;

I prefer CoffeeScript so I loaded the JavaScript in app/assets/javascripts/peek_bar.js.coffee.

1#= require peek
2#= require peek/views/performance_bar


The asset pipeline complains if you don't tell it about additional assets you intend to include separately (see the next section on View Configuration). Thing is, we don't always intend to include them or pre-compile them in this case. Just like when we initialized peek itself we must check for its availability.

Add this to config/initializers/assets.rb:

1if PeekBar.available?
2  Rails.application.config.assets.precompile += %w(peek_bar.css peek_bar.js)

Default Assets Gotcha

By default application.css and application.js include all the CSS and JavaScript in their respective directory trees. That's achieved through the use of require_tree . in each of those files.

Now that we've created a couple assets to conditionally load we need to remove require_tree. If you don't remove it your default assets will try to include peek_bar.* which will lead to the aforementioned failures on environments where peek is not installed.

If you were relying on it, which I don't recommend for reasons beyond the scope of this essay, learn how to deal with it by reading the Asset Pipeline Rails Guide.

View Configuration

All this hard work for what? Now it's time to tie it all together in our layout view. I like slim so this is an extremely condensed version of my default layout, app/views/layouts/application.html.slim.

 1doctype html
 3  head
 4    = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
 5    - if peek_enabled?
 6      = stylesheet_link_tag 'peek_bar', media: 'all', 'data-turbolinks-track' => true
 7    = csrf_meta_tags
 9  = render 'peek/bar' if peek_enabled?
10  = yield
11  = javascript_include_tag 'application', 'data-turbolinks-track' => true
12  - if peek_enabled?
13    = javascript_include_tag 'peek_bar', 'data-turbolinks-track' => true



At this point peek is incorporated into my application in a robust, responsible way. Environments we didn't install it into operate perfectly without it, and the ones we do have an encapsulation to use for managing its inclusion in our interface.

It was a little more work but it paid off. We have an excellent developer tool installed without hurting production.

If we want to use peek in production with only staff accounts, for example, all we have to do is change the Gemfile to install peek in the :production group and adjust the PeekBar.enabled? method:

1def self.enabled?(current_user)
2  return false unless available?
4  Rails.env.development? || current_user.staff?

That PeekBar class really tied the room together, did it not?

If you read my post on running bundle-audit from rake you'll also know I run RuboCop as part of continuous integration in Travis CI. Today I switched a project to run on Travis' new docker infrastructure and something hilarious happened.

Running Travis CI on Docker

This part is super easy. Amazingly easy. Add this to your .travis.yml file:

1# Docker Infrastructure
2sudo: false
3cache: bundler

The sudo key is the magic word to build your project in a Docker container. The cache key will enable bundler caching for your project.

These two lines can speed up your build times by nearly eliminating startup costs. I went from 2.5 minutes to 24 seconds on builds which used the cache and archived images. I'm running bundle-audit, brakeman, rubocop, and rspec in 24 seconds. That is awesome!

The funny thing is…

The first run I had a little hiccup. RuboCop was doing its thing and, on this project, it only has 30 files to inspect. I know that because I just ran it locally and, yep, 30 files. In the Travis environment, however, it was scanning 5320 files. That's probably wrong.

Turns out, by enabling the cache in Travis I caused RuboCop to lose its mind. How? Because of where the cache was stored. Travis put the cache in vendor/bundle which is in my Rails app directory and within sight of RuboCop. That's legit, but I don't actually want RuboCop to check any vendor code.

Why was RuboCop doing that? Because I wanted to Exclude a few things in my Rails app—mostly generated code—so I had a .rubocop.yml file that looked a little like this:

 1require: rubocop-rspec
 2inherit_from: .rubocop_todo.yml
 4  Exclude:
 5    - 'db/schema.rb'
 6    - 'bin/**/*'
 7    - 'config/initializers/devise.rb'
 8    - 'config/initializers/simple_form*'
 9    - 'db/migrate/*'
10  RunRailsCops: true

The default RuboCop configuration does exclude vendor. I need that, too, but my Exclude above overrides the default, so it was gone.

The simple fix

Lets just add vendor back into the exclusion list:

 1require: rubocop-rspec
 2inherit_from: .rubocop_todo.yml
 4  Exclude:
 5    - 'db/schema.rb'
 6    - 'bin/**/*'
 7    - 'config/initializers/devise.rb'
 8    - 'config/initializers/simple_form*'
 9    - 'db/migrate/*'
10    - 'vendor/**/*'
11  RunRailsCops: true


Be careful when modifying RuboCop's configuration that you copy what you need to keep from the defaults, because defining an entry in your project will discard the them, not merge them.

Originally published by me on December 18, 2006 and reprinted here as-is.

I recently read Frederick P. Brooks, Jr.'s essay No Silver Bullet: Essence and Accidents of Software Engineering. Brooks wrote this paper 20 years ago and it still rings true today. I like the whole essay and encourage you to read it if you haven't already.

Brooks is an oft quoted writer. I'm going to indulge in the practice by pointing out a few things from this essay that I think will always be true.

Likewise, a scaling-up of a software entity is not merely a repetition of the same elements in larger sizes, it is necessarily an increase in the number of different elements. In most cases, the elements interact with each other in some nonlinear fashion, and the complexity of the whole increases much more than linearly.

The complexity of software is an essential property, not an accidental one. Hence, descriptions of a software entity that abstract away its complexity often abstract away its essence.

I really like this, because today there are a lot of arguably excellent attempts to abstract complexity in modern application development. Many software developers look to frameworks and application environments to manage the complexity of software development for them. These developers expect incredible gains in productivity by using a tool that makes building applications "really easy." Don't be fooled. Tools help you build things, yes, but don't expect to get a pony when you install Ruby on Rails (although, as it happens, you do get a pony when you install Jifty). Frameworks are not 100% tools, they're 80% tools. If they were 100% tools then you wouldn't be making applications you'd be writing configuration files. What kind of programmer writes configuration files for fun?

In many cases, the software must conform because it is the most recent arrival on the scene. In others, it must conform because it is perceived as the most conformable. But in all cases, much complexity comes from conformation to other interfaces; this complexity cannot be simplified out by any redesign of the software alone.

This most obvious form of complexity through conformity in web applications is browser compatibility. I don't think that is the scenario that takes the cake, however. Anything relating to or interfacing with email clients increases complexity in an extremely non-linear fashion. There are other issues, too, such as representing information for RSS and Atom feeds. A lot of cruft exists around the edges of software because it must conform to its environment. You cannot simplify conformity within your software alone.

In spite of progress in restricting and simplifying the structures of software, they remain inherently unvisualizable, and thus do not permit the mind to use some of its most powerful conceptual tools. This lack not only impedes the process of design within one mind, it severely hinders communication among minds.

If you've ever worked with someone else when writing software you know this to be true. The inability to visualize software is a serious friction point between team members.

When asked "What do you think makes some programmers 10 or 100 times more productive than othes?" Peter Norvig answers, "The ability to fit the whole problem into their heads at one time." That's often a critical ability and it speaks to Brooks's admission that software design is hard enough within a single mind. David Heinemeier Hansson has a complimentary answer to the same question, "The ability to restate hard problems as easy ones." Having the ability to clearly communicate software design is important, yes. I would add to these answers, "The ability to explicitly articulate the design of complex software." Having these attributes brings you closer to making the invisible visible.

Imagine how important these skills must be if you're building a geographically distributed team like 37signals or Socialtext? The lowest common denominator for being hired on these teams is likely expressed in the previous paragraph.

I do not believe we will find productivity magic here. Program verification is a very powerful concept, and it will be very important for such things as secure operating-system kernels. The technology does not promise, however, to save labor. Verifications are so much work that only a few substantial programs have ever been verified.

20 years after writing this Brooks is still right. In context, Brooks is talking about "test first" methodologies here, asserting that testing during design and specification will not save you labor. He goes on:

More seriously, even perfect program verification can only establish that a program meets its specification. The hardest part of the software task is arriving at a complete and consistent specification, and much of the essence of building a program is in fact the debugging of the specification.

In my opinion this statement is a key gem. I interpret this statement to be a nod to iterative development, suggesting that the imperfect art of testing our assumptions over time is a large part of the essence of software development. One could bastardize this as "release early, release often." I feel that's an oversimplification that leaves large room for error.

Taking time to design software is critical. I agree with Joel when he says "Programmers and software engineers who dive into code without writing a spec tend to think they're cool gunslingers, shooting from the hip. They're not. They are terribly unproductive. They write bad code and produce shoddy software, and they threaten their projects by taking giant risks which are completely uncalled for."

Let's skip to the end, to the real message within the message of the essence of software development:

Hence, although I strongly support the technology-transfer and curriculum development efforts now under way, I think the most important single effort we can mount is to develop ways to grow great designers.

No software organization can ignore this challenge. Good managers, scarce though they be, are no scarcer than good designers. Great designers and great managers are both very rare. Most organizations spend considerable effort in finding and cultivating the management prospects; I know of none that spends equal effort in finding and developing the great designers upon whom the technical excellence of the products will ultimately depend.

This, I believe, is the most important advancement since Brooks wrote his paper. Organizations have recognized the need to grow great software designers. As a result the pace of development and product creation has increased dramatically. The state of the world is much better now than 1986 but we still have a long way to go.

bundle-audit is a command-line utility provided by the bundler-audit ruby gem. It does patch-level vulnerability scans on your Ruby dependencies by working with bundler to inspect your project's Gemfile.lock.

I want to run bundle-audit from a rake task in a Rails project. Here's how I did it.

This is my lib/tasks/bundle_audit.rake file.

 1require 'bundler/audit/cli'
 3namespace :bundle_audit do
 4  desc 'Update bundle-audit database'
 5  task :update do
 6    Bundler::Audit::CLI.new.update
 7  end
 9  desc 'Check gems for vulns using bundle-audit'
10  task :check do
11    Bundler::Audit::CLI.new.check
12  end
14  desc 'Update vulns database and check gems using bundle-audit'
15  task :run do
16    Rake::Task['bundle_audit:update'].invoke
17    Rake::Task['bundle_audit:check'].invoke
18  end
21task :bundle_audit do
22  Rake::Task['bundle_audit:run'].invoke

It defines a few rasks under the bundle_audit namespace. Thanks to the modular Ruby code built on top of thor I had no trouble tapping directly into the command-line implementation for bundle-audit.

1$ rake -T bundle_audit
2rake bundle_audit:check   # Check gems for vulns using bundle-audit
3rake bundle_audit:run     # Update vulns database and check gems using bundle-audit
4rake bundle_audit:update  # Update bundle-audit database

Invocation is as easy as you can imagine. On the command-line:

1$ rake bundle_audit:run
2Updating ruby-advisory-db ...
3From https://github.com/rubysec/ruby-advisory-db
4 * branch            master     -> FETCH_HEAD
5Already up-to-date.
6ruby-advisory-db: 108 advisories
7No unpatched versions found

As you can see this does two things. First, it updates your local copy of the ruby-advisory-db. Then it scans your gem dependency graph looking for unpatched dependencies. What you see above is the output when everything is OK.

I also incorporated the check into my rake test task, because I want my tests to fail if I'm shipping with vulnerable dependencies. Here's what I have going on in lib/tasks/test.rake:

 1# Remove the Rails defaults.
 7namespace :test do
 8  desc 'Run all tests'
 9  task all: :environment do
10    Rake::Task['bundle_audit'].invoke
11    Rake::Task['brakeman:run'].invoke
12    Rake::Task['rubocop'].invoke
13    Rake::Task['spec'].invoke
14  end
17task :test do
18  Rake::Task['test:all'].invoke
21# Running `rake` should run all my tests.
22task default: :test

Since this is part of my testing expectations I can also rest easy because my continuous integration tool has my back on every change, too.