Skip to content

Commit afc2033

Browse files
committed
feat: single point for security gate
1 parent 0abc979 commit afc2033

2 files changed

Lines changed: 16 additions & 69 deletions

File tree

NativeScript/runtime/HMRSupport.mm

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,16 @@ void InitializeImportMetaHot(v8::Isolate* isolate,
240240
}
241241

242242
bool HttpFetchText(const std::string& url, std::string& out, std::string& contentType, int& status) {
243+
// Security gate: check if remote module loading is allowed before any HTTP fetch.
244+
// This is the single point of enforcement for all HTTP module loading.
245+
if (!IsRemoteUrlAllowed(url)) {
246+
status = 403; // Forbidden
247+
if (IsScriptLoadingLogEnabled()) {
248+
Log(@"[http-esm][security][blocked] %s", url.c_str());
249+
}
250+
return false;
251+
}
252+
243253
@autoreleasepool {
244254
NSURL* u = [NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
245255
if (!u) { status = 0; return false; }

NativeScript/runtime/ModuleInternalCallbacks.mm

Lines changed: 6 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -96,28 +96,11 @@ static inline bool EndsWith(const std::string& value, const std::string& suffix)
9696
return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin());
9797
}
9898

99-
// HTTP ESM loader helpers
100-
10199
static inline bool StartsWith(const std::string& s, const char* prefix) {
102100
size_t n = strlen(prefix);
103101
return s.size() >= n && s.compare(0, n, prefix) == 0;
104102
}
105103

106-
// Security gate
107-
// In debug mode, all URLs are allowed. In production, checks security.allowRemoteModules and security.remoteModuleAllowlist
108-
static inline bool IsHttpUrlAllowedForLoading(const std::string& url) {
109-
return IsRemoteUrlAllowed(url);
110-
}
111-
112-
// Helper to create a security error message for blocked remote modules
113-
static std::string GetRemoteModuleBlockedMessage(const std::string& url) {
114-
if (!IsRemoteModulesAllowed()) {
115-
return "Remote ES modules are not allowed in production. URL: " + url +
116-
". Enable via security.allowRemoteModules in nativescript.config";
117-
}
118-
return "Remote URL not in security.remoteModuleAllowlist: " + url;
119-
}
120-
121104

122105
static v8::MaybeLocal<v8::Module> CompileModuleFromSource(v8::Isolate* isolate, v8::Local<v8::Context> context,
123106
const std::string& code, const std::string& urlStr) {
@@ -741,17 +724,8 @@ static bool IsDocumentsPath(const std::string& path) {
741724
// ── Early absolute-HTTP fast path ─────────────────────────────
742725
// If the specifier itself is an absolute HTTP(S) URL, resolve it immediately via
743726
// the HTTP loader and return before any filesystem candidate logic runs.
727+
// Security: HttpFetchText gates remote module access centrally.
744728
if (StartsWith(spec, "http://") || StartsWith(spec, "https://")) {
745-
// Security check: block if remote modules not allowed
746-
if (!IsHttpUrlAllowedForLoading(spec)) {
747-
std::string msg = GetRemoteModuleBlockedMessage(spec);
748-
if (IsScriptLoadingLogEnabled()) {
749-
Log(@"[resolver][security] blocked remote module: %s", spec.c_str());
750-
}
751-
isolate->ThrowException(v8::Exception::Error(tns::ToV8String(isolate, msg.c_str())));
752-
return v8::MaybeLocal<v8::Module>();
753-
}
754-
755729
std::string key = CanonicalizeHttpUrlKey(spec);
756730
// Added instrumentation for unified phase logging
757731
Log(@"[http-esm][compile][begin] %s", key.c_str());
@@ -850,7 +824,7 @@ static bool IsDocumentsPath(const std::string& path) {
850824
// ("./" or "../") or root-absolute ("/") specifiers should resolve against the
851825
// referrer's URL, not the local filesystem. Mirror browser behavior by using NSURL
852826
// to construct the absolute URL, then return an HTTP-loaded module immediately.
853-
// Security: Gated by IsHttpUrlAllowedForLoading.
827+
// Security: HttpFetchText gates remote module access centrally.
854828
bool referrerIsHttp = (!referrerPath.empty() && (StartsWith(referrerPath, "http://") || StartsWith(referrerPath, "https://")));
855829
bool specIsRootAbs = !spec.empty() && spec[0] == '/';
856830
if (referrerIsHttp && (specIsRelative || specIsRootAbs)) {
@@ -871,16 +845,7 @@ static bool IsDocumentsPath(const std::string& path) {
871845
}
872846
}
873847
if (!resolvedHttp.empty() && (StartsWith(resolvedHttp, "http://") || StartsWith(resolvedHttp, "https://"))) {
874-
// Security check: block if remote modules not allowed
875-
if (!IsHttpUrlAllowedForLoading(resolvedHttp)) {
876-
std::string msg = GetRemoteModuleBlockedMessage(resolvedHttp);
877-
if (IsScriptLoadingLogEnabled()) {
878-
Log(@"[resolver][security] blocked remote module (rel): %s", resolvedHttp.c_str());
879-
}
880-
isolate->ThrowException(v8::Exception::Error(tns::ToV8String(isolate, msg.c_str())));
881-
return v8::MaybeLocal<v8::Module>();
882-
}
883-
848+
// Security: HttpFetchText gates remote module access centrally.
884849
if (IsScriptLoadingLogEnabled()) {
885850
Log(@"[resolver][http-rel] base=%s spec=%s -> %s", referrerPath.c_str(), spec.c_str(), resolvedHttp.c_str());
886851
}
@@ -1045,17 +1010,8 @@ static bool IsDocumentsPath(const std::string& path) {
10451010
std::string absPath;
10461011

10471012
// If the specifier is an HTTP(S) URL, fetch via HTTP loader and return
1013+
// Security: HttpFetchText gates remote module access centrally.
10481014
if (StartsWith(spec, "http://") || StartsWith(spec, "https://")) {
1049-
// Security check: block if remote modules not allowed
1050-
if (!IsHttpUrlAllowedForLoading(spec)) {
1051-
std::string msg = GetRemoteModuleBlockedMessage(spec);
1052-
if (IsScriptLoadingLogEnabled()) {
1053-
Log(@"[resolver][security] blocked remote module: %s", spec.c_str());
1054-
}
1055-
isolate->ThrowException(v8::Exception::Error(tns::ToV8String(isolate, msg.c_str())));
1056-
return v8::MaybeLocal<v8::Module>();
1057-
}
1058-
10591015
std::string key = CanonicalizeHttpUrlKey(spec);
10601016
if (IsScriptLoadingLogEnabled()) {
10611017
Log(@"[http-esm][compile][begin] %s", key.c_str());
@@ -1141,7 +1097,7 @@ static bool IsDocumentsPath(const std::string& path) {
11411097

11421098
// If a candidate accidentally embeds a collapsed HTTP URL like '/app/http:/host/...',
11431099
// reconstruct the HTTP URL and resolve via the HTTP loader instead of touching the filesystem.
1144-
// Security: Gated by IsHttpUrlAllowedForLoading.
1100+
// Security: HttpFetchText gates remote module access centrally.
11451101
auto rerouteHttpIfEmbedded = [&](const std::string& p) -> bool {
11461102
size_t pos1 = p.find("/http:/");
11471103
size_t pos2 = p.find("/https:/");
@@ -1156,16 +1112,6 @@ static bool IsDocumentsPath(const std::string& path) {
11561112
}
11571113
if (!(StartsWith(tail, "http://") || StartsWith(tail, "https://"))) return false;
11581114

1159-
// Security check: block if remote modules not allowed
1160-
if (!IsHttpUrlAllowedForLoading(tail)) {
1161-
if (IsScriptLoadingLogEnabled()) {
1162-
Log(@"[resolver][security] blocked embedded remote module: %s", tail.c_str());
1163-
}
1164-
std::string msg = GetRemoteModuleBlockedMessage(tail);
1165-
isolate->ThrowException(v8::Exception::Error(tns::ToV8String(isolate, msg.c_str())));
1166-
return false;
1167-
}
1168-
11691115
if (IsScriptLoadingLogEnabled()) { Log(@"[resolver][http-embedded] %s -> %s", p.c_str(), tail.c_str()); }
11701116
std::string key = CanonicalizeHttpUrlKey(tail);
11711117
auto itExisting = g_moduleRegistry.find(key);
@@ -1970,17 +1916,8 @@ static bool IsDocumentsPath(const std::string& path) {
19701916
}
19711917

19721918
// If spec is an HTTP(S) URL, try HTTP fetch+compile directly
1919+
// Security: HttpFetchText gates remote module access centrally.
19731920
if (!normalizedSpec.empty() && (StartsWith(normalizedSpec, "http://") || StartsWith(normalizedSpec, "https://"))) {
1974-
// Security check: block if remote modules not allowed
1975-
if (!IsHttpUrlAllowedForLoading(normalizedSpec)) {
1976-
std::string msg = GetRemoteModuleBlockedMessage(normalizedSpec);
1977-
if (IsScriptLoadingLogEnabled()) {
1978-
Log(@"[dyn-import][security] blocked remote module: %s", normalizedSpec.c_str());
1979-
}
1980-
resolver->Reject(context, v8::Exception::Error(tns::ToV8String(isolate, msg.c_str()))).FromMaybe(false);
1981-
return scope.Escape(resolver->GetPromise());
1982-
}
1983-
19841921
if (IsScriptLoadingLogEnabled()) {
19851922
Log(@"[dyn-import][http-loader] trying URL %s", normalizedSpec.c_str());
19861923
}

0 commit comments

Comments
 (0)