Infosec pros spend most of their time finding and preventing bugs. But what about after you fix a bug? The odds are that in the future, some dev will refactor the code, making many already fixed vulns come back. The cycle goes on and on. Luckily, the field of software engineering has a solution made for exactly this kind of problem: regression testing. Every time you fix a bug, you should add a test that will fail if any devs ever add the buggy behavior back into the code. Better testing means safer Node.js code.
Node.js is one of the most popular ways to use JavaScript, and as such, it powers a big portion of the modern web. According to StackOverflow’s 2022 dev survey, Node.js is the most popular technology among software professionals.
Since you’re on this site, the odds are you work in infosec. That means you need to learn the tech that most devs are using, so you can learn how to secure it. This article is different from most because we are focused on preventing hacking, rather than teaching you how to hack. Regression tests are a great tool for helping bugs stay out of your team’s code after you’ve already fixed them.
Thus, in this guide, our aim is to teach you to do regression tests using two libs: mocha and chai. Let’s get started!
Introducing Mocha and Chai
Let’s look at a simple bit of code that does some basic tasks in JS. We don’t intend this code to be useful or realistic, just as simple as possible.
class Calculator {
add(a, b) {
return a + b;
}
multiply(a, b) {
return a * b
}
}
Chai is a library for asserting things about bits of code. Mocha is a framework that allows us to design tests around whether those assertions succeed or fail per what we expect. Now, let me show you what that means by writing a super basic test of our Calculator test above:
const { Calculator } = require('../calculator');
const { expect } = require('chai');
describe('Testing the Calculator methods', function() {
it('1. Addition', function(done) {
let c = new Calculator();
expect(c.addition(2, 3).to.equal(5);
done();
});
it('2. Multiplication', function(done) {
let c = new Calculator();
expect(c.multiplication(2, 3).to.equal(6);
done();
});
});
We can run this in our terminal and see that both tests pass. Hurrah! But this is pretty boring. I mean, for coding it’s cool. But how does this prevent hacking? Well, let’s go a bit deeper down the rabbithole…
How Testing Leads to Safer Node.js Code
So let’s say you just fixed a major prototype pollution bug in your team’s codebase. But the code is always changing. Just think about it – how long until a junior dev doesn’t get what some piece of security code does, so they just delete it? The app still works. True, it’s now vulnerable (again!) to an old bug. Yet they won’t notice that for a long time. Thus, the app ships out to production. And then the bug reports come in. And you fix it again. So the cycle continues forever.
This is where testing comes in. Security pros should team up with lead devs to mandate a policy: all PR’s should come with a test to ensure the new feature. All bug fixes and new features should work this way. That way, you can tell if new code breaks old code without waiting for client’s to report strange behavior.
But the top priority is always security. That means that any security bug fix or feature must have a test that comes with it. No exceptions.
Test driven development
Let me go even further – a new feature should start with a test. The test specifies how the behavior of the new feature should work. Then you (or another dev) implement the feature according to the standards specified in the test
When a project works this way, we call it Test Drive Development (TDD). It’s a core part of a modern, agile codebase. And if you want safer Node.js code for your team, stopping the influx of bugs should be your top goal. Good regression tests are just the start – by organizing your dev cycle around mocha and chai, you’ll drastically slow the creep of bugs and broken features.
Limits of TDD
Although TDD is a strong tactic, it isn’t perfect. Here are some common complaints:
- Adding excessive tests
In this scenario, your team begins imagining tons of minor features and writing tests for them. Soon enough, you’ve imagined all kinds of minor things and written tests. This can be a waste of time and create too much work without really knowing what you need. In software engineering, this is called bikeshedding.
- Forget to run tests frequently
Tests should be part of your CI/CD pipeline. Do you use Github? Then you’re in luck! Because Github supports running tests with every PR. So by using tests on Github, you can prevent any PR from merging if it doesn’t pass the tests on your repo. Beautiful.
- Tests for minor changes
Regression tests focus on functionality. You should be testing that features work the way you expect. So if you change the implementation of a feature or some minor irrelevant detail, it doesn’t require a test. How big a change requires tests is up to your team, but the basic principle is this: features, bug fixes, and security fixes always require a test.
Got it? Good. Not a huge price to pay for preventing security bugs from popping up again and again.
Conclusion: Safer Node.js code is possible!
But it’s not free. You’ll need to invest more time and effort upfront in order to set up tests. But the benefit for security is immense. If your team worries about slowing down development speed, don’t fret. TDD actually makes development faster, due to the amount of bugs it prevents later down the line. Much like strong typing and version control, TDD asks for a little bit of work in the beginning to prevent much more work later on.
As infosec pros, it’s really important that we stay up to date on the tech and methods devs use. Whether it’s Node.js, Mocha, Chai, or TDD, these methods are pillars of the modern dev ecosystem. You can’t afford to skip this crucial knowledge, so have fun writing tests and contributing to your team’s security and engineering culture!
Good luck, and as always, happy hacking!
Leave a Reply