Customize rulesets

Version history
  • Introduced in GitLab 13.5.
  • Added support for passthrough chains. Expanded to include additional passthrough types of file, git, and url in GitLab 14.6.
  • Added support for overriding rules in GitLab 14.8.
  • Added support for specifying ambiguous passthrough refs in GitLab 16.2.

You can customize the behavior of our SAST analyzers by defining a ruleset configuration file in the repository being scanned. There are two kinds of customization:

Disable predefined rules

You can disable predefined rules for any SAST analyzer.

When you disable a rule:

The Semgrep-based analyzer handles disabled rules differently:

  • To improve performance, the Semgrep-based analyzer doesn’t scan for disabled rules at all.
  • If you disable a rule in the Semgrep-based analyzer, existing vulnerability findings for that rule are automatically resolved after you merge the sast-ruleset.toml file to the default branch.

See the Schema and Examples sections for information on how to configure this behavior.

Override predefined rules

Certain attributes of predefined rules can be overridden for any SAST analyzer. This can be useful when adapting SAST to your existing workflow or tools. For example, you might want to override the severity of a vulnerability based on organizational policy, or choose a different message to display in the Vulnerability Report.

See the Schema and Examples sections for information on how to configure this behavior.

Synthesize a custom configuration

You can completely replace the predefined rules of some SAST analyzers:

You provide your customizations via passthroughs, which are composed into a passthrough chain at runtime and evaluated to produce a complete configuration. The underlying scanner is then executed against this new configuration.

There are multiple passthrough types that let you provide configuration in different ways, such as using a file committed to your repository or inline in the ruleset configuration file. You can also choose how subsequent passthroughs in the chain are handled; they can overwrite or append to previous configuration.

See the Schema and Examples sections for information on how to configure this behavior.

Create the configuration file

To create the ruleset configuration file:

  1. Create a .gitlab directory at the root of your project, if one doesn’t already exist.
  2. Create a file named sast-ruleset.toml in the .gitlab directory.

Specify a remote configuration file

Introduced in 16.1.

You can set a CI/CD variable to use a ruleset configuration file that’s stored outside of the current repository. This can help you apply the same rules across multiple projects.

The SAST_RULESET_GIT_REFERENCE variable uses a format similar to Git URLs for specifying a project URI, optional authentication, and optional Git SHA. The variable uses the following format:

[<AUTH_USER>[:<AUTH_PASSWORD>]@]<PROJECT_PATH>[@<GIT_SHA>]
note
If a project has a .gitlab/sast-ruleset.toml file committed, that local configuration takes precedence and the file from SAST_RULESET_GIT_REFERENCE isn’t used.

The following example enables SAST and uses a shared ruleset customization file. In this example, the file is committed on the default branch of example-ruleset-project at the path .gitlab/sast-ruleset.toml.

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

variables:
  SAST_RULESET_GIT_REFERENCE: "gitlab.com/example-group/example-ruleset-project"

See specify a private remote configuration example for advanced usage.

Schema

The top-level section

The top-level section contains one or more configuration sections, defined as TOML tables.

SettingDescription
[$analyzer]Declares a configuration section for an analyzer. The name follows the snake-case names defined in the list of SAST analyzers.

Configuration example:

[semgrep]
...

Avoid creating configuration sections that modify existing rules and synthesize a custom ruleset, as the latter replaces predefined rules completely.

The [$analyzer] configuration section

The [$analyzer] section lets you customize the behavior of an analyzer. Valid properties differ based on the kind of configuration you’re making.

SettingApplies toDescription
[[$analyzer.ruleset]]Predefined rulesDefines modifications to an existing rule.
interpolateAllIf set to true, you can use $VAR in the configuration to evaluate environment variables. Use this feature with caution, so you don’t leak secrets or tokens. (Default: false)
descriptionPassthroughsDescription of the custom ruleset.
targetdirPassthroughsThe directory where the final configuration should be persisted. If empty, a directory with a random name is created. The directory can contain up to 100 MB of files.
validatePassthroughsIf set to true, the content of each passthrough is validated. The validation works for yaml, xml, json and toml content. The proper validator is identified based on the extension used in the target parameter of the [[$analyzer.passthrough]] section. (Default: false)
timeoutPassthroughsThe maximum time to spend to evaluate the passthrough chain, before timing out. The timeout cannot exceed 300 seconds. (Default: 60)

interpolate

caution
To reduce the risk of leaking secrets, use this feature with caution.

The example below shows a configuration that uses the $GITURL environment variable to access a private repository. The variable contains a username and token (for example https://user:token@url), so they’re not explicitly stored in the configuration file.

[semgrep]
  description = "My private Semgrep ruleset"
  interpolate = true

  [[semgrep.passthrough]]
    type  = "git"
    value = "$GITURL"
    ref = "main"

The [[$analyzer.ruleset]] section

The [[$analyzer.ruleset]] section targets and modifies a single predefined rule. You can define one to many of these sections per analyzer.

SettingDescription
disableWhether the rule should be disabled. (Default: false)
[$analyzer.ruleset.identifier]Selects the predefined rule to be modified.
[$analyzer.ruleset.override]Defines the overrides for the rule.

Configuration example:

[semgrep]
  [[semgrep.ruleset]]
    disable = true
    ...

The [$analyzer.ruleset.identifier] section

The [$analyzer.ruleset.identifier] section defines the identifiers of the predefined rule that you wish to modify.

SettingDescription
typeThe type of identifier used by the predefined rule.
valueThe value of the identifier used by the predefined rule.

You can look up the correct values for type and value by viewing the gl-sast-report.json produced by the analyzer. You can download this file as a job artifact from the analyzer’s CI job.

For example, the snippet below shows a finding from a semgrep rule with three identifiers. The type and value keys in the JSON object correspond to the values you should provide in this section.

...
  "vulnerabilities": [
    {
      "id": "7331a4b7093875f6eb9f6eb1755b30cc792e9fb3a08c9ce673fb0d2207d7c9c9",
      "category": "sast",
      "message": "Key Exchange without Entity Authentication",
      "description": "Audit the use of ssh.InsecureIgnoreHostKey\n",
      ...
      "identifiers": [
        {
          "type": "semgrep_id",
          "name": "gosec.G106-1",
          "value": "gosec.G106-1"
        },
        {
          "type": "cwe",
          "name": "CWE-322",
          "value": "322",
          "url": "https://cwe.mitre.org/data/definitions/322.html"
        },
        {
          "type": "gosec_rule_id",
          "name": "Gosec Rule ID G106",
          "value": "G106"
        }
      ]
    }
    ...
  ]
...

Configuration example:

[semgrep]
  [[semgrep.ruleset]]
    [semgrep.ruleset.identifier]
      type = "semgrep_id"
      value = "gosec.G106-1
    ...

The [$analyzer.ruleset.override] section

The [$analyzer.ruleset.override] section allows you to override attributes of a predefined rule.

SettingDescription
descriptionA detailed description of the issue.
message(Deprecated) A description of the issue.
nameThe name of the rule.
severityThe severity of the rule. Valid options are: Critical, High, Medium, Low, Unknown, Info)
note
While message is populated by the analyzers, it has been deprecated in favor of name and description.

Configuration example:

[semgrep]
  [[semgrep.ruleset]]
    [semgrep.ruleset.override]
      severity = "Critical"
      name = "Command injection"
    ...

The [[$analyzer.passthrough]] section

note
This is currently supported by the nodejs-scan and semgrep analyzers only.

The [[$analyzer.passthrough]] section allows you to synthesize a custom configuration for an analyzer. You can define up to 20 of these sections per analyzer. Passthroughs are composed into a passthrough chain that evaluates into a complete configuration that replaces the predefined rules of the analyzer.

Passthroughs are evaluated in order. Passthroughs listed later in the chain have a higher precedence and can overwrite or append to data yielded by previous passthroughs (depending on the mode). This is useful for cases where you need to use or modify an existing configuration.

The amount of data generated by a single passthrough is limited to 1 MB.

SettingApplies toDescription
typeAllOne of file, raw, git or url.
targetAllThe target file to contain the data written by the passthrough evaluation. If empty, a random filename is used.
modeAllIf overwrite, the target file is overwritten. If append, new content is appended to the target file. The git type only supports overwrite. (Default: overwrite)
reftype = "git"Contains the name of the branch, tag, or the SHA to pull
subdirtype = "git"Used to select a subdirectory of the Git repository as the configuration source.
valueAllFor the file, url, and git types, defines the location of the file or Git repository. For the raw type, contains the inline configuration.
validatorAllUsed to explicitly invoke validators (xml, yaml, json, toml) on the target file after the evaluation of a passthrough.

Passthrough types

TypeDescription
fileUse a file that is present in the Git repository.
rawProvide the configuration inline.
gitPull the configuration from a remote Git repository.
urlFetch the configuration using HTTP.
caution
When using the raw passthrough with a YAML snippet, it’s recommended to format all indentation in the sast-ruleset.toml file as spaces. The YAML specification mandates spaces over tabs, and the analyzer fails to parse your custom ruleset unless the indentation is represented accordingly.

Examples

Disable predefined rules of SAST analyzers

With the following custom ruleset configuration, the following rules are omitted from the report:

  • semgrep rules with a semgrep_id of gosec.G106-1 or a cwe of 322.
  • sobelow rules with a sobelow_rule_id of sql_injection.
  • flawfinder rules with a flawfinder_func_name of memcpy.
[semgrep]
  [[semgrep.ruleset]]
    disable = true
    [semgrep.ruleset.identifier]
      type = "semgrep_id"
      value = "gosec.G106-1"

  [[semgrep.ruleset]]
    disable = true
    [semgrep.ruleset.identifier]
      type = "cwe"
      value = "322"

[sobelow]
  [[sobelow.ruleset]]
    disable = true
    [sobelow.ruleset.identifier]
      type = "sobelow_rule_id"
      value = "sql_injection"

[flawfinder]
  [[flawfinder.ruleset]]
    disable = true
    [flawfinder.ruleset.identifier]
      type = "flawfinder_func_name"
      value = "memcpy"

Override predefined rules of SAST analyzers

With the following custom ruleset configuration, vulnerabilities found with semgrep with a type CWE and a value 322 have their severity overridden to Critical.

[semgrep]
  [[semgrep.ruleset]]
    [semgrep.ruleset.identifier]
      type = "cwe"
      value = "322"
    [semgrep.ruleset.override]
      severity = "Critical"

Synthesize a custom configuration using a raw passthrough for nodejs-scan

With the following custom ruleset configuration, the predefined behavior of the nodejs-scan analyzer is replaced with a custom configuration.

The syntax used for the value follows the njsscan config format.

[nodejs-scan]
  description = "My custom ruleset for nodejs-scan"

  [[nodejs-scan.passthrough]]
    type  = "raw"
    value = '''
---
- nodejs-extensions:
  - .js

  template-extensions:
  - .new
  - .hbs
  - ''

  ignore-filenames:
  - skip.js

  ignore-paths:
  - __MACOSX
  - skip_dir
  - node_modules

  ignore-extensions:
  - .hbs

  ignore-rules:
  - regex_injection_dos
  - pug_jade_template
  - express_xss
'''

Synthesize a custom configuration using a file passthrough for semgrep

With the following custom ruleset configuration, the predefined ruleset of the semgrep analyzer is replaced with a custom ruleset contained in a file called my-semgrep-rules.yaml in the repository being scanned.

# my-semgrep-rules.yml
---
rules:
- id: my-custom-rule
  pattern: print("Hello World")
  message: |
    Unauthorized use of Hello World.
  severity: ERROR
  languages:
  - python
[semgrep]
  description = "My custom ruleset for Semgrep"

  [[semgrep.passthrough]]
    type  = "file"
    value = "my-semgrep-rules.yml"

Synthesize a custom configuration using a passthrough chain for semgrep

With the following custom ruleset configuration, the predefined ruleset of the semgrep analyzer is replaced with a custom ruleset produced by evaluating a chain of four passthroughs. Each passthrough produces a file that’s written to the /sgrules directory within the container. A timeout of 60 seconds is set in case any Git remotes are unresponsive.

Different passthrough types are demonstrated in this example:

  • Two git passthroughs, the first pulling develop branch from the myrules Git repository, and the second pulling revision 97f7686 from the sast-rules repository, and considering only files in the go subdirectory.
    • The sast-rules entry has a higher precedence because it appears later in the configuration.
    • If there’s a filename collision between the two checkouts, files from the sast-rules repository overwrite files from the myrules repository.
  • A raw passthrough, which writes its value to /sgrules/insecure.yml.
  • A url passthrough, which fetches a configuration hosted at a URL and writes it to /sgrules/gosec.yml.

Afterwards, Semgrep is invoked with the final configuration located under /sgrules.

[semgrep]
  description = "My custom ruleset for Semgrep"
  targetdir = "/sgrules"
  timeout = 60

  [[semgrep.passthrough]]
    type  = "git"
    value = "https://gitlab.com/user/myrules.git"
    ref = "develop"

  [[semgrep.passthrough]]
    type  = "git"
    value = "https://gitlab.com/gitlab-org/secure/gsoc-sast-vulnerability-rules/playground/sast-rules.git"
    ref = "97f7686db058e2141c0806a477c1e04835c4f395"
    subdir = "go"

  [[semgrep.passthrough]]
    type  = "raw"
    target = "insecure.yml"
    value = """
rules:
- id: "insecure"
  patterns:
    - pattern: "func insecure() {...}"
  message: |
    Insecure function insecure detected
  metadata:
    cwe: "CWE-200: Exposure of Sensitive Information to an Unauthorized Actor"
  severity: "ERROR"
  languages:
    - "go"
"""

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

Configure the mode for passthroughs in a chain

You can choose how to handle filename conflicts that occur between passthroughs in a chain. The default behavior is to overwrite existing files with the same name, but you can choose mode = append instead to append the content of later files onto earlier ones.

You can use the append mode for the file, url, and raw passthrough types only.

With the following custom ruleset configuration, two raw passthroughs are used to iteratively assemble the /sgrules/my-rules.yml file, which is then provided to Semgrep as the ruleset. Each passthrough appends a single rule to the ruleset. The first passthrough is responsible for initialising the top-level rules object, according to the Semgrep rule syntax.

[semgrep]
  description = "My custom ruleset for Semgrep"
  targetdir = "/sgrules"
  validate = true

  [[semgrep.passthrough]]
    type  = "raw"
    target = "my-rules.yml"
    value = """
rules:
- id: "insecure"
  patterns:
    - pattern: "func insecure() {...}"
  message: |
    Insecure function 'insecure' detected
  metadata:
    cwe: "..."
  severity: "ERROR"
  languages:
    - "go"
"""

  [[semgrep.passthrough]]
    type  = "raw"
    mode  = "append"
    target = "my-rules.yml"
    value = """
- id: "secret"
  patterns:
    - pattern-either:
        - pattern: '$MASK = "..."'
    - metavariable-regex:
        metavariable: "$MASK"
        regex: "(password|pass|passwd|pwd|secret|token)"
  message: |
    Use of hard-coded password
  metadata:
    cwe: "..."
  severity: "ERROR"
  languages:
    - "go"
"""
# /sgrules/my-rules.yml
rules:
- id: "insecure"
  patterns:
    - pattern: "func insecure() {...}"
  message: |
    Insecure function 'insecure' detected
  metadata:
    cwe: "..."
  severity: "ERROR"
  languages:
    - "go"
- id: "secret"
  patterns:
    - pattern-either:
        - pattern: '$MASK = "..."'
    - metavariable-regex:
        metavariable: "$MASK"
        regex: "(password|pass|passwd|pwd|secret|token)"
  message: |
    Use of hard-coded password
  metadata:
    cwe: "..."
  severity: "ERROR"
  languages:
    - "go"

Specify a private remote configuration

The following example enables SAST and uses a shared ruleset customization file. The file is:

  • Downloaded from a private project that requires authentication, by using a Group Access Token.
  • Checked out at a specific Git commit SHA instead of the default branch.

See group access tokens for how to find the username associated with a group token.

include:
  - template: Security/SAST.gitlab-ci.yml

variables:
  SAST_RULESET_GIT_REFERENCE: "group_2504721_bot_7c9311ffb83f2850e794d478ccee36f5:glpat-1234567@gitlab.com/example-group/example-ruleset-project@c8ea7e3ff126987fb4819cc35f2310755511c2ab"