In our previous lessons, we used tools like Semgrep and ESLint to find logic errors in our code, such as unsafe database queries or missing authentication checks. These tools analyze the structure of the application. However, even perfectly written logic can be compromised if the "keys to the castle" are left under the doormat. In software development, these keys are secrets — API keys, database passwords, and encryption tokens.
Leaving secrets inside your source code is one of the most common and critical security mistakes. If a repository is public, or if an attacker gains access to your private source code, hardcoded credentials allow them to immediately impersonate your application, steal data, or incur massive cloud computing costs. In this lesson, we will use Gitleaks, a specialized SAST tool designed to scan git repositories for these accidentally committed secrets, and learn how to remove them safely.
Gitleaks is a tool that scans your codebase for sensitive information that should never be committed to version control. While standard linters look for syntax errors, Gitleaks looks for data patterns that resemble credentials. It supports detecting specific formats (like Amazon Web Services keys or Facebook tokens) and high-entropy strings, which are strings of characters that look suspiciously random (like a8f391...), often indicating a password or key.
To understand what we are looking for, let's examine a typical configuration file that a developer might create while rushing to get a feature working. This file contains several hardcoded values that pose a security risk.
In the snippet above, the password field is explicitly visible. Anyone with read access to this file can access the pastebin_db.
Here is another section of that same file showing cloud credentials:
These patterns are exactly what Gitleaks is trained to spot. The AWS_CONFIG block contains keys that follow a very specific format defined by Amazon. Gitleaks uses Regular Expressions to identify the AKIA... prefix of the access key, instantly flagging this as a high-severity finding.
Now that we know what to look for, we need to run the tool. Gitleaks is a binary file that you can install on your local machine using package managers like Homebrew (on macOS) or Chocolatey (on Windows), or by downloading it directly from the releases page. On the CodeSignal platform, Gitleaks is already pre-installed and ready for you to use in the terminal.
To perform a basic scan of your current project directory, we use the detect command. We also add the --source . flag to tell it to look in the current folder, and the -v (verbose) flag so we can see the details of what it finds immediately in the console.
Here is the command you will use to scan the repository:
When you run this command, Gitleaks will analyze the files and print a report to your terminal. Here is an example of what the output looks like:
This output gives you the exact location of the issue. It tells you the File (src/config.ts), the Line number (25), and the specific RuleID that was triggered (aws-secret-access-key). The Secret field shows the actual sensitive string that was found. This immediate feedback allows you to quickly locate and remove the offending code.
One of the most dangerous misconceptions in version control is thinking that deleting a file removes it from the project. Git is designed to remember everything. If you commit a file containing a password, and then in the next commit you delete that file, the password still exists in the Git History. An attacker can simply clone your repository and browse through old commits to find the credentials you thought you deleted.
Gitleaks specializes in digging through this history. By default, the detect command focuses on your current files, but we can instruct it to scan every commit ever made in the project. This is crucial for auditing legacy codebases or verifying that a repository is truly clean before making it public.
To scan the entire history, we pass the --log-opts flag. This flag accepts standard git log arguments. By passing --all, we tell Gitleaks to look at every commit on every branch.
When you run this, you might find secrets in files that no longer exist in your project folder. If Gitleaks finds a secret in the history, simply deleting the file is not enough — you must consider that secret to be compromised. The correct response is to rotate (change) the credential immediately, rendering the leaked key useless.
While reading output in the terminal is great for a quick check, in a professional environment, you often want to automate this process. Security tools are frequently integrated into CI/CD (Continuous Integration/Continuous Deployment) pipelines. In these scenarios, you need the tool to generate a machine-readable report that can be parsed by other systems or saved for compliance audits.
Gitleaks allows you to format the output as JSON, CSV, or SARIF. JSON is a standard format that is easy to process. We can use the -f (format) flag to specify json and the -r (report) flag to specify the filename where we want to save the results.
Here is the command to generate a JSON report:
After running this, you won't see the colorful output in the terminal. Instead, a file named gitleaks-report.json is created. This file contains a structured list of all findings.
This report includes extra details like the Author who committed the secret and the commit Message. This is incredibly useful for security teams to track down who introduced a vulnerability and when it happened.
Finding secrets is only half the battle; we also need to fix them. The standard industry practice for handling secrets is to use Environment Variables. Instead of hardcoding values in your TypeScript or JavaScript files, you reference system variables. This keeps the sensitive data outside of your code logic.
To implement this, we first modify our code to read from process.env. We also use a library like dotenv to load these variables from a local file during development.
Here is how we rewrite the vulnerable config.ts file from earlier:
Notice that the actual password SuperSecret123! is gone. It has been replaced by process.env.DB_PASSWORD.
Next, we create a file named .env to store the real values. This file is local only.
Crucially, we must ensure this .env file is never committed to Git. We do this by adding it to the .gitignore file.
Finally, to help other developers know which variables they need to set, we create a .env.example file. This file contains the variable names but none of the real secrets. It is safe to commit this file.
By following this pattern, your code functionality remains the same, but your secrets are safely stored on the server or your local machine, completely invisible to Gitleaks and potential attackers.
In this lesson, we tackled the critical security risk of hardcoded secrets. We learned how to use Gitleaks to detect sensitive data like API keys and passwords in our source code. We explored how to scan our current working directory and how to dig deep into Git history to find deleted but compromised credentials. We also covered generating JSON reports for automation and the proper remediation workflow: moving secrets into .env files and updating .gitignore.
Now, it is time to secure the application yourself. In the following exercises, you will scan the provided Pastebin application, uncover hidden credentials in both the files and the git logs, and refactor the code to use safe environment variables.
