Advanced SAST C/C++ configuration

  • Tier: Ultimate
  • Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated

Turn on GitLab Advanced SAST C/C++ analysis

Prerequisites:

To turn on GitLab Advanced SAST C/C++ analysis in your project:

  1. In the top bar, select Search or go to and find your project.

  2. Go to Build > Pipeline editor.

  3. Add C/C++ analysis to your SAST job’s configuration. The instructions here assume the compilation database is named compile_commands.json.

    • If you’re using the CI/CD template, add to your configuration the following CI/CD variables after the include: statement:

      include:
        - template: Jobs/SAST.gitlab-ci.yml
      
      variables:
        GITLAB_ADVANCED_SAST_CPP_ENABLED: "true"
        SAST_COMPILATION_DATABASE: "compile_commands.json"
    • If you’re using the CI/CD component, add to your configuration the following input parameter and CI/CD variable:

      include:
      - component: gitlab.com/components/sast/sast@main
          inputs:
            run_advanced_sast_cpp: "true"
      
      variables:
        SAST_COMPILATION_DATABASE: "compile_commands.json"
  4. Select the Validate tab, then select Validate pipeline.

    The message Simulation completed successfully confirms the file is valid.

  5. Select the Edit tab.

  6. Complete the fields:

    • Commit message.
    • Branch. For example, add-sast.
  7. Select the Start a new merge request with these changes checkbox, then select Commit changes.

    The merge request page opens.

  8. Complete the fields according to your standard workflow, then select Create merge request.

  9. Review and edit the merge request according to your standard workflow, then select Merge.

Compilation database

The GitLab Advanced SAST CPP analyzer requires a compilation database (CDB) to correctly parse and analyze source files.

A CDB is a JSON file (compile_commands.json) that contains one entry for each translation unit. Each entry typically specifies the following:

  • The compiler command used to build the file
  • The compiler flags and include paths
  • The working directory where compilation is performed

The CDB allows the analyzer to reproduce the exact build environment, ensuring accurate parsing and semantic analysis.

To use GitLab Advanced SAST CPP, you must create a CDB and provide it to the analyzer.

Create a CDB

The way you generate a CDB depends on your build system. Below are common examples.

Example: CMake

CMake can generate a CDB directly with the -DCMAKE_EXPORT_COMPILE_COMMANDS=ON parameter added to a cmake call:

cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

This option configures the project and produces a compile_commands.json file in the build folder, which records the compiler commands for each source file.

The GitLab Advanced SAST CPP analyzer relies on this file to reproduce the build environment accurately.

Export the generated compile_commands.json file as a job artifact in the following build job example:

<YOUR-BUILD-JOB-NAME>:
  image: ubuntu:24.04
  before_script:
    - apt update -qq && apt install -y -qq cmake build-essential
  script:
    - mkdir -p build
    - cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
    - make -j$(nproc)
  artifacts:
    paths:
      - build/compile_commands.json # Pass the CDB file to the gitlab-advanced-sast-cpp job

Examples for various build systems

You can also find complete examples of creating a CDB and running the GitLab Advanced SAST CPP with different build systems:

Provide the CDB to the analyzer

Tell the GitLab Advanced SAST CPP analyzer where to find the CDB with the SAST_COMPILATION_DATABASE variable:

variables:
  SAST_COMPILATION_DATABASE: YOUR_COMPILATION_DATABASE.json

If SAST_COMPILATION_DATABASE is not specified, the GitLab Advanced SAST CPP analyzer defaults to using a file named compile_commands.json located in the project root directory.

The build job which generates the compile_commands.json file must export it as job artifact for the gitlab-advanced-sast-cpp job to consume:

variables:
  SAST_COMPILATION_DATABASE: build/compile_commands.json

<YOUR-BUILD-JOB-NAME>:
  image: ubuntu:24.04
  before_script:
    - apt update -qq && apt install -y -qq cmake build-essential
  script:
    - mkdir -p build
    - cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
    - make -j$(nproc)
  artifacts:
    paths:
      - build/compile_commands.json # Pass the CDB file to the gitlab-advanced-sast-cpp job

Alternatively, review caching the CDB.

Optimize analysis runtime

To optimize analysis runtime of C/C++ code, you can run the analysis in parallel by splitting the CDB into multiple fragments.

The GitLab Advanced SAST CPP repository provides helper scripts to split the CDB and run parallel analysis.

To run C/C++ code analysis in parallel:

  1. Include the scripts:

    include:
      - project: "gitlab-org/security-products/demos/sast/gitlab-advanced-sast-cpp-templates"
        file: "templates/scripts.yml"
  2. Reference the helper scripts and split the CDB in your build job:

    <YOUR-BUILD-JOB-NAME>:
      script:
        - <your-script to generate the CDB>
        - !reference [.gitlab-advanced-sast-cpp-scripts]
        - split_cdb "${BUILD_DIR}" 1 4 # Split into 4 fragments
      artifacts:
        paths:
          - ${BUILD_DIR} # Pass the split CDB files to the parallelized gitlab-advanced-sast-cpp jobs

    split_cdb is hardcoded to read ${BUILD_DIR}/compile_commands.json. Your build must generate the CDB at this exact location before calling split_cdb.

  3. Run parallel analyzer jobs:

    gitlab-advanced-sast-cpp:
      parallel: 4
      variables:
        SAST_COMPILATION_DATABASE: "${BUILD_DIR}/compile_commands${CI_NODE_INDEX}.json"
      needs:
        - job: <YOUR-BUILD-JOB-NAME>
          artifacts: true
    • parallel: 4 shards execution across 4 jobs.
    • ${CI_NODE_INDEX} (1, 2, 3, 4) selects the correct CDB fragment.
    • needs ensures the analyzer jobs receive the artifacts produced by your build job.

With this setup, your build job produces a single compile_commands.json. The split_cdb script creates multiple partitions, and the analyzer jobs run in parallel, with each job processing one partition.

Ruleset configuration

GitLab Advanced SAST CPP supports custom rulesets where a “rule” is a GitLab Advanced SAST CPP checker.

Custom rulesets can be created with passthroughs composed of CodeChecker configuration files.

Passthrough configuration is handled as follows:

  • targetDir and target are ignored. After processing passthroughs, any resulting flags are passed directly to CodeChecker.
  • overwrite mode replaces the entire configuration and append mode appends flags.
  • Specific CodeChecker flags cannot be customized, including the analyzer flags -o, --output and the parse flags -o, --output, -e, --export.
  • server and store configuration items are ignored.

The following example shows how passthroughs from different sources combine:

File .gitlab/sastconfig.toml:

[gitlab-advanced-sast-cpp]
    description = "My ruleset"

    [[gitlab-advanced-sast-cpp.passthrough]]
        # replace the GitLab default configuration with my own
        mode  = "overwrite"
        type  = "url"
        value = "https://example.com/gitlab-advanced-sast-cpp.yaml"

    [[gitlab-advanced-sast-cpp.passthrough]]
        # append flags from a file in the current repository
        mode  = "append"
        type  = "file"
        value = "gitlab-advanced-sast-cpp.yml"

Remote configuration at https://example.com/gitlab-advanced-sast-cpp.yaml:

analyzer:
  - --disable-all
  - --enable=core.DivideZero

Local configuration at gitlab-advanced-sast-cpp.yml:

analyzer:
  - --enable=core.CallAndMessage

The resulting combined configuration is as follows:

analyzer:
  - --disable-all
  - --enable=core.DivideZero
  - --enable=core.CallAndMessage

Troubleshooting

Rebasing paths with cdb-rebase

If the paths inside the CDB do not match the container paths in your CI/CD job, adjust them with cdb-rebase.

Install:

go install gitlab.com/gitlab-org/secure/tools/cdb-rebase@latest

The binary is installed in $GOPATH/bin or $HOME/go/bin. Ensure this directory is in your PATH.

Example usage:

cdb-rebase compile_commands.json /host/path /container/path > rebased_compile_commands.json

Fixing the CDB

If the build environment differs from the scan environment, the generated CDB might require adjustments. You can modify it with jq, or use cdb_append, a shell function from the predefined helper script.

cdb_append appends compiler options to an existing CDB. It accepts:

  • First argument: the folder containing compile_commands.json
  • Subsequent arguments: additional compiler options to append

Example in CI/CD:

include:
  - project: "gitlab-org/security-products/demos/sast/gitlab-advanced-sast-cpp-templates"
    file: "templates/scripts.yml"

<YOUR-BUILD-JOB-NAME>:
  script:
    - !reference [.gitlab-advanced-sast-cpp-scripts]
    - <your-script to generate the CDB>
    - cdb_append "${BUILD_DIR}" "-I'$PWD/include-cache'" "-Wno-error=register"

Caching a CDB

To accelerate the compilation and analysis process, the CDB can be cached.

.cdb_cache:
  cache: &cdb_cache
    key:
      files:
        - Makefile
        - src/
    paths:
      - compile_commands.json

<YOUR-BUILD-JOB-NAME>:
  script:
    - <your-script to generate the CDB>
  cache:
    <<: *cdb_cache
    policy: pull-push

gitlab-advanced-sast-cpp:
  cache:
    <<: *cdb_cache
    policy: pull

For a complete example see the demo project cached-cdb.

Handling absolute paths in a CDB

In the demo project, bear is run from the Builds directory of a Docker job. The CDB paths are absolute, based at /builds/$CI_PROJECT_PATH. The analyzer job gitlab-advanced-sast-cpp runs in the same location, so paths are correct.

If the CDB was generated at a path not available during analysis, it must be rebased. The cdb-rebase tool, included in the analyzer image, rewrites directory, file, and output paths.

Example:

gitlab-advanced-sast-cpp:
  before-script:
    # Rebase the original CDB to be relative to the current directory.
    #
    # ORIGINAL_CDB_PATH     - Path to the CDB artifact from a previous job (e.g., artifacts/compile_commands.json)
    # ORIGINAL_CDB_BASEPATH - The absolute path to the project root when the ORIGINAL_CDB_PATH was generated.
    #                         (e.g., /mnt/custom_build_area/my-project or /home/user/my-project)
    - /cdb-rebase   --input "$ORIGINAL_CDB_PATH" \
                    --output compile_commands.json \
                    --src "$ORIGINAL_CDB_BASEPATH" \
                    --dst .

For a full demonstration, see cdb-rebase-demo

Beyond simple path rebasing, cdb-rebase can also manage include files between the build and scan environments:

  • Cache external headers: with --include-cache, headers outside the source tree are copied into a portable cache.
  • Add include paths: with --include, specify extra include directories to be cached.
  • Exclude files: with --exclude, skip headers you don’t want to carry over.

Example:

/cdb-rebase --src /my-project \
            --dst /scan-env \
            --input build/compile_commands.json \
            --output rebased_cdb.json \
            --include-cache include-cache \
            --include third_party/include \
            --exclude dummy.h

The cde-rebase tool is also available to environments with Go installed, so rebasing the CDB when it is generated is possible, e.g.

go install gitlab.com/gitlab-org/security-products/analyzers/clangsa/cmd/cdb-rebase@latest
bear -o compile_commands_abs.json -- make
cdb-rebase -i compile_commands_abs.json -o compile_commands.json -s "$PWD" -d .

The go install command above installs cdb-rebase to the GOBIN path, which can be found with go env GOBIN.

Partial scan coverage due to missing header files

Partial scan coverage can occur when required system or third-party header files are not available in the scan job. Headers installed during the build job must be explicitly forwarded to the scan job and made resolvable through the include paths recorded in the compilation database.

One common approach is to cache the required headers and update the compilation database to reference them:

# Create and populate an include cache
mkdir -p include-cache
dpkg -L <build-dependency-packages> | sed -n 's:^/usr/include/::p' > headers.txt
rsync -a --files-from=headers.txt /usr/include/ include-cache/

# Add cached headers to compile flags
cdb_append . "-I'$PWD/include-cache'"

For a full demonstration, see the example.