mirror of
				https://github.com/f-droid/fdroidserver.git
				synced 2025-11-04 14:30:30 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			3003 lines
		
	
	
	
		
			108 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			3003 lines
		
	
	
	
		
			108 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/env python3
 | 
						|
#
 | 
						|
# scanner.py - part of the FDroid server tools
 | 
						|
# Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com
 | 
						|
#
 | 
						|
# This program is free software: you can redistribute it and/or modify
 | 
						|
# it under the terms of the GNU Affero General Public License as published by
 | 
						|
# the Free Software Foundation, either version 3 of the License, or
 | 
						|
# (at your option) any later version.
 | 
						|
#
 | 
						|
# This program is distributed in the hope that it will be useful,
 | 
						|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
# GNU Affero General Public License for more details.
 | 
						|
#
 | 
						|
# You should have received a copy of the GNU Affero General Public License
 | 
						|
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
import itertools
 | 
						|
import json
 | 
						|
import logging
 | 
						|
import os
 | 
						|
import re
 | 
						|
import sys
 | 
						|
import traceback
 | 
						|
import urllib.parse
 | 
						|
import urllib.request
 | 
						|
import zipfile
 | 
						|
from argparse import ArgumentParser
 | 
						|
from dataclasses import dataclass, field, fields
 | 
						|
from datetime import datetime, timedelta, timezone
 | 
						|
from enum import IntEnum
 | 
						|
from pathlib import Path
 | 
						|
from tempfile import TemporaryDirectory
 | 
						|
from typing import Union
 | 
						|
 | 
						|
try:
 | 
						|
    import magic
 | 
						|
except ImportError:
 | 
						|
    import puremagic as magic
 | 
						|
 | 
						|
if sys.version_info >= (3, 11):
 | 
						|
    import tomllib
 | 
						|
else:
 | 
						|
    import tomli as tomllib
 | 
						|
 | 
						|
from . import _, common, metadata, scanner
 | 
						|
from .exception import BuildException, ConfigurationException, VCSException
 | 
						|
 | 
						|
 | 
						|
@dataclass
 | 
						|
class MessageStore:
 | 
						|
    infos: list = field(default_factory=list)
 | 
						|
    warnings: list = field(default_factory=list)
 | 
						|
    errors: list = field(default_factory=list)
 | 
						|
 | 
						|
 | 
						|
MAVEN_URL_REGEX = re.compile(
 | 
						|
    r"""\smaven\s*(?:{.*?(?:setUrl|url)|\(\s*(?:url)?)\s*=?\s*(?:uri|URI|Uri\.create)?\(?\s*["']?([^\s"']+)["']?[^})]*[)}]""",
 | 
						|
    re.DOTALL,
 | 
						|
)
 | 
						|
 | 
						|
DEPFILE = {
 | 
						|
    "Cargo.toml": ["Cargo.lock"],
 | 
						|
    "pubspec.yaml": ["pubspec.lock"],
 | 
						|
    "package.json": ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "bun.lock"],
 | 
						|
}
 | 
						|
 | 
						|
SCANNER_CACHE_VERSION = 1
 | 
						|
 | 
						|
DEFAULT_CATALOG_PREFIX_REGEX = re.compile(
 | 
						|
    r'''defaultLibrariesExtensionName\s*=\s*['"](\w+)['"]'''
 | 
						|
)
 | 
						|
GRADLE_CATALOG_FILE_REGEX = re.compile(
 | 
						|
    r'''(?:create\()?['"]?(\w+)['"]?\)?\s*\{[^}]*from\(files\(['"]([^"]+)['"]\)\)'''
 | 
						|
)
 | 
						|
VERSION_CATALOG_REGEX = re.compile(r'versionCatalogs\s*\{')
 | 
						|
 | 
						|
APK_SIGNING_BLOCK_IDS = {
 | 
						|
    # https://source.android.com/docs/security/features/apksigning/v2#apk-signing-block
 | 
						|
    # 0x7109871a: 'APK signature scheme v2',
 | 
						|
    # https://source.android.com/docs/security/features/apksigning/v3#apk-signing-block
 | 
						|
    # 0xf05368c0: 'APK signature scheme v3',
 | 
						|
    # See "Security metadata in early 2018"
 | 
						|
    # https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html
 | 
						|
    0x2146444E: 'Google Play Signature aka "Frosting"',
 | 
						|
    # 0x42726577: 'Verity padding',
 | 
						|
    # 0x6DFF800D: 'Source stamp V2 X509 cert',
 | 
						|
    # JSON with some metadata, used by Chinese company Meituan
 | 
						|
    0x71777777: 'Meituan payload',
 | 
						|
    # Dependencies metadata generated by Gradle and encrypted by Google Play.
 | 
						|
    # '...The data is compressed, encrypted by a Google Play signing key...'
 | 
						|
    # https://developer.android.com/studio/releases/gradle-plugin#dependency-metadata
 | 
						|
    0x504B4453: 'Dependency metadata',
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
class ExitCode(IntEnum):
 | 
						|
    NONFREE_CODE = 1
 | 
						|
 | 
						|
 | 
						|
class GradleVersionCatalog:
 | 
						|
    """Parse catalog from libs.versions.toml.
 | 
						|
 | 
						|
    https://docs.gradle.org/current/userguide/platforms.html
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, catalog):
 | 
						|
        self.version = {
 | 
						|
            alias: self.get_version(version)
 | 
						|
            for alias, version in catalog.get("versions", {}).items()
 | 
						|
        }
 | 
						|
        self.libraries = {
 | 
						|
            self.alias_to_accessor(alias): self.library_to_coordinate(library)
 | 
						|
            for alias, library in catalog.get("libraries", {}).items()
 | 
						|
        }
 | 
						|
        self.plugins = {
 | 
						|
            self.alias_to_accessor(alias): self.plugin_to_coordinate(plugin)
 | 
						|
            for alias, plugin in catalog.get("plugins", {}).items()
 | 
						|
        }
 | 
						|
        self.bundles = {
 | 
						|
            self.alias_to_accessor(alias): self.bundle_to_coordinates(bundle)
 | 
						|
            for alias, bundle in catalog.get("bundles", {}).items()
 | 
						|
        }
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def alias_to_accessor(alias: str) -> str:
 | 
						|
        """Covert alias to accessor.
 | 
						|
 | 
						|
        https://docs.gradle.org/current/userguide/platforms.html#sub:mapping-aliases-to-accessors
 | 
						|
        Alias is used to define a lib in catalog. Accessor is used to access it.
 | 
						|
        """
 | 
						|
        return alias.replace("-", ".").replace("_", ".")
 | 
						|
 | 
						|
    def get_version(self, version: Union[dict, str]) -> str:
 | 
						|
        if isinstance(version, str):
 | 
						|
            return version
 | 
						|
        ref = version.get("ref")
 | 
						|
        if ref:
 | 
						|
            return self.version.get(ref, "")
 | 
						|
        return (
 | 
						|
            version.get("prefer", "")
 | 
						|
            or version.get("require", "")
 | 
						|
            or version.get("strictly", "")
 | 
						|
        )
 | 
						|
 | 
						|
    def library_to_coordinate(self, library: Union[dict, str]) -> str:
 | 
						|
        """Generate the Gradle dependency coordinate from catalog."""
 | 
						|
        if isinstance(library, str):
 | 
						|
            return library
 | 
						|
        module = library.get("module")
 | 
						|
        if not module:
 | 
						|
            group = library.get("group")
 | 
						|
            name = library.get("name")
 | 
						|
            if group and name:
 | 
						|
                module = f"{group}:{name}"
 | 
						|
            else:
 | 
						|
                return ""
 | 
						|
 | 
						|
        version = library.get("version")
 | 
						|
        if version:
 | 
						|
            return f"{module}:{self.get_version(version)}"
 | 
						|
        else:
 | 
						|
            return module
 | 
						|
 | 
						|
    def plugin_to_coordinate(self, plugin: Union[dict, str]) -> str:
 | 
						|
        """Generate the Gradle plugin coordinate from catalog."""
 | 
						|
        if isinstance(plugin, str):
 | 
						|
            return plugin
 | 
						|
        id = plugin.get("id")
 | 
						|
        if not id:
 | 
						|
            return ""
 | 
						|
 | 
						|
        version = plugin.get("version")
 | 
						|
        if version:
 | 
						|
            return f"{id}:{self.get_version(version)}"
 | 
						|
        else:
 | 
						|
            return id
 | 
						|
 | 
						|
    def bundle_to_coordinates(self, bundle: list[str]) -> list[str]:
 | 
						|
        """Generate the Gradle dependency bundle coordinate from catalog."""
 | 
						|
        coordinates = []
 | 
						|
        for alias in bundle:
 | 
						|
            library = self.libraries.get(self.alias_to_accessor(alias))
 | 
						|
            if library:
 | 
						|
                coordinates.append(library)
 | 
						|
        return coordinates
 | 
						|
 | 
						|
    def get_coordinate(self, accessor: str) -> list[str]:
 | 
						|
        """Get the Gradle coordinate from the catalog with an accessor."""
 | 
						|
        if accessor.startswith("plugins."):
 | 
						|
            return [
 | 
						|
                self.plugins.get(accessor[8:].removesuffix(".asLibraryDependency"), "")
 | 
						|
            ]
 | 
						|
        if accessor.startswith("bundles."):
 | 
						|
            return self.bundles.get(accessor[8:], [])
 | 
						|
        return [self.libraries.get(accessor, "")]
 | 
						|
 | 
						|
 | 
						|
def get_catalogs(root: str) -> dict[str, GradleVersionCatalog]:
 | 
						|
    """Get all Gradle dependency catalogs from settings.gradle[.kts].
 | 
						|
 | 
						|
    Returns a dict with the extension and the corresponding catalog.
 | 
						|
    The extension is used as the prefix of the accessor to access libs in the catalog.
 | 
						|
    """
 | 
						|
    root = Path(root)
 | 
						|
    catalogs = {}
 | 
						|
    default_prefix = "libs"
 | 
						|
    catalog_files_m = []
 | 
						|
 | 
						|
    def find_block_end(s, start):
 | 
						|
        pat = re.compile("[{}]")
 | 
						|
        depth = 1
 | 
						|
        for m in pat.finditer(s, pos=start):
 | 
						|
            if m.group() == "{":
 | 
						|
                depth += 1
 | 
						|
            else:
 | 
						|
                depth -= 1
 | 
						|
            if depth == 0:
 | 
						|
                return m.start()
 | 
						|
        else:
 | 
						|
            return -1
 | 
						|
 | 
						|
    groovy_file = root / "settings.gradle"
 | 
						|
    kotlin_file = root / "settings.gradle.kts"
 | 
						|
    if groovy_file.is_file():
 | 
						|
        gradle_file = groovy_file
 | 
						|
    elif kotlin_file.is_file():
 | 
						|
        gradle_file = kotlin_file
 | 
						|
    else:
 | 
						|
        return {}
 | 
						|
 | 
						|
    s = gradle_file.read_text(encoding="utf-8")
 | 
						|
    version_catalogs_m = VERSION_CATALOG_REGEX.search(s)
 | 
						|
    if version_catalogs_m:
 | 
						|
        start = version_catalogs_m.end()
 | 
						|
        end = find_block_end(s, start)
 | 
						|
        catalog_files_m = GRADLE_CATALOG_FILE_REGEX.finditer(s, start, end)
 | 
						|
 | 
						|
    m_default = DEFAULT_CATALOG_PREFIX_REGEX.search(s)
 | 
						|
    if m_default:
 | 
						|
        default_prefix = m_default.group(1)
 | 
						|
    default_catalog_file = Path(root) / "gradle/libs.versions.toml"
 | 
						|
    if default_catalog_file.is_file():
 | 
						|
        with default_catalog_file.open("rb") as f:
 | 
						|
            catalogs[default_prefix] = GradleVersionCatalog(tomllib.load(f))
 | 
						|
    for m in catalog_files_m:
 | 
						|
        catalog_file = Path(root) / m.group(2).replace("$rootDir/", "")
 | 
						|
        if catalog_file.is_file():
 | 
						|
            with catalog_file.open("rb") as f:
 | 
						|
                catalogs[m.group(1)] = GradleVersionCatalog(tomllib.load(f))
 | 
						|
    return catalogs
 | 
						|
 | 
						|
 | 
						|
def get_gradle_compile_commands(build):
 | 
						|
    compileCommands = [
 | 
						|
        'alias',
 | 
						|
        'api',
 | 
						|
        'apk',
 | 
						|
        'classpath',
 | 
						|
        'compile',
 | 
						|
        'compileOnly',
 | 
						|
        'id',
 | 
						|
        'implementation',
 | 
						|
        'provided',
 | 
						|
        'runtimeOnly',
 | 
						|
    ]
 | 
						|
    buildTypes = ['', 'release']
 | 
						|
    if build.gradle and build.gradle != ['yes']:
 | 
						|
        flavors = common.calculate_gradle_flavor_combination(build.gradle)
 | 
						|
    else:
 | 
						|
        flavors = ['']
 | 
						|
 | 
						|
    return [''.join(c) for c in itertools.product(flavors, buildTypes, compileCommands)]
 | 
						|
 | 
						|
 | 
						|
def get_gradle_compile_commands_without_catalog(build):
 | 
						|
    return [
 | 
						|
        re.compile(rf'''\s*{c}.*\s*\(?['"].*['"]''', re.IGNORECASE)
 | 
						|
        for c in get_gradle_compile_commands(build)
 | 
						|
    ]
 | 
						|
 | 
						|
 | 
						|
def get_gradle_compile_commands_with_catalog(build, prefix):
 | 
						|
    return [
 | 
						|
        re.compile(rf'\s*{c}.*\s*\(?{prefix}\.([a-z0-9.]+)', re.IGNORECASE)
 | 
						|
        for c in get_gradle_compile_commands(build)
 | 
						|
    ]
 | 
						|
 | 
						|
 | 
						|
def get_embedded_classes(apkfile, depth=0):
 | 
						|
    """Get the list of Java classes embedded into all DEX files.
 | 
						|
 | 
						|
    :return: set of Java classes names as string
 | 
						|
    """
 | 
						|
    if depth > 10:  # zipbomb protection
 | 
						|
        return {_('Max recursion depth in ZIP file reached: %s') % apkfile}
 | 
						|
 | 
						|
    archive_regex = re.compile(r'.*\.(aab|aar|apk|apks|jar|war|xapk|zip)$')
 | 
						|
    class_regex = re.compile(r'classes.*\.dex')
 | 
						|
    classes = set()
 | 
						|
 | 
						|
    try:
 | 
						|
        with TemporaryDirectory() as tmp_dir, zipfile.ZipFile(apkfile, 'r') as apk_zip:
 | 
						|
            for info in apk_zip.infolist():
 | 
						|
                # apk files can contain apk files, again
 | 
						|
                with apk_zip.open(info) as apk_fp:
 | 
						|
                    if zipfile.is_zipfile(apk_fp):
 | 
						|
                        classes = classes.union(get_embedded_classes(apk_fp, depth + 1))
 | 
						|
                        if not archive_regex.search(info.filename):
 | 
						|
                            classes.add(
 | 
						|
                                'ZIP file without proper file extension: %s'
 | 
						|
                                % info.filename
 | 
						|
                            )
 | 
						|
                        continue
 | 
						|
 | 
						|
                with apk_zip.open(info.filename) as fp:
 | 
						|
                    file_magic = fp.read(3)
 | 
						|
                if file_magic == b'dex':
 | 
						|
                    if not class_regex.search(info.filename):
 | 
						|
                        classes.add('DEX file with fake name: %s' % info.filename)
 | 
						|
                    apk_zip.extract(info, tmp_dir)
 | 
						|
                    run = common.SdkToolsPopen(
 | 
						|
                        ["dexdump", '{}/{}'.format(tmp_dir, info.filename)],
 | 
						|
                        output=False,
 | 
						|
                    )
 | 
						|
                    classes = classes.union(
 | 
						|
                        set(re.findall(r'[A-Z]+((?:\w+\/)+\w+)', run.output))
 | 
						|
                    )
 | 
						|
    except zipfile.BadZipFile as ex:
 | 
						|
        return {_('Problem with ZIP file: %s, error %s') % (apkfile, ex)}
 | 
						|
 | 
						|
    return classes
 | 
						|
 | 
						|
 | 
						|
def _datetime_now():
 | 
						|
    """Get datetime.now(), using this funciton allows mocking it for testing."""
 | 
						|
    return datetime.now(timezone.utc)
 | 
						|
 | 
						|
 | 
						|
def _scanner_cachedir():
 | 
						|
    """Get `Path` to fdroidserver cache dir."""
 | 
						|
    cfg = common.get_config()
 | 
						|
    if not cfg:
 | 
						|
        raise ConfigurationException('config not initialized')
 | 
						|
    if "cachedir_scanner" not in cfg:
 | 
						|
        raise ConfigurationException("could not load 'cachedir_scanner' from config")
 | 
						|
    cachedir = Path(cfg["cachedir_scanner"])
 | 
						|
    cachedir.mkdir(exist_ok=True, parents=True)
 | 
						|
    return cachedir
 | 
						|
 | 
						|
 | 
						|
class SignatureDataMalformedException(Exception):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class SignatureDataOutdatedException(Exception):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class SignatureDataCacheMissException(Exception):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class SignatureDataNoDefaultsException(Exception):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class SignatureDataVersionMismatchException(Exception):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class SignatureDataController:
 | 
						|
    def __init__(self, name, filename, url):
 | 
						|
        self.name = name
 | 
						|
        self.filename = filename
 | 
						|
        self.url = url
 | 
						|
        # by default we assume cache is valid indefinitely
 | 
						|
        self.cache_duration = timedelta(days=999999)
 | 
						|
        self.data = {}
 | 
						|
 | 
						|
    def check_data_version(self):
 | 
						|
        if self.data.get("version") != SCANNER_CACHE_VERSION:
 | 
						|
            raise SignatureDataVersionMismatchException()
 | 
						|
 | 
						|
    def check_last_updated(self):
 | 
						|
        """Check if the last_updated value is ok and raise an exception if expired or inaccessible.
 | 
						|
 | 
						|
        :raises SignatureDataMalformedException: when timestamp value is
 | 
						|
                                                 inaccessible or not parse-able
 | 
						|
        :raises SignatureDataOutdatedException: when timestamp is older then
 | 
						|
                                                `self.cache_duration`
 | 
						|
        """
 | 
						|
        last_updated = self.data.get("last_updated", None)
 | 
						|
        if last_updated:
 | 
						|
            try:
 | 
						|
                last_updated = datetime.fromtimestamp(last_updated, timezone.utc)
 | 
						|
            except ValueError as e:
 | 
						|
                raise SignatureDataMalformedException() from e
 | 
						|
            except TypeError as e:
 | 
						|
                raise SignatureDataMalformedException() from e
 | 
						|
            delta = (last_updated + self.cache_duration) - scanner._datetime_now()
 | 
						|
            if delta > timedelta(seconds=0):
 | 
						|
                logging.debug(
 | 
						|
                    _('next {name} cache update due in {time}').format(
 | 
						|
                        name=self.filename, time=delta
 | 
						|
                    )
 | 
						|
                )
 | 
						|
            else:
 | 
						|
                raise SignatureDataOutdatedException()
 | 
						|
 | 
						|
    def fetch(self):
 | 
						|
        try:
 | 
						|
            self.fetch_signatures_from_web()
 | 
						|
            self.write_to_cache()
 | 
						|
        except Exception as e:
 | 
						|
            raise Exception(
 | 
						|
                _("downloading scanner signatures from '{}' failed").format(self.url)
 | 
						|
            ) from e
 | 
						|
 | 
						|
    def load(self):
 | 
						|
        try:
 | 
						|
            try:
 | 
						|
                self.load_from_cache()
 | 
						|
                self.verify_data()
 | 
						|
                self.check_last_updated()
 | 
						|
            except SignatureDataCacheMissException:
 | 
						|
                self.load_from_defaults()
 | 
						|
        except (SignatureDataOutdatedException, SignatureDataNoDefaultsException):
 | 
						|
            self.fetch_signatures_from_web()
 | 
						|
            self.write_to_cache()
 | 
						|
        except (
 | 
						|
            SignatureDataMalformedException,
 | 
						|
            SignatureDataVersionMismatchException,
 | 
						|
        ) as e:
 | 
						|
            logging.critical(
 | 
						|
                _(
 | 
						|
                    "scanner cache is malformed! You can clear it with: '{clear}'"
 | 
						|
                ).format(
 | 
						|
                    clear='rm -r {}'.format(common.get_config()['cachedir_scanner'])
 | 
						|
                )
 | 
						|
            )
 | 
						|
            raise e
 | 
						|
 | 
						|
    def load_from_defaults(self):
 | 
						|
        raise SignatureDataNoDefaultsException()
 | 
						|
 | 
						|
    def load_from_cache(self):
 | 
						|
        sig_file = scanner._scanner_cachedir() / self.filename
 | 
						|
        if not sig_file.exists():
 | 
						|
            raise SignatureDataCacheMissException()
 | 
						|
        with open(sig_file) as f:
 | 
						|
            self.set_data(json.load(f))
 | 
						|
 | 
						|
    def write_to_cache(self):
 | 
						|
        sig_file = scanner._scanner_cachedir() / self.filename
 | 
						|
        with open(sig_file, "w", encoding="utf-8") as f:
 | 
						|
            json.dump(self.data, f, indent=2)
 | 
						|
        logging.debug("write '{}' to cache".format(self.filename))
 | 
						|
 | 
						|
    def verify_data(self):
 | 
						|
        """Clean and validate `self.data`.
 | 
						|
 | 
						|
        Right now this function does just a basic key sanitation.
 | 
						|
        """
 | 
						|
        self.check_data_version()
 | 
						|
        valid_keys = [
 | 
						|
            'timestamp',
 | 
						|
            'last_updated',
 | 
						|
            'version',
 | 
						|
            'signatures',
 | 
						|
            'cache_duration',
 | 
						|
        ]
 | 
						|
 | 
						|
        for k in list(self.data.keys()):
 | 
						|
            if k not in valid_keys:
 | 
						|
                del self.data[k]
 | 
						|
 | 
						|
    def set_data(self, new_data):
 | 
						|
        self.data = new_data
 | 
						|
        if 'cache_duration' in new_data:
 | 
						|
            self.cache_duration = timedelta(seconds=new_data['cache_duration'])
 | 
						|
 | 
						|
    def fetch_signatures_from_web(self):
 | 
						|
        if not self.url.startswith("https://"):
 | 
						|
            raise Exception(_("can't open non-https url: '{};".format(self.url)))
 | 
						|
        logging.debug(_("downloading '{}'").format(self.url))
 | 
						|
        with urllib.request.urlopen(self.url) as f:  # nosec B310 scheme filtered above
 | 
						|
            self.set_data(json.load(f))
 | 
						|
        self.data['last_updated'] = scanner._datetime_now().timestamp()
 | 
						|
 | 
						|
 | 
						|
class ExodusSignatureDataController(SignatureDataController):
 | 
						|
    def __init__(self):
 | 
						|
        super().__init__(
 | 
						|
            'Exodus signatures',
 | 
						|
            'exodus.json',
 | 
						|
            'https://reports.exodus-privacy.eu.org/api/trackers',
 | 
						|
        )
 | 
						|
        self.cache_duration = timedelta(days=1)  # refresh exodus cache after one day
 | 
						|
        self.has_trackers_json_key = True
 | 
						|
 | 
						|
    def fetch_signatures_from_web(self):
 | 
						|
        logging.debug(_("downloading '{}'").format(self.url))
 | 
						|
 | 
						|
        data = {
 | 
						|
            "signatures": {},
 | 
						|
            "timestamp": scanner._datetime_now().timestamp(),
 | 
						|
            "last_updated": scanner._datetime_now().timestamp(),
 | 
						|
            "version": SCANNER_CACHE_VERSION,
 | 
						|
        }
 | 
						|
 | 
						|
        if not self.url.startswith("https://"):
 | 
						|
            raise Exception(_("can't open non-https url: '{};".format(self.url)))
 | 
						|
        with urllib.request.urlopen(self.url) as f:  # nosec B310 scheme filtered above
 | 
						|
            trackerlist = json.load(f)
 | 
						|
            if self.has_trackers_json_key:
 | 
						|
                trackerlist = trackerlist["trackers"].values()
 | 
						|
            for tracker in trackerlist:
 | 
						|
                if tracker.get('code_signature'):
 | 
						|
                    data["signatures"][tracker["name"]] = {
 | 
						|
                        "name": tracker["name"],
 | 
						|
                        "warn_code_signatures": [tracker["code_signature"]],
 | 
						|
                        # exodus also provides network signatures, unused atm.
 | 
						|
                        # "network_signatures": [tracker["network_signature"]],
 | 
						|
                        "AntiFeatures": ["Tracking"],  # TODO
 | 
						|
                        "license": "NonFree",  # We assume all trackers in exodus
 | 
						|
                        # are non-free, although free
 | 
						|
                        # trackers like piwik, acra,
 | 
						|
                        # etc. might be listed by exodus
 | 
						|
                        # too.
 | 
						|
                    }
 | 
						|
        self.set_data(data)
 | 
						|
 | 
						|
 | 
						|
class EtipSignatureDataController(ExodusSignatureDataController):
 | 
						|
    def __init__(self):
 | 
						|
        super().__init__()
 | 
						|
        self.name = 'ETIP signatures'
 | 
						|
        self.filename = 'etip.json'
 | 
						|
        self.url = 'https://etip.exodus-privacy.eu.org/api/trackers/?format=json'
 | 
						|
        self.has_trackers_json_key = False
 | 
						|
 | 
						|
 | 
						|
class SUSSDataController(SignatureDataController):
 | 
						|
    def __init__(self):
 | 
						|
        super().__init__(
 | 
						|
            'SUSS', 'suss.json', 'https://fdroid.gitlab.io/fdroid-suss/suss.json'
 | 
						|
        )
 | 
						|
 | 
						|
    def load_from_defaults(self):
 | 
						|
        self.set_data(json.loads(SUSS_DEFAULT))
 | 
						|
 | 
						|
 | 
						|
class ScannerTool:
 | 
						|
    refresh_allowed = True
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        # we could add support for loading additional signature source
 | 
						|
        # definitions from config.yml here
 | 
						|
 | 
						|
        self.scanner_data_lookup()
 | 
						|
 | 
						|
        options = common.get_options()
 | 
						|
        options_refresh_scanner = (
 | 
						|
            hasattr(options, "refresh_scanner")
 | 
						|
            and options.refresh_scanner
 | 
						|
            and ScannerTool.refresh_allowed
 | 
						|
        )
 | 
						|
        if options_refresh_scanner or common.get_config().get('refresh_scanner'):
 | 
						|
            self.refresh()
 | 
						|
 | 
						|
        self.load()
 | 
						|
        self.compile_regexes()
 | 
						|
 | 
						|
    def scanner_data_lookup(self):
 | 
						|
        sigsources = common.get_config().get('scanner_signature_sources', [])
 | 
						|
        logging.debug(
 | 
						|
            "scanner is configured to use signature data from: '{}'".format(
 | 
						|
                "', '".join(sigsources)
 | 
						|
            )
 | 
						|
        )
 | 
						|
        self.sdcs = []
 | 
						|
        for i, source_url in enumerate(sigsources):
 | 
						|
            if source_url.lower() == 'suss':
 | 
						|
                self.sdcs.append(SUSSDataController())
 | 
						|
            elif source_url.lower() == 'exodus':
 | 
						|
                self.sdcs.append(ExodusSignatureDataController())
 | 
						|
            elif source_url.lower() == 'etip':
 | 
						|
                self.sdcs.append(EtipSignatureDataController())
 | 
						|
            else:
 | 
						|
                u = urllib.parse.urlparse(source_url)
 | 
						|
                if u.scheme != 'https' or u.path == "":
 | 
						|
                    raise ConfigurationException(
 | 
						|
                        "Invalid 'scanner_signature_sources' configuration: '{}'. "
 | 
						|
                        "Has to be a valid HTTPS-URL or match a predefined "
 | 
						|
                        "constants: 'suss', 'exodus'".format(source_url)
 | 
						|
                    )
 | 
						|
                self.sdcs.append(
 | 
						|
                    SignatureDataController(
 | 
						|
                        source_url,
 | 
						|
                        '{}_{}'.format(i, os.path.basename(u.path)),
 | 
						|
                        source_url,
 | 
						|
                    )
 | 
						|
                )
 | 
						|
 | 
						|
    def load(self):
 | 
						|
        for sdc in self.sdcs:
 | 
						|
            sdc.load()
 | 
						|
 | 
						|
    def compile_regexes(self):
 | 
						|
        self.regexs = {
 | 
						|
            'err_code_signatures': {},
 | 
						|
            'err_gradle_signatures': {},
 | 
						|
            'warn_code_signatures': {},
 | 
						|
            'warn_gradle_signatures': {},
 | 
						|
        }
 | 
						|
        for sdc in self.sdcs:
 | 
						|
            for signame, sigdef in sdc.data.get('signatures', {}).items():
 | 
						|
                for sig in sigdef.get('code_signatures', []):
 | 
						|
                    self.regexs['err_code_signatures'][sig] = re.compile(
 | 
						|
                        '.*' + sig, re.IGNORECASE
 | 
						|
                    )
 | 
						|
                for sig in sigdef.get('gradle_signatures', []):
 | 
						|
                    self.regexs['err_gradle_signatures'][sig] = re.compile(
 | 
						|
                        '.*' + sig, re.IGNORECASE
 | 
						|
                    )
 | 
						|
                for sig in sigdef.get('warn_code_signatures', []):
 | 
						|
                    self.regexs['warn_code_signatures'][sig] = re.compile(
 | 
						|
                        '.*' + sig, re.IGNORECASE
 | 
						|
                    )
 | 
						|
                for sig in sigdef.get('warn_gradle_signatures', []):
 | 
						|
                    self.regexs['warn_gradle_signatures'][sig] = re.compile(
 | 
						|
                        '.*' + sig, re.IGNORECASE
 | 
						|
                    )
 | 
						|
 | 
						|
    def refresh(self):
 | 
						|
        for sdc in self.sdcs:
 | 
						|
            sdc.fetch_signatures_from_web()
 | 
						|
            sdc.write_to_cache()
 | 
						|
 | 
						|
    def add(self, new_controller: SignatureDataController):
 | 
						|
        self.sdcs.append(new_controller)
 | 
						|
        self.compile_regexes()
 | 
						|
 | 
						|
 | 
						|
# TODO: change this from singleton instance to dependency injection
 | 
						|
# use `_get_tool()` instead of accessing this directly
 | 
						|
_SCANNER_TOOL = None
 | 
						|
 | 
						|
 | 
						|
def _get_tool():
 | 
						|
    """Lazy loading function for getting a ScannerTool instance.
 | 
						|
 | 
						|
    ScannerTool initialization need to access `common.config` values. Those are only available after initialization through `common.read_config()`. So this factory assumes config was called at an erlier point in time.
 | 
						|
    """
 | 
						|
    if not scanner._SCANNER_TOOL:
 | 
						|
        scanner._SCANNER_TOOL = ScannerTool()
 | 
						|
    return scanner._SCANNER_TOOL
 | 
						|
 | 
						|
 | 
						|
def scan_binary(apkfile):
 | 
						|
    """Scan output of dexdump for known non-free classes."""
 | 
						|
    logging.info(_('Scanning APK with dexdump for known non-free classes.'))
 | 
						|
    result = get_embedded_classes(apkfile)
 | 
						|
    problems, warnings = 0, 0
 | 
						|
    for classname in result:
 | 
						|
        for suspect, regexp in _get_tool().regexs['warn_code_signatures'].items():
 | 
						|
            if regexp.match(classname):
 | 
						|
                logging.debug("Warning: found class '%s'" % classname)
 | 
						|
                warnings += 1
 | 
						|
        for suspect, regexp in _get_tool().regexs['err_code_signatures'].items():
 | 
						|
            if regexp.match(classname):
 | 
						|
                logging.debug("Problem: found class '%s'" % classname)
 | 
						|
                problems += 1
 | 
						|
 | 
						|
    logging.info(_('Scanning APK for extra signing blocks.'))
 | 
						|
    a = common.get_androguard_APK(str(apkfile))
 | 
						|
    a.parse_v2_v3_signature()
 | 
						|
    for b in a._v2_blocks:
 | 
						|
        if b in APK_SIGNING_BLOCK_IDS:
 | 
						|
            logging.debug(
 | 
						|
                f"Problem: found extra signing block '{APK_SIGNING_BLOCK_IDS[b]}'"
 | 
						|
            )
 | 
						|
            problems += 1
 | 
						|
 | 
						|
    if warnings:
 | 
						|
        logging.warning(
 | 
						|
            _("Found {count} warnings in {filename}").format(
 | 
						|
                count=warnings, filename=apkfile
 | 
						|
            )
 | 
						|
        )
 | 
						|
    if problems:
 | 
						|
        logging.critical(
 | 
						|
            _("Found {count} problems in {filename}").format(
 | 
						|
                count=problems, filename=apkfile
 | 
						|
            )
 | 
						|
        )
 | 
						|
    return problems
 | 
						|
 | 
						|
 | 
						|
def scan_source(build_dir, build=metadata.Build(), json_per_build=None):
 | 
						|
    """Scan the source code in the given directory (and all subdirectories).
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    the number of fatal problems encountered.
 | 
						|
 | 
						|
    """
 | 
						|
    count = 0
 | 
						|
 | 
						|
    if not json_per_build:
 | 
						|
        json_per_build = MessageStore()
 | 
						|
 | 
						|
    def suspects_found(s):
 | 
						|
        for n, r in _get_tool().regexs['err_gradle_signatures'].items():
 | 
						|
            if r.match(s):
 | 
						|
                yield n
 | 
						|
 | 
						|
    allowed_repos = [
 | 
						|
        re.compile(r'^https://' + re.escape(repo) + r'/*')
 | 
						|
        for repo in [
 | 
						|
            'repo1.maven.org/maven2',  # mavenCentral()
 | 
						|
            'jitpack.io',
 | 
						|
            'www.jitpack.io',
 | 
						|
            'repo.maven.apache.org/maven2',
 | 
						|
            'oss.jfrog.org/artifactory/oss-snapshot-local',
 | 
						|
            'central.sonatype.com/repository/maven-snapshots',
 | 
						|
            'oss.sonatype.org/content/repositories/snapshots',
 | 
						|
            'oss.sonatype.org/content/repositories/releases',
 | 
						|
            'oss.sonatype.org/content/groups/public',
 | 
						|
            'oss.sonatype.org/service/local/staging/deploy/maven2',
 | 
						|
            's01.oss.sonatype.org/content/repositories/snapshots',
 | 
						|
            's01.oss.sonatype.org/content/repositories/releases',
 | 
						|
            's01.oss.sonatype.org/content/groups/public',
 | 
						|
            's01.oss.sonatype.org/service/local/staging/deploy/maven2',
 | 
						|
            'clojars.org/repo',  # Clojure free software libs
 | 
						|
            'repo.clojars.org',  # Clojure free software libs
 | 
						|
            's3.amazonaws.com/repo.commonsware.com',  # CommonsWare
 | 
						|
            'plugins.gradle.org/m2',  # Gradle plugin repo
 | 
						|
            'maven.google.com',  # google()
 | 
						|
        ]
 | 
						|
    ] + [
 | 
						|
        re.compile(r'^file://' + re.escape(repo) + r'/*')
 | 
						|
        for repo in [
 | 
						|
            '/usr/share/maven-repo',  # local repo on Debian installs
 | 
						|
        ]
 | 
						|
    ]
 | 
						|
 | 
						|
    scanignore, scanignore_not_found_paths = common.getpaths_map(
 | 
						|
        build_dir, build.scanignore
 | 
						|
    )
 | 
						|
    scandelete, scandelete_not_found_paths = common.getpaths_map(
 | 
						|
        build_dir, build.scandelete
 | 
						|
    )
 | 
						|
 | 
						|
    scanignore_worked = set()
 | 
						|
    scandelete_worked = set()
 | 
						|
 | 
						|
    def toignore(path_in_build_dir):
 | 
						|
        for k, paths in scanignore.items():
 | 
						|
            for p in paths:
 | 
						|
                if path_in_build_dir.startswith(p):
 | 
						|
                    scanignore_worked.add(k)
 | 
						|
                    return True
 | 
						|
        return False
 | 
						|
 | 
						|
    def todelete(path_in_build_dir):
 | 
						|
        for k, paths in scandelete.items():
 | 
						|
            for p in paths:
 | 
						|
                if path_in_build_dir.startswith(p):
 | 
						|
                    scandelete_worked.add(k)
 | 
						|
                    return True
 | 
						|
        return False
 | 
						|
 | 
						|
    def ignoreproblem(what, path_in_build_dir, json_per_build):
 | 
						|
        """No summary.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        what: string
 | 
						|
          describing the problem, will be printed in log messages
 | 
						|
        path_in_build_dir
 | 
						|
          path to the file relative to `build`-dir
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        0 as we explicitly ignore the file, so don't count an error
 | 
						|
 | 
						|
        """
 | 
						|
        msg = 'Ignoring %s at %s' % (what, path_in_build_dir)
 | 
						|
        logging.info(msg)
 | 
						|
        if json_per_build is not None:
 | 
						|
            json_per_build.infos.append([msg, path_in_build_dir])
 | 
						|
        return 0
 | 
						|
 | 
						|
    def removeproblem(what, path_in_build_dir, filepath, json_per_build):
 | 
						|
        """No summary.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        what: string
 | 
						|
          describing the problem, will be printed in log messages
 | 
						|
        path_in_build_dir
 | 
						|
          path to the file relative to `build`-dir
 | 
						|
        filepath
 | 
						|
          Path (relative to our current path) to the file
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        0 as we deleted the offending file
 | 
						|
 | 
						|
        """
 | 
						|
        msg = 'Removing %s at %s' % (what, path_in_build_dir)
 | 
						|
        logging.info(msg)
 | 
						|
        if json_per_build is not None:
 | 
						|
            json_per_build.infos.append([msg, path_in_build_dir])
 | 
						|
        try:
 | 
						|
            os.remove(filepath)
 | 
						|
        except FileNotFoundError:
 | 
						|
            # File is already gone, nothing to do.
 | 
						|
            # This can happen if we find multiple problems in one file that is setup for scandelete
 | 
						|
            # I.e. build.gradle files containig multiple unknown maven repos.
 | 
						|
            pass
 | 
						|
        return 0
 | 
						|
 | 
						|
    def warnproblem(what, path_in_build_dir, json_per_build):
 | 
						|
        """No summary.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        what: string
 | 
						|
          describing the problem, will be printed in log messages
 | 
						|
        path_in_build_dir
 | 
						|
          path to the file relative to `build`-dir
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        0, as warnings don't count as errors
 | 
						|
 | 
						|
        """
 | 
						|
        if toignore(path_in_build_dir):
 | 
						|
            return 0
 | 
						|
        logging.warning('Found %s at %s' % (what, path_in_build_dir))
 | 
						|
        if json_per_build is not None:
 | 
						|
            json_per_build.warnings.append([what, path_in_build_dir])
 | 
						|
        return 0
 | 
						|
 | 
						|
    def handleproblem(what, path_in_build_dir, filepath, json_per_build):
 | 
						|
        """Dispatches to problem handlers (ignore, delete, warn).
 | 
						|
 | 
						|
        Or returns 1 for increasing the error count.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        what: string
 | 
						|
          describing the problem, will be printed in log messages
 | 
						|
        path_in_build_dir
 | 
						|
          path to the file relative to `build`-dir
 | 
						|
        filepath
 | 
						|
          Path (relative to our current path) to the file
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        0 if the problem was ignored/deleted/is only a warning, 1 otherwise
 | 
						|
 | 
						|
        """
 | 
						|
        options = common.get_options()
 | 
						|
        if toignore(path_in_build_dir):
 | 
						|
            return ignoreproblem(what, path_in_build_dir, json_per_build)
 | 
						|
        if todelete(path_in_build_dir):
 | 
						|
            return removeproblem(what, path_in_build_dir, filepath, json_per_build)
 | 
						|
        if 'src/test' in path_in_build_dir or '/test/' in path_in_build_dir:
 | 
						|
            return warnproblem(what, path_in_build_dir, json_per_build)
 | 
						|
        if options and 'json' in vars(options) and options.json:
 | 
						|
            json_per_build.errors.append([what, path_in_build_dir])
 | 
						|
        if options and (
 | 
						|
            options.verbose or not ('json' in vars(options) and options.json)
 | 
						|
        ):
 | 
						|
            logging.error('Found %s at %s' % (what, path_in_build_dir))
 | 
						|
        return 1
 | 
						|
 | 
						|
    def is_executable(path):
 | 
						|
        return os.path.exists(path) and os.access(path, os.X_OK)
 | 
						|
 | 
						|
    textchars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7f})  # fmt: skip
 | 
						|
 | 
						|
    def is_binary(path):
 | 
						|
        d = None
 | 
						|
        with open(path, 'rb') as f:
 | 
						|
            d = f.read(1024)
 | 
						|
        return bool(d.translate(None, textchars))
 | 
						|
 | 
						|
    # False positives patterns for files that are binary and executable.
 | 
						|
    safe_paths = [
 | 
						|
        re.compile(r)
 | 
						|
        for r in [
 | 
						|
            r".*/drawable[^/]*/.*\.png$",  # png drawables
 | 
						|
            r".*/mipmap[^/]*/.*\.png$",  # png mipmaps
 | 
						|
        ]
 | 
						|
    ]
 | 
						|
 | 
						|
    def is_image_file(path):
 | 
						|
        try:
 | 
						|
            mimetype = magic.from_file(path, mime=True)
 | 
						|
            if mimetype and mimetype.startswith('image/'):
 | 
						|
                return True
 | 
						|
        except Exception as e:
 | 
						|
            logging.info(e)
 | 
						|
 | 
						|
    def safe_path(path_in_build_dir):
 | 
						|
        for sp in safe_paths:
 | 
						|
            if sp.match(path_in_build_dir):
 | 
						|
                return True
 | 
						|
        return False
 | 
						|
 | 
						|
    def is_used_by_gradle_without_catalog(line):
 | 
						|
        return any(
 | 
						|
            command.match(line)
 | 
						|
            for command in get_gradle_compile_commands_without_catalog(build)
 | 
						|
        )
 | 
						|
 | 
						|
    def is_used_by_gradle_with_catalog(line, prefix):
 | 
						|
        for m in (
 | 
						|
            command.match(line)
 | 
						|
            for command in get_gradle_compile_commands_with_catalog(build, prefix)
 | 
						|
        ):
 | 
						|
            if m:
 | 
						|
                return m
 | 
						|
 | 
						|
    all_catalogs = {}
 | 
						|
    # Iterate through all files in the source code
 | 
						|
    for root, dirs, files in os.walk(build_dir, topdown=True):
 | 
						|
        # It's topdown, so checking the basename is enough
 | 
						|
        for ignoredir in ('.hg', '.git', '.svn', '.bzr'):
 | 
						|
            if ignoredir in dirs:
 | 
						|
                dirs.remove(ignoredir)
 | 
						|
 | 
						|
        if "settings.gradle" in files or "settings.gradle.kts" in files:
 | 
						|
            all_catalogs[str(root)] = get_catalogs(root)
 | 
						|
 | 
						|
        for curfile in files:
 | 
						|
            if curfile in ['.DS_Store']:
 | 
						|
                continue
 | 
						|
 | 
						|
            # Path (relative) to the file
 | 
						|
            filepath = os.path.join(root, curfile)
 | 
						|
 | 
						|
            if os.path.islink(filepath):
 | 
						|
                continue
 | 
						|
 | 
						|
            path_in_build_dir = os.path.relpath(filepath, build_dir)
 | 
						|
 | 
						|
            if curfile in ('gradle-wrapper.jar', 'gradlew', 'gradlew.bat'):
 | 
						|
                removeproblem(curfile, path_in_build_dir, filepath, json_per_build)
 | 
						|
            elif curfile.endswith('.apk'):
 | 
						|
                removeproblem(
 | 
						|
                    _('Android APK file'), path_in_build_dir, filepath, json_per_build
 | 
						|
                )
 | 
						|
 | 
						|
            elif curfile.endswith('.a'):
 | 
						|
                count += handleproblem(
 | 
						|
                    _('static library'), path_in_build_dir, filepath, json_per_build
 | 
						|
                )
 | 
						|
            elif curfile.endswith('.aar'):
 | 
						|
                count += handleproblem(
 | 
						|
                    _('Android AAR library'),
 | 
						|
                    path_in_build_dir,
 | 
						|
                    filepath,
 | 
						|
                    json_per_build,
 | 
						|
                )
 | 
						|
            elif curfile.endswith('.class'):
 | 
						|
                count += handleproblem(
 | 
						|
                    _('Java compiled class'),
 | 
						|
                    path_in_build_dir,
 | 
						|
                    filepath,
 | 
						|
                    json_per_build,
 | 
						|
                )
 | 
						|
            elif curfile.endswith('.dex'):
 | 
						|
                count += handleproblem(
 | 
						|
                    _('Android DEX code'), path_in_build_dir, filepath, json_per_build
 | 
						|
                )
 | 
						|
            elif curfile.endswith('.gz') or curfile.endswith('.tgz'):
 | 
						|
                count += handleproblem(
 | 
						|
                    _('gzip file archive'), path_in_build_dir, filepath, json_per_build
 | 
						|
                )
 | 
						|
            # We use a regular expression here to also match versioned shared objects like .so.0.0.0
 | 
						|
            elif re.match(r'.*\.so(\..+)*$', curfile):
 | 
						|
                count += handleproblem(
 | 
						|
                    _('shared library'), path_in_build_dir, filepath, json_per_build
 | 
						|
                )
 | 
						|
            elif curfile.endswith('.zip'):
 | 
						|
                count += handleproblem(
 | 
						|
                    _('ZIP file archive'), path_in_build_dir, filepath, json_per_build
 | 
						|
                )
 | 
						|
            elif curfile.endswith('.jar'):
 | 
						|
                for name in suspects_found(curfile):
 | 
						|
                    count += handleproblem(
 | 
						|
                        'usual suspect \'%s\'' % name,
 | 
						|
                        path_in_build_dir,
 | 
						|
                        filepath,
 | 
						|
                        json_per_build,
 | 
						|
                    )
 | 
						|
                count += handleproblem(
 | 
						|
                    _('Java JAR file'), path_in_build_dir, filepath, json_per_build
 | 
						|
                )
 | 
						|
            elif curfile.endswith('.wasm'):
 | 
						|
                count += handleproblem(
 | 
						|
                    _('WebAssembly binary file'),
 | 
						|
                    path_in_build_dir,
 | 
						|
                    filepath,
 | 
						|
                    json_per_build,
 | 
						|
                )
 | 
						|
 | 
						|
            elif curfile.endswith('.java'):
 | 
						|
                if not os.path.isfile(filepath):
 | 
						|
                    continue
 | 
						|
                with open(filepath, 'r', errors='replace') as f:
 | 
						|
                    for line in f:
 | 
						|
                        if 'DexClassLoader' in line:
 | 
						|
                            count += handleproblem(
 | 
						|
                                'DexClassLoader',
 | 
						|
                                path_in_build_dir,
 | 
						|
                                filepath,
 | 
						|
                                json_per_build,
 | 
						|
                            )
 | 
						|
                            break
 | 
						|
 | 
						|
            elif curfile.endswith('.gradle') or curfile.endswith('.gradle.kts'):
 | 
						|
                catalog_path = str(build_dir)
 | 
						|
                # Find the longest path of dir that the curfile is in
 | 
						|
                for p in all_catalogs:
 | 
						|
                    if os.path.commonpath([root, p]) == p:
 | 
						|
                        catalog_path = p
 | 
						|
                catalogs = all_catalogs.get(catalog_path, {})
 | 
						|
 | 
						|
                if not os.path.isfile(filepath):
 | 
						|
                    continue
 | 
						|
                with open(filepath, 'r', errors='replace') as f:
 | 
						|
                    lines = f.readlines()
 | 
						|
                for i, line in enumerate(lines):
 | 
						|
                    if is_used_by_gradle_without_catalog(line):
 | 
						|
                        for name in suspects_found(line):
 | 
						|
                            count += handleproblem(
 | 
						|
                                f"usual suspect '{name}'",
 | 
						|
                                path_in_build_dir,
 | 
						|
                                filepath,
 | 
						|
                                json_per_build,
 | 
						|
                            )
 | 
						|
                    for prefix, catalog in catalogs.items():
 | 
						|
                        m = is_used_by_gradle_with_catalog(line, prefix)
 | 
						|
                        if not m:
 | 
						|
                            continue
 | 
						|
                        accessor = m[1]
 | 
						|
                        coordinates = catalog.get_coordinate(accessor)
 | 
						|
                        for coordinate in coordinates:
 | 
						|
                            for name in suspects_found(coordinate):
 | 
						|
                                count += handleproblem(
 | 
						|
                                    f"usual suspect '{prefix}.{accessor}: {name}'",
 | 
						|
                                    path_in_build_dir,
 | 
						|
                                    filepath,
 | 
						|
                                    json_per_build,
 | 
						|
                                )
 | 
						|
                noncomment_lines = [
 | 
						|
                    line for line in lines if not common.gradle_comment.match(line)
 | 
						|
                ]
 | 
						|
                no_comments = re.sub(
 | 
						|
                    r'/\*.*?\*/', '', ''.join(noncomment_lines), flags=re.DOTALL
 | 
						|
                )
 | 
						|
                for url in MAVEN_URL_REGEX.findall(no_comments):
 | 
						|
                    if not any(r.match(url) for r in allowed_repos):
 | 
						|
                        count += handleproblem(
 | 
						|
                            'unknown maven repo \'%s\'' % url,
 | 
						|
                            path_in_build_dir,
 | 
						|
                            filepath,
 | 
						|
                            json_per_build,
 | 
						|
                        )
 | 
						|
 | 
						|
            elif os.path.splitext(path_in_build_dir)[1] in ['', '.bin', '.out', '.exe']:
 | 
						|
                if is_binary(filepath):
 | 
						|
                    count += handleproblem(
 | 
						|
                        'binary', path_in_build_dir, filepath, json_per_build
 | 
						|
                    )
 | 
						|
 | 
						|
            elif curfile in DEPFILE:
 | 
						|
                d = root
 | 
						|
                while d.startswith(str(build_dir)):
 | 
						|
                    for lockfile in DEPFILE[curfile]:
 | 
						|
                        if os.path.isfile(os.path.join(d, lockfile)):
 | 
						|
                            break
 | 
						|
                    else:
 | 
						|
                        d = os.path.dirname(d)
 | 
						|
                        continue
 | 
						|
                    break
 | 
						|
                else:
 | 
						|
                    count += handleproblem(
 | 
						|
                        _('dependency file without lock'),
 | 
						|
                        path_in_build_dir,
 | 
						|
                        filepath,
 | 
						|
                        json_per_build,
 | 
						|
                    )
 | 
						|
 | 
						|
            elif is_executable(filepath):
 | 
						|
                if is_binary(filepath) and not (
 | 
						|
                    safe_path(path_in_build_dir) or is_image_file(filepath)
 | 
						|
                ):
 | 
						|
                    warnproblem(
 | 
						|
                        _('executable binary, possibly code'),
 | 
						|
                        path_in_build_dir,
 | 
						|
                        json_per_build,
 | 
						|
                    )
 | 
						|
 | 
						|
    for p in scanignore_not_found_paths:
 | 
						|
        logging.error(_("Non-exist scanignore path: %s") % p)
 | 
						|
        count += 1
 | 
						|
 | 
						|
    for p in scanignore:
 | 
						|
        if p not in scanignore_worked:
 | 
						|
            logging.error(_('Unused scanignore path: %s') % p)
 | 
						|
            count += 1
 | 
						|
 | 
						|
    for p in scandelete_not_found_paths:
 | 
						|
        logging.error(_("Non-exist scandelete path: %s") % p)
 | 
						|
        count += 1
 | 
						|
 | 
						|
    for p in scandelete:
 | 
						|
        if p not in scandelete_worked:
 | 
						|
            logging.error(_('Unused scandelete path: %s') % p)
 | 
						|
            count += 1
 | 
						|
 | 
						|
    return count
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    parser = ArgumentParser(
 | 
						|
        usage="%(prog)s [options] [(APPID[:VERCODE] | path/to.apk) ...]"
 | 
						|
    )
 | 
						|
    common.setup_global_opts(parser)
 | 
						|
    parser.add_argument(
 | 
						|
        "appid",
 | 
						|
        nargs='*',
 | 
						|
        help=_("application ID with optional versionCode in the form APPID[:VERCODE]"),
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "-f",
 | 
						|
        "--force",
 | 
						|
        action="store_true",
 | 
						|
        default=False,
 | 
						|
        help=_("Force scan of disabled apps and builds."),
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "--json", action="store_true", default=False, help=_("Output JSON to stdout.")
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "-r",
 | 
						|
        "--refresh",
 | 
						|
        dest="refresh_scanner",
 | 
						|
        action="store_true",
 | 
						|
        default=False,
 | 
						|
        help=_("fetch the latest version of signatures from the web"),
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "-e",
 | 
						|
        "--exit-code",
 | 
						|
        action="store_true",
 | 
						|
        default=False,
 | 
						|
        help=_("Exit with a non-zero code if problems were found"),
 | 
						|
    )
 | 
						|
    metadata.add_metadata_arguments(parser)
 | 
						|
    options = common.parse_args(parser)
 | 
						|
    metadata.warnings_action = options.W
 | 
						|
 | 
						|
    json_output = dict()
 | 
						|
    if options.json:
 | 
						|
        if options.verbose:
 | 
						|
            logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
 | 
						|
        else:
 | 
						|
            logging.getLogger().setLevel(logging.ERROR)
 | 
						|
 | 
						|
    # initialize/load configuration values
 | 
						|
    common.get_config()
 | 
						|
 | 
						|
    probcount = 0
 | 
						|
 | 
						|
    appids = []
 | 
						|
    for apk in options.appid:
 | 
						|
        if os.path.isfile(apk):
 | 
						|
            count = scanner.scan_binary(apk)
 | 
						|
            if count > 0:
 | 
						|
                logging.warning(
 | 
						|
                    _('Scanner found {count} problems in {apk}').format(
 | 
						|
                        count=count, apk=apk
 | 
						|
                    )
 | 
						|
                )
 | 
						|
                probcount += count
 | 
						|
        else:
 | 
						|
            appids.append(apk)
 | 
						|
 | 
						|
    if not appids:
 | 
						|
        if options.exit_code and probcount > 0:
 | 
						|
            sys.exit(ExitCode.NONFREE_CODE)
 | 
						|
        if options.refresh_scanner:
 | 
						|
            _get_tool()
 | 
						|
        return
 | 
						|
 | 
						|
    apps = common.read_app_args(appids, allow_version_codes=True)
 | 
						|
 | 
						|
    build_dir = 'build'
 | 
						|
    if not os.path.isdir(build_dir):
 | 
						|
        logging.info("Creating build directory")
 | 
						|
        os.makedirs(build_dir)
 | 
						|
    srclib_dir = os.path.join(build_dir, 'srclib')
 | 
						|
    extlib_dir = os.path.join(build_dir, 'extlib')
 | 
						|
 | 
						|
    for appid, app in apps.items():
 | 
						|
        json_per_appid = dict()
 | 
						|
 | 
						|
        if app.Disabled and not options.force:
 | 
						|
            logging.info(_("Skipping {appid}: disabled").format(appid=appid))
 | 
						|
            json_per_appid['disabled'] = MessageStore().infos.append(
 | 
						|
                'Skipping: disabled'
 | 
						|
            )
 | 
						|
            continue
 | 
						|
 | 
						|
        try:
 | 
						|
            if app.RepoType == 'srclib':
 | 
						|
                build_dir = os.path.join('build', 'srclib', app.Repo)
 | 
						|
            else:
 | 
						|
                build_dir = os.path.join('build', appid)
 | 
						|
 | 
						|
            if app.get('Builds'):
 | 
						|
                logging.info(_("Processing {appid}").format(appid=appid))
 | 
						|
                # Set up vcs interface and make sure we have the latest code...
 | 
						|
                vcs = common.getvcs(app.RepoType, app.Repo, build_dir)
 | 
						|
            else:
 | 
						|
                logging.info(
 | 
						|
                    _(
 | 
						|
                        "{appid}: no builds specified, running on current source state"
 | 
						|
                    ).format(appid=appid)
 | 
						|
                )
 | 
						|
                json_per_build = MessageStore()
 | 
						|
                json_per_appid['current-source-state'] = json_per_build
 | 
						|
                count = scan_source(build_dir, json_per_build=json_per_build)
 | 
						|
                if count > 0:
 | 
						|
                    logging.warning(
 | 
						|
                        _('Scanner found {count} problems in {appid}:').format(
 | 
						|
                            count=count, appid=appid
 | 
						|
                        )
 | 
						|
                    )
 | 
						|
                    probcount += count
 | 
						|
                app['Builds'] = []
 | 
						|
 | 
						|
            for build in app.get('Builds', []):
 | 
						|
                json_per_build = MessageStore()
 | 
						|
                json_per_appid[build.versionCode] = json_per_build
 | 
						|
 | 
						|
                if build.disable and not options.force:
 | 
						|
                    logging.info(
 | 
						|
                        "...skipping version %s - %s"
 | 
						|
                        % (build.versionName, build.get('disable', build.commit[1:]))
 | 
						|
                    )
 | 
						|
                    continue
 | 
						|
 | 
						|
                logging.info("...scanning version " + build.versionName)
 | 
						|
                # Prepare the source code...
 | 
						|
                common.prepare_source(
 | 
						|
                    vcs, app, build, build_dir, srclib_dir, extlib_dir, False
 | 
						|
                )
 | 
						|
 | 
						|
                count = scan_source(build_dir, build, json_per_build=json_per_build)
 | 
						|
                if count > 0:
 | 
						|
                    logging.warning(
 | 
						|
                        _(
 | 
						|
                            'Scanner found {count} problems in {appid}:{versionCode}:'
 | 
						|
                        ).format(
 | 
						|
                            count=count, appid=appid, versionCode=build.versionCode
 | 
						|
                        )
 | 
						|
                    )
 | 
						|
                    probcount += count
 | 
						|
 | 
						|
        except BuildException as be:
 | 
						|
            logging.warning(
 | 
						|
                'Could not scan app %s due to BuildException: %s' % (appid, be)
 | 
						|
            )
 | 
						|
            probcount += 1
 | 
						|
        except VCSException as vcse:
 | 
						|
            logging.warning('VCS error while scanning app %s: %s' % (appid, vcse))
 | 
						|
            probcount += 1
 | 
						|
        except Exception:
 | 
						|
            logging.warning(
 | 
						|
                'Could not scan app %s due to unknown error: %s'
 | 
						|
                % (appid, traceback.format_exc())
 | 
						|
            )
 | 
						|
            probcount += 1
 | 
						|
 | 
						|
        for k, v in json_per_appid.items():
 | 
						|
            if len(v.errors) or len(v.warnings) or len(v.infos):
 | 
						|
                json_output[appid] = {
 | 
						|
                    k: dict((field.name, getattr(v, field.name)) for field in fields(v))
 | 
						|
                    for k, v in json_per_appid.items()
 | 
						|
                }
 | 
						|
                break
 | 
						|
 | 
						|
    logging.info(_("Finished"))
 | 
						|
    if options.json:
 | 
						|
        print(json.dumps(json_output))
 | 
						|
    elif probcount or options.verbose:
 | 
						|
        print(_("%d problems found") % probcount)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 | 
						|
 | 
						|
 | 
						|
SUSS_DEFAULT = r'''{
 | 
						|
  "cache_duration": 86400,
 | 
						|
  "signatures": {
 | 
						|
    "com.amazon.device.ads": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/amazon/device/ads"
 | 
						|
      ],
 | 
						|
      "description": "an interface for views used to retrieve and display Amazon ads.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.amazon.device.associates": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/amazon/device/associates"
 | 
						|
      ],
 | 
						|
      "description": "library for Amazon\u2019s affiliate marketing program.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.amazon.device.iap": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/amazon/device/iap"
 | 
						|
      ],
 | 
						|
      "description": "allows an app to present, process, and fulfill purchases of digital content and subscriptions within your app.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.amazonaws": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/amazonaws/AbortedException",
 | 
						|
        "com/amazonaws/AmazonClientException",
 | 
						|
        "com/amazonaws/AmazonServiceException$ErrorType",
 | 
						|
        "com/amazonaws/AmazonServiceException",
 | 
						|
        "com/amazonaws/AmazonWebServiceClient",
 | 
						|
        "com/amazonaws/AmazonWebServiceRequest",
 | 
						|
        "com/amazonaws/AmazonWebServiceResponse",
 | 
						|
        "com/amazonaws/async",
 | 
						|
        "com/amazonaws/auth",
 | 
						|
        "com/amazonaws/ClientConfiguration",
 | 
						|
        "com/amazonaws/cognito",
 | 
						|
        "com/amazonaws/DefaultRequest",
 | 
						|
        "com/amazonaws/event",
 | 
						|
        "com/amazonaws/handlers",
 | 
						|
        "com/amazonaws/http",
 | 
						|
        "com/amazonaws/HttpMethod",
 | 
						|
        "com/amazonaws/internal",
 | 
						|
        "com/amazonaws/logging",
 | 
						|
        "com/amazonaws/metrics",
 | 
						|
        "com/amazonaws/mobile",
 | 
						|
        "com/amazonaws/mobileconnectors",
 | 
						|
        "com/amazonaws/Protocol",
 | 
						|
        "com/amazonaws/regions",
 | 
						|
        "com/amazonaws/RequestClientOptions$Marker",
 | 
						|
        "com/amazonaws/RequestClientOptions",
 | 
						|
        "com/amazonaws/Request",
 | 
						|
        "com/amazonaws/ResponseMetadata",
 | 
						|
        "com/amazonaws/Response",
 | 
						|
        "com/amazonaws/retry",
 | 
						|
        "com/amazonaws/SDKGlobalConfiguration",
 | 
						|
        "com/amazonaws/ServiceNameFactory",
 | 
						|
        "com/amazonaws/services",
 | 
						|
        "com/amazonaws/transform",
 | 
						|
        "com/amazonaws/util"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.amazonaws:amazon-kinesis-aggregator",
 | 
						|
        "com.amazonaws:amazon-kinesis-connectors",
 | 
						|
        "com.amazonaws:amazon-kinesis-deaggregator",
 | 
						|
        "com.amazonaws:aws-android-sdk-apigateway-core",
 | 
						|
        "com.amazonaws:aws-android-sdk-auth-core",
 | 
						|
        "com.amazonaws:aws-android-sdk-auth-facebook",
 | 
						|
        "com.amazonaws:aws-android-sdk-auth-google",
 | 
						|
        "com.amazonaws:aws-android-sdk-auth-ui",
 | 
						|
        "com.amazonaws:aws-android-sdk-auth-userpools",
 | 
						|
        "com.amazonaws:aws-android-sdk-cognito",
 | 
						|
        "com.amazonaws:aws-android-sdk-cognitoauth",
 | 
						|
        "com.amazonaws:aws-android-sdk-cognitoidentityprovider-asf",
 | 
						|
        "com.amazonaws:aws-android-sdk-comprehend",
 | 
						|
        "com.amazonaws:aws-android-sdk-core",
 | 
						|
        "com.amazonaws:aws-android-sdk-ddb",
 | 
						|
        "com.amazonaws:aws-android-sdk-ddb-document",
 | 
						|
        "com.amazonaws:aws-android-sdk-iot",
 | 
						|
        "com.amazonaws:aws-android-sdk-kinesis",
 | 
						|
        "com.amazonaws:aws-android-sdk-kinesisvideo",
 | 
						|
        "com.amazonaws:aws-android-sdk-kinesisvideo-archivedmedia",
 | 
						|
        "com.amazonaws:aws-android-sdk-kms",
 | 
						|
        "com.amazonaws:aws-android-sdk-lambda",
 | 
						|
        "com.amazonaws:aws-android-sdk-lex",
 | 
						|
        "com.amazonaws:aws-android-sdk-location",
 | 
						|
        "com.amazonaws:aws-android-sdk-logs",
 | 
						|
        "com.amazonaws:aws-android-sdk-mobileanalytics",
 | 
						|
        "com.amazonaws:aws-android-sdk-mobile-client",
 | 
						|
        "com.amazonaws:aws-android-sdk-pinpoint",
 | 
						|
        "com.amazonaws:aws-android-sdk-polly",
 | 
						|
        "com.amazonaws:aws-android-sdk-rekognition",
 | 
						|
        "com.amazonaws:aws-android-sdk-s3",
 | 
						|
        "com.amazonaws:aws-android-sdk-ses",
 | 
						|
        "com.amazonaws:aws-android-sdk-sns",
 | 
						|
        "com.amazonaws:aws-android-sdk-sqs",
 | 
						|
        "com.amazonaws:aws-android-sdk-textract",
 | 
						|
        "com.amazonaws:aws-android-sdk-transcribe",
 | 
						|
        "com.amazonaws:aws-android-sdk-translate",
 | 
						|
        "com.amazonaws:dynamodb-key-diagnostics-library",
 | 
						|
        "com.amazonaws:DynamoDBLocal",
 | 
						|
        "com.amazonaws:dynamodb-lock-client",
 | 
						|
        "com.amazonaws:ivs-broadcast",
 | 
						|
        "com.amazonaws:ivs-player",
 | 
						|
        "com.amazonaws:kinesis-storm-spout"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "AmazonAWS"
 | 
						|
    },
 | 
						|
    "com.android.billingclient": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/android/billingclient"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://developer.android.com/google/play/billing/integrate"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.android.billingclient",
 | 
						|
        "com.google.androidbrowserhelper:billing",
 | 
						|
        "com.anjlab.android.iab.v3:library",
 | 
						|
        "com.github.penn5:donations",
 | 
						|
        "me.proton.core:payment-iap"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "BillingClient"
 | 
						|
    },
 | 
						|
    "com.android.installreferrer": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeDep",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/android/installreferrer"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://developer.android.com/google/play/installreferrer/library"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.android.installreferrer"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Play Install Referrer Library"
 | 
						|
    },
 | 
						|
    "com.anychart": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/anychart"
 | 
						|
      ],
 | 
						|
      "description": "a data visualization library for easily creating interactive charts in Android apps.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.appboy": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/appboy"
 | 
						|
      ],
 | 
						|
      "description": "Targets customers based on personal interests, location, past purchases, and more; profiles users, segments audiences, and utilizes analytics for targeted advertisements.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.appbrain": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/appbrain"
 | 
						|
      ],
 | 
						|
      "description": "See <a rel='nofollow' href='https://reports.exodus-privacy.eu.org/en/trackers/136/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.applause.android": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/applause/android"
 | 
						|
      ],
 | 
						|
      "description": "crowd-sourced testing. See <a rel='nofollow' href='https://www.crunchbase.com/organization/applause'>Crunchbase</a> and <a href='https://reports.exodus-privacy.eu.org/en/trackers/132/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.applovin": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/applovin"
 | 
						|
      ],
 | 
						|
      "description": "a mobile advertising technology company that enables brands to create mobile marketing campaigns that are fueled by data. Primary targets games.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.appsflyer": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/appsflyer"
 | 
						|
      ],
 | 
						|
      "description": "a mobile & attribution analytics platform.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.apptentive": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/apptentive"
 | 
						|
      ],
 | 
						|
      "description": "See <a href='https://reports.exodus-privacy.eu.org/en/trackers/115/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.apptimize": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/apptimize"
 | 
						|
      ],
 | 
						|
      "description": "See <a href='https://reports.exodus-privacy.eu.org/en/trackers/135/'>Exodus Privacy</a> and <a rel='nofollow' href='https://www.crunchbase.com/organization/apptimize'>Crunchbase</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.askingpoint": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/askingpoint"
 | 
						|
      ],
 | 
						|
      "description": "complete mobile user engagement solution (power local, In-application evaluations and audits, input, user support, mobile reviews and informing).",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.baidu.mobstat": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/baidu/mobstat"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://mtj.baidu.com/web/sdk/index"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.baidu.mobstat"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "\u767e\u5ea6\u79fb\u52a8\u7edf\u8ba1SDK"
 | 
						|
    },
 | 
						|
    "com.batch": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/batch"
 | 
						|
      ],
 | 
						|
      "description": "mobile engagement platform to execute CRM tactics over iOS, Android & mobile websites.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.bosch.mtprotocol": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/bosch/mtprotocol"
 | 
						|
      ],
 | 
						|
      "description": "simplify and manage use of Bosch GLM and PLR laser rangefinders with Bluetooth connectivity.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.bugsee.library.Bugsee": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/bugsee/library/Bugsee"
 | 
						|
      ],
 | 
						|
      "description": "see video, network and logs that led to bugs and crashes in live apps. No need to reproduce intermittent bugs. With Bugsee, all the crucial data is always there.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.bugsense": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/bugsense"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/bugsense/docs/blob/master/android.md"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.bugsense"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "BugSense"
 | 
						|
    },
 | 
						|
    "com.chartboost.sdk": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/chartboost/sdk"
 | 
						|
      ],
 | 
						|
      "description": "create customized interstitial and video ads, promote new games, and swap traffic with one another. For more details, see <a href='https://en.wikipedia.org/wiki/Chartboost'>Wikipedia</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.cloudrail": {
 | 
						|
      "code_signature": [
 | 
						|
        "com/cloudrail"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://cloudrail.com/"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.cloudrail"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "CloudRail"
 | 
						|
    },
 | 
						|
    "com.comscore.analytics": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/comscore"
 | 
						|
      ],
 | 
						|
      "description": "See <a href='https://en.wikipedia.org/wiki/Comscore'>Wikipedia</a> for details.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.crashlytics.sdk.android": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/crashlytics"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://firebase.google.com/docs/crashlytics"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "crashlytics"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Firebase Crashlytics"
 | 
						|
    },
 | 
						|
    "com.crittercism": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/crittercism"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/crittercism/crittercism-unity-android"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.crittercism"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Crittercism Plugin for Unity Crash Reporting"
 | 
						|
    },
 | 
						|
    "com.criware": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeAssets"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/criware"
 | 
						|
      ],
 | 
						|
      "description": "audio and video solutions that can be integrated with popular game engines.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.deezer.sdk": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/deezer/sdk"
 | 
						|
      ],
 | 
						|
      "description": "a closed-source API for the Deezer music streaming service.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.dynamicyield": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/dynamicyield"
 | 
						|
      ],
 | 
						|
      "description": "targeted advertising. Tracks user via location (GPS, WiFi, location data). Collects PII, profiling. See <a href='https://reports.exodus-privacy.eu.org/en/trackers/152/'>Exodus Privacy</a> for more details.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.dynatrace.android.app": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/dynatrace/android/app"
 | 
						|
      ],
 | 
						|
      "description": "See <a rel='nofollow' href='https://www.crunchbase.com/organization/dynatrace-software'>Crunchbase</a> and <a href='https://reports.exodus-privacy.eu.org/en/trackers/137/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.ensighten": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/ensighten"
 | 
						|
      ],
 | 
						|
      "description": "organizations can leverage first-party customer data and profiles to fuel omni-channel action and insight using their existing technology investments. See <a rel='nofollow' href='https://www.crunchbase.com/organization/ensighten'>Crunchbase</a> and <a href='https://reports.exodus-privacy.eu.org/en/trackers/151/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.epicgames.mobile.eossdk": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/epicgames/mobile/eossdk"
 | 
						|
      ],
 | 
						|
      "description": "integrate games with Epic Account Services and Epic Games Store",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.facebook.android": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/facebook/AccessToken",
 | 
						|
        "com/facebook/AccessTokenCache",
 | 
						|
        "com/facebook/AccessTokenManager",
 | 
						|
        "com/facebook/AccessTokenSource",
 | 
						|
        "com/facebook/AccessTokenTracker",
 | 
						|
        "com/facebook/all/All",
 | 
						|
        "com/facebook/appevents/aam/MetadataIndexer",
 | 
						|
        "com/facebook/appevents/aam/MetadataMatcher",
 | 
						|
        "com/facebook/appevents/aam/MetadataRule",
 | 
						|
        "com/facebook/appevents/aam/MetadataViewObserver",
 | 
						|
        "com/facebook/appevents/AccessTokenAppIdPair",
 | 
						|
        "com/facebook/appevents/AnalyticsUserIDStore",
 | 
						|
        "com/facebook/appevents/AppEvent",
 | 
						|
        "com/facebook/appevents/AppEventCollection",
 | 
						|
        "com/facebook/appevents/AppEventDiskStore",
 | 
						|
        "com/facebook/appevents/AppEventQueue",
 | 
						|
        "com/facebook/appevents/AppEventsConstants",
 | 
						|
        "com/facebook/appevents/AppEventsLogger",
 | 
						|
        "com/facebook/appevents/AppEventsLoggerImpl",
 | 
						|
        "com/facebook/appevents/AppEventsManager",
 | 
						|
        "com/facebook/appevents/AppEventStore",
 | 
						|
        "com/facebook/appevents/cloudbridge/AppEventsCAPIManager",
 | 
						|
        "com/facebook/appevents/cloudbridge/AppEventsConversionsAPITransformer",
 | 
						|
        "com/facebook/appevents/cloudbridge/AppEventsConversionsAPITransformerWebRequests",
 | 
						|
        "com/facebook/appevents/codeless/CodelessLoggingEventListener",
 | 
						|
        "com/facebook/appevents/codeless/CodelessManager",
 | 
						|
        "com/facebook/appevents/codeless/CodelessMatcher",
 | 
						|
        "com/facebook/appevents/codeless/internal/Constants",
 | 
						|
        "com/facebook/appevents/codeless/internal/EventBinding",
 | 
						|
        "com/facebook/appevents/codeless/internal/ParameterComponent",
 | 
						|
        "com/facebook/appevents/codeless/internal/PathComponent",
 | 
						|
        "com/facebook/appevents/codeless/internal/SensitiveUserDataUtils",
 | 
						|
        "com/facebook/appevents/codeless/internal/UnityReflection",
 | 
						|
        "com/facebook/appevents/codeless/internal/ViewHierarchy",
 | 
						|
        "com/facebook/appevents/codeless/RCTCodelessLoggingEventListener",
 | 
						|
        "com/facebook/appevents/codeless/ViewIndexer",
 | 
						|
        "com/facebook/appevents/codeless/ViewIndexingTrigger",
 | 
						|
        "com/facebook/appevents/eventdeactivation/EventDeactivationManager",
 | 
						|
        "com/facebook/appevents/FacebookSDKJSInterface",
 | 
						|
        "com/facebook/appevents/FlushReason",
 | 
						|
        "com/facebook/appevents/FlushResult",
 | 
						|
        "com/facebook/appevents/FlushStatistics",
 | 
						|
        "com/facebook/appevents/iap/InAppPurchaseActivityLifecycleTracker",
 | 
						|
        "com/facebook/appevents/iap/InAppPurchaseAutoLogger",
 | 
						|
        "com/facebook/appevents/iap/InAppPurchaseBillingClientWrapper",
 | 
						|
        "com/facebook/appevents/iap/InAppPurchaseEventManager",
 | 
						|
        "com/facebook/appevents/iap/InAppPurchaseLoggerManager",
 | 
						|
        "com/facebook/appevents/iap/InAppPurchaseManager",
 | 
						|
        "com/facebook/appevents/iap/InAppPurchaseSkuDetailsWrapper",
 | 
						|
        "com/facebook/appevents/iap/InAppPurchaseUtils",
 | 
						|
        "com/facebook/appevents/integrity/BlocklistEventsManager",
 | 
						|
        "com/facebook/appevents/integrity/IntegrityManager",
 | 
						|
        "com/facebook/appevents/integrity/MACARuleMatchingManager",
 | 
						|
        "com/facebook/appevents/integrity/ProtectedModeManager",
 | 
						|
        "com/facebook/appevents/integrity/RedactedEventsManager",
 | 
						|
        "com/facebook/appevents/internal/ActivityLifecycleTracker",
 | 
						|
        "com/facebook/appevents/InternalAppEventsLogger",
 | 
						|
        "com/facebook/appevents/internal/AppEventsLoggerUtility",
 | 
						|
        "com/facebook/appevents/internal/AppEventUtility",
 | 
						|
        "com/facebook/appevents/internal/AutomaticAnalyticsLogger",
 | 
						|
        "com/facebook/appevents/internal/Constants",
 | 
						|
        "com/facebook/appevents/internal/FileDownloadTask",
 | 
						|
        "com/facebook/appevents/internal/HashUtils",
 | 
						|
        "com/facebook/appevents/internal/SessionInfo",
 | 
						|
        "com/facebook/appevents/internal/SessionLogger",
 | 
						|
        "com/facebook/appevents/internal/SourceApplicationInfo",
 | 
						|
        "com/facebook/appevents/internal/ViewHierarchyConstants",
 | 
						|
        "com/facebook/appevents/ml/Model",
 | 
						|
        "com/facebook/appevents/ml/ModelManager",
 | 
						|
        "com/facebook/appevents/ml/MTensor",
 | 
						|
        "com/facebook/appevents/ml/Operator",
 | 
						|
        "com/facebook/appevents/ml/Utils",
 | 
						|
        "com/facebook/appevents/ondeviceprocessing/OnDeviceProcessingManager",
 | 
						|
        "com/facebook/appevents/ondeviceprocessing/RemoteServiceParametersHelper",
 | 
						|
        "com/facebook/appevents/ondeviceprocessing/RemoteServiceWrapper",
 | 
						|
        "com/facebook/appevents/PersistedEvents",
 | 
						|
        "com/facebook/appevents/restrictivedatafilter/RestrictiveDataManager",
 | 
						|
        "com/facebook/appevents/SessionEventsState",
 | 
						|
        "com/facebook/appevents/suggestedevents/FeatureExtractor",
 | 
						|
        "com/facebook/appevents/suggestedevents/PredictionHistoryManager",
 | 
						|
        "com/facebook/appevents/suggestedevents/SuggestedEventsManager",
 | 
						|
        "com/facebook/appevents/suggestedevents/SuggestedEventViewHierarchy",
 | 
						|
        "com/facebook/appevents/suggestedevents/ViewObserver",
 | 
						|
        "com/facebook/appevents/suggestedevents/ViewOnClickListener",
 | 
						|
        "com/facebook/appevents/UserDataStore",
 | 
						|
        "com/facebook/applinks/AppLinkData",
 | 
						|
        "com/facebook/applinks/AppLinks",
 | 
						|
        "com/facebook/applinks/FacebookAppLinkResolver",
 | 
						|
        "com/facebook/AuthenticationToken",
 | 
						|
        "com/facebook/AuthenticationTokenCache",
 | 
						|
        "com/facebook/AuthenticationTokenClaims",
 | 
						|
        "com/facebook/AuthenticationTokenHeader",
 | 
						|
        "com/facebook/AuthenticationTokenManager",
 | 
						|
        "com/facebook/AuthenticationTokenTracker",
 | 
						|
        "com/facebook/bolts/AggregateException",
 | 
						|
        "com/facebook/bolts/AndroidExecutors",
 | 
						|
        "com/facebook/bolts/AppLink",
 | 
						|
        "com/facebook/bolts/AppLinkResolver",
 | 
						|
        "com/facebook/bolts/AppLinks",
 | 
						|
        "com/facebook/bolts/BoltsExecutors",
 | 
						|
        "com/facebook/bolts/CancellationToken",
 | 
						|
        "com/facebook/bolts/CancellationTokenRegistration",
 | 
						|
        "com/facebook/bolts/CancellationTokenSource",
 | 
						|
        "com/facebook/bolts/Continuation",
 | 
						|
        "com/facebook/bolts/ExecutorException",
 | 
						|
        "com/facebook/bolts/Task",
 | 
						|
        "com/facebook/bolts/TaskCompletionSource",
 | 
						|
        "com/facebook/bolts/UnobservedErrorNotifier",
 | 
						|
        "com/facebook/bolts/UnobservedTaskException",
 | 
						|
        "com/facebook/CallbackManager",
 | 
						|
        "com/facebook/common/Common",
 | 
						|
        "com/facebook/core/Core",
 | 
						|
        "com/facebook/CurrentAccessTokenExpirationBroadcastReceiver",
 | 
						|
        "com/facebook/CustomTabActivity",
 | 
						|
        "com/facebook/CustomTabMainActivity",
 | 
						|
        "com/facebook/devicerequests/internal/DeviceRequestsHelper",
 | 
						|
        "com/facebook/FacebookActivity",
 | 
						|
        "com/facebook/FacebookAuthorizationException",
 | 
						|
        "com/facebook/FacebookBroadcastReceiver",
 | 
						|
        "com/facebook/FacebookButtonBase",
 | 
						|
        "com/facebook/FacebookCallback",
 | 
						|
        "com/facebook/FacebookContentProvider",
 | 
						|
        "com/facebook/FacebookDialog",
 | 
						|
        "com/facebook/FacebookDialogException",
 | 
						|
        "com/facebook/FacebookException",
 | 
						|
        "com/facebook/FacebookGraphResponseException",
 | 
						|
        "com/facebook/FacebookOperationCanceledException",
 | 
						|
        "com/facebook/FacebookRequestError",
 | 
						|
        "com/facebook/FacebookSdk",
 | 
						|
        "com/facebook/FacebookSdkNotInitializedException",
 | 
						|
        "com/facebook/FacebookSdkVersion",
 | 
						|
        "com/facebook/FacebookServiceException",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/AppToUserNotificationSender",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/CloudGameLoginHandler",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/DaemonReceiver",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/DaemonRequest",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/GameFeaturesLibrary",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/InAppAdLibrary",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/InAppPurchaseLibrary",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/internal/SDKAnalyticsEvents",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/internal/SDKConstants",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/internal/SDKLogger",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/internal/SDKMessageEnum",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/internal/SDKShareIntentEnum",
 | 
						|
        "com/facebook/gamingservices/cloudgaming/PlayableAdsLibrary",
 | 
						|
        "com/facebook/gamingservices/ContextChooseDialog",
 | 
						|
        "com/facebook/gamingservices/ContextCreateDialog",
 | 
						|
        "com/facebook/gamingservices/ContextSwitchDialog",
 | 
						|
        "com/facebook/gamingservices/CustomUpdate",
 | 
						|
        "com/facebook/gamingservices/FriendFinderDialog",
 | 
						|
        "com/facebook/gamingservices/GameRequestDialog",
 | 
						|
        "com/facebook/gamingservices/GamingContext",
 | 
						|
        "com/facebook/gamingservices/GamingGroupIntegration",
 | 
						|
        "com/facebook/gamingservices/GamingImageUploader",
 | 
						|
        "com/facebook/gamingservices/GamingPayload",
 | 
						|
        "com/facebook/gamingservices/GamingServices",
 | 
						|
        "com/facebook/gamingservices/GamingVideoUploader",
 | 
						|
        "com/facebook/gamingservices/internal/DateFormatter",
 | 
						|
        "com/facebook/gamingservices/internal/GamingMediaUploader",
 | 
						|
        "com/facebook/gamingservices/internal/TournamentJoinDialogURIBuilder",
 | 
						|
        "com/facebook/gamingservices/internal/TournamentScoreType",
 | 
						|
        "com/facebook/gamingservices/internal/TournamentShareDialogURIBuilder",
 | 
						|
        "com/facebook/gamingservices/internal/TournamentSortOrder",
 | 
						|
        "com/facebook/gamingservices/model/ContextChooseContent",
 | 
						|
        "com/facebook/gamingservices/model/ContextCreateContent",
 | 
						|
        "com/facebook/gamingservices/model/ContextSwitchContent",
 | 
						|
        "com/facebook/gamingservices/model/CustomUpdateContent",
 | 
						|
        "com/facebook/gamingservices/OpenGamingMediaDialog",
 | 
						|
        "com/facebook/gamingservices/Tournament",
 | 
						|
        "com/facebook/gamingservices/TournamentConfig",
 | 
						|
        "com/facebook/gamingservices/TournamentFetcher",
 | 
						|
        "com/facebook/gamingservices/TournamentJoinDialog",
 | 
						|
        "com/facebook/gamingservices/TournamentShareDialog",
 | 
						|
        "com/facebook/gamingservices/TournamentUpdater",
 | 
						|
        "com/facebook/GraphRequest",
 | 
						|
        "com/facebook/GraphRequestAsyncTask",
 | 
						|
        "com/facebook/GraphRequestBatch",
 | 
						|
        "com/facebook/GraphResponse",
 | 
						|
        "com/facebook/HttpMethod",
 | 
						|
        "com/facebook/internal/AnalyticsEvents",
 | 
						|
        "com/facebook/internal/AppCall",
 | 
						|
        "com/facebook/internal/AttributionIdentifiers",
 | 
						|
        "com/facebook/internal/BoltsMeasurementEventListener",
 | 
						|
        "com/facebook/internal/BundleJSONConverter",
 | 
						|
        "com/facebook/internal/CallbackManagerImpl",
 | 
						|
        "com/facebook/internal/CollectionMapper",
 | 
						|
        "com/facebook/internal/CustomTab",
 | 
						|
        "com/facebook/internal/CustomTabUtils",
 | 
						|
        "com/facebook/internal/DialogFeature",
 | 
						|
        "com/facebook/internal/DialogPresenter",
 | 
						|
        "com/facebook/internal/FacebookDialogBase",
 | 
						|
        "com/facebook/internal/FacebookDialogFragment",
 | 
						|
        "com/facebook/internal/FacebookGamingAction",
 | 
						|
        "com/facebook/internal/FacebookInitProvider",
 | 
						|
        "com/facebook/internal/FacebookRequestErrorClassification",
 | 
						|
        "com/facebook/internal/FacebookSignatureValidator",
 | 
						|
        "com/facebook/internal/FacebookWebFallbackDialog",
 | 
						|
        "com/facebook/internal/FeatureManager",
 | 
						|
        "com/facebook/internal/FetchedAppGateKeepersManager",
 | 
						|
        "com/facebook/internal/FetchedAppSettings",
 | 
						|
        "com/facebook/internal/FetchedAppSettingsManager",
 | 
						|
        "com/facebook/internal/FileLruCache",
 | 
						|
        "com/facebook/internal/FragmentWrapper",
 | 
						|
        "com/facebook/internal/gatekeeper/GateKeeper",
 | 
						|
        "com/facebook/internal/gatekeeper/GateKeeperRuntimeCache",
 | 
						|
        "com/facebook/internal/ImageDownloader",
 | 
						|
        "com/facebook/internal/ImageRequest",
 | 
						|
        "com/facebook/internal/ImageResponse",
 | 
						|
        "com/facebook/internal/ImageResponseCache",
 | 
						|
        "com/facebook/internal/InstagramCustomTab",
 | 
						|
        "com/facebook/internal/InstallReferrerUtil",
 | 
						|
        "com/facebook/internal/instrument/anrreport/ANRDetector",
 | 
						|
        "com/facebook/internal/instrument/anrreport/ANRHandler",
 | 
						|
        "com/facebook/internal/instrument/crashreport/CrashHandler",
 | 
						|
        "com/facebook/internal/instrument/crashshield/AutoHandleExceptions",
 | 
						|
        "com/facebook/internal/instrument/crashshield/CrashShieldHandler",
 | 
						|
        "com/facebook/internal/instrument/crashshield/NoAutoExceptionHandling",
 | 
						|
        "com/facebook/internal/instrument/errorreport/ErrorReportData",
 | 
						|
        "com/facebook/internal/instrument/errorreport/ErrorReportHandler",
 | 
						|
        "com/facebook/internal/instrument/ExceptionAnalyzer",
 | 
						|
        "com/facebook/internal/instrument/InstrumentData",
 | 
						|
        "com/facebook/internal/instrument/InstrumentManager",
 | 
						|
        "com/facebook/internal/instrument/InstrumentUtility",
 | 
						|
        "com/facebook/internal/instrument/threadcheck/ThreadCheckHandler",
 | 
						|
        "com/facebook/internal/InternalSettings",
 | 
						|
        "com/facebook/internal/LockOnGetVariable",
 | 
						|
        "com/facebook/internal/Logger",
 | 
						|
        "com/facebook/internal/logging/dumpsys/EndToEndDumper",
 | 
						|
        "com/facebook/internal/Mutable",
 | 
						|
        "com/facebook/internal/NativeAppCallAttachmentStore",
 | 
						|
        "com/facebook/internal/NativeProtocol",
 | 
						|
        "com/facebook/internal/PlatformServiceClient",
 | 
						|
        "com/facebook/internal/ProfileInformationCache",
 | 
						|
        "com/facebook/internal/qualityvalidation/Excuse",
 | 
						|
        "com/facebook/internal/qualityvalidation/ExcusesForDesignViolations",
 | 
						|
        "com/facebook/internal/security/CertificateUtil",
 | 
						|
        "com/facebook/internal/security/OidcSecurityUtil",
 | 
						|
        "com/facebook/internal/ServerProtocol",
 | 
						|
        "com/facebook/internal/SmartLoginOption",
 | 
						|
        "com/facebook/internal/UrlRedirectCache",
 | 
						|
        "com/facebook/internal/Utility",
 | 
						|
        "com/facebook/internal/Validate",
 | 
						|
        "com/facebook/internal/WebDialog",
 | 
						|
        "com/facebook/internal/WorkQueue",
 | 
						|
        "com/facebook/LegacyTokenHelper",
 | 
						|
        "com/facebook/LoggingBehavior",
 | 
						|
        "com/facebook/login/CodeChallengeMethod",
 | 
						|
        "com/facebook/login/CustomTabLoginMethodHandler",
 | 
						|
        "com/facebook/login/CustomTabPrefetchHelper",
 | 
						|
        "com/facebook/login/DefaultAudience",
 | 
						|
        "com/facebook/login/DeviceAuthDialog",
 | 
						|
        "com/facebook/login/DeviceAuthMethodHandler",
 | 
						|
        "com/facebook/login/DeviceLoginManager",
 | 
						|
        "com/facebook/login/GetTokenClient",
 | 
						|
        "com/facebook/login/GetTokenLoginMethodHandler",
 | 
						|
        "com/facebook/login/InstagramAppLoginMethodHandler",
 | 
						|
        "com/facebook/login/KatanaProxyLoginMethodHandler",
 | 
						|
        "com/facebook/login/Login",
 | 
						|
        "com/facebook/login/LoginBehavior",
 | 
						|
        "com/facebook/login/LoginClient",
 | 
						|
        "com/facebook/login/LoginConfiguration",
 | 
						|
        "com/facebook/login/LoginFragment",
 | 
						|
        "com/facebook/login/LoginLogger",
 | 
						|
        "com/facebook/login/LoginManager",
 | 
						|
        "com/facebook/login/LoginMethodHandler",
 | 
						|
        "com/facebook/login/LoginResult",
 | 
						|
        "com/facebook/login/LoginStatusClient",
 | 
						|
        "com/facebook/login/LoginTargetApp",
 | 
						|
        "com/facebook/login/NativeAppLoginMethodHandler",
 | 
						|
        "com/facebook/login/NonceUtil",
 | 
						|
        "com/facebook/login/PKCEUtil",
 | 
						|
        "com/facebook/login/StartActivityDelegate",
 | 
						|
        "com/facebook/LoginStatusCallback",
 | 
						|
        "com/facebook/login/WebLoginMethodHandler",
 | 
						|
        "com/facebook/login/WebViewLoginMethodHandler",
 | 
						|
        "com/facebook/login/widget/DeviceLoginButton",
 | 
						|
        "com/facebook/login/widget/LoginButton",
 | 
						|
        "com/facebook/login/widget/ProfilePictureView",
 | 
						|
        "com/facebook/login/widget/ToolTipPopup",
 | 
						|
        "com/facebook/messenger/Messenger",
 | 
						|
        "com/facebook/messenger/MessengerThreadParams",
 | 
						|
        "com/facebook/messenger/MessengerUtils",
 | 
						|
        "com/facebook/messenger/ShareToMessengerParams",
 | 
						|
        "com/facebook/messenger/ShareToMessengerParamsBuilder",
 | 
						|
        "com/facebook/Profile",
 | 
						|
        "com/facebook/ProfileCache",
 | 
						|
        "com/facebook/ProfileManager",
 | 
						|
        "com/facebook/ProfileTracker",
 | 
						|
        "com/facebook/ProgressNoopOutputStream",
 | 
						|
        "com/facebook/ProgressOutputStream",
 | 
						|
        "com/facebook/RequestOutputStream",
 | 
						|
        "com/facebook/RequestProgress",
 | 
						|
        "com/facebook/share/internal/CameraEffectFeature",
 | 
						|
        "com/facebook/share/internal/CameraEffectJSONUtility",
 | 
						|
        "com/facebook/share/internal/GameRequestValidation",
 | 
						|
        "com/facebook/share/internal/LegacyNativeDialogParameters",
 | 
						|
        "com/facebook/share/internal/MessageDialogFeature",
 | 
						|
        "com/facebook/share/internal/NativeDialogParameters",
 | 
						|
        "com/facebook/share/internal/ResultProcessor",
 | 
						|
        "com/facebook/share/internal/ShareConstants",
 | 
						|
        "com/facebook/share/internal/ShareContentValidation",
 | 
						|
        "com/facebook/share/internal/ShareDialogFeature",
 | 
						|
        "com/facebook/share/internal/ShareFeedContent",
 | 
						|
        "com/facebook/share/internal/ShareInternalUtility",
 | 
						|
        "com/facebook/share/internal/ShareStoryFeature",
 | 
						|
        "com/facebook/share/internal/VideoUploader",
 | 
						|
        "com/facebook/share/internal/WebDialogParameters",
 | 
						|
        "com/facebook/share/model/AppGroupCreationContent",
 | 
						|
        "com/facebook/share/model/CameraEffectArguments",
 | 
						|
        "com/facebook/share/model/CameraEffectTextures",
 | 
						|
        "com/facebook/share/model/GameRequestContent",
 | 
						|
        "com/facebook/share/model/ShareCameraEffectContent",
 | 
						|
        "com/facebook/share/model/ShareContent",
 | 
						|
        "com/facebook/share/model/ShareHashtag",
 | 
						|
        "com/facebook/share/model/ShareLinkContent",
 | 
						|
        "com/facebook/share/model/ShareMedia",
 | 
						|
        "com/facebook/share/model/ShareMediaContent",
 | 
						|
        "com/facebook/share/model/ShareMessengerActionButton",
 | 
						|
        "com/facebook/share/model/ShareMessengerURLActionButton",
 | 
						|
        "com/facebook/share/model/ShareModel",
 | 
						|
        "com/facebook/share/model/ShareModelBuilder",
 | 
						|
        "com/facebook/share/model/SharePhoto",
 | 
						|
        "com/facebook/share/model/SharePhotoContent",
 | 
						|
        "com/facebook/share/model/ShareStoryContent",
 | 
						|
        "com/facebook/share/model/ShareVideo",
 | 
						|
        "com/facebook/share/model/ShareVideoContent",
 | 
						|
        "com/facebook/share/Share",
 | 
						|
        "com/facebook/share/ShareApi",
 | 
						|
        "com/facebook/share/ShareBuilder",
 | 
						|
        "com/facebook/share/Sharer",
 | 
						|
        "com/facebook/share/widget/GameRequestDialog",
 | 
						|
        "com/facebook/share/widget/MessageDialog",
 | 
						|
        "com/facebook/share/widget/SendButton",
 | 
						|
        "com/facebook/share/widget/ShareButton",
 | 
						|
        "com/facebook/share/widget/ShareButtonBase",
 | 
						|
        "com/facebook/share/widget/ShareDialog",
 | 
						|
        "com/facebook/UserSettingsManager",
 | 
						|
        "com/facebook/WebDialog"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://developers.facebook.com/docs/android"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.facebook.android"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Facebook Android SDK"
 | 
						|
    },
 | 
						|
    "com.flurry.android": {
 | 
						|
      "code_signature": [
 | 
						|
        "com/flurry"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://www.flurry.com/"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.flurry.android"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Flurry Android SDK"
 | 
						|
    },
 | 
						|
    "com.garmin.android.connectiq": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/garmin/android/apps/connectmobile/connectiq"
 | 
						|
      ],
 | 
						|
      "description": "SDK to build unique wearable experiences leveraging Garmin device sensors and features.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.garmin.connectiq": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/garmin/android/connectiq"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://developer.garmin.com/connect-iq/core-topics/mobile-sdk-for-android/"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.garmin.connectiq:ciq-companion-app-sdk"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Connect IQ Mobile SDK for Android"
 | 
						|
    },
 | 
						|
    "com.garmin.fit": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/garmin/fit"
 | 
						|
      ],
 | 
						|
      "description": "SDK to access the Garmin Fit.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.geetest": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/geetest"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://docs.geetest.com/"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.geetest"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "GeeTest"
 | 
						|
    },
 | 
						|
    "com.github.junrar": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/github/junrar"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/junrar/junrar"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.github.junrar:junrar"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Junrar"
 | 
						|
    },
 | 
						|
    "com.github.omicronapps.7-Zip-JBinding-4Android": {
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/omicronapps/7-Zip-JBinding-4Android"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.github.omicronapps:7-Zip-JBinding-4Android"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "7-Zip-JBinding-4Android"
 | 
						|
    },
 | 
						|
    "com.google.ads": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/ads"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.google.ads",
 | 
						|
        "com.google.android.exoplayer:extension-ima",
 | 
						|
        "androidx.media3:media3-exoplayer-ima"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "IMA SDK for Android"
 | 
						|
    },
 | 
						|
    "com.google.android.apps.auto.sdk": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/apps/auto/sdk"
 | 
						|
      ],
 | 
						|
      "description": "Framework to develop apps for Android Auto",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.android.gcm": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/gcm"
 | 
						|
      ],
 | 
						|
      "description": "<a href='https://en.wikipedia.org/wiki/Google_Cloud_Messaging' target='_blank'>Google Cloud Messaging</a> is a mobile notification service developed by Google that enables third-party application developers to send notification data or information from developer-run servers to app.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.android.gms": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/gms"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://www.android.com/gms/"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.google.android.gms(?!.(oss-licenses-plugin|strict-version-matcher-plugin))",
 | 
						|
        "com.google.android.ump",
 | 
						|
        "androidx.core:core-google-shortcuts",
 | 
						|
        "androidx.credentials:credentials-play-services-auth",
 | 
						|
        "androidx.media3:media3-cast",
 | 
						|
        "androidx.media3:media3-datasource-cronet",
 | 
						|
        "androidx.wear:wear-remote-interactions",
 | 
						|
        "androidx.work:work-gcm",
 | 
						|
        "com.google.android.exoplayer:extension-cast",
 | 
						|
        "com.google.android.exoplayer:extension-cronet",
 | 
						|
        "com.evernote:android-job",
 | 
						|
        "com.cloudinary:cloudinary-android.*:2\\.[12]\\.",
 | 
						|
        "com.pierfrancescosoffritti.androidyoutubeplayer:chromecast-sender",
 | 
						|
        "com.yayandroid:locationmanager",
 | 
						|
        "(?<!org.microg.gms:)play-services",
 | 
						|
        "xyz.belvi.mobilevision:barcodescanner",
 | 
						|
        "com.google.api-client:google-api-client-android",
 | 
						|
        "com.google.maps.android:android-maps-utils",
 | 
						|
        "com.github.budowski:android-maps-utils",
 | 
						|
        "com.microsoft.identity:common",
 | 
						|
        "com.microsoft.identity.client:msal"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_negative_examples": [
 | 
						|
        "com.google.android.gms.oss-licenses-plugin",
 | 
						|
        "com.google.android.gms.strict-version-matcher-plugin"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_positive_examples": [
 | 
						|
        "com.google.android.gms:play-services-base",
 | 
						|
        "com.google.android.gms:play-services-oss-licenses"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Google Mobile Services"
 | 
						|
    },
 | 
						|
    "com.google.android.gms.analytics": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/apps/analytics"
 | 
						|
      ],
 | 
						|
      "description": "a web analytics service offered by Google that tracks and reports. 'NoAnalytics' srclib will provide stubs for these classes.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.android.libraries": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/libraries(?!/accessibility)"
 | 
						|
      ],
 | 
						|
      "code_signatures_negative_examples": [
 | 
						|
        "com/google/android/libraries/accessibility"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.google.android.libraries(?!.mapsplatform.secrets-gradle-plugin)"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_negative_examples": [
 | 
						|
        "classpath \"com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1\""
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Google Android Libraries"
 | 
						|
    },
 | 
						|
    "com.google.android.mediahome.video": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/mediahome/video"
 | 
						|
      ],
 | 
						|
      "description": "integrate video content with <a href='https://developer.android.com/training/home-channels' target='_blank' rel='nofollow'>Home channels for mobile apps</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.android.play": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeDep",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/play/core"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://developer.android.com/guide/playcore"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.google.android.play:app-update",
 | 
						|
        "com.google.android.play:asset-delivery",
 | 
						|
        "com.google.android.play:core.*",
 | 
						|
        "com.google.android.play:feature-delivery",
 | 
						|
        "com.google.android.play:review",
 | 
						|
        "androidx.navigation:navigation-dynamic-features",
 | 
						|
        "com.github.SanojPunchihewa:InAppUpdater",
 | 
						|
        "com.suddenh4x.ratingdialog:awesome-app-rating"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Google Play Core"
 | 
						|
    },
 | 
						|
    "com.google.android.play.appupdate": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/play/appupdate"
 | 
						|
      ],
 | 
						|
      "description": "manages operations that allow an app to initiate its own updates.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.android.play.integrity": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/play/integrity"
 | 
						|
      ],
 | 
						|
      "description": "helps you check that interactions and server requests are coming from your genuine app binary running on a genuine Android device.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.android.play.review": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/play/review"
 | 
						|
      ],
 | 
						|
      "description": "lets you prompt users to submit Play Store ratings and reviews without the inconvenience of leaving your app or game.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.android.vending": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/vending/(?!licensing|expansion)"
 | 
						|
      ],
 | 
						|
      "description": "the Google Play Store app and its libaries, parts are FOSS and get vendored in libs as they are",
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/google/play-licensing/tree/master/lvl_library/src/main",
 | 
						|
        "https://github.com/googlearchive/play-apk-expansion/tree/master/zip_file/src/com/google/android/vending/expansion/zipfile",
 | 
						|
        "https://github.com/googlearchive/play-apk-expansion/tree/master/apkx_library/src/com/google/android/vending/expansion/downloader"
 | 
						|
      ],
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.android.wearable": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/wearable/(?!compat/WearableActivityController)"
 | 
						|
      ],
 | 
						|
      "description": "an API for the Android Wear platform, note that androidx.wear:wear has a stub https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-release/wear/wear/src/androidTest/java/com/google/android/wearable/compat/WearableActivityController.java#26",
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.google.android.support:wearable",
 | 
						|
        "com.google.android.wearable:wearable"
 | 
						|
      ],
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.android.youtube.player": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/android/youtube/player"
 | 
						|
      ],
 | 
						|
      "description": "enables you to easily play YouTube videos and display thumbnails of YouTube videos in your Android application.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.google.mlkit": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/mlkit"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://developers.google.com/ml-kit"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.google.mlkit",
 | 
						|
        "io.github.g00fy2.quickie"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "ML Kit"
 | 
						|
    },
 | 
						|
    "com.google.vr": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/vr"
 | 
						|
      ],
 | 
						|
      "description": "enables Daydream and Cardboard app development on Android.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.heapanalytics": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/heapanalytics"
 | 
						|
      ],
 | 
						|
      "description": "automatically captures every web, mobile, and cloud interaction: clicks, submits, transactions, emails, and more. Retroactively analyze your data without writing code.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.heyzap": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/heyzap"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://www.digitalturbine.com/"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Heyzap"
 | 
						|
    },
 | 
						|
    "com.huawei.hms": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/huawei/hms"
 | 
						|
      ],
 | 
						|
      "description": "Huawei's pendant to GMS (Google Mobile Services)",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.hypertrack": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/hypertrack/(?!hyperlog)"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/hypertrack/sdk-android"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.hypertrack(?!:hyperlog)"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_negative_examples": [
 | 
						|
        "com.hypertrack:hyperlog"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "HyperTrack SDK for Android"
 | 
						|
    },
 | 
						|
    "com.instabug": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/instabug"
 | 
						|
      ],
 | 
						|
      "description": "In-App Feedback and Bug Reporting for Mobile Apps.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.kiddoware.kidsplace.sdk": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/kiddoware/kidsplace/sdk"
 | 
						|
      ],
 | 
						|
      "description": "parental control",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.kochava.android.tracker": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/kochava/android/tracker"
 | 
						|
      ],
 | 
						|
      "description": "provides holistic, unbiased measurement for precise, real-time visualization of app performance through the funnel. See <a rel='nofollow' href='https://www.crunchbase.com/organization/kochava'>Crunchbase</a> and <a href='https://reports.exodus-privacy.eu.org/en/trackers/127/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.mapbox": {
 | 
						|
      "MaintainerNotes": "It seems that all libs in https://github.com/mapbox/mapbox-java is fully FOSS\nsince 3.0.0.\n",
 | 
						|
      "documentation": [
 | 
						|
        "https://docs.mapbox.com/android/java/overview/",
 | 
						|
        "https://github.com/mapbox/mapbox-java"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com\\.mapbox(?!\\.mapboxsdk:mapbox-sdk-(services|geojson|turf):([3-5]))"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_negative_examples": [
 | 
						|
        "com.mapbox.mapboxsdk:mapbox-sdk-services:5.0.0",
 | 
						|
        "com.github.johan12345:mapbox-events-android:a21c324501",
 | 
						|
        "implementation(\"com.github.johan12345.AnyMaps:anymaps-mapbox:$anyMapsVersion\")"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_positive_examples": [
 | 
						|
        "com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v7:0.6.0",
 | 
						|
        "com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v8:0.7.0",
 | 
						|
        "com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v7:0.7.0",
 | 
						|
        "com.mapbox.mapboxsdk:mapbox-android-plugin-locationlayer:0.4.0",
 | 
						|
        "com.mapbox.mapboxsdk:mapbox-android-plugin-markerview-v8:0.3.0",
 | 
						|
        "com.mapbox.mapboxsdk:mapbox-android-plugin-places-v8:0.9.0",
 | 
						|
        "com.mapbox.mapboxsdk:mapbox-android-plugin-scalebar-v8:0.2.0",
 | 
						|
        "com.mapbox.mapboxsdk:mapbox-android-sdk:7.3.0"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Mapbox Java SDK"
 | 
						|
    },
 | 
						|
    "com.microblink": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/microblink"
 | 
						|
      ],
 | 
						|
      "description": "verify users at scale and automate your document-based workflow with computer vision tech built for a remote world.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.microsoft.band": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/microsoft/band"
 | 
						|
      ],
 | 
						|
      "description": "library to access the Microsoft Band smartwatch.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.mopub.mobileads": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/mopub/mobileads"
 | 
						|
      ],
 | 
						|
      "description": "ad framework run by Twitter until 1/2022, then sold to AppLovin.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.newrelic.agent": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/newrelic/agent"
 | 
						|
      ],
 | 
						|
      "description": "delivering full-stack visibility and analytics to enterprises around the world. See <a rel='nofollow' href='https://www.crunchbase.com/organization/new-relic'>Crunchbase</a> and <a href='https://reports.exodus-privacy.eu.org/en/trackers/130/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.onesignal": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/onesignal"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/OneSignal/OneSignal-Android-SDK"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.onesignal:OneSignal"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "OneSignal Android Push Notification Plugin"
 | 
						|
    },
 | 
						|
    "com.optimizely": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/optimizely"
 | 
						|
      ],
 | 
						|
      "description": "part of the comScore, Inc. market research community, a leading global market research effort that studies and reports on Internet trends and behavior.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.paypal.sdk": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/paypal"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/paypal/PayPal-Android-SDK",
 | 
						|
        "https://github.com/paypal/android-checkout-sdk"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.paypal"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "PayPal Android SDK"
 | 
						|
    },
 | 
						|
    "com.pushwoosh": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/pushwoosh"
 | 
						|
      ],
 | 
						|
      "description": "mobile analytics under the cover of push messaging.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.quantcast.measurement.service": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/quantcast/measurement/service"
 | 
						|
      ],
 | 
						|
      "description": "processes real-time data at the intersection of commerce and culture, providing useful, actionable insights for brands and publishers. See <a rel='nofollow' href='https://www.crunchbase.com/organization/quantcast'>Crunchbase</a> and <a href='https://reports.exodus-privacy.eu.org/en/trackers/133/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.revenuecat.purchases": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/revenuecat/purchases"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://www.revenuecat.com/"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.revenuecat.purchases"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "RevenueCat Purchases"
 | 
						|
    },
 | 
						|
    "com.samsung.accessory": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/samsung/accessory"
 | 
						|
      ],
 | 
						|
      "description": "provides a stable environment in which you can use a variety features by connecting accessories to your mobile device.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.samsung.android.sdk.look": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/samsung/android/sdk/look"
 | 
						|
      ],
 | 
						|
      "description": "offers specialized widgets and service components for extended functions of the Samsung Android devices.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.sendbird.android": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/sendbird/android"
 | 
						|
      ],
 | 
						|
      "description": "an easy-to-use Chat API, native Chat SDKs, and a fully-managed chat platform on the backend means faster time-to-market.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.smaato.soma": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/smaato/soma"
 | 
						|
      ],
 | 
						|
      "description": "a mobile ad platform that includes video ads.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.spotify.sdk": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/spotify/sdk"
 | 
						|
      ],
 | 
						|
      "description": "allows your application to interact with the Spotify app service. (Note that while the SDK repo claims Apache license, the code is not available there)",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.startapp.android": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "Tracking",
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/startapp"
 | 
						|
      ],
 | 
						|
      "description": "partly quite intrusive ad network.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.telerik.android": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/telerik/android"
 | 
						|
      ],
 | 
						|
      "description": "offers high quality Xamarin Forms UI components and Visual Studio item templates to enable every developer.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.tencent.bugly": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/tencent/bugly"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://bugly.qq.com/"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.tencent.bugly"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Bugly Android SDK"
 | 
						|
    },
 | 
						|
    "com.tencent.mapsdk": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/tencent/tencentmap"
 | 
						|
      ],
 | 
						|
      "description": "giving access to <a href='https://en.wikipedia.org/wiki/Tencent_Maps' target='_blank'>Tencent Maps</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.tenjin.android.TenjinSDK": {
 | 
						|
      "anti_features": [
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/tenjin/android/TenjinSDK"
 | 
						|
      ],
 | 
						|
      "description": "a marketing platform designed for mobile that features analytics, automated aggregation, and direct data visualization with direct SQL access.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.umeng.umsdk": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/umeng"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://developer.umeng.com/docs/119267/detail/118584"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.umeng"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Umeng SDK"
 | 
						|
    },
 | 
						|
    "com.wei.android.lib": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/wei/android/lib/fingerprintidentify"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/uccmawei/FingerprintIdentify"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com.wei.android.lib:fingerprintidentify",
 | 
						|
        "com.github.uccmawei:FingerprintIdentify"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_positive_examples": [
 | 
						|
        "implementation \"com.github.uccmawei:fingerprintidentify:${safeExtGet(\"fingerprintidentify\", \"1.2.6\")}\""
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "FingerprintIdentify"
 | 
						|
    },
 | 
						|
    "com.yandex.android": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/yandex/android/(?!:authsdk)"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com\\.yandex\\.android(?!:authsdk)"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_negative_examples": [
 | 
						|
        "com.yandex.android:authsdk"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Yandex SDK"
 | 
						|
    },
 | 
						|
    "com.yandex.metrica": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/yandex/metrica"
 | 
						|
      ],
 | 
						|
      "description": "a mobile attribution and analytics platform developed by Yandex. It is free, real-time and has no data limits restriction. See <a rel='nofollow' href='https://www.crunchbase.com/organization/appmetrica'>Crunchbase</a> and <a href='https://reports.exodus-privacy.eu.org/en/trackers/140/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "com.yandex.mobile.ads": {
 | 
						|
      "anti_features": [
 | 
						|
        "Ads",
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "com/yandex/mobile/ads"
 | 
						|
      ],
 | 
						|
      "description": "See <a href='https://reports.exodus-privacy.eu.org/en/trackers/124/'>Exodus Privacy</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "de.epgpaid": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "de/epgpaid"
 | 
						|
      ],
 | 
						|
      "description": "access paid <a href='https://en.wikipedia.org/wiki/EPG' target='_blank'>EPG</a> (Electronic Program Guide, for TV) data (after payment, of course). Part of <a href='https://github.com/ds10git/tvbrowserandroid' target='_blank' rel='nofollow'>TVBrowser</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "de.innosystec.unrar": {
 | 
						|
      "code_signatures": [
 | 
						|
        "de/innosystec/unrar"
 | 
						|
      ],
 | 
						|
      "description": "java unrar util",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "firebase": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/google/firebase"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://www.firebase.com"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "com(\\.google)?\\.firebase[.:](?!firebase-jobdispatcher|geofire-java)",
 | 
						|
        "com.microsoft.appcenter:appcenter-push"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_negative_examples": [
 | 
						|
        "    compile 'com.firebase:firebase-jobdispatcher:0.8.4'",
 | 
						|
        "implementation 'com.firebase:geofire-java:3.0.0'",
 | 
						|
        "    compile 'com.firebaseui:firebase-ui-auth:3.1.3'",
 | 
						|
        "com.firebaseui:firebase-ui-database",
 | 
						|
        "com.firebaseui:firebase-ui-storage",
 | 
						|
        "com.github.axet:android-firebase-fake",
 | 
						|
        "com.github.b3er.rxfirebase:firebase-database",
 | 
						|
        "com.github.b3er.rxfirebase:firebase-database-kotlin",
 | 
						|
        "com.segment.analytics.android.integrations:firebase"
 | 
						|
      ],
 | 
						|
      "gradle_signatures_positive_examples": [
 | 
						|
        "\tcompile 'com.google.firebase:firebase-crash:11.0.8'",
 | 
						|
        "\tcompile 'com.google.firebase:firebase-core:11.0.8'",
 | 
						|
        "com.firebase:firebase-client-android:2.5.2",
 | 
						|
        "com.google.firebase.crashlytics",
 | 
						|
        "com.google.firebase.firebase-perf",
 | 
						|
        "com.google.firebase:firebase-ads",
 | 
						|
        "com.google.firebase:firebase-analytics",
 | 
						|
        "com.google.firebase:firebase-appindexing",
 | 
						|
        "com.google.firebase:firebase-auth",
 | 
						|
        "com.google.firebase:firebase-config",
 | 
						|
        "com.google.firebase:firebase-core",
 | 
						|
        "com.google.firebase:firebase-crash",
 | 
						|
        "com.google.firebase:firebase-crashlytics",
 | 
						|
        "com.google.firebase:firebase-database",
 | 
						|
        "com.google.firebase:firebase-dynamic-links",
 | 
						|
        "com.google.firebase:firebase-firestore",
 | 
						|
        "com.google.firebase:firebase-inappmessaging",
 | 
						|
        "com.google.firebase:firebase-inappmessaging-display",
 | 
						|
        "com.google.firebase:firebase-messaging",
 | 
						|
        "com.google.firebase:firebase-ml-natural-language",
 | 
						|
        "com.google.firebase:firebase-ml-natural-language-smart-reply-model",
 | 
						|
        "com.google.firebase:firebase-ml-vision",
 | 
						|
        "com.google.firebase:firebase-perf",
 | 
						|
        "com.google.firebase:firebase-plugins",
 | 
						|
        "com.google.firebase:firebase-storage"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Firebase"
 | 
						|
    },
 | 
						|
    "google-maps": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeDep",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "api_key_ids": [
 | 
						|
        "com\\.google\\.android\\.geo\\.API_KEY",
 | 
						|
        "com\\.google\\.android\\.maps\\.v2\\.API_KEY"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://developers.google.com/maps/documentation/android-sdk/overview"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Google Maps"
 | 
						|
    },
 | 
						|
    "io.fabric.sdk.android": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "Tracking"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "io/fabric/sdk/android"
 | 
						|
      ],
 | 
						|
      "description": "Framework to integrate services. Provides e.g. crash reports and analytics. <a rel='nofollow' href='http://fabric.io/blog/fabric-joins-google/' target='_blank' rel='nofollow'>Aquired by Google in 2017</a>.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "io.github.sinaweibosdk": {
 | 
						|
      "code_signatures": [
 | 
						|
        "com/sina"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/sinaweibosdk/weibo_android_sdk"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "io.github.sinaweibosdk"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "SinaWeiboSDK"
 | 
						|
    },
 | 
						|
    "io.intercom": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp",
 | 
						|
        "NonFreeNet"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "io/intercom"
 | 
						|
      ],
 | 
						|
      "description": "engage customers with email, push, and in\u2011app messages and support them with an integrated knowledge base and help desk.",
 | 
						|
      "license": "NonFree"
 | 
						|
    },
 | 
						|
    "io.objectbox": {
 | 
						|
      "code_signatures": [
 | 
						|
        "io/objectbox"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://objectbox.io/faq/#license-pricing"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "io.objectbox:objectbox-gradle-plugin"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "ObjectBox Database"
 | 
						|
    },
 | 
						|
    "me.pushy": {
 | 
						|
      "code_signatures": [
 | 
						|
        "me/pushy"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://pushy.me/"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "me.pushy"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "Pushy"
 | 
						|
    },
 | 
						|
    "org.gradle.toolchains.foojay-resolver-convention": {
 | 
						|
      "documentation": [
 | 
						|
        "https://github.com/gradle/foojay-toolchains"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "org.gradle.toolchains.foojay-resolver"
 | 
						|
      ],
 | 
						|
      "license": "Apache-2.0",
 | 
						|
      "name": "Foojay Toolchains Plugin"
 | 
						|
    },
 | 
						|
    "org.mariuszgromada.math": {
 | 
						|
      "code_signatures": [
 | 
						|
        "org/mariuszgromada/math/mxparser/parsertokens/SyntaxStringBuilder",
 | 
						|
        "org/mariuszgromada/math/mxparser/CalcStepRecord",
 | 
						|
        "org/mariuszgromada/math/mxparser/CalcStepsRegister",
 | 
						|
        "org/mariuszgromada/math/mxparser/License",
 | 
						|
        "org/mariuszgromada/math/mxparser/CloneCache",
 | 
						|
        "org/mariuszgromada/math/mxparser/ElementAtTheEnd",
 | 
						|
        "org/mariuszgromada/math/mxparser/CompilationDetails",
 | 
						|
        "org/mariuszgromada/math/mxparser/CompiledElement"
 | 
						|
      ],
 | 
						|
      "documentation": [
 | 
						|
        "https://mathparser.org",
 | 
						|
        "https://mathparser.org/mxparser-license/"
 | 
						|
      ],
 | 
						|
      "gradle_signatures": [
 | 
						|
        "org.mariuszgromada.math:MathParser.org-mXparser:[5-9]"
 | 
						|
      ],
 | 
						|
      "license": "NonFree",
 | 
						|
      "name": "mXparser"
 | 
						|
    },
 | 
						|
    "tornaco.android.sec": {
 | 
						|
      "anti_features": [
 | 
						|
        "NonFreeComp"
 | 
						|
      ],
 | 
						|
      "code_signatures": [
 | 
						|
        "tornaco/android/sec"
 | 
						|
      ],
 | 
						|
      "description": "proprietary part of the <a href='https://github.com/Tornaco/Thanox' target='_blank' rel='nofollow noopener'>Thanox</a> application",
 | 
						|
      "license": "NonFree"
 | 
						|
    }
 | 
						|
  },
 | 
						|
  "timestamp": 1747829076.702502,
 | 
						|
  "version": 1,
 | 
						|
  "last_updated": 1750710966.431471
 | 
						|
}'''
 |