@onekeyfe/react-native-split-bundle-loader 1.1.50 → 1.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -85,6 +85,26 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
85
85
  }
86
86
  }
87
87
 
88
+ // -----------------------------------------------------------------------
89
+ // resolveSegmentPath (Phase 3)
90
+ // -----------------------------------------------------------------------
91
+
92
+ override fun resolveSegmentPath(relativePath: String, sha256: String, promise: Promise) {
93
+ try {
94
+ val absolutePath = resolveSegmentPath(relativePath, sha256)
95
+ if (absolutePath != null) {
96
+ promise.resolve(absolutePath)
97
+ } else {
98
+ promise.reject(
99
+ "SPLIT_BUNDLE_NOT_FOUND",
100
+ "Segment file not found: $relativePath"
101
+ )
102
+ }
103
+ } catch (e: Exception) {
104
+ promise.reject("SPLIT_BUNDLE_RESOLVE_ERROR", e.message, e)
105
+ }
106
+ }
107
+
88
108
  // -----------------------------------------------------------------------
89
109
  // loadSegment
90
110
  // -----------------------------------------------------------------------
@@ -96,6 +116,10 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
96
116
  sha256: String,
97
117
  promise: Promise
98
118
  ) {
119
+ // NOTE (#44): sha256 param is not verified at load time by design.
120
+ // Per §6.4.1, runtime trusts that OTA install has already verified
121
+ // segment integrity. Builtin segments are signed as part of the APK/IPA.
122
+ // If runtime SHA-256 verification is needed, add it here.
99
123
  try {
100
124
  val segId = segmentId.toInt()
101
125
 
@@ -157,14 +181,29 @@ class SplitBundleLoaderModule(reactContext: ReactApplicationContext) :
157
181
  // Path resolution helpers
158
182
  // -----------------------------------------------------------------------
159
183
 
184
+ /**
185
+ * Verify resolved path stays within the expected root directory (#45).
186
+ * Prevents path traversal via ".." components in relativePath.
187
+ */
188
+ private fun isPathWithinRoot(root: File, resolved: File): Boolean {
189
+ return resolved.canonicalPath.startsWith(root.canonicalPath + File.separator) ||
190
+ resolved.canonicalPath == root.canonicalPath
191
+ }
192
+
160
193
  private fun resolveSegmentPath(relativePath: String, expectedSha256: String): String? {
194
+ // Path traversal guard (#45)
195
+ if (relativePath.contains("..")) {
196
+ SBLLogger.warn("Path traversal rejected: $relativePath")
197
+ return null
198
+ }
199
+
161
200
  // 1. Try OTA bundle directory first
162
201
  val otaBundlePath = getOtaBundlePath()
163
202
  if (!otaBundlePath.isNullOrEmpty()) {
164
203
  val otaRoot = File(otaBundlePath).parentFile
165
204
  if (otaRoot != null) {
166
205
  val candidate = File(otaRoot, relativePath)
167
- if (candidate.exists()) {
206
+ if (candidate.exists() && isPathWithinRoot(otaRoot, candidate)) {
168
207
  return candidate.absolutePath
169
208
  }
170
209
  }
@@ -10,5 +10,9 @@
10
10
  sha256:(NSString *)sha256
11
11
  resolve:(RCTPromiseResolveBlock)resolve
12
12
  reject:(RCTPromiseRejectBlock)reject;
13
+ - (void)resolveSegmentPath:(NSString *)relativePath
14
+ sha256:(NSString *)sha256
15
+ resolve:(RCTPromiseResolveBlock)resolve
16
+ reject:(RCTPromiseRejectBlock)reject;
13
17
 
14
18
  @end
@@ -66,6 +66,11 @@
66
66
 
67
67
  /// Registers a segment with the current runtime, supporting both legacy bridge
68
68
  /// and bridgeless (RCTHost) architectures (#13).
69
+ ///
70
+ /// Thread safety (#57): This method is called from the TurboModule (JS thread).
71
+ /// RCTBridge.registerSegmentWithId:path: internally registers the segment with
72
+ /// the Hermes runtime on the JS thread, which is the correct calling context.
73
+ /// No queue dispatch is needed.
69
74
  + (BOOL)registerSegment:(int)segmentId path:(NSString *)path error:(NSError **)outError
70
75
  {
71
76
  // Try legacy bridge first
@@ -127,6 +132,62 @@
127
132
  }
128
133
  }
129
134
 
135
+ // MARK: - Path resolution helper
136
+
137
+ /// Resolves a relative segment path to an absolute path, checking OTA then builtin.
138
+ /// Returns nil if the segment file is not found.
139
+ + (nullable NSString *)resolveAbsolutePath:(NSString *)relativePath
140
+ {
141
+ // 1. Try OTA bundle root first
142
+ NSString *otaPath = [SplitBundleLoader otaBundlePath];
143
+ if (otaPath) {
144
+ NSString *otaRoot = [otaPath stringByDeletingLastPathComponent];
145
+ NSString *candidate = [[otaRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
146
+ if ([candidate hasPrefix:otaRoot] &&
147
+ [[NSFileManager defaultManager] fileExistsAtPath:candidate]) {
148
+ return candidate;
149
+ }
150
+ }
151
+
152
+ // 2. Fallback to builtin resource path
153
+ NSString *builtinRoot = [[NSBundle mainBundle] resourcePath];
154
+ NSString *candidate = [[builtinRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
155
+ if ([candidate hasPrefix:builtinRoot] &&
156
+ [[NSFileManager defaultManager] fileExistsAtPath:candidate]) {
157
+ return candidate;
158
+ }
159
+
160
+ return nil;
161
+ }
162
+
163
+ // MARK: - resolveSegmentPath (Phase 3)
164
+
165
+ - (void)resolveSegmentPath:(NSString *)relativePath
166
+ sha256:(NSString *)sha256
167
+ resolve:(RCTPromiseResolveBlock)resolve
168
+ reject:(RCTPromiseRejectBlock)reject
169
+ {
170
+ @try {
171
+ if ([relativePath containsString:@".."]) {
172
+ reject(@"SPLIT_BUNDLE_INVALID_PATH",
173
+ [NSString stringWithFormat:@"Path traversal rejected: %@", relativePath],
174
+ nil);
175
+ return;
176
+ }
177
+
178
+ NSString *absolutePath = [SplitBundleLoader resolveAbsolutePath:relativePath];
179
+ if (absolutePath) {
180
+ resolve(absolutePath);
181
+ } else {
182
+ reject(@"SPLIT_BUNDLE_NOT_FOUND",
183
+ [NSString stringWithFormat:@"Segment file not found: %@", relativePath],
184
+ nil);
185
+ }
186
+ } @catch (NSException *exception) {
187
+ reject(@"SPLIT_BUNDLE_RESOLVE_ERROR", exception.reason, nil);
188
+ }
189
+ }
190
+
130
191
  // MARK: - loadSegment
131
192
 
132
193
  - (void)loadSegment:(double)segmentId
@@ -138,26 +199,16 @@
138
199
  {
139
200
  @try {
140
201
  int segId = (int)segmentId;
141
- NSString *absolutePath = nil;
142
202
 
143
- // 1. Try OTA bundle root first
144
- NSString *otaPath = [SplitBundleLoader otaBundlePath];
145
- if (otaPath) {
146
- NSString *otaRoot = [otaPath stringByDeletingLastPathComponent];
147
- NSString *candidate = [otaRoot stringByAppendingPathComponent:relativePath];
148
- if ([[NSFileManager defaultManager] fileExistsAtPath:candidate]) {
149
- absolutePath = candidate;
150
- }
203
+ // Path traversal guard (#45)
204
+ if ([relativePath containsString:@".."]) {
205
+ reject(@"SPLIT_BUNDLE_INVALID_PATH",
206
+ [NSString stringWithFormat:@"Path traversal rejected: %@", relativePath],
207
+ nil);
208
+ return;
151
209
  }
152
210
 
153
- // 2. Fallback to builtin resource path
154
- if (!absolutePath) {
155
- NSString *builtinRoot = [[NSBundle mainBundle] resourcePath];
156
- NSString *candidate = [builtinRoot stringByAppendingPathComponent:relativePath];
157
- if ([[NSFileManager defaultManager] fileExistsAtPath:candidate]) {
158
- absolutePath = candidate;
159
- }
160
- }
211
+ NSString *absolutePath = [SplitBundleLoader resolveAbsolutePath:relativePath];
161
212
 
162
213
  if (!absolutePath) {
163
214
  reject(@"SPLIT_BUNDLE_NOT_FOUND",
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeSplitBundleLoader.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAAQ,cAAc;AAqBlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,mBAAmB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeSplitBundleLoader.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAAQ,cAAc;AAsBlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,mBAAmB,CAAC","ignoreList":[]}
@@ -9,6 +9,7 @@ export interface Spec extends TurboModule {
9
9
  bundleVersion?: string;
10
10
  }>;
11
11
  loadSegment(segmentId: number, segmentKey: string, relativePath: string, sha256: string): Promise<void>;
12
+ resolveSegmentPath(relativePath: string, sha256: string): Promise<string>;
12
13
  }
13
14
  declare const _default: Spec;
14
15
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"NativeSplitBundleLoader.d.ts","sourceRoot":"","sources":["../../../src/NativeSplitBundleLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,uBAAuB,IAAI,OAAO,CAAC;QACjC,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC,CAAC;IACH,WAAW,CACT,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;;AAED,wBAA2E"}
1
+ {"version":3,"file":"NativeSplitBundleLoader.d.ts","sourceRoot":"","sources":["../../../src/NativeSplitBundleLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,uBAAuB,IAAI,OAAO,CAAC;QACjC,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC,CAAC;IACH,WAAW,CACT,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3E;;AAED,wBAA2E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-split-bundle-loader",
3
- "version": "1.1.50",
3
+ "version": "1.1.51",
4
4
  "description": "react-native-split-bundle-loader",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -17,6 +17,7 @@ export interface Spec extends TurboModule {
17
17
  relativePath: string,
18
18
  sha256: string,
19
19
  ): Promise<void>;
20
+ resolveSegmentPath(relativePath: string, sha256: string): Promise<string>;
20
21
  }
21
22
 
22
23
  export default TurboModuleRegistry.getEnforcing<Spec>('SplitBundleLoader');