How much do you know about the importance of credentials? Credentials are the privileges that give you access to an application or system, such as API keys, database access information, session tokens, and more. What happens if these credentials are exposed to the outside world?
It's a scary thing to think about. For starters, if an attacker gets hold of your credentials, they'll be able to take control of some of your services. It's not uncommon for them to crash your site, trick users into taking over their accounts, or even worse, compromise your customers' information. Services today store a lot of valuable data, including personal information. What happens when an attacker steals this data? Individuals could suffer identity theft or financial harm, and organisations could face legal sanctions and huge losses in trust. It's a devastating blow that can threaten a company's very existence.
In other words, credentials are like the keys to a safe. You don't want to hand over the keys to your organisation's most valuable assets - your data - to the wrong hands. So, just like keys, credentials need to be locked away and access to them tightly controlled, especially when they're embedded in front-end code. Front-end code that runs in the open space of the browser can be easily snooped on by anyone, so sensitive credentials should never be left on the front-end for any reason.
But that's exactly what happened to a startup called Resend in January this year. The problem was that Resend's front-end code left hard-coded database access information exposed. Hackers were able to learn environment variables from client source code and access customer databases at will. As a result, the company suffered a serious security breach.
But is this an exception? Unfortunately, this story is not unique. At Cremit, we used Ferret to validate the front-end code of some of South Korea's most popular websites and found that 14 out of 70 sites had exposed credentials. In short, they're making similar mistakes to Resend.
In terms of specific issues, the most common was the exposure of OAuth's Client Secret, which is a sensitive value used for OAuth authentication that, if compromised, can put the entire service at risk. The next most common issue was third-party access tokens with all permissions open. These tokens are used as authentication when calling external APIs, and if the permissions are not set correctly, an attacker can exploit the token to manipulate the core functionality of the service.
We've also seen critical credentials, such as AWS IAM secret keys, embedded directly into the code - hard-coded. This may seem like a small mistake at first glance, but it poses a huge security risk.
On some sites, the OIDC access token used by the build server was left as an environment variable, which was left in the front-end build files, or the credentials were left in a configuration file that was accidentally committed to the repository.
These examples suggest that many organisations overlook the importance of front-end security, perhaps out of a vague sense of complacency that the front-end is less risky than the back-end, but it's important to recognise that the security risks inherent in the front-end are not to be taken lightly.
So why do so many developers neglect front-end security? The truth is, in haste, they often hardcode credentials instead of setting cumbersome environment variables. But more importantly, this has become increasingly common in recent years because today's front-end code doesn't just draw on the screen, it often deals directly with sensitive data.
Let's take a look at an example of how this mistake can be made during development. First, let's take a look at the next.config.js
file, which is responsible for configuring the Next.js application.
The above code is a good example of a common security issue that can arise in the pursuit of ease of development. The publicRuntimeConfig
option is a feature provided by Next.js that allows you to set client-side accessible environment variables. This is useful for things like public API keys or settings needed for business logic. However, in the code
above, we're assigning process
.env in its entirety using the spread syntax. This means that we're exposing all of the server's environment variables to the client without exception.
What if process.env
contains sensitive credentials, such as database access information, secret keys for third-party services, and so on? It's just bundled into the client-side bundle, making it accessible to everyone. If you open your browser's developer tools, you'll see code like process.env.DB_PASSWORD
in plain text, which is obviously a development convenience, but it's a pretty risky code from a security perspective.
The code above shows a case where Next.js's page router includes a hardcoded credential key during server-side rendering (SSR). Typically, Next.js doesn't bundle server-side code with the client, so many developers hardcode the secret key as a convenience, thinking it's relatively safe compared to the API route. But there's a pitfall. It's the source map.
For example, you might upload a source map to introduce an error monitoring solution like Sentry. This is because they need the ource map to map the obfuscated stack traces to the original code.
The problem is that these source maps can also contain server-side code. This means that not only the API routes are exposed, but also any code written to getServerSideProps
, etc. This is a very dangerous situation. This goes to show that Sentry is a very useful tool, but it can also be an attack vector.
Recently, the Next.js and React ecosystem has been undergoing a revolutionary transformation. The introduction of the React Server Component (RSC) in Next.js 13 revolutionised the development paradigm by blurring the lines between server-side and client-side rendering, and Next.js 14 added a new feature called Server Actions.
These technologies have greatly improved development productivity and user experience. But they also introduce new security risks. The blurring of the lines between server and client code has increased the potential for credential exposure. Let's take a look at this risk in the code below.
The above code is a simple form component using RSC. The logic is to get
an environment variable called SECRET_TOKEN
via the getEnv
function, pass it to the run
function, and run it on the server.
Here, the run
function has the "use server
" directive added to it. This indicates that the function will only be executed on the server and will not be sent to the client. So at first glance, it looks like the SECRET_TOKEN
will never be exposed to the client. It's being used securely on the server.
But there's a big pitfall here. By default, Next.js encrypts the Server Component and sends it to the client. However, if you bind it like in the code above, the encryption is automatically opted out when you use the Server Action, meaning the value of SECRET_TOKEN
is plainly embedded in the client. You can see this by opening your browser's developer tools.
As such, securing front-end applications requires a systematic approach throughout the development process. The most basic and important thing is to never include sensitive information in front-end code. Credentials like API keys or secret tokens should never be included in JS bundles, no matter how convenient.
To achieve this, it's important that your entire development team understands and practices secure coding principles. Regular training and workshops will help build secure coding habits, and a culture of code review within the team will help proactively identify vulnerabilities. Creating checklists to review all code changes from a security perspective is also effective.
However, even the best processes and design can't completely eliminate human error, especially with newer technologies like Next.js' Server Action and React Server Component blurring the lines between server and client, making it easier for developers to accidentally expose credentials. It's wise to enlist the help of an automated scanning tool. Ferret by Cremit not only detects hard-coded credentials in source code, but also finds and alerts you to API keys, passwords, and more scattered across collaboration tools like Notion and Jira.
When it comes to front-end development, security is no longer an option, it's a necessity. And not just from a technical standpoint, but also from a business sustainability standpoint. You need to do everything you can to protect your customers' trust and increase your brand value.
With Resend as a wake-up call, it's time to realise the importance of front-end security and get proactive. Let Ferret be your vigilant guardian - the more secure your code is, the better the internet will be for everyone. Contact us today to get started.
We investigated the credential leaks of major sites in South Korea and notified their administrators. In addition to the numbers in this post, we also found other highly sensitive leaks (plain text exposure of credentials in Alibaba Cloud, administrator credentials in AWS Cloud, and other authentication tokens).
Most of the vulnerable sites still did not read the notifications (about 20%) or did not respond to the notifications (about 35%), but Cremit's continuous contact (with the AWS Account Manager team) helped us remediate most of the threats.
We apologise to the users who were surprised to receive the emails, but the gratitude we received from those who were both surprised and spared from the threat of malicious hacking continues to validate our mission.
Want to have a technical discussion with Cremit? Anything security related is great. Sign up for a meeting and demo at Meet the Cremit Team.