runcloud 0.2.152 → 0.2.154

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.
@@ -151,7 +151,9 @@ function evaluateLocalItem(root, appJson, item) {
151
151
  return evaluateConfigItem(appJson, item, rule);
152
152
  switch (item.id) {
153
153
  case 'hardcoded_secrets':
154
- return patternItem(root, item, /sk-[A-Za-z0-9]{20,}|AIza[0-9A-Za-z_-]{30,}|-----BEGIN.*PRIVATE KEY|api[_-]?key\s*[:=]\s*['"][A-Za-z0-9]{16,}/i, 'fail', 'Hardcoded secret-like value found.', 'No secret-like values found.');
154
+ return patternItem(root, item, /sk-[A-Za-z0-9]{20,}|AIza[0-9A-Za-z_-]{30,}|-----BEGIN.*PRIVATE KEY|api[_-]?key\s*[:=]\s*['"][A-Za-z0-9]{16,}/i, 'fail', 'Hardcoded secret-like value found.', 'No secret-like values found.', {
155
+ filter: (match) => !isFirebaseClientKey(root, match),
156
+ });
155
157
  case 'http_endpoints':
156
158
  return patternItem(root, item, /http:\/\/(?!localhost|127\.0\.0\.1)/i, 'warning', 'Non-local HTTP endpoint found.', 'No non-local HTTP endpoints found.');
157
159
  case 'placeholder_content':
@@ -287,21 +289,60 @@ function patternItem(root, item, regex, statusOnHit, messageOnHit, messageOnMiss
287
289
  return makeItem(item, statusOnHit, messageOnHit, evidence(matches));
288
290
  return makeItem(item, 'pass', messageOnMiss, `scan: no matches for ${regex}`);
289
291
  }
292
+ const MICROPHONE_USE = /expo-av|AudioRecorder|requestRecordingPermissionsAsync|requestMicrophonePermissions|useMicrophonePermissions|recordAsync/i;
293
+ const CAMERA_USE = /expo-camera|CameraView|requestCameraPermissions|launchCameraAsync/i;
294
+ const LOCATION_USE = /expo-location|getCurrentPositionAsync|watchPositionAsync/i;
295
+ const ANDROID_PACKAGE_PERMISSIONS = [
296
+ { pkg: 'expo-camera', permissions: ['CAMERA'] },
297
+ { pkg: 'expo-camera', option: 'recordAudioAndroid', permissions: ['RECORD_AUDIO'] },
298
+ { pkg: 'expo-image-picker', permissions: ['CAMERA', 'READ_EXTERNAL_STORAGE'] },
299
+ { pkg: 'expo-image-picker', option: 'microphonePermission', permissions: ['RECORD_AUDIO'] },
300
+ { pkg: 'expo-audio', option: 'recordAudioAndroid', permissions: ['RECORD_AUDIO'] },
301
+ { pkg: 'expo-location', permissions: ['ACCESS_COARSE_LOCATION', 'ACCESS_FINE_LOCATION'] },
302
+ { pkg: 'expo-media-library', permissions: ['READ_EXTERNAL_STORAGE'] },
303
+ { pkg: 'expo-notifications', permissions: ['POST_NOTIFICATIONS'] },
304
+ ];
305
+ function isFirebaseClientKey(root, match) {
306
+ if (!/AIza[0-9A-Za-z_-]{30,}/.test(match.text))
307
+ return false;
308
+ if (/(^|\/)(GoogleService-Info\.plist|google-services\.json)$/.test(match.file))
309
+ return true;
310
+ if (!/\bapiKey['"]?\s*:/.test(match.text))
311
+ return false;
312
+ return /initializeApp\s*\(|authDomain/.test(readText(path.join(root, match.file)) ?? '');
313
+ }
290
314
  function missingPermissionRequirements(root, appJson) {
291
315
  const missing = [...missingIosUsageDescriptions(root, appJson)];
292
- const permissions = arrayOfStrings(getPath(appJson, ['expo', 'android', 'permissions']));
316
+ const bare = (permission) => permission.replace(/^android\.permission\./, '');
317
+ const blocked = new Set(arrayOfStrings(getPath(appJson, ['expo', 'android', 'blockedPermissions'])).map(bare));
318
+ const picker = configPluginOptions(appJson, 'expo-image-picker');
319
+ if (picker?.cameraPermission === false)
320
+ blocked.add('CAMERA');
321
+ if (picker?.microphonePermission === false)
322
+ blocked.add('RECORD_AUDIO');
323
+ const installed = installedPackages(root);
324
+ const granted = new Set(arrayOfStrings(getPath(appJson, ['expo', 'android', 'permissions'])).map(bare));
325
+ for (const { pkg, option, permissions } of ANDROID_PACKAGE_PERMISSIONS) {
326
+ const listed = configPluginOptions(appJson, pkg);
327
+ const auto = PLUGIN_PURPOSE_STRINGS[pkg]?.auto ?? false;
328
+ const adds = option === undefined
329
+ ? installed.has(pkg) || listed !== undefined
330
+ : listed ? listed[option] !== false : auto && installed.has(pkg);
331
+ if (adds)
332
+ permissions.forEach((permission) => granted.add(permission));
333
+ }
293
334
  const source = sourceText(root, ['app', 'src', 'components']);
294
335
  const androidChecks = [
295
- { name: 'camera', used: /expo-camera|CameraView|requestCameraPermissions/i, permissions: ['CAMERA'] },
296
- { name: 'location', used: /expo-location|getCurrentPositionAsync|watchPositionAsync/i, permissions: ['ACCESS_FINE_LOCATION', 'ACCESS_COARSE_LOCATION'] },
297
- { name: 'photos', used: /expo-image-picker|expo-media-library|MediaLibrary/i, permissions: ['READ_MEDIA_IMAGES', 'READ_EXTERNAL_STORAGE'] },
298
- { name: 'microphone', used: /expo-av|expo-audio|Audio\.|requestMicrophonePermissions/i, permissions: ['RECORD_AUDIO'] },
336
+ { name: 'camera', used: CAMERA_USE, permissions: ['CAMERA'] },
337
+ { name: 'location', used: LOCATION_USE, permissions: ['ACCESS_FINE_LOCATION', 'ACCESS_COARSE_LOCATION'] },
338
+ { name: 'photos', used: /expo-media-library|MediaLibrary/i, permissions: ['READ_MEDIA_IMAGES', 'READ_EXTERNAL_STORAGE'] },
339
+ { name: 'microphone', used: MICROPHONE_USE, permissions: ['RECORD_AUDIO'] },
299
340
  { name: 'notifications', used: /expo-notifications/i, permissions: ['POST_NOTIFICATIONS'] },
300
341
  ];
301
342
  for (const check of androidChecks) {
302
343
  if (!check.used.test(source))
303
344
  continue;
304
- if (!check.permissions.some((permission) => permissions.includes(permission))) {
345
+ if (!check.permissions.some((permission) => granted.has(permission) && !blocked.has(permission))) {
305
346
  missing.push(`android ${check.name} permission (${check.permissions.join(' or ')})`);
306
347
  }
307
348
  }
@@ -311,13 +352,12 @@ function missingIosUsageDescriptions(root, appJson) {
311
352
  const infoPlist = objectValue(getPath(appJson, ['expo', 'ios', 'infoPlist']));
312
353
  const source = sourceText(root, ['app', 'src', 'components']);
313
354
  const checks = [
314
- { name: 'camera', used: /expo-camera|CameraView|requestCameraPermissions/i, keys: ['NSCameraUsageDescription'] },
315
- { name: 'location', used: /expo-location|getCurrentPositionAsync|watchPositionAsync/i, keys: ['NSLocationWhenInUseUsageDescription', 'NSLocationAlwaysAndWhenInUseUsageDescription'] },
355
+ { name: 'camera', used: CAMERA_USE, keys: ['NSCameraUsageDescription'] },
356
+ { name: 'location', used: LOCATION_USE, keys: ['NSLocationWhenInUseUsageDescription', 'NSLocationAlwaysAndWhenInUseUsageDescription'] },
316
357
  { name: 'photos', used: /expo-image-picker|expo-media-library|MediaLibrary/i, keys: ['NSPhotoLibraryUsageDescription'] },
317
- { name: 'microphone', used: /expo-av|AudioRecorder|requestRecordingPermissionsAsync|requestMicrophonePermissions/i, keys: ['NSMicrophoneUsageDescription'] },
358
+ { name: 'microphone', used: MICROPHONE_USE, keys: ['NSMicrophoneUsageDescription'] },
318
359
  ];
319
- const pkg = readJson(path.join(root, 'package.json'));
320
- const installed = new Set(['dependencies', 'devDependencies'].flatMap((field) => Object.keys(objectValue(getPath(pkg, [field])))));
360
+ const installed = installedPackages(root);
321
361
  const missing = [];
322
362
  for (const check of checks) {
323
363
  if (!check.used.test(source))
@@ -358,6 +398,10 @@ function builtWithPurposeString(key, infoPlist, appJson, installed) {
358
398
  }
359
399
  return written;
360
400
  }
401
+ function installedPackages(root) {
402
+ const pkg = readJson(path.join(root, 'package.json'));
403
+ return new Set(['dependencies', 'devDependencies'].flatMap((field) => Object.keys(objectValue(getPath(pkg, [field])))));
404
+ }
361
405
  function configPluginOptions(appJson, name) {
362
406
  const plugins = getPath(appJson, ['expo', 'plugins']);
363
407
  if (!Array.isArray(plugins))
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.2.152';
1
+ export const CLI_VERSION = '0.2.154';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.2.152",
3
+ "version": "0.2.154",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
Binary file
Binary file