From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Tue, 18 Mar 2025 10:41:27 +0100
Subject: fix: ensure TraverseParent bails on resource path exit

Electron's security model requires that we do not traverse outside of the
resource path. This commit ensures that the TraverseParent function
bails out if the parent path is outside of the resource path.

diff --git a/src/node_modules.cc b/src/node_modules.cc
index 210a01d24e11764dc9fc37a77b354f11383693f8..4e9f70a1c41b44d2a1863b778d4f1e37279178d9 100644
--- a/src/node_modules.cc
+++ b/src/node_modules.cc
@@ -290,8 +290,41 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
     Realm* realm, const std::filesystem::path& check_path) {
   std::filesystem::path current_path = check_path;
   auto env = realm->env();
+  Isolate* isolate = env->isolate();
   const bool is_permissions_enabled = env->permission()->enabled();
 
+  // Get the resources path with trailing slash.
+  std::string resources_path;
+  {
+    HandleScope handle_scope(isolate);
+    Local<Value> resources_path_value;
+    Local<Object> process_object = env->process_object();
+
+    Local<String> resources_path_key =
+        String::NewFromUtf8Literal(isolate, "resourcesPath");
+    if (process_object->Get(env->context(), resources_path_key)
+            .ToLocal(&resources_path_value) &&
+        resources_path_value->IsString()) {
+      resources_path = *String::Utf8Value(isolate, resources_path_value);
+      if (!resources_path.empty() && !resources_path.ends_with(kPathSeparator)) {
+        resources_path += kPathSeparator;
+      }
+    }
+  }
+
+  auto starts_with = [](const std::string& str, const std::string& prefix) -> bool {
+    if (prefix.size() > str.size()) return false;
+    return std::equal(
+        prefix.begin(), prefix.end(), str.begin(),
+        [](char a, char b) {
+          return std::tolower(static_cast<unsigned char>(a)) ==
+                std::tolower(static_cast<unsigned char>(b));
+        });
+  };
+
+  bool did_original_path_start_with_resources_path = starts_with(check_path.
+      generic_string(), resources_path);
+
   do {
     current_path = current_path.parent_path();
 
@@ -310,6 +343,12 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
       return nullptr;
     }
 
+    // If current path is outside the resources path, bail.
+    if (did_original_path_start_with_resources_path &&
+        !starts_with(current_path.generic_string(), resources_path)) {
+      return nullptr;
+    }
+
     // Check if the path ends with `/node_modules`
     if (current_path.generic_string().ends_with("/node_modules")) {
       return nullptr;
