GitLab Security Specialist study materials
GitLab Security Specialist study resources

Dependency Confusion and Unsigned Images in Checkout

GitLab Security Specialist · Put your knowledge to work in context.

All case studies

Dependency Confusion and Unsigned Images in Checkout

Kestrel Retail's checkout service builds a container image in GitLab CI and deploys it to Kubernetes with Helm. The Dockerfile runs npm install from the public registry. One internal package, scoped as @kestrel/auth-utils, was also published to the public npm registry by a former employee's personal account. Container scanning runs but fails the job only on Critical findings. SBOM generation was enabled last quarter and currently lists 3,100 components. A deployment last week used the latest tag because the commit SHA tag was overwritten when the pipeline was re-run for the same commit. The security team wants provenance, signature verification, and a rule that no unsigned image enters the cluster. The platform team has six weeks and one engineer; the security team can review but cannot add headcount. The container registry is GitLab's registry, and images are tagged with both the commit SHA and latest.

1. Explain the dependency confusion exposure and the concrete controls that close it without breaking the build.

Read the worked answer

The exposure is a name collision. npm resolves packages by registry, and if the project's registry configuration does not explicitly map the @kestrel scope to the private GitLab package registry, npm can fall back to the public registry for that scope. A public package with the same name can then be installed, and its install scripts run with CI credentials in scope. The lockfile may already point at the public registry URL, so fixing configuration without regenerating the lockfile leaves the exposure.

Controls: add an .npmrc that maps @kestrel to the GitLab package registry endpoint, and commit it so every developer and CI job uses the same mapping. Regenerate the lockfile and verify that every @kestrel entry resolves to the private registry. Publish internal packages only to the private registry, and delete or deprecate the public copy where the registry allows it, understanding that a public package cannot always be removed. Use npm ci with the lockfile so installation is deterministic, and add a CI check that fails if any scoped dependency resolves to a public registry host.

For defense in depth, pull public dependencies through the GitLab dependency proxy so versions are cached and the build is not dependent on a live public registry at deploy time. Restrict CI job tokens to read-only package access for builds, and never run install scripts with a token that can write to the registry.

2. Design image integrity for the cluster in six weeks with one engineer. Include tagging, signing, and admission control.

Read the worked answer

Week 1: immutable references. Stop deploying latest. Deploy by digest, for example image@sha256, and tag every build with the commit SHA plus the pipeline ID so a re-run cannot collide with an earlier build. Make the latest tag non-deployable, or remove it from the deployment manifests entirely. This alone fixes traceability, which was broken when the commit SHA tag was overwritten.

Weeks 2 and 3: signing in CI. Add a cosign step after the build and before the push-to-production stage. Use keyless signing with the CI job's OIDC identity so the signature is bound to the pipeline and repository, and store the signature and attestation alongside the image in the registry. Sign the digest, not the tag, because the tag can move.

Weeks 4 and 5: admission control. Deploy a Kubernetes admission policy that rejects images without a valid signature from the expected CI identity. Run it in audit mode for one week to enumerate exceptions, then enforce. Keep a documented break-glass namespace with an alert for any unsigned image admitted.

Week 6: verification and documentation. Test by attempting to deploy an unsigned image from a developer laptop and confirm it is rejected. Document the re-run procedure, the signing identity, and the exception process. Keep the SBOM and attestation attached to the digest so responders can trace a deployed image back to a commit and pipeline.

3. The SBOM lists 3,100 components. How would you make it useful for supply chain decisions, and what can it not tell you?

Read the worked answer

An SBOM is an inventory, not a risk score. Its first useful application is incident response: when a critical vulnerability in a library is announced, query the SBOMs to find which image digests contain it, instead of rebuilding everything. To enable that, generate the SBOM per image digest and retain it with the release, not only as a pipeline artifact that expires.

Second, correlate the SBOM with container scanning results. The 3,100 components are mostly transitive and many are never loaded at runtime. Prioritize components that are present in the final image, reachable in the running process, or part of the exposed service path. Use the dependency graph to find which direct dependency pulls in a vulnerable transitive package, because that is where an upgrade or override can be applied.

Third, use it for license and provenance review, particularly for the internal @kestrel package and any dependency coming from an unexpected registry.

What it cannot do: it does not prove a vulnerability is exploitable, it does not detect malicious code in a package, and it does not cover components the build misses, such as OS packages in some configurations, vendored code, or binaries copied into the image. It also does not replace signing. Pair the SBOM with image signatures and admission control, and treat the SBOM as the map you use when an incident starts, not as a gate that fails the build on component count.