Introduction: From Generic to Application-Specific Detection

In our previous lesson, we learned how to use ESLint with security plugins to catch common vulnerabilities like Object Injection. These tools are fantastic for finding generic problems that apply to almost any JavaScript project. However, security issues are often specific to how your particular application is built. For example, a generic scanner might not know that your team decided every database query must use a specific wrapper function or that every API route requires a specific piece of middleware to be secure.

This is where Semgrep (Semantic Grep) shines. Unlike standard grep, which searches for simple strings of text, Semgrep understands the structure of your code. It allows you to write custom rules that act like unit tests for your security policies. In this lesson, we will move beyond enabling pre-made rules and start writing our own. We will build a custom ruleset to detect two specific issues in our Pastebin application: usage of unsafe Raw SQL queries and API routes that are missing Authentication Middleware.

Understanding Semgrep Rule Structure

Before we can catch bugs, we need to understand how to tell Semgrep what to look for. Semgrep rules are written in YAML files. This format is human-readable and uses indentation to organize information. A rule file isn't just a list of bad patterns; it's a structured document that defines the problem, the solution, and the severity.

Every Semgrep rule requires a few key fields to function correctly. The id is a unique name for your rule, which helps you track it later. The message is the text that will appear in the report when a vulnerability is found; this should explain what went wrong to the developer. The severity tells the system how serious the finding is. Semgrep supports two sets of severity levels. The newer, preferred levels are HIGH, MEDIUM, LOW, and INFO, where HIGH represents a critical issue, MEDIUM is a significant concern, LOW is a minor issue, and INFO is purely informational. The older levels ERROR, WARNING, and INFO are still accepted and map roughly to HIGH, MEDIUM, and LOW respectively. Finally, the languages field specifies which code files to scan, such as javascript or typescript.

Here is the skeleton of the rule file we will create, located at rules/raw-sql.yaml:

rules:
  - id: sequelize-raw-query
    message: "Raw SQL query detected - use parameterized queries"
    severity: ERROR
    languages: [typescript, javascript]
    # Patterns will go here next

In the structure above, we are preparing to detect raw queries in a Sequelize application. We chose ERROR as the severity because raw SQL queries are a high-risk vector for SQL Injection attacks. In the CodeSignal environment, Semgrep is pre-installed, but typically you would save this file in a .semgrep folder or a dedicated rules directory in your project root.

Pattern Matching Fundamentals
Building a Raw SQL Detection Rule
Using Negative Patterns for Exceptions
Detecting Missing Authentication Middleware
Adding Metadata for Context

When a developer sees a security warning, their first question is often "So what?" or "Is this actually a bug?". To help them answer this, Semgrep allows you to attach a metadata block to your rules. This field doesn't change how the scan works, but it provides crucial context to the human reading the report.

In the metadata section, you can include links to documentation, references to OWASP categories (like "OWASP A01: Broken Access Control"), or simple notes explaining the business logic behind the rule. For our authentication rule, we want to acknowledge that not every finding is a bug — some routes should be public (like a login page).

Here is how we add context to our auth rule:

    metadata:
      note: "May require manual review - some routes are intentionally public"
      owasp: "A01:2021-Broken Access Control"
      category: "security"

By adding the note "May require manual review," we set the right expectation. We aren't telling the developer they definitively broke the code; we are flagging an anomaly that requires human judgment. This builds trust between the security team and the development team, as it shows the tool is being "smart" about its limitations.

Running the Scans

With the rules written, the final step is actually executing the scanner. Semgrep is run from the command line and accepts a --config flag pointing to your rules file or directory, followed by the path to the code you want to scan.

To scan your application using a single rule file, run:

semgrep --config rules/raw-sql.yaml src/

To run all rules in a directory at once, point --config at the folder:

semgrep --config rules/ src/

Semgrep will print each match directly to the terminal, showing the file path, line number, rule ID, and the message you wrote. If you want a machine-readable output — for example, to pipe results into another tool or a CI pipeline — you can request JSON format:

semgrep --config rules/ src/ --json > results.json

When all rules pass without findings, Semgrep exits with code 0. When findings are present, it exits with a non-zero code, which causes most CI systems to fail the build automatically. This makes it straightforward to enforce your rules as a quality gate on every pull request.

Summary and Practice Preparation

In this lesson, we transitioned from using generic security scanners to writing custom logic with Semgrep. We learned how to use YAML to define rules and explored the power of Metavariables ($VAR) and the Ellipsis (...) operator to match complex code structures. We built a rule to detect unsafe raw SQL queries using pattern-either and a more advanced rule to detect missing authentication middleware using pattern-not to filter out secure routes.

We also discussed the importance of metadata to communicate with developers, ensuring they understand why a rule flagged their code. Finally, we covered how to run Semgrep from the command line against a target directory and how to export results for use in a CI pipeline.

Now, it is time to put this into practice. In the upcoming exercises, you will be given a vulnerable Pastebin application. You will write these rules from scratch, run the Semgrep scanner against the codebase, and verify that it correctly identifies the insecure routes while ignoring the safe ones. This hands-on experience allows you to start thinking like a security engineer, automating the detection of logic flaws before they ever reach production.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal