Gatekeeper Constraint Bypass

Affected Products

Open Policy Agent Gatekeeper Library

Overview

A bypass was discovered within a template in Open Policy Agent’s (OPA) Gatekeeper Library, allowing a latest-tagged image to be deployed even if it had been specifically designated as disallowed. Anyone using the examples within the OPA documentation is likely to be vulnerable to this bypass.

The Kubernetes documentation states that “You should avoid using the :latest tag when deploying containers in production as it is harder to track which version of the image is running and more difficult to roll back properly”. Additionally, clients often wish to perform their own assurance testing on container images before allowing them to be deployed into their internal environments. They do so by allowing particular versions of images, with latest being explicitly denied.

Technical Details and Proof of Concept

The issue was discovered in the disallowedtags template. To attempt to ensure that a container image was not missing a tag, the following code was executed, which was set to trigger a violation on an evaluation of true:

not contains(container.image, ":")

The expected format of a container image in the YAML or JSON files used to deploy resources was a string similar to the below:

docker.style.registry/skybound/net-utils:testing

This string consisted of a repository docker.style.registry/skybound, an image path /net-utils, and a specified tag :testing. However, the code to determine whether a tag was missing was only checking for the inclusion of the : character in the string. As such, an alternative container image identifier could be used, for example:

docker.style.registry:443/skybound/net-utils

In this example there is no tag defined, however, this bypasses the original check for a missing tag by specifying a port after the repository (docker.style.registry:443) and therefore the string still contains the : character. As per the Kuberenetes documentation, “If you don’t specify a tag, Kubernetes assumes you mean the tag latest”. Using the above as the image identifier therefore pulls the latest image. Naturally in this case, any OPA checks conducted to determine whether the container image identifier ends with :latest would not flag a violation either.

Remediation

A pull request was opened on the OPA Gatekeeper Library repository to perform a new check for the presence of an image tag. The new code is as follows:

violation[{"msg": msg}] {
    container := input_containers[_]
    not is_exempt(container)
    parts := split(container.image, "/")
    not contains(parts[count(parts) - 1], ":")
    msg := sprintf("container <%v> didn't specify an image tag <%v>", [container.name, container.image])
}

In this update, after checks that the container is not exempted from the policy, it splits the image path into parts using / as the dividing character. For example, our previous image identifier that bypassed the original policy would be split into the following parts:

0 - docker.style.registry:443
1 - skybound
2 - net-utils

Then the last of these parts (2 - net-utils in this example) is checked to ensure it contains the : character. In this case, the last part does not contain : and so the check would lead to a violation. A tag is only allowed to use alphanumeric characters, . and -, so it will not contain another / or a second :. Similarly, an image name may not contain the : or / characters, for most repositories.

Where you wish to ensure that the latest version of an image is not used in your systems, a combination of checks should be conducted:

  1. Ensure that the image path does not end with :latest
  2. Ensure that the image path contains a tag, using code similar to that given above

Timeline

Date Action
23 Jun 2025 Issue reported to OPA
24 Jun 2025 Pull request submitted by Reversec consultant