diff --git a/package_manager/src/ComposerUtility.php b/package_manager/src/ComposerUtility.php
index 4cf3d48ddd4217940db31810de90063d16e04e40..a518bcfee800ab8dfaa25520b7d04e05e7d6fad5 100644
--- a/package_manager/src/ComposerUtility.php
+++ b/package_manager/src/ComposerUtility.php
@@ -73,9 +73,14 @@ class ComposerUtility {
     // @see https://getcomposer.org/doc/06-config.md#htaccess-protect
     $htaccess = getenv('COMPOSER_HTACCESS_PROTECT');
 
+    $factory = new Factory();
     putenv("COMPOSER_HOME=$dir");
     putenv("COMPOSER_HTACCESS_PROTECT=false");
-    $composer = Factory::create($io, $configuration);
+    // Initialize the Composer API with plugins disabled and only the root
+    // package loaded (i.e., nothing from the global Composer project will be
+    // considered or loaded). This allows us to inspect the project directory
+    // using Composer's API in a "hands-off" manner.
+    $composer = $factory->createComposer($io, $configuration, TRUE, $dir, FALSE);
     putenv("COMPOSER_HOME=$home");
     putenv("COMPOSER_HTACCESS_PROTECT=$htaccess");
 
@@ -104,7 +109,7 @@ class ComposerUtility {
   }
 
   /**
-   * Returns the names of the core packages in the lock file.
+   * Returns the names of the installed core packages.
    *
    * All packages listed in ../core_packages.json are considered core packages.
    *
@@ -116,7 +121,7 @@ class ComposerUtility {
    */
   public function getCorePackageNames(): array {
     $core_packages = array_intersect(
-      array_keys($this->getLockedPackages()),
+      array_keys($this->getInstalledPackages()),
       static::getCorePackageList()
     );
 
@@ -147,22 +152,23 @@ class ComposerUtility {
       ];
       return in_array($package->getType(), $drupal_package_types, TRUE);
     };
-    return array_filter($this->getLockedPackages(), $filter);
+    return array_filter($this->getInstalledPackages(), $filter);
   }
 
   /**
-   * Returns all packages in the lock file.
+   * Returns information on all installed packages.
    *
    * @return \Composer\Package\PackageInterface[]
-   *   All packages in the lock file, keyed by name.
+   *   All installed packages, keyed by name.
    */
-  protected function getLockedPackages(): array {
-    $locked_packages = $this->getComposer()->getLocker()
-      ->getLockedRepository(TRUE)
+  protected function getInstalledPackages(): array {
+    $installed_packages = $this->getComposer()
+      ->getRepositoryManager()
+      ->getLocalRepository()
       ->getPackages();
 
     $packages = [];
-    foreach ($locked_packages as $package) {
+    foreach ($installed_packages as $package) {
       $key = $package->getName();
       $packages[$key] = $package;
     }
diff --git a/package_manager/tests/fixtures/distro_core/vendor/composer/installed.json b/package_manager/tests/fixtures/distro_core/vendor/composer/installed.json
new file mode 100644
index 0000000000000000000000000000000000000000..33e328278743cbba936dc9dd4d6a1d84c26d16fd
--- /dev/null
+++ b/package_manager/tests/fixtures/distro_core/vendor/composer/installed.json
@@ -0,0 +1,15 @@
+{
+    "packages": [
+        {
+            "name": "drupal/test-distribution",
+            "version": "1.0.0",
+            "require": {
+                "drupal/core": "*"
+            }
+        },
+        {
+            "name": "drupal/core",
+            "version": "9.8.0"
+        }
+    ]
+}
diff --git a/package_manager/tests/fixtures/distro_core_recommended/vendor/composer/installed.json b/package_manager/tests/fixtures/distro_core_recommended/vendor/composer/installed.json
new file mode 100644
index 0000000000000000000000000000000000000000..f68c6fc621d1c03b5d3d2f0d52396342a0bf92f3
--- /dev/null
+++ b/package_manager/tests/fixtures/distro_core_recommended/vendor/composer/installed.json
@@ -0,0 +1,22 @@
+{
+    "packages": [
+        {
+            "name": "drupal/test-distribution",
+            "version": "1.0.0",
+            "require": {
+                "drupal/core-recommended": "*"
+            }
+        },
+        {
+            "name": "drupal/core-recommended",
+            "version": "9.8.0",
+            "require": {
+                "drupal/core": "9.8.0"
+            }
+        },
+        {
+            "name": "drupal/core",
+            "version": "9.8.0"
+        }
+    ]
+}
diff --git a/tests/fixtures/fake-site/vendor/composer/installed.json b/tests/fixtures/fake-site/vendor/composer/installed.json
new file mode 100644
index 0000000000000000000000000000000000000000..c8ad1ba32e3c37d5c7d32c01759e4c940812fbb0
--- /dev/null
+++ b/tests/fixtures/fake-site/vendor/composer/installed.json
@@ -0,0 +1,30 @@
+{
+    "packages": [
+        {
+            "name": "drupal/test-distribution",
+            "version": "1.0.0",
+            "require": {
+                "drupal/core-recommended": "*"
+            }
+        },
+        {
+            "name": "drupal/core-recommended",
+            "version": "9.8.0",
+            "require": {
+                "drupal/core": "9.8.0"
+            }
+        },
+        {
+            "name": "drupal/core",
+            "version": "9.8.0"
+        },
+        {
+            "name": "drupal/core-dev",
+            "version": "9.8.0"
+        }
+    ],
+    "dev": true,
+    "dev-package-names": [
+        "drupal/core-dev"
+    ]
+}
diff --git a/tests/fixtures/project_staged_validation/new_project_added/active/composer.lock b/tests/fixtures/project_staged_validation/new_project_added/active/vendor/composer/installed.json
similarity index 88%
rename from tests/fixtures/project_staged_validation/new_project_added/active/composer.lock
rename to tests/fixtures/project_staged_validation/new_project_added/active/vendor/composer/installed.json
index 67668976a3a41b6dd7a23c72c3fd9e3e126aa0f8..df420decb6b9afd2f34b48172f32c736e2cec1f6 100644
--- a/tests/fixtures/project_staged_validation/new_project_added/active/composer.lock
+++ b/tests/fixtures/project_staged_validation/new_project_added/active/vendor/composer/installed.json
@@ -15,9 +15,7 @@
       "description": "This project is removed but there should be no error because it is not a Drupal project.",
       "version": "1.3.1",
       "type": "library"
-    }
-  ],
-  "packages-dev": [
+    },
     {
       "name": "drupal/dev-test_module",
       "version": "1.3.0",
@@ -29,6 +27,10 @@
       "version": "1.3.1",
       "type": "library"
     }
-
+  ],
+  "dev": true,
+  "dev-package-names": [
+    "drupal/dev-test_module",
+    "other/dev-removed"
   ]
 }
diff --git a/tests/fixtures/project_staged_validation/new_project_added/staged/composer.lock b/tests/fixtures/project_staged_validation/new_project_added/staged/vendor/composer/installed.json
similarity index 87%
rename from tests/fixtures/project_staged_validation/new_project_added/staged/composer.lock
rename to tests/fixtures/project_staged_validation/new_project_added/staged/vendor/composer/installed.json
index ba776321961b650a90d5459d0e5441b5a3575912..db60c8425c0a5a33340c7436d494b62e3c2a7059 100644
--- a/tests/fixtures/project_staged_validation/new_project_added/staged/composer.lock
+++ b/tests/fixtures/project_staged_validation/new_project_added/staged/vendor/composer/installed.json
@@ -20,9 +20,7 @@
       "description": "This is newly added project but there should be no error because it is not a drupal project",
       "version": "1.3.1",
       "type": "library"
-    }
-  ],
-  "packages-dev": [
+    },
     {
       "name": "drupal/dev-test_module",
       "version": "1.3.0",
@@ -39,5 +37,11 @@
       "version": "1.3.1",
       "type": "library"
     }
+  ],
+  "dev": true,
+  "dev-package-names": [
+    "drupal/dev-test_module",
+    "drupal/dev-test_module2",
+    "other/dev-new_project"
   ]
 }
diff --git a/tests/fixtures/project_staged_validation/no_core_requirements/composer.lock b/tests/fixtures/project_staged_validation/no_core_requirements/composer.lock
deleted file mode 100644
index b44dcb4aea1835fe4164c00b3abdff1e29337a2f..0000000000000000000000000000000000000000
--- a/tests/fixtures/project_staged_validation/no_core_requirements/composer.lock
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-    "packages": [],
-    "packages-dev": []
-}
diff --git a/tests/fixtures/project_staged_validation/no_core_requirements/vendor/.gitkeep b/tests/fixtures/project_staged_validation/no_core_requirements/vendor/.gitkeep
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/tests/fixtures/project_staged_validation/no_core_requirements/vendor/composer/installed.json b/tests/fixtures/project_staged_validation/no_core_requirements/vendor/composer/installed.json
new file mode 100644
index 0000000000000000000000000000000000000000..aee57674eca3eafe9fd5d7923635abe707e0cfe1
--- /dev/null
+++ b/tests/fixtures/project_staged_validation/no_core_requirements/vendor/composer/installed.json
@@ -0,0 +1,3 @@
+{
+    "packages": []
+}
diff --git a/tests/fixtures/project_staged_validation/no_errors/active/composer.lock b/tests/fixtures/project_staged_validation/no_errors/active/vendor/composer/installed.json
similarity index 90%
rename from tests/fixtures/project_staged_validation/no_errors/active/composer.lock
rename to tests/fixtures/project_staged_validation/no_errors/active/vendor/composer/installed.json
index 24e46dc84c259bf9a0edf3400294e65e73488615..cc5be9cb71d644d6cf9e7735f865a287a4853c59 100644
--- a/tests/fixtures/project_staged_validation/no_errors/active/composer.lock
+++ b/tests/fixtures/project_staged_validation/no_errors/active/vendor/composer/installed.json
@@ -21,9 +21,7 @@
       "description": "This project version is changed but there should be no error because it is not a Drupal project.",
       "version": "1.3.1",
       "type": "library"
-    }
-  ],
-  "packages-dev": [
+    },
     {
       "name": "drupal/dev-test_module",
       "version": "1.3.0",
@@ -41,5 +39,11 @@
       "version": "1.3.1",
       "type": "library"
     }
+  ],
+  "dev": true,
+  "dev-package-names": [
+    "drupal/dev-test_module",
+    "other/dev-removed",
+    "other/dev-changed"
   ]
 }
diff --git a/tests/fixtures/project_staged_validation/no_errors/staged/composer.lock b/tests/fixtures/project_staged_validation/no_errors/staged/vendor/composer/installed.json
similarity index 90%
rename from tests/fixtures/project_staged_validation/no_errors/staged/composer.lock
rename to tests/fixtures/project_staged_validation/no_errors/staged/vendor/composer/installed.json
index 5b77d4cbbd893f431d2b27e18efaf489988feea0..e7061278bd8b42cd337d4457ea5d612f690ef23b 100644
--- a/tests/fixtures/project_staged_validation/no_errors/staged/composer.lock
+++ b/tests/fixtures/project_staged_validation/no_errors/staged/vendor/composer/installed.json
@@ -21,9 +21,7 @@
       "description": "This project version is changed but there should be no error because it is not a Drupal project.",
       "version": "1.3.2",
       "type": "library"
-    }
-  ],
-  "packages-dev": [
+    },
     {
       "name": "drupal/dev-test_module",
       "version": "1.3.0",
@@ -41,5 +39,11 @@
       "version": "1.3.2",
       "type": "library"
     }
+  ],
+  "dev": true,
+  "dev-package-names": [
+    "drupal/dev-test_module",
+    "other/dev-new_project",
+    "other/dev-changed"
   ]
 }
diff --git a/tests/fixtures/project_staged_validation/project_removed/active/composer.lock b/tests/fixtures/project_staged_validation/project_removed/active/vendor/composer/installed.json
similarity index 88%
rename from tests/fixtures/project_staged_validation/project_removed/active/composer.lock
rename to tests/fixtures/project_staged_validation/project_removed/active/vendor/composer/installed.json
index ef86aa78d8df4942a0bcf8fe67d0561b044bfe62..ecede582dfe97f47dc980d770cc023715eb8c966 100644
--- a/tests/fixtures/project_staged_validation/project_removed/active/composer.lock
+++ b/tests/fixtures/project_staged_validation/project_removed/active/vendor/composer/installed.json
@@ -20,9 +20,7 @@
       "description": "This project is removed but there should be no error because it is not a Drupal project.",
       "version": "1.3.1",
       "type": "library"
-    }
-  ],
-  "packages-dev": [
+    },
     {
       "name": "drupal/dev-test_theme",
       "version": "1.3.0",
@@ -39,5 +37,11 @@
       "version": "1.3.1",
       "type": "library"
     }
+  ],
+  "dev": true,
+  "dev-package-names": [
+    "drupal/dev-test_theme",
+    "drupal/dev-test_module2",
+    "other/dev-removed"
   ]
 }
diff --git a/tests/fixtures/project_staged_validation/project_removed/staged/composer.lock b/tests/fixtures/project_staged_validation/project_removed/staged/vendor/composer/installed.json
similarity index 81%
rename from tests/fixtures/project_staged_validation/project_removed/staged/composer.lock
rename to tests/fixtures/project_staged_validation/project_removed/staged/vendor/composer/installed.json
index 07773fab4cc96d6d4f41f2e0d0067e3fc51f4e59..d9247b6a62bd0a285f787cbc06aa5b2728e5fa7d 100644
--- a/tests/fixtures/project_staged_validation/project_removed/staged/composer.lock
+++ b/tests/fixtures/project_staged_validation/project_removed/staged/vendor/composer/installed.json
@@ -9,13 +9,15 @@
       "name": "drupal/test_module2",
       "version": "1.3.1",
       "type": "drupal-module"
-    }
-  ],
-  "packages-dev": [
+    },
     {
       "name": "drupal/dev-test_module2",
       "version": "1.3.1",
       "type": "drupal-module"
     }
+  ],
+  "dev": true,
+  "dev-package-names": [
+    "drupal/dev-test_module2"
   ]
 }
diff --git a/tests/fixtures/project_staged_validation/version_changed/active/composer.lock b/tests/fixtures/project_staged_validation/version_changed/active/vendor/composer/installed.json
similarity index 88%
rename from tests/fixtures/project_staged_validation/version_changed/active/composer.lock
rename to tests/fixtures/project_staged_validation/version_changed/active/vendor/composer/installed.json
index 36980fee6ffcbaa1fc93d2c970f8a1d951dd4d2d..da5feae3bc3e1721a115b0d927596b6cad798a4c 100644
--- a/tests/fixtures/project_staged_validation/version_changed/active/composer.lock
+++ b/tests/fixtures/project_staged_validation/version_changed/active/vendor/composer/installed.json
@@ -15,9 +15,7 @@
       "description": "This project version is changed but there should be no error because it is not a Drupal project.",
       "version": "1.3.1",
       "type": "library"
-    }
-  ],
-  "packages-dev": [
+    },
     {
       "name": "drupal/dev-test_module",
       "version": "1.3.0",
@@ -29,5 +27,10 @@
       "version": "1.3.1",
       "type": "library"
     }
+  ],
+  "dev": true,
+  "dev-package-names": [
+    "drupal/dev-test_module",
+    "other/dev-changed"
   ]
 }
diff --git a/tests/fixtures/project_staged_validation/version_changed/staged/composer.lock b/tests/fixtures/project_staged_validation/version_changed/staged/vendor/composer/installed.json
similarity index 88%
rename from tests/fixtures/project_staged_validation/version_changed/staged/composer.lock
rename to tests/fixtures/project_staged_validation/version_changed/staged/vendor/composer/installed.json
index c16841c9dd296bbf7cbeab7bd5373bd020e38890..d35464c3cd1b74a3415fd99045e4850e7bd4b178 100644
--- a/tests/fixtures/project_staged_validation/version_changed/staged/composer.lock
+++ b/tests/fixtures/project_staged_validation/version_changed/staged/vendor/composer/installed.json
@@ -15,9 +15,7 @@
       "description": "This project version is changed but there should be no error because it is not a Drupal project.",
       "version": "1.3.2",
       "type": "library"
-    }
-  ],
-  "packages-dev": [
+    },
     {
       "name": "drupal/dev-test_module",
       "version": "1.3.1",
@@ -29,5 +27,10 @@
       "version": "1.3.2",
       "type": "library"
     }
+  ],
+  "dev": true,
+  "dev-package-names": [
+    "drupal/dev-test_module",
+    "other/dev-changed"
   ]
 }
diff --git a/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php
index bf1469e4a89eeb5ba72331a77008f66faf18e1f1..212f3df1f8af60dbd5d15b3a204d381eaa622212 100644
--- a/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/StagedDatabaseUpdateValidatorTest.php
@@ -52,6 +52,8 @@ class StagedDatabaseUpdateValidatorTest extends AutomaticUpdatesKernelTestBase {
     $active_dir = $this->getDrupalRoot();
     @copy("$active_dir/composer.json", "$stage_dir/composer.json");
     @copy("$active_dir/composer.lock", "$stage_dir/composer.lock");
+    mkdir("$stage_dir/vendor/composer", 0777, TRUE);
+    @copy("$active_dir/vendor/composer/installed.json", "$stage_dir/vendor/composer/installed.json");
 
     // Copy the .install and .post_update.php files from every installed module
     // into the staging directory.
diff --git a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php
index 2ed0e70035b8c605b41909d16356e37887d40c41..13db2035d0e4c7b5003de0d18079ca08b5b4c103 100644
--- a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php
@@ -114,7 +114,8 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase {
     // Composer data.
     $fixture = __DIR__ . '/../../../fixtures/fake-site';
     copy("$fixture/composer.json", 'public://composer.json');
-    copy("$fixture/composer.lock", 'public://composer.lock');
+    mkdir('public://vendor/composer', 0777, TRUE);
+    copy("$fixture/vendor/composer/installed.json", 'public://vendor/composer/installed.json');
 
     $event_dispatcher = $this->container->get('event_dispatcher');
     // Disable the disk space validator, since it doesn't work with vfsStream,
@@ -216,19 +217,6 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase {
     $this->assertEmpty($results);
   }
 
-  /**
-   * Tests validation when a composer.lock file is not found.
-   */
-  public function testNoLockFile(): void {
-    $fixtures_dir = realpath(__DIR__ . '/../../../fixtures/project_staged_validation/no_errors');
-
-    $results = $this->validate("$fixtures_dir/active", $fixtures_dir);
-    $this->assertCount(1, $results);
-    $result = array_pop($results);
-    $this->assertSame("No lockfile found. Unable to read locked packages", (string) $result->getMessages()[0]);
-    $this->assertSame('', (string) $result->getSummary());
-  }
-
 }
 
 /**