3 min read

Secret scanning and Next.js builds

CI is not just for tests - it’s an important part of the many security layers you should wrap around your application. How can you use CI to detect leaked secrets in Next.js?

Secret scanning and Next.js builds

When combined, Next.js and Vercel offer an amazing developer experience. Next.js makes it easy to build rich applications with advanced features and then it’s really easy to get your code to production - just push to GitHub and it’s automatically built & deployed by Vercel.

In this flow, Vercel handles the complexities of CI. They maintain the environment, provide caching, and have optimized workflows so building a Next.js app is fast. However in doing so you are handing over control of CI.

This becomes important when you want to customize the build steps and add other workflows. CI is not just for running tests. It’s also an important security layer on the road from development to production because of the opportunity to add additional checks. 

In this post we consider one of the items on our Next.js security checklist - guarding against accidentally leaking environment variables.

Secret scanning containers

Arcjet’s developer security SDK communicates with our low-latency API which is deployed to multiple AWS regions. The API runs inside Docker containers and we use GitHub Actions to build and deploy to production. This includes several security steps which will cause the workflow to fail if they don’t pass.

Running in production inevitably means accessing other services which means storing secrets. The only place we want secrets to live is in AWS Secrets Manager, so we’re careful to avoid them appearing elsewhere. 

This involves pre-commit checks with Trunk running Trufflehog and Gitleaks. GitHub also has secret scanning on every push to help prevent secrets being committed to Git. However, the build step includes various environment variables which might accidentally leak into the build. The risk is low, but a mistake is possible, and security is all about creating layers of protection.

To protect against accidentally leaking secrets, we run a secret scanner on our container artifacts after the build step in CI. The GitHub Actions workflow uses the docker/build-push-action to build the container as normal and then we save it to disk. Once on disk, we use Trufflehog to scan it.

- name: Docker save
  run: |
    docker save -o /tmp/decide.tar aj-decide:latest
    docker image ls -a

# The trufflesecurity/trufflehog action only supports running in `git`
# mode so we need to run it manually to scan the Docker image
- name: Run Trufflehog
  run: |
    docker run --rm -v "$PWD:/pwd" -v "/tmp:/tmp" trufflesecurity/trufflehog:latest docker --image file:///tmp/decide.tar --fail --github-actions --no-update --debug --only-verified

Secret scanning Next.js

Next.js takes steps to make it difficult to expose environment variables in your build - you have to specifically prefix them with NEXT_PUBLIC - but there is still room for error. You might mistakenly expose sensitive credentials or something could leak from the build environment.

If you’re self-hosting Next.js in a container then you can follow the same steps as above: build the container, export it to disk, and then run Trufflehog against the artifact.

If you’re deploying to Vercel then this is more difficult because you can’t run custom steps. You could set a custom build command, add additional packages to the build image, and write a script to run the scanner, but it’s a bit of a hack.

Instead you’ll need to run your own CI to build & deploy to Vercel. They have instructions for doing this with both GitHub Actions and Bitbucket Pipelines where you can then run Trufflehog in filesystem mode. The .vercel/output directory gets uploaded as part of the build (see the Build Output API), so that’s what you want to scan:

trufflehog filesystem .vercel/output --fail --github-actions --no-update --debug --only-verified

If any secrets are detected then the build will fail. The --only-verified flag performs additional validation to check whether the secrets are real, otherwise you may have a lot of false positives.

Other scanners

There is no one true tool or scanner that can protect you from everything. Security is all about layers of protection. I also recommend running Semgrep in your code editor and in CI because it can detect unsafe patterns in your code as you write. You can also block the build on known vulnerabilities in dependencies with osv-scanner.

CI is not just for tests - it’s an important part of the many security layers you should wrap around your application.

Related articles

Security advice for self-hosting Next.js in Docker
8 min read

Security advice for self-hosting Next.js in Docker

Learn how to improve the security of your self-hosted Next.js applications. This guide covers Docker container image best practices, secret management, vulnerability mitigation, and more, so your Next.js projects are better protected from threats.

Subscribe by email

Get the full posts by email every week.