Skip to content

Conversation

@NathanWalker
Copy link
Contributor

@NathanWalker NathanWalker commented Dec 26, 2025

Security gating system for remote (HTTP/HTTPS) ES module loading in production.

  • Enabled a new security section nativescript.config, enabling remote modules and specifying an allowlist of URL prefixes for permitted remote module sources.

In development, defaults to always enabled.
In production, only enabled via explicit developer approval in nativescript.config settings:

{
  "security": {
    "allowRemoteModules": true,   
    "remoteModuleAllowlist": [   // Optional: restrict to specific URL prefixes
      "https://cdn.example.com/modules/",
      "https://esm.sh/"
    ]
  }
}

If the optional remoteModuleAllowlist is omitted, it allows any url.

Docs added: https://github.com/NativeScript/docs/pull/191/files

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a security gating system for remote (HTTP/HTTPS) ES module loading in production builds. The feature allows developers to enable remote module imports via a security configuration section, with optional URL allowlisting for restricting module sources.

Key Changes:

  • Added security configuration support in nativescript.config (package.json) with allowRemoteModules flag and optional remoteModuleAllowlist
  • Implemented security checks at all HTTP module loading points in the runtime
  • Added comprehensive test suite for security behavior

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
TestRunner/app/tests/index.js Added import for new RemoteModuleSecurityTests
TestRunner/app/tests/RemoteModuleSecurityTests.js New comprehensive test suite covering debug mode behavior, security configuration, URL allowlist matching, and edge cases
TestRunner/app/package.json Added security configuration with allowRemoteModules flag and allowlist for testing
NativeScript/runtime/DevFlags.h Added public API declarations for security config functions (IsRemoteModulesAllowed, IsRemoteUrlAllowed, InitializeSecurityConfig)
NativeScript/runtime/DevFlags.mm Implemented security configuration parsing and URL allowlist prefix matching logic
NativeScript/runtime/ModuleInternalCallbacks.mm Integrated security checks at all HTTP module loading entry points (absolute URLs, relative URLs from HTTP referrers, embedded URLs, dynamic imports)
Comments suppressed due to low confidence (1)

NativeScript/runtime/ModuleInternalCallbacks.mm:1202

  • Bug: After throwing a security exception at line 1165, the lambda returns false, which allows execution to continue at line 1204 and beyond. This is inconsistent with other security checks in this file (e.g., lines 746-753, 875-882, 1050-1057) which immediately return an empty MaybeLocal<v8::Module>() after throwing. The function should check if an exception was thrown and return immediately, or the lambda should signal an error differently so the caller at line 1191 can detect it and return early.
      // Security check: block if remote modules not allowed
      if (!IsHttpUrlAllowedForLoading(tail)) {
        if (IsScriptLoadingLogEnabled()) {
          Log(@"[resolver][security] blocked embedded remote module: %s", tail.c_str());
        }
        std::string msg = GetRemoteModuleBlockedMessage(tail);
        isolate->ThrowException(v8::Exception::Error(tns::ToV8String(isolate, msg.c_str())));
        return false;
      }
      
      if (IsScriptLoadingLogEnabled()) { Log(@"[resolver][http-embedded] %s -> %s", p.c_str(), tail.c_str()); }
      std::string key = CanonicalizeHttpUrlKey(tail);
      auto itExisting = g_moduleRegistry.find(key);
      if (itExisting != g_moduleRegistry.end()) {
        v8::Local<v8::Module> existing = itExisting->second.Get(isolate);
        if (!existing.IsEmpty() && existing->GetStatus() != v8::Module::kErrored) {
          return !v8::MaybeLocal<v8::Module>(existing).IsEmpty();
        }
      }
      std::string body; std::string ct; int status = 0;
      if (HttpFetchText(tail, body, ct, status) && !body.empty()) {
        v8::MaybeLocal<v8::Module> m = CompileModuleForResolveRegisterOnly(isolate, context, body, key);
        if (!m.IsEmpty()) {
          v8::Local<v8::Module> mod; if (m.ToLocal(&mod)) { return true; }
        }
      }
      if (RuntimeConfig.IsDebug) {
        std::string msg = "HTTP embedded import failed: " + tail;
        isolate->ThrowException(v8::Exception::Error(tns::ToV8String(isolate, msg.c_str())));
      }
      return false;
    };
    if (rerouteHttpIfEmbedded(absPath)) {
      // Return the module from registry; V8 will pick it up
      std::string key = CanonicalizeHttpUrlKey(absPath.substr(absPath.find("http")));
      auto itExisting = g_moduleRegistry.find(key);
      if (itExisting != g_moduleRegistry.end()) {
        v8::Local<v8::Module> existing = itExisting->second.Get(isolate);
        if (!existing.IsEmpty()) {
          return v8::MaybeLocal<v8::Module>(existing);
        }
      }
      // If not present, fall through to normal checks
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@NathanWalker NathanWalker merged commit 721ceaf into main Jan 4, 2026
8 checks passed
@NathanWalker NathanWalker deleted the feat/remote-module-security branch January 4, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants