Architecting Static Application Security Testing within GitLab CI/CD Pipelines

Static Application Security Testing (SAST) serves as a foundational pillar of the DevSecOps paradigm, enabling the discovery of vulnerabilities within source code before they are ever promoted to production environments. By integrating these scans directly into the Continuous Integration and Continuous Delivery (CI/CD) pipeline, organizations can identify security flaws during the earliest stages of development. This "shift-left" approach ensures that vulnerabilities are addressed when they are most cost-effective to remediate, thereby preventing costly delays and catastrophic security breaches that typically occur when vulnerabilities are discovered late in the software development lifecycle.

The implementation of SAST in GitLab is designed to be versatile, supporting a wide array of license tiers including Free, Premium, and Ultimate. It is available across various deployment models, including GitLab.com (SaaS), GitLab Self-Managed, and GitLab Dedicated. While the basic functionality is available to all, higher tiers such as Ultimate provide advanced capabilities like GitLab Duo, which utilizes artificial intelligence to analyze critical and high-severity SAST vulnerabilities to automatically identify and filter out false positives, reducing noise for security teams.

Core Implementation Framework and Requirements

The deployment of SAST requires specific environmental configurations to ensure the analyzers can execute correctly. The primary mechanism for triggering these scans is the .gitlab-ci.yml file located at the root of the project repository.

The technical prerequisites for a successful SAST deployment include:

  • User Permissions: The user configuring the pipeline must possess either the Maintainer or Owner role for the project to modify security settings and CI/CD configurations.
  • Runner Environment: A Linux-based GitLab Runner is mandatory. The runner must utilize either the Docker or Kubernetes executor. While hosted runners on GitLab.com enable these by default, self-hosted environments must be explicitly configured for these executors.
  • Architecture Constraints: Only AMD64 CPU architectures are supported; other architectures will result in execution failure.
  • OS Limitations: Windows-based GitLab Runners are not supported for SAST operations.
  • Pipeline Structure: The .gitlab-ci.yml file must include a test stage. If a developer redefines the stages in the configuration file, the test stage must be explicitly declared, or the SAST jobs will fail to initialize.

Configuration Methodologies

GitLab provides two primary paths for enabling SAST: the UI-driven approach and the manual YAML configuration.

The UI configuration method is accessed via the top bar by navigating to the project and selecting Secure > Security configuration. If the latest pipeline for the default branch has already produced valid SAST artifacts, the user can select Configure SAST; otherwise, they must select Enable SAST in the static application security testing row. This method is ideal for projects with minimal or no existing .gitlab-ci.yml files. However, for projects with complex, highly customized CI/CD configurations, the UI tool may fail to parse the existing YAML, necessitating a manual edit of the CI/CD file.

Manual configuration via the .gitlab-ci.yml file allows for granular control over how scanners are invoked. A basic implementation involves including the official GitLab template:

```yaml
stages:
- test

sast:
stage: test
include:
- template: Security/SAST.gitlab-ci.yml
```

Deep Dive into SAST Analyzers and Tooling

The GitLab SAST template is not a monolithic tool but rather an orchestrator that triggers different analyzers based on the programming languages detected in the repository.

Specialized Security Tooling

Different tools are employed depending on the language and the type of scan required:

  • Semgrep: A general-purpose static analysis tool. When used via the built-in GitLab template, it utilizes a GitLab-managed rule-set, which differs from the default Semgrep community rule-set.
  • Bandit: A specialized tool for Python code. It constructs an Abstract Syntax Tree (AST) from the processed files and runs specific plugins against the AST nodes to identify common security issues.
  • Terrascan: An analyzer dedicated to Infrastructure as Code (IaC). It is capable of detecting security violations within HCL (Terraform language), Dockerfiles, Kubernetes manifests, and Helm charts.
  • SpotBugs: A Java-focused analyzer that scans compiled bytecode, making it particularly effective for Groovy projects.

Custom Analyzer Configuration

Advanced users can override default analyzer behaviors or pin specific versions. For example, to use version 6 of the SpotBugs analyzer (which adds support for JDK21 while removing JDK11), the following configuration is used:

yaml spotbugs-sast: variables: SAST_ANALYZER_IMAGE_TAG: "6"

For those requiring a completely custom implementation of a tool like Semgrep or Terrascan, the following configurations illustrate the manual process:

Manual Semgrep configuration:

yaml semgrep: image: semgrep/semgrep variables: SEMGREP_GITLAB_JSON: "1" script: - semgrep scan . --config="r/all" --metrics="off" --error --gitlab-sast -o gl-sast-report.json || true artifacts: reports: sast: gl-sast-report.json

Manual Terrascan configuration for Terraform:

```yaml
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml

stages:
- test

terrascan:
stage: test
image:
name: tenable/terrascan:latest
entrypoint: ["/bin/sh", "-c"]
script:
- /go/bin/terrascan scan --iac-type terraform --iac-dir .
```

Managing Pipeline Execution and Merge Request Integration

A common challenge for developers is the "double pipeline" phenomenon, where a commit triggers both a branch pipeline and a merge request pipeline, leading to redundant SAST executions. By default, the SAST template may run in the branch pipeline, meaning security alerts do not appear directly in the Merge Request (MR) UI.

To force SAST jobs to run specifically on Merge Requests to enable security alert visibility in the MR interface, developers often attempt to use rules. However, misconfiguration can lead to parsing errors. A common incorrect attempt involves placing the include statement inside a rules block, which is syntactically invalid.

The correct approach to managing the execution of SAST involves utilizing the rules keyword at the job level rather than the global level. For instance, to prevent the job from running on the master branch but ensure it runs on merge request events, the configuration should target the specific job.

Variable Controls and Default Settings

The SAST framework relies on several critical variables to define its behavior and image sources. These are typically defined in the global variables section of the CI/CD file:

Variable Description Default Value/Context
SAST_ANALYZER_IMAGE_PREFIX The registry path for the analyzer images registry.gitlab.com/gitlab-org/security-products/analyzers
SAST_DEFAULT_ANALYZERS List of analyzers to run by default bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, tslint, secrets, sobelow, pmd-apex, kubesec
SAST_ANALYZER_IMAGE_TAG The specific version tag for the analyzer images 2
SAST_DISABLE_DIND Controls whether Docker-in-Docker is disabled false
SCAN_KUBERNETES_MANIFESTS Toggles the scanning of Kubernetes files false

Advanced Customization and Rule-set Management

For organizations that require specific security checks beyond the default GitLab provided rules, custom rule-sets can be implemented.

Semgrep Custom Rule-sets

To implement a custom rule-set for Semgrep (for example, for the Rust language), a file named sast-ruleset.toml must be created within a .gitlab/ directory at the root of the repository. An example configuration for a Rust rule-set is as follows:

```toml
[semgrep]
description = "Rust ruleset for Semgrep"
targetdir = "/sgrules"
timeout = 60

[[semgrep.passthrough]]
type = "url"
value = "https://semgrep.dev/c/p/rust"
target = "rust.yml"
```

To ensure the semgrep-sast job actually triggers for Rust files, the .gitlab-ci.yml must be overridden to include the correct file extensions:

```yaml
include:
- template: Jobs/SAST.gitlab-ci.yml

semgrep-sast:
rules:
- if: $CICOMMITBRANCH
exists:
- '*/.rs'
```

Technical Execution Flow and Artifact Handling

The SAST process follows a strict execution flow: the pipeline triggers, the appropriate analyzer image is pulled from the registry, the code is scanned, and the results are exported into a specific JSON format.

The mandatory artifact report format for GitLab SAST is gl-sast-report.json. This file is what the GitLab UI parses to display security vulnerabilities in the project's security dashboard and merge request widgets.

A standard SAST job definition includes the following critical components:

  • allow_failure: true: This ensures that the pipeline does not stop if a vulnerability is found, allowing the developer to see the report and fix the issue without blocking the entire CI process.
  • artifacts: reports: sast: gl-sast-report.json: This specifically tells GitLab to treat the output file as a SAST report.
  • image: docker:stable: Many analyzers require a Docker environment to run.
  • services: docker:stable-dind: Provides the Docker-in-Docker service required for certain analysis tasks.

Analysis of SAST Implementation Challenges

The integration of SAST is not without friction, particularly regarding the "Double Pipeline" issue and the limitations of the built-in Semgrep implementation.

The "Double Pipeline" issue occurs when a commit is pushed to a branch that has an open Merge Request. GitLab triggers a branch pipeline and a merge request pipeline simultaneously. Because the standard SAST template is often configured for branches, the security results are tied to the branch pipeline, making them invisible in the MR widget. Attempts to use AST_ENABLE_MR_PIPELINES: "true" have been reported as ineffective in some versions (e.g., v17.11.1), suggesting that the solution must reside in explicit rules configuration within the .gitlab-ci.yml.

Furthermore, there is a documented performance and quality gap between the GitLab-managed Semgrep rule-set and the standalone Semgrep tool. Using Semgrep as a standalone tool via a custom image and script allows developers to access the full community rule-set and avoid the limitations of the GitLab-managed version, though it requires manual configuration of the gl-sast-report.json artifact to maintain visibility within the GitLab UI.

Conclusion

The deployment of Static Application Security Testing within GitLab CI/CD is a sophisticated process that balances ease of use (via UI templates) with deep technical flexibility (via YAML overrides). The efficacy of a SAST implementation depends on the correct alignment of the runner environment—specifically the requirement for Linux-based AMD64 runners using Docker or Kubernetes executors—and the precise configuration of the .gitlab-ci.yml file.

The transition from basic enablement to an advanced security posture involves moving away from the "one-size-fits-all" template and toward tailored analyzer configurations. This includes pinning analyzer versions for Java/Groovy via SAST_ANALYZER_IMAGE_TAG, implementing custom .toml rule-sets for specific languages like Rust, and manually configuring the pipeline to resolve the "double pipeline" conflict to ensure that security alerts are surfaced exactly where the developer spends their time: the Merge Request. By leveraging these advanced configurations and the AI-driven noise reduction of GitLab Duo, organizations can achieve a high-signal, low-noise security pipeline that effectively eliminates vulnerabilities before they reach the production environment.

Sources

  1. GitLab Forum - Run SAST on MR Pipelines
  2. GitLab Documentation - SAST
  3. Muni Security - Guide to SAST Pipelines
  4. GitHub - Carlos Wong GitLab CE Templates

Related Posts