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.
- package/dist/commands/appStoreEvaluate.js +56 -12
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runcloud-0.2.154.tgz +0 -0
- package/runcloud-0.2.152.tgz +0 -0
|
@@ -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
|
|
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:
|
|
296
|
-
{ name: 'location', used:
|
|
297
|
-
{ name: 'photos', used: /expo-
|
|
298
|
-
{ name: 'microphone', used:
|
|
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) =>
|
|
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:
|
|
315
|
-
{ name: 'location', used:
|
|
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:
|
|
358
|
+
{ name: 'microphone', used: MICROPHONE_USE, keys: ['NSMicrophoneUsageDescription'] },
|
|
318
359
|
];
|
|
319
|
-
const
|
|
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.
|
|
1
|
+
export const CLI_VERSION = '0.2.154';
|
package/package.json
CHANGED
|
Binary file
|
package/runcloud-0.2.152.tgz
DELETED
|
Binary file
|