GitLab Dependency Scanning

GitLab built-in · Free / Premium / Ultimate (feature-gated) · GitLab docs · Analyser: Gemnasium (MIT)

GitLab’s Dependency Scanning job (part of the Secure stage) walks manifest files in your repository, queries the GitLab Advisory Database, and writes a gl-dependency-scanning-report.json artefact. Findings surface in the merge-request Security widget, the project’s vulnerability report, and the security dashboard at the group level. The JSON artefact is the canonical source for triage work — the UI widgets are summaries on top of the same data.

The job is auto-included when you include the Secure template in your .gitlab-ci.yml. No extra config needed for the common ecosystems.

What GitLab Dep Scanning finds in the JSON

# In CI the artefact is uploaded automatically; locally:
cat gl-dependency-scanning-report.json

The top-level shape:

{
  "version": "15.0.7",
  "vulnerabilities": [ /* ... */ ],
  "remediations": [ /* ... */ ],
  "dependency_files": [ /* manifest paths */ ],
  "scan": { /* metadata */ }
}

Per-finding fields in vulnerabilities[]:

FieldPurpose
idGitLab’s stable finding ID (a UUID) — used for tracking across pipelines
category"dependency_scanning" — distinguishes from secret-detection / SAST findings if you merge reports
cveThe CVE reference if available; sometimes empty for newer or vendor-specific advisories
identifiers[]Cross-references with type: cve, ghsa, gemnasium, osvdb, snyk
location.dependency.package.name + .versionThe affected component
location.fileThe manifest file the dep was resolved from
severityCritical / High / Medium / Low / Info / Unknown (note the capitalisation)
solutionFree-text upgrade recommendation, typically “Upgrade to X.Y.Z”
links[]URLs to upstream advisories

Querying with jq

# Every finding flattened
jq '.vulnerabilities[] | {
      id,
      cve,
      severity,
      package: .location.dependency.package.name,
      version: .location.dependency.package.version,
      file: .location.file,
      solution
    }' gl-dependency-scanning-report.json

# Critical + High only — the gating triage queue
jq '.vulnerabilities[]
    | select(.severity == "Critical" or .severity == "High")
    | {id, cve, severity, package: .location.dependency.package.name}' \
   gl-dependency-scanning-report.json

# Group by manifest file — split the work across maintainers
jq '[.vulnerabilities[] | {file: .location.file}]
    | group_by(.file)
    | map({file: .[0].file, count: length})' \
   gl-dependency-scanning-report.json

# All CVE identifiers — feed into vulnetix vdb in a loop
jq -r '.vulnerabilities[]
       | .identifiers[]?
       | select(.type == "cve") | .value' \
   gl-dependency-scanning-report.json | sort -u

# Pivot: GHSA where CVE is missing
jq '.vulnerabilities[]
    | select(.cve == "" or .cve == null)
    | .identifiers[]? | select(.type == "ghsa") | .value' \
   gl-dependency-scanning-report.json

From finding to root cause

GitLab’s identifiers[] is your bridge to Vulnetix’s richer data. Pivot to a CVE (or GHSA) and call vdb vuln:

# Extract every CVE from the report, then pull SSVC + affected routines for each
jq -r '.vulnerabilities[].identifiers[]?
       | select(.type == "cve") | .value' \
   gl-dependency-scanning-report.json | sort -u | while read cve; do
  echo "=== $cve ==="
  vulnetix vdb vuln "$cve" --output json | jq '.[0].containers.adp[0] | {
    coordinator: .x_ssvc.decision,
    exploitation: .x_exploitationMaturity.level,
    kev: .x_kev.knownRansomwareCampaignUse,
    routines: .x_affectedRoutines
  }'
done

Engineer Triage inputs map from the GitLab finding as follows:

  • Reachability — apply the ecosystem-specific reachability tool from the package managers appendix against the function names from x_affectedRoutines.
  • Remediation Option — read your lockfile constraint for location.dependency.package.name. If it allows the version in solution, it’s PATCHABLE_DEPLOYMENT; if pinned, PATCHABLE_VERSION_LOCKED.
  • Mitigation Option — typically AUTOMATION (Dependabot equivalent on GitLab is the dependency-update merge requests opened by the Auto-Merge workflow).
  • Priority — GitLab severity plus Vulnetix coordinator + exploitation.

See SSVC Engineer Triage for the framework.

Verify-affected and direct-vs-transitive

Before triaging, prove the artefact ships in the deployed artefact (not just the manifest) and classify direct vs transitive.

  • The gemnasium report’s vulnerabilities[].location.dependency.iid is 0 for direct deps and non-zero for transitives; vulnerabilities[].location.dependency.package.name tells you which artefact. Cross-check with the ecosystem-native walk (mvn dependency:tree, ./gradlew dependencyInsight, npm ls, pip show, go list -m).
  • For Java specifically (location.file is pom.xml, build.gradle.kts, or gradle.lockfile): if the finding is BOM-managed (Spring Boot, AWS SDK, Jackson), the right knob is the BOM-property override or a <dependencyManagement> entry placed before the BOM import. The JVM appendix walks the dozen-plus mechanisms in order of blast radius (direct bump → property override → dependencyManagement → exclusions → enforcer gate).

Full workflow: Vulnetix SCA verify-affected and direct-vs-transitive triage.

Patching mechanics

The package managers appendix covers every supported ecosystem with the lockfile edit, transitive-coercion mechanism, integrity verification, and reachability tooling — JavaScript, Python, Java/Kotlin/Scala (JVM), .NET, Go, Rust, Ruby, PHP, Swift, and others.

Decision tree

Decision tree
Is the vulnerable package declared in your SBOM?
  ├─ Yes → CycloneDX VEX entry referencing the PURL from the SBOM
  └─ No  → OpenVEX statement (transitive not declared, or dev-only)

Is the risk mitigated by a WAF / IPS / SIEM rule from `vulnetix vdb traffic-filters `?
  └─ If yes, status is `affected` with `workaround_available` and the rule reference

Worked example: CVE-2022-1471 (SnakeYaml deserialization in a Spring Boot project)

GitLab Dep Scanning flags org.yaml:snakeyaml@1.30 in pom.xml. The JSON entry:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "category": "dependency_scanning",
  "name": "Improper deserialization of YAML input in SnakeYaml",
  "severity": "High",
  "cve": "CVE-2022-1471",
  "identifiers": [
    { "type": "cve", "value": "CVE-2022-1471", "name": "CVE-2022-1471" },
    { "type": "ghsa", "value": "GHSA-mjmj-j48q-9wg2", "name": "GHSA-mjmj-j48q-9wg2" }
  ],
  "location": {
    "file": "pom.xml",
    "dependency": {
      "package": { "name": "org.yaml:snakeyaml" },
      "version": "1.30"
    }
  },
  "solution": "Upgrade org.yaml:snakeyaml to 2.0 or later."
}

SnakeYaml 1.x’s Yaml.load() instantiates Ruby/Java objects from YAML — exploitable if your application calls it on untrusted input. The reachability question is whether you do.

Pull x_affectedRoutines to confirm the function names:

vulnetix vdb vuln CVE-2022-1471 --output json \
  | jq -r '.[0].containers.adp[0].x_affectedRoutines[]?
           | select(.kind == "function") | .name'
# → org.yaml.snakeyaml.Yaml.load
#   org.yaml.snakeyaml.Yaml.loadAll

Grep the codebase:

git grep -nE '\bYaml\.(load|loadAll)\b' src/main/java/

If Yaml.load is called on a request body or a user-uploaded file, the finding is reachable and exploitable — Engineer Triage moves toward DROP_TOOLS for a critical-priority deployment. If it’s only called on a bundled config file the user can’t influence, Reachability: VERIFIED_REACHABLE but Priority drops because the input is constant.

The patch is non-trivial — SnakeYaml 2.0 changes the default loader to SafeConstructor, which breaks code that relied on automatic class instantiation. From the JVM appendix:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <version>2.2</version>
    </dependency>
  </dependencies>
</dependencyManagement>

Spring Boot 3.1+ already brings SnakeYaml ≥ 2.0 transitively, so bumping Spring Boot resolves it. Spring Boot 2.x users coerce via dependencyManagement. Engineer Triage: Remediation: PATCHABLE_VERSION_LOCKED (the bump may break custom YAML loaders), Mitigation: CODE_CHANGE (audit all Yaml.load call sites), Priority: HIGH — outcome SPIKE_EFFORT.

CycloneDX VEX outcome
{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "vulnerabilities": [{
    "id": "CVE-2022-1471",
    "source": { "name": "GitLab Advisory DB", "url": "https://advisories.gitlab.com/" },
    "ratings": [{ "source": { "name": "NVD" }, "severity": "high", "method": "CVSSv3" }],
    "affects": [{
      "ref": "pkg:maven/org.yaml/snakeyaml@2.2",
      "versions": [
        { "version": "1.30", "status": "affected" },
        { "version": "2.2", "status": "unaffected" }
      ]
    }],
    "analysis": {
      "state": "resolved",
      "detail": "Engineer Triage: SPIKE_EFFORT. Inputs: reachability=VERIFIED_REACHABLE (Yaml.load called in com.example.config.AppConfig.parseConfig), remediation=PATCHABLE_VERSION_LOCKED (default loader changed in 2.0; required audit of 3 call sites), mitigation=CODE_CHANGE (switched 1 site to SafeConstructor explicitly, the other 2 only load bundled resources), priority=HIGH. Pinned snakeyaml to 2.2 via pom.xml's <dependencyManagement>. mvn dependency:tree confirms no transitive resolves a 1.x version. See MR !154."
    }
  }]
}

Producing an OpenVEX

For findings against dev-only or build-time deps (rare for dependency_scanning since GitLab scopes are usually production), the subject is the repo:

OpenVEX outcome
{
  "@context": "https://openvex.dev/ns/v0.2.0",
  "@id": "https://gitlab.com/yourorg/yourrepo/-/vex/2026-05-14-gldeps-001.json",
  "author": "developer@example.com",
  "timestamp": "2026-05-14T10:00:00Z",
  "version": 1,
  "statements": [{
    "vulnerability": {
      "name": "CVE-2022-1471",
      "description": "Improper deserialization in SnakeYaml. See https://nvd.nist.gov/vuln/detail/CVE-2022-1471"
    },
    "products": [{
      "@id": "https://gitlab.com/yourorg/yourrepo",
      "identifiers": { "purl": "pkg:gitlab/yourorg/yourrepo@abc1234" }
    }],
    "status": "not_affected",
    "justification": "component_not_present",
    "action_statement": "snakeyaml@1.30 is in test scope only via the spring-boot-starter-test BOM. The production artefact is built with mvn package -DskipTests and does not include the test classpath. Verified with mvn dependency:tree -Dscope=runtime showing no snakeyaml. Engineer Triage: NIGHTLY_AUTO_PATCH — Spring Boot bumps will pick up SnakeYaml 2.x transitively in due course."
  }]
}

Capability snapshot

See the capability matrix for the full comparison. GitLab Dependencies (gemnasium) summary:

  • Coverage: SCA, license. Container scanning is a separate GitLab analyser; IaC is yet another (SAST-IaC).
  • Database quality: CVE + GitLab Advisory DB (blend of GHSA + curated entries) — between minimal and sufficient.
  • Reachability: Tier 1 — package-level. For function-level reach, drive grep from Vulnetix x_affectedRoutines.
  • Exploit maturity: severity label only.
  • EOL: not native; cross-reference endoflife.date or Vulnetix.
  • Supply-chain threats: reactive via feed.
  • Outputs: gemnasium Security Report JSON (GitLab-native), SARIF (via analyser).
  • VEX: no native emission; GitLab Vulnerability Management dashboard records dismissals.

See also