How to Ensure Security as a Front-End Engineer
Let’s get straight to the point: Security is everyone’s responsibility. Just because you’re not a security engineer, doesn’t mean you can be careless about the code you’re writing and the structure you’re building.
In this article, we explore essential security practices for front-end engineers. However, given the impracticality of reviewing every single conceivable scenario, we focus on some of the most problematic areas. Additionally, we provide further steps to expand your knowledge beyond the scope of what we’ve addressed here.
Understand what you use
This aspect should be a no-brainer. You must understand what you’re using. Yes, we’re talking about dependencies.
We don’t need to reinvent the wheel every time we start a new project; that’s why we use dependencies. However, it’s unacceptable to blindly add dependencies because they’re famous or because someone recommends them. Before you add any new package to your project, you should ask yourself the following questions:
- Is this new dependency safe?
- Are there any vulnerabilities I should be aware of?
- Is this a deprecated project, or does it receive updates?
- Is there a community in case I have questions about this library?
- Does this project have active issues that will compromise my project?
We can use tools like snyk.io or socket to answer these questions.dev or go to the official repo of the library, if available, and see if there are pull requests (PRs) or open issues. There are multiple things to do and ask before you choose a dependency, but the main idea is that you should make an informed decision.
Justify your decisions
This item is similar to the previous one but more focused on the reason behind the decisions. It is essential to have the skill to justify every choice.
If you use a library for a new project, can you explain why you use option A instead of option B? Suppose the option you chose is abandoned in the future. In that case, you need to decide whether to keep a deprecated library or migrate to a newer one, and to do that, you would have to ask yourself why you started using that library in the first place.
Can you keep using the library without worrying about security patches, or is this a critical part of your infrastructure that could leverage new updates?
Be conscious of your infrastructure
Ask yourself this: Are there any technologies that can automate processes in this project? This includes tools like Webpack, Gulp.js, and Rollup.js. These tools often affect the final result of your code.
Incorrect configurations, outdated plugins, failed tasks, or unwanted default options can lead to security vulnerabilities that you will only be aware of if you understand what your infrastructure is doing.
Handle credentials correctly
The most common and recommended way to store sensitive credentials like API keys is with a .env file that will not even be part of the commit history or the repository. This practice prevents the exposure of valuable information. Using Github’s search bar, it is easy to determine which public repositories have .env files uploaded — and, you guessed it, they generally have credentials in them.
Remember: if you have the credentials inside your code, people will access them, and the .env file will be useless if you commit it to your repo.
Acknowledge your flaws (vulnerabilities).
Ideally, a project should have zero vulnerabilities. However, that’s not the reality. At some point, you will use a package with a vulnerability that doesn’t have a fix yet, or a subdependency of a dependency will require a patch that doesn’t exist. What can you do in this scenario?
First, we need to evaluate if a vulnerability affects us or not. The term “vulnerability” often triggers alarms, but its impact depends on the context. For instance, consider a scenario where you have a masonry grid of products with static content. In this setup, everything is already in the HTML: the images are there, and there are no API requests or databases serving the assets.
Let’s say this static content setup includes an input that can filter content. If the library managing this filter has a vulnerability that allows SQL injection attacks, it wouldn’t impact this particular project because there is no database connection. In other words, while the flaw exists in the code and you are using a library with a vulnerability, it is rendered irrelevant by the specific context of your application.
The previous example is not trying to diminish the importance of patching vulnerabilities. It simply illustrates that, if you understand a vulnerability, you may know how to approach it. You don’t need to worry about it if it doesn’t affect you — this doesn’t mean we don’t need to patch it when the update is available. We’ll talk more about this in the next section.
But how do we proceed if the vulnerability affects us but doesn’t have a fix yet? This question can be tricky to answer. It depends on the exposure itself, and every scenario will be different. However, these are some options:
- Stop using the specific function affected by the vulnerability and write a manual solution.
- Write a provisional fix until the official one is ready.
- Install a temporary package to handle the compromised logic.
- Look for a replacement.
Working with principles
As we saw in the previous example, there are scenarios where a vulnerability doesn’t harm your application. However, we are responsible for having as few weak points as possible. Because of this, you should fix that vulnerability even if it doesn’t affect you.
It is easier to adopt the mindset of patching every vulnerability as soon as possible, even if it doesn’t represent an immediate threat to your system, rather than debating whether or not to address a vulnerability—unless you have an excellent reason not to do so. Will patching that harmless vulnerability benefit the project? Perhaps not in the immediate future, but this is more about being mindful about this topic and avoiding future tragedies.
At times, it is not a single significant failure that causes a major problem. Instead, it’s the accumulation of small failures that exacerbate the severe outcome.
To reinforce the concept of being cautious in our actions, even when they don’t present a threat, let’s consider the following scenario:
// Option 1
document.querySelector('#title').textContent = 'Your new text';
// Option 2
document.querySelector('#title').innerHTML = 'Your new text';
Technically speaking, both options will give you the same visual result. However, if we are not explicitly injecting HTML into this element, we shouldn’t use ‘innerHTML’. The value ‘Your new text’ is plain text.
We don’t gain anything if we use the second option, except the potential for template injection or cross-site scripting vulnerabilities, which will probably not be catastrophic given the context… However, as we said before, this is more about working with principles, understanding what you are doing, and closing the door to future pitfalls as much as possible. Remember that game over is the first vulnerability that creates a chain of actions that causes an issue.
Beyond this article
Reading this article is an excellent way to embrace good habits. However, more is needed.
To keep polishing your skills and stay updated on this rapidly changing field, you must explore sources of information as a habit, not as a sporadic activity. Again, this depends on you, what you like and your routine.
If you don’t have much time, you can subscribe to newsletters like tldr.tech or install the daily.dev extension on your browser. If you prefer to use official websites for information, you can visit sites like The Hacker News and, obviously, this blog for more.
Whatever method you choose, the most important thing is staying informed and updated about coding practices and engineering skills.