Static Application Security Testing (SAST) in GitLab CI isn’t just about finding vulnerabilities; it’s about making security a built-in, automated part of your development workflow.
Let’s see it in action. Imagine you have a GitLab CI/CD pipeline. We’ll add a SAST job to it.
stages:
- build
- test
- sast
- deploy
sast:
stage: sast
image: registry.gitlab.com/security-products/saast/saast-cli:latest
script:
- saast-cli scan --project-dir . --output ./gl-sast-report.json
artifacts:
reports:
sast: gl-sast-report.json
When this pipeline runs, the sast job will execute. It pulls a dedicated SAST image, runs a scan against your project’s source code, and crucially, outputs a gl-sast-report.json file. GitLab then automatically picks up this file, parses it, and displays the found vulnerabilities directly in your merge requests and pipeline views. No manual reporting, no separate tools to check – it’s all integrated.
The core problem SAST in CI solves is the "shift-left" movement in security. Traditionally, security testing happened late in the development lifecycle, making fixes expensive and time-consuming. By integrating SAST into CI, you catch vulnerabilities as soon as the code is committed, when they are cheapest and easiest to fix.
Internally, the SAST tool (in this example, saast-cli, which is a generic placeholder for various SAST engines GitLab supports) analyzes your codebase without executing it. It looks for patterns that are known to be insecure. This can include things like:
- Insecure direct dereferences: Using pointers without checking if they are null.
- SQL injection flaws: Allowing untrusted input to be interpreted as SQL commands.
- Cross-Site Scripting (XSS) vulnerabilities: Injecting malicious scripts into web pages viewed by other users.
- Use of deprecated or insecure functions: Employing functions known to have security weaknesses.
- Hardcoded secrets: Passwords, API keys, or other sensitive information embedded directly in the code.
The image in the .gitlab-ci.yml file specifies the container image that contains the SAST scanner. GitLab provides curated images for various languages and frameworks. The script section contains the actual commands to run the scanner. The --project-dir . tells the scanner to analyze the current directory (your project’s root), and --output ./gl-sast-report.json directs the findings to the standardized JSON report format that GitLab understands. The artifacts:reports:sast section is key; it tells GitLab to treat the specified file as a SAST report, enabling its integration into the UI.
You control the SAST process through configuration within your .gitlab-ci.yml and potentially through configuration files specific to the chosen SAST tool (e.g., a .semgrepignore or rules.yml for Semgrep, which is a popular engine integrated with GitLab). You can specify which files to include or exclude from the scan, tune the sensitivity of the scanner, and even define custom rules. For instance, to exclude a specific directory from a scan using a common SAST tool like Semgrep, you might add a .semgrepignore file in your project root:
# .semgrepignore
tests/fixtures/
This tells Semgrep to skip scanning anything within the tests/fixtures/ directory, which might contain example code that isn’t part of the production application but could trigger false positives.
The most surprising part for many is how SAST tools can detect vulnerabilities that aren’t immediately obvious from a code review alone. They use abstract syntax trees (ASTs) and data flow analysis to understand the structure and potential execution paths of your code. This allows them to trace how user input might propagate through your application and reach sensitive sinks (like database queries or HTML output) without proper sanitization, even across multiple function calls. It’s not just pattern matching; it’s a deeper, structural analysis of your code’s logic.
The next step after integrating SAST is to explore Dynamic Application Security Testing (DAST) in your CI/CD pipeline.