Support yanked versions for Bzlmod modules

- If present, parse the `metadata.json` of registry modules and cause the build to fail if any yanked version is present in your dep graph.

- `--allow_yanked_versions` flag and `BZLMOD_ALLOWED_YANKED_VERSIONS` env var which allowlist the specified yanked versions from causing the build to fail. `all` keyword allowed to disable completely.

https://github.com/bazelbuild/bazel/issues/16180

Closes #16180.

PiperOrigin-RevId: 479043848
Change-Id: Ie883349a67083623c5c78fba68d1eaf8387409e3
diff --git a/src/MODULE.tools b/src/MODULE.tools
index fdd9993..5afa295 100644
--- a/src/MODULE.tools
+++ b/src/MODULE.tools
@@ -7,7 +7,7 @@
 bazel_dep(name = "rules_python", version = "0.4.0")
 
 bazel_dep(name = "platforms", version = "0.0.4")
-bazel_dep(name = "protobuf", version = "3.19.0", repo_name = "com_google_protobuf")
+bazel_dep(name = "protobuf", version = "3.19.2", repo_name = "com_google_protobuf")
 
 cc_configure = use_extension("//tools/cpp:cc_configure.bzl", "cc_configure_extension")
 use_repo(cc_configure, "local_config_cc", "local_config_cc_toolchains")
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
index 1175e3c..60a1bec 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -138,6 +138,7 @@
   private final AtomicBoolean ignoreDevDeps = new AtomicBoolean(false);
   private CheckDirectDepsMode checkDirectDepsMode = CheckDirectDepsMode.WARNING;
   private BazelCompatibilityMode bazelCompatibilityMode = BazelCompatibilityMode.ERROR;
+  private List<String> allowedYankedVersions = ImmutableList.of();
   private SingleExtensionEvalFunction singleExtensionEvalFunction;
 
   public BazelRepositoryModule() {
@@ -417,6 +418,7 @@
       ignoreDevDeps.set(repoOptions.ignoreDevDependency);
       checkDirectDepsMode = repoOptions.checkDirectDependencies;
       bazelCompatibilityMode = repoOptions.bazelCompatibilityMode;
+      allowedYankedVersions = repoOptions.allowedYankedVersions;
 
       if (repoOptions.registries != null && !repoOptions.registries.isEmpty()) {
         registries = repoOptions.registries;
@@ -491,7 +493,9 @@
         PrecomputedValue.injected(
             BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES, checkDirectDepsMode),
         PrecomputedValue.injected(
-            BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE, bazelCompatibilityMode));
+            BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE, bazelCompatibilityMode),
+        PrecomputedValue.injected(
+            BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, allowedYankedVersions));
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
index 9fb8cc3..ae357a4 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
@@ -156,6 +156,8 @@
         "//src/main/java/com/google/devtools/build/lib/rules:repository/repository_directory_value",
         "//src/main/java/com/google/devtools/build/lib/rules:repository/repository_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:bzl_load_value",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:client_environment_function",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:client_environment_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:precomputed_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster",
         "//src/main/java/com/google/devtools/build/lib/vfs",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java
index 93762a8..750c3c4 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunction.java
@@ -19,16 +19,20 @@
 import static com.google.common.collect.ImmutableMap.toImmutableMap;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableTable;
 import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
 import com.google.devtools.build.lib.bazel.BazelVersion;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
 import com.google.devtools.build.lib.bazel.bzlmod.Selection.SelectionResult;
+import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException;
 import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.BazelCompatibilityMode;
 import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.CheckDirectDepsMode;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
@@ -36,8 +40,11 @@
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
 import com.google.devtools.build.lib.packages.LabelConverter;
 import com.google.devtools.build.lib.server.FailureDetails.ExternalDeps.Code;
+import com.google.devtools.build.lib.skyframe.ClientEnvironmentFunction;
+import com.google.devtools.build.lib.skyframe.ClientEnvironmentValue;
 import com.google.devtools.build.lib.skyframe.PrecomputedValue.Precomputed;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.skyframe.SkyFunction;
@@ -45,8 +52,11 @@
 import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import javax.annotation.Nullable;
 
 /**
@@ -61,10 +71,20 @@
   public static final Precomputed<BazelCompatibilityMode> BAZEL_COMPATIBILITY_MODE =
       new Precomputed<>("bazel_compatibility_mode");
 
+  public static final Precomputed<List<String>> ALLOWED_YANKED_VERSIONS =
+      new Precomputed<>("allowed_yanked_versions");
+  private static final String BZLMOD_ALLOWED_YANKED_VERSIONS_ENV = "BZLMOD_ALLOW_YANKED_VERSIONS";
+
   @Override
   @Nullable
   public SkyValue compute(SkyKey skyKey, Environment env)
       throws SkyFunctionException, InterruptedException {
+    ClientEnvironmentValue allowedYankedVersionsFromEnv =
+        (ClientEnvironmentValue)
+            env.getValue(ClientEnvironmentFunction.key(BZLMOD_ALLOWED_YANKED_VERSIONS_ENV));
+    if (allowedYankedVersionsFromEnv == null) {
+      return null;
+    }
     RootModuleFileValue root =
         (RootModuleFileValue) env.getValue(ModuleFileValue.KEY_FOR_ROOT_MODULE);
     if (root == null) {
@@ -83,23 +103,24 @@
     }
     ImmutableMap<ModuleKey, Module> resolvedDepGraph = selectionResult.getResolvedDepGraph();
 
-    try {
-      checkCompatibility(
-          resolvedDepGraph.values(),
-          Objects.requireNonNull(BAZEL_COMPATIBILITY_MODE.get(env)),
-          env.getListener());
-    } catch (ExternalDepsException e) {
-      throw new BazelModuleResolutionFunctionException(e, Transience.PERSISTENT);
-    }
-
+    checkBazelCompatibility(
+        resolvedDepGraph.values(),
+        Objects.requireNonNull(BAZEL_COMPATIBILITY_MODE.get(env)),
+        env.getListener());
+    verifyYankedVersions(
+        resolvedDepGraph,
+        parseYankedVersions(
+            allowedYankedVersionsFromEnv.getValue(),
+            Objects.requireNonNull(ALLOWED_YANKED_VERSIONS.get(env))),
+        env.getListener());
     verifyRootModuleDirectDepsAreAccurate(
         env, initialDepGraph.get(ModuleKey.ROOT), resolvedDepGraph.get(ModuleKey.ROOT));
     return createValue(resolvedDepGraph, selectionResult.getUnprunedDepGraph(), overrides);
   }
 
-  public static void checkCompatibility(
+  public static void checkBazelCompatibility(
       ImmutableCollection<Module> modules, BazelCompatibilityMode mode, EventHandler eventHandler)
-      throws ExternalDepsException {
+      throws BazelModuleResolutionFunctionException {
     if (mode == BazelCompatibilityMode.OFF) {
       return;
     }
@@ -126,14 +147,150 @@
             eventHandler.handle(Event.warn(message));
           } else {
             eventHandler.handle(Event.error(message));
-            throw ExternalDepsException.withMessage(
-                Code.VERSION_RESOLUTION_ERROR, "Bazel compatibility check failed");
+            throw new BazelModuleResolutionFunctionException(
+                ExternalDepsException.withMessage(
+                    Code.VERSION_RESOLUTION_ERROR, "Bazel compatibility check failed"),
+                Transience.PERSISTENT);
           }
         }
       }
     }
   }
 
+  /**
+   * Parse a set of allowed yanked version from command line flag (--allowed_yanked_versions) and
+   * environment variable (ALLOWED_YANKED_VERSIONS). If `all` is specified, return Optional.empty();
+   * otherwise returns the set of parsed modulel key.
+   */
+  private Optional<ImmutableSet<ModuleKey>> parseYankedVersions(
+      String allowedYankedVersionsFromEnv, List<String> allowedYankedVersionsFromFlag)
+      throws BazelModuleResolutionFunctionException {
+    ImmutableSet.Builder<ModuleKey> allowedYankedVersionBuilder = new ImmutableSet.Builder<>();
+    if (allowedYankedVersionsFromEnv != null) {
+      if (parseModuleKeysFromString(
+          allowedYankedVersionsFromEnv,
+          allowedYankedVersionBuilder,
+          String.format(
+              "envirnoment variable %s=%s",
+              BZLMOD_ALLOWED_YANKED_VERSIONS_ENV, allowedYankedVersionsFromEnv))) {
+        return Optional.empty();
+      }
+    }
+    for (String allowedYankedVersions : allowedYankedVersionsFromFlag) {
+      if (parseModuleKeysFromString(
+          allowedYankedVersions,
+          allowedYankedVersionBuilder,
+          String.format("command line flag --allow_yanked_versions=%s", allowedYankedVersions))) {
+        return Optional.empty();
+      }
+    }
+    return Optional.of(allowedYankedVersionBuilder.build());
+  }
+
+  /**
+   * Parse of a comma-separated list of module version(s) of the form '<module name>@<version>' or
+   * 'all' from the string. Returns true if 'all' is present, otherwise returns false.
+   */
+  private boolean parseModuleKeysFromString(
+      String input, ImmutableSet.Builder<ModuleKey> allowedYankedVersionBuilder, String context)
+      throws BazelModuleResolutionFunctionException {
+    ImmutableList<String> moduleStrs = ImmutableList.copyOf(Splitter.on(',').split(input));
+
+    for (String moduleStr : moduleStrs) {
+      if (moduleStr.equals("all")) {
+        return true;
+      }
+
+      if (moduleStr.isEmpty()) {
+        continue;
+      }
+
+      String[] pieces = moduleStr.split("@", 2);
+
+      if (pieces.length != 2) {
+        throw new BazelModuleResolutionFunctionException(
+            ExternalDepsException.withMessage(
+                Code.VERSION_RESOLUTION_ERROR,
+                "Parsing %s failed, module versions must be of the form '<module name>@<version>'",
+                context),
+            Transience.PERSISTENT);
+      }
+
+      if (!RepositoryName.VALID_MODULE_NAME.matcher(pieces[0]).matches()) {
+        throw new BazelModuleResolutionFunctionException(
+            ExternalDepsException.withMessage(
+                Code.VERSION_RESOLUTION_ERROR,
+                "Parsing %s failed, invalid module name '%s': valid names must 1) only contain"
+                    + " lowercase letters (a-z), digits (0-9), dots (.), hyphens (-), and"
+                    + " underscores (_); 2) begin with a lowercase letter; 3) end with a lowercase"
+                    + " letter or digit.",
+                context,
+                pieces[0]),
+            Transience.PERSISTENT);
+      }
+
+      Version version;
+      try {
+        version = Version.parse(pieces[1]);
+      } catch (ParseException e) {
+        throw new BazelModuleResolutionFunctionException(
+            ExternalDepsException.withCauseAndMessage(
+                Code.VERSION_RESOLUTION_ERROR,
+                e,
+                "Parsing %s failed, invalid version specified for module: %s",
+                context,
+                pieces[1]),
+            Transience.PERSISTENT);
+      }
+
+      allowedYankedVersionBuilder.add(ModuleKey.create(pieces[0], version));
+    }
+    return false;
+  }
+
+  private void verifyYankedVersions(
+      ImmutableMap<ModuleKey, Module> depGraph,
+      Optional<ImmutableSet<ModuleKey>> allowedYankedVersions,
+      ExtendedEventHandler eventHandler)
+      throws BazelModuleResolutionFunctionException, InterruptedException {
+    // Check whether all resolved modules are either not yanked or allowed. Modules with a
+    // NonRegistryOverride are ignored as their metadata is not available whatsoever.
+    for (Module m : depGraph.values()) {
+      if (m.getKey().equals(ModuleKey.ROOT) || m.getRegistry() == null) {
+        continue;
+      }
+      Optional<ImmutableMap<Version, String>> yankedVersions;
+      try {
+        yankedVersions = m.getRegistry().getYankedVersions(m.getKey().getName(), eventHandler);
+      } catch (IOException e) {
+        eventHandler.handle(
+            Event.warn(
+                String.format(
+                    "Could not read metadata file for module %s: %s", m.getKey(), e.getMessage())));
+        continue;
+      }
+      if (yankedVersions.isEmpty()) {
+        continue;
+      }
+      String yankedInfo = yankedVersions.get().get(m.getVersion());
+      if (yankedInfo != null
+          && allowedYankedVersions.isPresent()
+          && !allowedYankedVersions.get().contains(m.getKey())) {
+        throw new BazelModuleResolutionFunctionException(
+            ExternalDepsException.withMessage(
+                Code.VERSION_RESOLUTION_ERROR,
+                "Yanked version detected in your resolved dependency graph: %s, for the reason: "
+                    + "%s.\nYanked versions may contain serious vulnerabilities and should not be "
+                    + "used. To fix this, use a bazel_dep on a newer version of this module. To "
+                    + "continue using this version, allow it using the --allow_yanked_versions "
+                    + "flag or the BZLMOD_ALLOW_YANKED_VERSIONS env variable.",
+                m.getKey(),
+                yankedInfo),
+            Transience.PERSISTENT);
+      }
+    }
+  }
+
   private static void verifyRootModuleDirectDepsAreAccurate(
       Environment env, Module discoveredRootModule, Module resolvedRootModule)
       throws InterruptedException, BazelModuleResolutionFunctionException {
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistry.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistry.java
index 8e2b022..4f885ba 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistry.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistry.java
@@ -20,6 +20,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException;
 import com.google.devtools.build.lib.bazel.repository.downloader.DownloadManager;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.ExtendedEventHandler;
@@ -33,6 +34,7 @@
 import java.net.URI;
 import java.net.URL;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
 
 /**
@@ -113,7 +115,7 @@
   private <T> Optional<T> grabJson(String url, Class<T> klass, ExtendedEventHandler eventHandler)
       throws IOException, InterruptedException {
     Optional<byte[]> bytes = grabFile(url, eventHandler);
-    if (!bytes.isPresent()) {
+    if (bytes.isEmpty()) {
       return Optional.empty();
     }
     String jsonString = new String(bytes.get(), UTF_8);
@@ -123,7 +125,8 @@
     try {
       return Optional.of(gson.fromJson(jsonString, klass));
     } catch (JsonParseException e) {
-      throw new IOException(String.format("Unable to parse json at url %s", url), e);
+      throw new IOException(
+          String.format("Unable to parse json at url %s: %s", url, e.getMessage()), e);
     }
   }
 
@@ -140,7 +143,7 @@
                 getUrl(), "modules", key.getName(), key.getVersion().toString(), "source.json"),
             SourceJson.class,
             eventHandler);
-    if (!sourceJson.isPresent()) {
+    if (sourceJson.isEmpty()) {
       throw new FileNotFoundException(
           String.format("Module %s's source information not found in registry %s", key, getUrl()));
     }
@@ -194,4 +197,39 @@
         .setRemotePatchStrip(sourceJson.get().patchStrip)
         .build();
   }
+
+  @Override
+  public Optional<ImmutableMap<Version, String>> getYankedVersions(
+      String moduleName, ExtendedEventHandler eventHandler)
+      throws IOException, InterruptedException {
+    Optional<MetadataJson> metadataJson =
+        grabJson(
+            constructUrl(getUrl(), "modules", moduleName, "metadata.json"),
+            MetadataJson.class,
+            eventHandler);
+    if (metadataJson.isEmpty()) {
+      return Optional.empty();
+    }
+
+    try {
+      ImmutableMap.Builder<Version, String> yankedVersionsBuilder = new ImmutableMap.Builder<>();
+      if (metadataJson.get().yankedVersions != null) {
+        for (Entry<String, String> e : metadataJson.get().yankedVersions.entrySet()) {
+          yankedVersionsBuilder.put(Version.parse(e.getKey()), e.getValue());
+        }
+      }
+      return Optional.of(yankedVersionsBuilder.buildOrThrow());
+    } catch (ParseException e) {
+      throw new IOException(
+          String.format(
+              "Could not parse module %s's metadata file: %s", moduleName, e.getMessage()));
+    }
+  }
+
+  /** Represents fields available in {@code metadata.json} for each module. */
+  static class MetadataJson {
+    // There are other attributes in the metadata.json file, but for now, we only care about
+    // the yanked_version attribute.
+    Map<String, String> yankedVersions;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Registry.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Registry.java
index a6eff34..547bba7 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Registry.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/Registry.java
@@ -15,6 +15,7 @@
 
 package com.google.devtools.build.lib.bazel.bzlmod;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.ExtendedEventHandler;
 import java.io.IOException;
@@ -39,4 +40,12 @@
    */
   RepoSpec getRepoSpec(ModuleKey key, RepositoryName repoName, ExtendedEventHandler eventHandler)
       throws IOException, InterruptedException;
+
+  /**
+   * Retrieves yanked versions of the module identified by {@code key.getName()} from the registry.
+   * Returns {@code Optional.empty()} when the information is not found in the registry.
+   */
+  Optional<ImmutableMap<Version, String>> getYankedVersions(
+      String moduleName, ExtendedEventHandler eventHandler)
+      throws IOException, InterruptedException;
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/repository/BUILD
index 8c18a58..32f9580 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/BUILD
@@ -68,5 +68,6 @@
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
         "//src/main/java/com/google/devtools/common/options",
         "//third_party:auto_value",
+        "//third_party:guava",
     ],
 )
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java
index f0e52f6..2e515fc 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java
@@ -58,6 +58,22 @@
   public List<String> registries;
 
   @Option(
+      name = "allow_yanked_versions",
+      defaultValue = "null",
+      allowMultiple = true,
+      documentationCategory = OptionDocumentationCategory.BZLMOD,
+      effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
+      help =
+          "Specified the module versions in the form of"
+              + " `<module1>@<version1>,<module2>@<version2>` that will be allowed in the resolved"
+              + " dependency graph even if they are declared yanked in the registry where they come"
+              + " from (if they are not coming from a NonRegistryOverride). Otherwise, yanked"
+              + " versions will cause the resolution to fail. You can also define allowed yanked"
+              + " version with the `BZLMOD_ALLOW_YANKED_VERSIONS` environment variable. You can"
+              + " disable this check by using the keyword 'all' (not recommended).")
+  public List<String> allowedYankedVersions;
+
+  @Option(
       name = "experimental_repository_cache_hardlinks",
       defaultValue = "false",
       documentationCategory = OptionDocumentationCategory.BAZEL_CLIENT_OPTIONS,
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
index fa4b6bb..5acdfe6 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
@@ -70,6 +70,8 @@
         PrecomputedValue.injected(ModuleFileFunction.IGNORE_DEV_DEPS, false),
         PrecomputedValue.injected(ModuleFileFunction.MODULE_OVERRIDES, ImmutableMap.of()),
         PrecomputedValue.injected(
+            BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
+        PrecomputedValue.injected(
             BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES, CheckDirectDepsMode.WARNING),
         PrecomputedValue.injected(
             BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE, BazelCompatibilityMode.ERROR));
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java
index bde660f..55fca94 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java
@@ -36,6 +36,7 @@
 import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
 import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
+import com.google.devtools.build.lib.skyframe.ClientEnvironmentFunction;
 import com.google.devtools.build.lib.skyframe.SkyFunctions;
 import com.google.devtools.build.lib.skyframe.packages.PackageFactoryBuilderWithSkyframeForTesting;
 import com.google.devtools.build.lib.testutil.TestConstants;
@@ -46,6 +47,7 @@
 import java.lang.reflect.Field;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
 /** Create a mock client for the analysis phase, as well as a configuration factory. */
 public abstract class AnalysisMock extends LoadingMock {
@@ -140,6 +142,8 @@
             getBuiltinModules(directories)),
         SkyFunctions.BAZEL_MODULE_RESOLUTION,
         new BazelModuleResolutionFunction(),
+        SkyFunctions.CLIENT_ENVIRONMENT_VARIABLE,
+        new ClientEnvironmentFunction(new AtomicReference<>(ImmutableMap.of())),
         CcSkyframeFdoSupportValue.SKYFUNCTION,
         new CcSkyframeFdoSupportFunction(directories));
   }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
index 8579f2e..f28ef3a 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
@@ -238,6 +238,8 @@
                         BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES,
                         CheckDirectDepsMode.WARNING),
                     PrecomputedValue.injected(
+                        BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
+                    PrecomputedValue.injected(
                         BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE,
                         BazelCompatibilityMode.ERROR)))
             .build(ruleClassProvider, fileSystem);
@@ -284,6 +286,8 @@
                 BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES,
                 CheckDirectDepsMode.WARNING),
             PrecomputedValue.injected(
+                BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
+            PrecomputedValue.injected(
                 BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE,
                 BazelCompatibilityMode.WARNING)));
   }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD b/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD
index 0976108..d8ff4b6 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BUILD
@@ -104,6 +104,7 @@
         "//src/main/java/com/google/devtools/build/lib/shell",
         "//src/main/java/com/google/devtools/build/lib/skyframe:aspect_key_creator",
         "//src/main/java/com/google/devtools/build/lib/skyframe:build_configuration",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:client_environment_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
         "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_key",
         "//src/main/java/com/google/devtools/build/lib/skyframe:diff_awareness",
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
index 72a1903..71844cb 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
@@ -55,6 +55,7 @@
         "//src/main/java/com/google/devtools/build/lib/skyframe:bzl_load_cycle_reporter",
         "//src/main/java/com/google/devtools/build/lib/skyframe:bzl_load_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:bzlmod_repo_cycle_reporter",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:client_environment_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:containing_package_lookup_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:file_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:ignored_package_prefixes_function",
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunctionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunctionTest.java
index 907ec3b..56d1b10 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelModuleResolutionFunctionTest.java
@@ -37,6 +37,7 @@
 import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
+import com.google.devtools.build.lib.skyframe.ClientEnvironmentFunction;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
 import com.google.devtools.build.lib.skyframe.FileFunction;
@@ -117,6 +118,9 @@
                     new ModuleFileFunction(registryFactory, rootDirectory, ImmutableMap.of()))
                 .put(SkyFunctions.PRECOMPUTED, new PrecomputedFunction())
                 .put(SkyFunctions.BAZEL_MODULE_RESOLUTION, new BazelModuleResolutionFunction())
+                .put(
+                    SkyFunctions.CLIENT_ENVIRONMENT_VARIABLE,
+                    new ClientEnvironmentFunction(new AtomicReference<>(ImmutableMap.of())))
                 .buildOrThrow(),
             differencer);
 
@@ -129,6 +133,7 @@
         differencer, CheckDirectDepsMode.OFF);
     BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE.set(
         differencer, BazelCompatibilityMode.ERROR);
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(differencer, ImmutableList.of());
   }
 
   @Test
@@ -447,4 +452,68 @@
     ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of(registry.getUrl()));
   }
 
+  @Test
+  public void testYankedVersionCheckSuccess() throws Exception {
+    setupModulesForYankedVersion();
+    reporter.removeHandler(failFastHandler);
+    EvaluationResult<BazelModuleResolutionValue> result =
+        evaluator.evaluate(ImmutableList.of(BazelModuleResolutionValue.KEY), evaluationContext);
+
+    assertThat(result.hasError()).isTrue();
+    assertThat(result.getError().toString())
+        .contains(
+            "Yanked version detected in your resolved dependency graph: b@1.0, for the reason: 1.0"
+                + " is a bad version!");
+  }
+
+  @Test
+  public void testYankedVersionCheckIgnoredByAll() throws Exception {
+    setupModulesForYankedVersion();
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(differencer, ImmutableList.of("all"));
+    EvaluationResult<BazelModuleResolutionValue> result =
+        evaluator.evaluate(ImmutableList.of(BazelModuleResolutionValue.KEY), evaluationContext);
+    assertThat(result.hasError()).isFalse();
+  }
+
+  @Test
+  public void testYankedVersionCheckIgnoredBySpecific() throws Exception {
+    setupModulesForYankedVersion();
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(
+        differencer, ImmutableList.of("b@1.0"));
+    EvaluationResult<BazelModuleResolutionValue> result =
+        evaluator.evaluate(ImmutableList.of(BazelModuleResolutionValue.KEY), evaluationContext);
+    assertThat(result.hasError()).isFalse();
+  }
+
+  @Test
+  public void testBadYankedVersionFormat() throws Exception {
+    setupModulesForYankedVersion();
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(
+        differencer, ImmutableList.of("b~1.0"));
+    EvaluationResult<BazelModuleResolutionValue> result =
+        evaluator.evaluate(ImmutableList.of(BazelModuleResolutionValue.KEY), evaluationContext);
+    assertThat(result.hasError()).isTrue();
+    assertThat(result.getError().toString())
+        .contains(
+            "Parsing command line flag --allow_yanked_versions=b~1.0 failed, module versions must"
+                + " be of the form '<module name>@<version>'");
+  }
+
+  private void setupModulesForYankedVersion() throws Exception {
+    scratch.file(
+        rootDirectory.getRelative("MODULE.bazel").getPathString(),
+        "module(name='mod', version='1.0')",
+        "bazel_dep(name = 'a', version = '1.0')");
+
+    FakeRegistry registry =
+        registryFactory
+            .newFakeRegistry("/bar")
+            .addModule(
+                createModuleKey("a", "1.0"),
+                "module(name='a', version='1.0');",
+                "bazel_dep(name='b', version='1.0')")
+            .addModule(createModuleKey("b", "1.0"), "module(name='b', version='1.0');")
+            .addYankedVersion("b", ImmutableMap.of(Version.parse("1.0"), "1.0 is a bad version!"));
+    ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of(registry.getUrl()));
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodRepoRuleHelperTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodRepoRuleHelperTest.java
index 8a71301..eece035 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodRepoRuleHelperTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BzlmodRepoRuleHelperTest.java
@@ -33,6 +33,7 @@
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
+import com.google.devtools.build.lib.skyframe.ClientEnvironmentFunction;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
 import com.google.devtools.build.lib.skyframe.FileFunction;
@@ -123,12 +124,16 @@
                     GET_REPO_SPEC_BY_NAME_FUNCTION,
                     new GetRepoSpecByNameFunction(new BzlmodRepoRuleHelperImpl()))
                 .put(SkyFunctions.PRECOMPUTED, new PrecomputedFunction())
+                .put(
+                    SkyFunctions.CLIENT_ENVIRONMENT_VARIABLE,
+                    new ClientEnvironmentFunction(new AtomicReference<>(ImmutableMap.of())))
                 .buildOrThrow(),
             differencer);
 
     PrecomputedValue.STARLARK_SEMANTICS.set(differencer, StarlarkSemantics.DEFAULT);
     ModuleFileFunction.IGNORE_DEV_DEPS.set(differencer, false);
     ModuleFileFunction.MODULE_OVERRIDES.set(differencer, ImmutableMap.of());
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(differencer, ImmutableList.of());
     BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES.set(
         differencer, CheckDirectDepsMode.WARNING);
     BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE.set(
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/DiscoveryTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/DiscoveryTest.java
index 96d082b..40bf514 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/DiscoveryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/DiscoveryTest.java
@@ -197,6 +197,7 @@
     RepositoryDelegatorFunction.RESOLVED_FILE_FOR_VERIFICATION.set(differencer, Optional.empty());
     ModuleFileFunction.IGNORE_DEV_DEPS.set(differencer, false);
     ModuleFileFunction.MODULE_OVERRIDES.set(differencer, ImmutableMap.of());
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(differencer, ImmutableList.of());
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/FakeRegistry.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/FakeRegistry.java
index df6aa5a..e731065 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/FakeRegistry.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/FakeRegistry.java
@@ -37,6 +37,7 @@
   private final String url;
   private final String rootPath;
   private final Map<ModuleKey, String> modules = new HashMap<>();
+  private final Map<String, ImmutableMap<Version, String>> yankedVersionMap = new HashMap<>();
 
   public FakeRegistry(String url, String rootPath) {
     this.url = url;
@@ -49,6 +50,13 @@
     return this;
   }
 
+  @CanIgnoreReturnValue
+  public FakeRegistry addYankedVersion(
+      String moduleName, ImmutableMap<Version, String> yankedVersions) {
+    yankedVersionMap.put(moduleName, yankedVersions);
+    return this;
+  }
+
   @Override
   public String getUrl() {
     return url;
@@ -71,6 +79,12 @@
   }
 
   @Override
+  public Optional<ImmutableMap<Version, String>> getYankedVersions(
+      String moduleName, ExtendedEventHandler eventHandler) {
+    return Optional.ofNullable(yankedVersionMap.get(moduleName));
+  }
+
+  @Override
   public boolean equals(Object other) {
     return other instanceof FakeRegistry
         && this.url.equals(((FakeRegistry) other).url)
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistryTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistryTest.java
index a50a629..fc66fc0 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/IndexRegistryTest.java
@@ -39,6 +39,7 @@
 import java.io.IOException;
 import java.io.Writer;
 import java.nio.file.Files;
+import java.util.Optional;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -235,4 +236,36 @@
             registry.getRepoSpec(
                 createModuleKey("foo", "1.0"), RepositoryName.create("foorepo"), reporter));
   }
+
+  @Test
+  public void testGetYankedVersion() throws Exception {
+    server.serve(
+        "/modules/red-pill/metadata.json",
+        "{\n"
+            + "    'homepage': 'https://docs.matrix.org/red-pill',\n"
+            + "    'maintainers': [\n"
+            + "        {\n"
+            + "            'email': 'neo@matrix.org',\n"
+            + "            'github': 'neo',\n"
+            + "            'name': 'Neo'\n"
+            + "        }\n"
+            + "    ],\n"
+            + "    'versions': [\n"
+            + "        '1.0',\n"
+            + "        '2.0'\n"
+            + "    ],\n"
+            + "    'yanked_versions': {"
+            + "        '1.0': 'red-pill 1.0 is yanked due to CVE-2000-101, please upgrade to 2.0'\n"
+            + "    }\n"
+            + "}");
+    server.start();
+    Registry registry = registryFactory.getRegistryWithUrl(server.getUrl());
+    Optional<ImmutableMap<Version, String>> yankedVersion =
+        registry.getYankedVersions("red-pill", reporter);
+    assertThat(yankedVersion)
+        .hasValue(
+            ImmutableMap.of(
+                Version.parse("1.0"),
+                "red-pill 1.0 is yanked due to CVE-2000-101, please upgrade to 2.0"));
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java
index 7f1ad04..5f0b49c 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java
@@ -54,6 +54,7 @@
 import com.google.devtools.build.lib.skyframe.BzlLoadValue;
 import com.google.devtools.build.lib.skyframe.BzlmodRepoCycleReporter;
 import com.google.devtools.build.lib.skyframe.BzlmodRepoRuleFunction;
+import com.google.devtools.build.lib.skyframe.ClientEnvironmentFunction;
 import com.google.devtools.build.lib.skyframe.ContainingPackageLookupFunction;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
@@ -243,6 +244,9 @@
                 .put(SkyFunctions.BAZEL_MODULE_RESOLUTION, new BazelModuleResolutionFunction())
                 .put(SkyFunctions.SINGLE_EXTENSION_USAGES, new SingleExtensionUsagesFunction())
                 .put(SkyFunctions.SINGLE_EXTENSION_EVAL, singleExtensionEvalFunction)
+                .put(
+                    SkyFunctions.CLIENT_ENVIRONMENT_VARIABLE,
+                    new ClientEnvironmentFunction(new AtomicReference<>(ImmutableMap.of())))
                 .build(),
             differencer);
 
@@ -261,6 +265,7 @@
     RepositoryDelegatorFunction.RESOLVED_FILE_FOR_VERIFICATION.set(differencer, Optional.empty());
     ModuleFileFunction.IGNORE_DEV_DEPS.set(differencer, false);
     ModuleFileFunction.MODULE_OVERRIDES.set(differencer, ImmutableMap.of());
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(differencer, ImmutableList.of());
     ModuleFileFunction.REGISTRIES.set(differencer, ImmutableList.of(registry.getUrl()));
     BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES.set(
         differencer, CheckDirectDepsMode.WARNING);
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java
index e4167a1..cdedc1b 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java
@@ -152,9 +152,7 @@
                 .put(
                     BzlmodRepoRuleValue.BZLMOD_REPO_RULE,
                     new BzlmodRepoRuleFunction(
-                        ruleClassProvider,
-                        directories,
-                        new BzlmodRepoRuleHelperImpl()))
+                        ruleClassProvider, directories, new BzlmodRepoRuleHelperImpl()))
                 .buildOrThrow(),
             differencer);
 
@@ -173,6 +171,7 @@
     RepositoryDelegatorFunction.RESOLVED_FILE_FOR_VERIFICATION.set(differencer, Optional.empty());
     ModuleFileFunction.IGNORE_DEV_DEPS.set(differencer, false);
     ModuleFileFunction.MODULE_OVERRIDES.set(differencer, ImmutableMap.of());
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(differencer, ImmutableList.of());
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/query2/testutil/SkyframeQueryHelper.java b/src/test/java/com/google/devtools/build/lib/query2/testutil/SkyframeQueryHelper.java
index 12be403..c791ada 100644
--- a/src/test/java/com/google/devtools/build/lib/query2/testutil/SkyframeQueryHelper.java
+++ b/src/test/java/com/google/devtools/build/lib/query2/testutil/SkyframeQueryHelper.java
@@ -366,6 +366,8 @@
                         BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES,
                         CheckDirectDepsMode.WARNING),
                     PrecomputedValue.injected(
+                        BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
+                    PrecomputedValue.injected(
                         BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE,
                         BazelCompatibilityMode.ERROR)))
             .setEnvironmentExtensions(getEnvironmentExtensions())
@@ -405,6 +407,9 @@
                     CheckDirectDepsMode.WARNING))
             .add(
                 PrecomputedValue.injected(
+                    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()))
+            .add(
+                PrecomputedValue.injected(
                     BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE,
                     BazelCompatibilityMode.ERROR))
             .build());
diff --git a/src/test/java/com/google/devtools/build/lib/rules/LabelBuildSettingTest.java b/src/test/java/com/google/devtools/build/lib/rules/LabelBuildSettingTest.java
index 7f81617..78fb2d9 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/LabelBuildSettingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/LabelBuildSettingTest.java
@@ -53,6 +53,8 @@
         PrecomputedValue.injected(ModuleFileFunction.IGNORE_DEV_DEPS, false),
         PrecomputedValue.injected(ModuleFileFunction.MODULE_OVERRIDES, ImmutableMap.of()),
         PrecomputedValue.injected(
+            BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
+        PrecomputedValue.injected(
             BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES, CheckDirectDepsMode.WARNING),
         PrecomputedValue.injected(
             BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE, BazelCompatibilityMode.ERROR));
diff --git a/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD b/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD
index 240b70b..f9aa10a 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD
@@ -23,7 +23,6 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:server_directories",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:repo_rule_helper",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:repo_rule_value",
-        "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution",
         "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution_impl",
         "//src/main/java/com/google/devtools/build/lib/bazel/repository:repository_options",
         "//src/main/java/com/google/devtools/build/lib/bazel/repository/downloader",
@@ -38,6 +37,7 @@
         "//src/main/java/com/google/devtools/build/lib/rules:repository/repository_directory_value",
         "//src/main/java/com/google/devtools/build/lib/rules:repository/repository_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:bzl_compile",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:client_environment_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:containing_package_lookup_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:file_function",
         "//src/main/java/com/google/devtools/build/lib/skyframe:ignored_package_prefixes_function",
diff --git a/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java b/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java
index 220e7b7..4bc793d 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java
@@ -53,6 +53,7 @@
 import com.google.devtools.build.lib.skyframe.BzlCompileFunction;
 import com.google.devtools.build.lib.skyframe.BzlLoadFunction;
 import com.google.devtools.build.lib.skyframe.BzlmodRepoRuleFunction;
+import com.google.devtools.build.lib.skyframe.ClientEnvironmentFunction;
 import com.google.devtools.build.lib.skyframe.ContainingPackageLookupFunction;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
@@ -227,6 +228,9 @@
                     BzlmodRepoRuleValue.BZLMOD_REPO_RULE,
                     new BzlmodRepoRuleFunction(
                         ruleClassProvider, directories, new BzlmodRepoRuleHelperImpl()))
+                .put(
+                    SkyFunctions.CLIENT_ENVIRONMENT_VARIABLE,
+                    new ClientEnvironmentFunction(new AtomicReference<>(ImmutableMap.of())))
                 .build(),
             differencer);
     overrideDirectory = scratch.dir("/foo");
@@ -246,6 +250,7 @@
     RepositoryDelegatorFunction.RESOLVED_FILE_FOR_VERIFICATION.set(differencer, Optional.empty());
     ModuleFileFunction.IGNORE_DEV_DEPS.set(differencer, false);
     ModuleFileFunction.MODULE_OVERRIDES.set(differencer, ImmutableMap.of());
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(differencer, ImmutableList.of());
     BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES.set(
         differencer, CheckDirectDepsMode.WARNING);
     BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE.set(
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadFunctionTest.java
index c99f8cb..f0ac79e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadFunctionTest.java
@@ -87,9 +87,9 @@
             packageOptions,
             Options.getDefaults(BuildLanguageOptions.class),
             UUID.randomUUID(),
-            ImmutableMap.<String, String>of(),
+            ImmutableMap.of(),
             new TimestampGranularityMonitor(BlazeClock.instance()));
-    skyframeExecutor.setActionEnv(ImmutableMap.<String, String>of());
+    skyframeExecutor.setActionEnv(ImmutableMap.of());
   }
 
   @Override
@@ -106,6 +106,8 @@
         PrecomputedValue.injected(ModuleFileFunction.IGNORE_DEV_DEPS, false),
         PrecomputedValue.injected(ModuleFileFunction.MODULE_OVERRIDES, ImmutableMap.of()),
         PrecomputedValue.injected(
+            BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
+        PrecomputedValue.injected(
             BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES, CheckDirectDepsMode.WARNING),
         PrecomputedValue.injected(
             BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE, BazelCompatibilityMode.ERROR));
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionTest.java
index e19df5e..aefc7e6 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternsFunctionTest.java
@@ -242,6 +242,8 @@
         PrecomputedValue.injected(
             BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES, CheckDirectDepsMode.WARNING),
         PrecomputedValue.injected(
+            BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
+        PrecomputedValue.injected(
             BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE, BazelCompatibilityMode.ERROR));
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunctionTest.java
index 5ac9ca7..1a3547b 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredExecutionPlatformsFunctionTest.java
@@ -106,6 +106,8 @@
         PrecomputedValue.injected(ModuleFileFunction.IGNORE_DEV_DEPS, false),
         PrecomputedValue.injected(ModuleFileFunction.MODULE_OVERRIDES, ImmutableMap.of()),
         PrecomputedValue.injected(
+            BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
+        PrecomputedValue.injected(
             BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES, CheckDirectDepsMode.WARNING),
         PrecomputedValue.injected(
             BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE, BazelCompatibilityMode.ERROR));
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunctionTest.java
index 3db6120..3178509 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RegisteredToolchainsFunctionTest.java
@@ -62,6 +62,8 @@
         PrecomputedValue.injected(ModuleFileFunction.IGNORE_DEV_DEPS, false),
         PrecomputedValue.injected(ModuleFileFunction.MODULE_OVERRIDES, ImmutableMap.of()),
         PrecomputedValue.injected(
+            BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS, ImmutableList.of()),
+        PrecomputedValue.injected(
             BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES, CheckDirectDepsMode.WARNING),
         PrecomputedValue.injected(
             BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE, BazelCompatibilityMode.ERROR));
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunctionTest.java
index 8de2cb0..b3a0d4d 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RepositoryMappingFunctionTest.java
@@ -75,6 +75,8 @@
     ModuleFileFunction.IGNORE_DEV_DEPS.set(getSkyframeExecutor().getDifferencerForTesting(), false);
     ModuleFileFunction.MODULE_OVERRIDES.set(
         getSkyframeExecutor().getDifferencerForTesting(), ImmutableMap.of());
+    BazelModuleResolutionFunction.ALLOWED_YANKED_VERSIONS.set(
+        getSkyframeExecutor().getDifferencerForTesting(), ImmutableList.of());
     BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES.set(
         getSkyframeExecutor().getDifferencerForTesting(), CheckDirectDepsMode.WARNING);
     BazelModuleResolutionFunction.BAZEL_COMPATIBILITY_MODE.set(
diff --git a/src/test/py/bazel/bzlmod/bazel_module_test.py b/src/test/py/bazel/bzlmod/bazel_module_test.py
index 321df9b..58ce6fd 100644
--- a/src/test/py/bazel/bzlmod/bazel_module_test.py
+++ b/src/test/py/bazel/bzlmod/bazel_module_test.py
@@ -33,18 +33,24 @@
         .createCcModule('aaa', '1.1') \
         .createCcModule('bbb', '1.0', {'aaa': '1.0'}, {'aaa': 'com_foo_aaa'}) \
         .createCcModule('bbb', '1.1', {'aaa': '1.1'}) \
-        .createCcModule('ccc', '1.1', {'aaa': '1.1', 'bbb': '1.1'})
+        .createCcModule('ccc', '1.1', {'aaa': '1.1', 'bbb': '1.1'}) \
+        .createCcModule('ddd', '1.0', {'yanked1': '1.0', 'yanked2': '1.0'}) \
+        .createCcModule('eee', '1.0', {'yanked1': '1.0'}) \
+        .createCcModule('yanked1', '1.0') \
+        .createCcModule('yanked2', '1.0') \
+        .addMetadata('yanked1', yanked_versions={'1.0': 'dodgy'}) \
+        .addMetadata('yanked2', yanked_versions={'1.0': 'sketchy'})
     self.ScratchFile(
         '.bazelrc',
         [
             # In ipv6 only network, this has to be enabled.
             # 'startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true',
-            'build --experimental_enable_bzlmod',
-            'build --registry=' + self.main_registry.getURL(),
+            'common --experimental_enable_bzlmod',
+            'common --registry=' + self.main_registry.getURL(),
             # We need to have BCR here to make sure built-in modules like
             # bazel_tools can work.
-            'build --registry=https://bcr.bazel.build',
-            'build --verbose_failures',
+            'common --registry=https://bcr.bazel.build',
+            'common --verbose_failures',
         ])
     self.ScratchFile('WORKSPACE')
     # The existence of WORKSPACE.bzlmod prevents WORKSPACE prefixes or suffixes
@@ -426,5 +432,105 @@
     ])
     self.RunBazel(['build', '@no_op//:no_op'], allow_failure=False)
 
+  def testNonRegistryOverriddenModulesIgnoreYanked(self):
+    src_yanked1 = self.main_registry.projects.joinpath('yanked1', '1.0')
+    self.ScratchFile('MODULE.bazel', [
+        'bazel_dep(name = "yanked1", version = "1.0")', 'local_path_override(',
+        '  module_name = "yanked1",',
+        '  path = "%s",' % str(src_yanked1.resolve()).replace('\\', '/'), ')'
+    ])
+    self.ScratchFile('WORKSPACE')
+    self.ScratchFile('BUILD', [
+        'cc_binary(',
+        '  name = "main",',
+        '  srcs = ["main.cc"],',
+        '  deps = ["@yanked1//:lib_yanked1"],',
+        ')',
+    ])
+    self.RunBazel(['build', '--nobuild', '//:main'], allow_failure=False)
+
+  def testContainingYankedDepFails(self):
+    self.ScratchFile('MODULE.bazel', [
+        'bazel_dep(name = "yanked1", version = "1.0")',
+    ])
+    self.ScratchFile('WORKSPACE')
+    self.ScratchFile('BUILD', [
+        'cc_binary(',
+        '  name = "main",',
+        '  srcs = ["main.cc"],',
+        '  deps = ["@ddd//:lib_ddd"],',
+        ')',
+    ])
+    exit_code, _, stderr = self.RunBazel(['build', '--nobuild', '//:main'],
+                                         allow_failure=True)
+    self.AssertExitCode(exit_code, 48, stderr)
+    self.assertIn(
+        'Yanked version detected in your resolved dependency graph: ' +
+        'yanked1@1.0, for the reason: dodgy.', ''.join(stderr))
+
+  def testAllowedYankedDepsSuccessByFlag(self):
+    self.ScratchFile('MODULE.bazel', [
+        'bazel_dep(name = "ddd", version = "1.0")',
+    ])
+    self.ScratchFile('WORKSPACE')
+    self.ScratchFile('BUILD', [
+        'cc_binary(',
+        '  name = "main",',
+        '  srcs = ["main.cc"],',
+        '  deps = ["@ddd//:lib_ddd"],',
+        ')',
+    ])
+    self.RunBazel([
+        'build', '--nobuild', '--allow_yanked_versions=yanked1@1.0,yanked2@1.0',
+        '//:main'
+    ],
+                  allow_failure=False)
+
+  def testAllowedYankedDepsByEnvVar(self):
+    self.ScratchFile('MODULE.bazel', [
+        'bazel_dep(name = "ddd", version = "1.0")',
+    ])
+    self.ScratchFile('WORKSPACE')
+    self.ScratchFile('BUILD', [
+        'cc_binary(',
+        '  name = "main",',
+        '  srcs = ["main.cc"],',
+        '  deps = ["@ddd//:lib_ddd"],',
+        ')',
+    ])
+    self.RunBazel(
+        ['build', '--nobuild', '//:main'],
+        env_add={'BZLMOD_ALLOW_YANKED_VERSIONS': 'yanked1@1.0,yanked2@1.0'},
+        allow_failure=False)
+
+    # Test changing the env var, the build should fail again.
+    exit_code, _, stderr = self.RunBazel(
+        ['build', '--nobuild', '//:main'],
+        env_add={'BZLMOD_ALLOW_YANKED_VERSIONS': 'yanked2@1.0'},
+        allow_failure=True)
+    self.AssertExitCode(exit_code, 48, stderr)
+    self.assertIn(
+        'Yanked version detected in your resolved dependency graph: ' +
+        'yanked1@1.0, for the reason: dodgy.', ''.join(stderr))
+
+  def testAllowedYankedDepsSuccessMix(self):
+    self.ScratchFile('MODULE.bazel', [
+        'bazel_dep(name = "ddd", version = "1.0")',
+    ])
+    self.ScratchFile('WORKSPACE')
+    self.ScratchFile('BUILD', [
+        'cc_binary(',
+        '  name = "main",',
+        '  srcs = ["main.cc"],',
+        '  deps = ["@ddd//:lib_ddd"],',
+        ')',
+    ])
+    self.RunBazel([
+        'build', '--nobuild', '--allow_yanked_versions=yanked1@1.0', '//:main'
+    ],
+                  env_add={'BZLMOD_ALLOW_YANKED_VERSIONS': 'yanked2@1.0'},
+                  allow_failure=False)
+
+
 if __name__ == '__main__':
   unittest.main()
diff --git a/src/test/py/bazel/bzlmod/test_utils.py b/src/test/py/bazel/bzlmod/test_utils.py
index a7ff119..9de182a 100644
--- a/src/test/py/bazel/bzlmod/test_utils.py
+++ b/src/test/py/bazel/bzlmod/test_utils.py
@@ -238,3 +238,32 @@
       module.set_patches(patches, patch_strip)
     self.addModule(module)
     return self
+
+  def addMetadata(self,
+                  name,
+                  homepage=None,
+                  maintainers=None,
+                  versions=None,
+                  yanked_versions=None):
+    """Generate a module metadata file and add it to the registry."""
+    if maintainers is None:
+      maintainers = []
+    if versions is None:
+      versions = []
+    if yanked_versions is None:
+      yanked_versions = {}
+
+    module_dir = self.root.joinpath('modules', name)
+    module_dir.mkdir(parents=True, exist_ok=True)
+
+    metadata = {
+        'homepage': homepage,
+        'maintainers': maintainers,
+        'versions': versions,
+        'yanked_versions': yanked_versions
+    }
+
+    with module_dir.joinpath('metadata.json').open('w') as f:
+      json.dump(metadata, f, indent=4, sort_keys=True)
+
+    return self