Skip to content

Releases: maksimtech/apkradar

APKRadar 2026.45

Choose a tag to compare

@github-actions github-actions released this 10 Oct 09:20
v2026.45
64e757f

Fixed

  • A manifest component whose class is not in the DEX is no longer a tracker, nor a
    transfer.
    Fennec 157.0.0 as F-Droid builds it (org.mozilla.fennec_fdroid 1570020,
    SHA-256 04a5f4d3…47a9) declares com.adjust.sdk.AdjustPreinstallReferrerReceiver
    and holds no class under com.adjust in any of its three DEX files: the build is made
    without the Adjust SDK and the manifest keeps the receiver. The audit reported "Adjust"
    and a transfer to "Adjust GmbH (Germany) → USA", and the DPO letter would have put both
    in front of Mozilla — an allegation about code that is not in the file. Declared
    components are now confirmed against the DEX (Lcom/adjust/sdk/Adjust…Receiver;,
    the whole descriptor) before they count; an APK with no readable code is still read on
    the manifest's word, since "nothing to check against" is not "confirmed absent".
    tests/test_declared_without_code.py holds the manifest excerpt as a real APK.
  • --full no longer audits every deep-link host of an app that opens other sites'
    links.
    OsmAnd~ 5.4.9 (net.osmand.plus 540903) declares 427 deep-link hosts —
    maps.google.com and some 200 Google country domains, map.baidu.com, maps.yandex.ru,
    here.com, maps.apple.com — because it opens links to other maps; NewPipe 0.29.1 declares
    56 for YouTube, SoundCloud, Bandcamp and PeerTube instances. Each was taken as "SDK or
    deep-link domain found in the APK" and queued for MailRadar, a TLS handshake and a
    headless browser: on OsmAnd about three and a half hours, at the ~30 s per domain
    measured on Mastodon the same day, of traffic to Google, Baidu and Yandex about an app
    that talks to none of them. Above MAX_DEEP_LINK_DOMAINS (10; the most a publisher was
    seen declaring for itself is F-Droid's four) no deep-link host is audited, the report
    says how many were set aside and why, and the hosts stay in ScanResult.manifest_domains
    as data. tests/test_deep_links_cap.py holds 24 of OsmAnd's hosts as a real APK.
  • Namespace URIs and RFC 2606 names are references, not endpoints — and not vendors.
    Measured on 2026-10-09: ExoPlayer's PlayReady code carries
    http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense, the SOAPAction of a
    licence request, and the hosts block listed it under "a vendor this tool reports on" as
    Microsoft Corporation (USA) on Nextcloud 35.0.1, AntennaPod 3.12.2, NewPipe 0.29.1 and
    Fennec 157.0.0 alike — none of which talks to Microsoft, and a reader takes that table
    for a transfer. ns.adobe.com (the XMP namespace), xml.org and
    javax.xml.XMLConstants (parser feature names) and spdx.org (licence identifiers)
    join the reference list for the same reason. So do the names RFC 2606 reserves for
    documentation: F-Droid 2.0.1 carries https://mirror.example.com/fdroid/repo as a
    sample address, and example.com, example.net, example.org, three mirror.example.* and
    dummy.example were listed as endpoints the code can reach. They resolve to nothing and
    belong to nobody. All of them stay in the list, under "references", as the block has
    always done with a specification URI. tests/test_hosts_identifiers.py holds the
    literals as they appear in those DEX files.
  • A batch-excel row that was not analysed keeps its name and shows no counts.
    Measured on 2026-10-09 with a registry of eleven F-Droid APKs and one row naming an app
    whose file was not there: the report wrote that row as None | None | APK | … | N/A | 0 | 0 | 0. The name and package the registry gave it were gone — a scan that could not open
    the file knows neither, and the row was built from the result rather than from the
    registry — so the one line a reader most needs to identify was the one that could not
    be. "APK" was the dataclass default, not a reading of the file, and three zeros in the
    columns that get summed and charted said the file had been found to hold nothing, when
    it had not been found. The score cell was already blank for exactly that reason; the
    format and the counts now are too, in both the report and --augment mode, and the
    SKIPPED row (package name, no file) loses its zeros the same way.
    tests/test_excel_not_measured.py runs the command on a real registry.
  • audit --output refuses a target it cannot write before the scan, without a
    traceback.
    Measured on 2026-10-09 in the published image, docker run … -v apk:/data:ro … audit /data/fennec.apk --output /data/x.html ran the whole audit, printed
    it, and ended in a Rich traceback through console.save_html — OSError: [Errno 30] Read-only file system. The target was checked for its extension and for its parent
    directory existing, and a read-only directory exists. So does a directory that happens
    to carry the report's name. Both are now refused up front with exit code 2 and one line,
    which is the point of checking the target before the APK is opened.
    tests/test_report_target_writable.py.
  • Report tables fold long identifiers instead of cutting them. Measured on 2026-10-09:
    audit fennec.apk --output report.html through a pipe — how a report is saved from a
    script, and where Rich settles on 80 columns — wrote the tracker row as
    com.google.android.gms.ads.identifi…, and the saved HTML held identifi… and nowhere
    the string ads.identifier. Rich's default for a cell that does not fit is an ellipsis;
    the package column is the finding, and a report file does not know how wide the terminal
    was. The package, permission, prefix and host columns now fold onto the next line.
    tests/test_report_identifiers_whole.py renders at 80 and 56 columns.
  • The README's counts are the code's. It said "43 SDKs", "24 Android permissions" and,
    in Known limitations, "43 trackers, 23 permissions": the code recognises 44 SDK names
    over 46 signatures and 28 permissions, and the permissions table left out the four
    advertising identifiers (AD_ID and the three Privacy Sandbox permissions) added on
    2026-09-25. tests/test_readme_counts.py now reads the numbers and the table from the
    README and compares them with scanner.py, so the next addition fails a test instead of
    ageing the document. The limitation also names what the list does not have, measured
    against εxodus (432 trackers on 2026-10-09): Glean and ACRA are the two found in the
    F-Droid apps audited that day.

Changed

  • mailradar 2026.44 and cookieradar 2026.44 are now the floor. Both carry the fixes their own real-world runs found — GPG credited only when the keyserver verifies the address, all 33 DKIM selectors asked; TrustArc and Usercentrics refusals clicked, an error page in any session reported as NOT MEASURED — and apkradar's audit leans on those checks. pip install -U apkradar brings them along.

APKRadar 2026.44.1

Choose a tag to compare

@github-actions github-actions released this 09 Oct 12:30
v2026.44.1
ba456b8

Changed

  • The suite also runs on Python 3.15-dev, as a row that may fail. 3.15 goes final
    on 2026-10-09 (PEP 790). The classifiers and the stable matrix stay at 3.11-3.14 and
    tests/test_ci_dependencies.py now checks that they are the same list, with the
    experimental row being the version after the last one. A dependency without a wheel
    for 3.15 shows up as a yellow row before the release rather than as a red matrix
    after the classifier is added; mailradar and patchradar have had the row since
    3.14-dev.

Security

  • The image no longer installs gnupg and default-jre-headless, which nothing in it
    ran.
    Both stood in the Dockerfile from the first commit (2026-09-12; the JRE as
    openjdk-17-jre-headless until trixie stopped shipping it). No code in apkradar
    executes a system binary: the APK is read by androguard, which is pure Python; the DPO
    letter goes out over SMTP from this package's own sender; mailradar.checker, the one
    mailradar entry point apkradar calls, looks GPG keys up over HTTP through
    mailradar.gpg, and cookieradar.scanner runs no Java. The gpg binary is run by
    mailradar.sender, which apkradar never imports.

    What gnupg did bring in was dirmngr → libldap2 → libsasl2-2, and Docker Scout
    reported CVE-2026-107161 (high) in cyrus-sasl2 against it — "not fixed" in trixie, open
    in every Debian suite according to patchradar debian, so no rebuild would ever have
    closed it. libsasl2-2 is not in python:3.12-slim-trixie itself, measured with
    apt-get install -s gnupg in the base image: it arrives only with gnupg, and leaves
    with it. The apt step is now update && upgrade, as in exeradar and patchradar.

    Measured on the two images: 913 MB → 612 MB (docker images, 301 MB less), 117 → 87
    Debian packages, and docker scout cves --only-package cyrus-sasl2 goes from one high
    to no package at all. tests/test_docker_contract.py now parses the apt step and
    fails on any package it names, and tests/docker/inspect.sh, run against the built
    image in CI, fails if libsasl2-2, gnupg or default-jre-headless is installed or
    gpg or java is on PATH — the one judgement in a script that otherwise only reports.
    The smoke test, --help, --version and an audit of a real APK were run inside the
    new image before this was written down.

APKRadar 2026.44

Choose a tag to compare

@github-actions github-actions released this 08 Oct 15:28
v2026.44
0ab60ce

Added

  • The files the build is told to include are checked to be there. apkradar lost its
    LICENSE out of the working tree on 2026-10-04 and the loss reached main: pyproject
    names the file, so python -m build failed with License file does not exist: LICENSE,
    and the PyPI publish and the image went down with it. cookieradar lost its own a few
    hours later, during a run of the suite. Neither suite noticed, because neither looked.

    What removes them is still not known, and these cases do not explain it. They stop it
    reaching a commit, which is the part that can be fixed without knowing.

    The expectation is read out of the declarations rather than written down as LICENSE,
    because the five Radar do not declare it the same way: patchradar states its licence as
    text and only its Dockerfile names the file, the other four name it in pyproject, and of
    those apkradar and mailradar do not copy it into the image. So two cases — every file
    pyproject names, and every path the Dockerfile copies — and between them each repository
    is covered, three of them twice.

    Checked by moving the file aside in all five: it fails where it should and passes where
    the declaration genuinely does not name it, and pointing pyproject at a file that is not
    there fails too.

Fixed

  • The OCI licence label is the key the standard names. It read
    org.opencontainers.image.license, singular, which nothing reads — so a tool asking the
    image what it is licensed under got no answer, while the label looked right in the file.
    cookieradar's suite has rejected that spelling for a while; this one had not been asked.

  • Two defences in release.sh that the tests did not actually measure. Found by
    mutating the script rather than by reading it.

    Replacing the existing-tag check's fail with an echo of the same words left every
    case green: the script carried on, bumped, committed, and only then did git tag
    refuse the tag that already existed. The release was still refused — one commit too
    late, which is the opposite of what the script promises, that a refusal leaves the
    version file modified and nothing else. The cases now check that it did not commit on
    its way to refusing.

    And git push --atomic was not measured at all; two separate pushes passed. With two
    pushes main arrives and the tag does not, so the repository carries a version bump
    that no release and no published artifact corresponds to — and the tag that would
    produce them cannot be pushed afterwards either, because the version it would be
    given is by then "already the current version". A pre-receive hook on the test
    remote now refuses tags, which is the way to make the second half fail on demand.

    Both mutations fail now, along with the three that already did.

  • The PyPI wait allows a margin once the index answers. apkradar's Docker build
    failed on 2026-10-03 with No matching distribution found fifteen seconds after
    the wait had reported the version available — 16:31:21 against 16:31:36. The poll is
    not wrong and not enough: it establishes that the file is reachable from the runner,
    while the build container, multi-platform through buildx, resolves the index again
    and can reach an edge still serving the old one.

    This is not the sleep 60 that stood in that step before polling and lost the race
    twice. That was a guess about how long publishing takes, made before knowing
    anything; this waits for the fact first and then allows a bounded margin for it to
    propagate, and says so in the log when it uses one.

    It narrows the window; it does not close it. What closes it is not asking the index
    during the build at all — which is what exeradar's Dockerfile already does, with
    pip install /app/src, and why exeradar has no wait script and did not hit this.
    cookieradar and patchradar are one word from that (_SOURCE=local); apkradar and
    mailradar would need the build argument added. That follow-up is the first entry
    under Changed, below.

    Three cases hold the margin, and they were needed twice over. The two cases that
    already drove this script pass the retry interval as zero so they stay fast, and the
    new default made every success wait 45 seconds past their timeout — so the suite was
    red in all four repositories until those two were told to ask for no grace. Telling
    them that alone would have left the margin itself unmeasured, which is the shape of
    defect this script was written to fix in the first place. So: one case times a
    two-second grace and checks the log says why it waited, one checks that no grace
    waits for nothing and claims nothing, and one measures the default without the
    suite paying 45 seconds for it — started with no grace argument, the script must
    still be running three seconds after the index answered. All four ways of undoing
    the margin were checked against them: the default set to zero, the wait removed
    while the log still claims it, the log removed while the wait still happens, and the
    guard removed so zero waits anyway.

  • Deep links from the manifest are read on real APKs. androguard 4 returns the
    manifest as bytes, the search for android:host was a str pattern, and the TypeError
    went into an except Exception: pass — so manifest_domains was empty on every APK,
    and a host like where.areu.lombardia.it never reached audit --full or
    batch --full. The suite did not notice because its mocks returned a str, which is
    what they return no longer.

  • A deep-link host has to be a hostname, and so does what is sent to a proxy. Only
    localhost, 127.0.0.1 and 192.* were refused: 10.0.2.2 (the emulator's host),
    other private addresses, values with spaces and values with a CR/LF went on to
    MailRadar, the certificate check and the CONNECT line, which wrote the domain in
    unchecked — a header of the APK's choosing, sent to the proxy. Latent while the deep
    links were never read, which is why the two were fixed together. The DEX scan's
    hostname rules now apply to the manifest too, and _open_tunnel refuses anything
    outside [A-Za-z0-9.-] before connecting.

  • batch-excel --augment writes each result on the row it came from. Blank rows
    were skipped when reading and not when writing, so one blank line moved every later
    score, grade and tracker list onto the app above — in the file being overwritten.
    Both sides now use the first sheet, as the README says, rather than whichever sheet
    the file was last saved on.

  • batch-excel no longer calls a failed scan GOOD. Its own chain of ifs fell
    through to "🟢 GOOD — N/A — 0 trackers" for a file that was never opened; it prints
    batch's line now, sensitive permissions included.

  • batch --output reports start empty. A failed scan saves no report, but stopping
    the recording did not clear Rich's buffer, and its output opened the next APK's file.

  • Corroboration by the APK checks the whole host. example.com.br counted as a
    mention of example.com, and https://example.com.attacker.net/privacy or
    https://example.com@attacker.net/privacy as its policy page — enough to make the
    domain verified and the letter state an art. 32 finding against it. And the commonest
    case could never corroborate: Play's https://www.example.com is normalised to
    example.com, and the same www. host in the APK was refused for the dot before it.
    www. is the one prefix taken as the same host.

  • The letter is in one language. Only the Italian template exists, and with
    --lang en the art. 9 reasons and the TLP block were still written in English inside
    it. The language is now the template's. --lang is also matched against the
    templates that exist before it reaches a path: on Windows /../.. used to load any
    .txt as a Jinja template.

  • Smaller ones. BODY_SENSORS_BACKGROUND is a sensitive permission, so its art. 9
    reason, which law_checker always had, can be reached. A DEX with exactly 500 hosts
    is no longer reported as stopped at 500. NO_PROXY=* means no proxy, as it does to
    curl, requests and httpx. tlp.subject does not take TLP:AMBER+STRICT for
    TLP:AMBER. Report names in batch are compared casefolded, so App.apk and
    app.apk no longer overwrite each other on Windows and macOS. A failed extraction
    from an XAPK or APKM removes its temporary directory. com.my_company.app maps to
    my-company.com, not to a name with an underscore, and domain_to_url no longer
    takes httpbin.org for a URL. python -m apkradar.cli batch-excel exists: the
    __main__ block ran before that command was defined.

  • docker.yml no longer pastes the dispatch input into a script. The version typed
    into the form was substituted into bash, and from there into Python, in the job that
    holds the Docker Hub token. It arrives through env: now and has to look like a
    release tag; the unused NORMALIZED output went with it. A case reads every workflow
    for the pattern.

  • Shell scripts are LF on every clone. With Git for Windows' core.autocrlf=true,
    release.sh came out as set -euo pipefail\r and bash refused it, which a bash on
    Linux reading that checkout reports as 23 failing release cases. .gitattributes
    now says *.sh text eol=lf, and a case asks git that it does.

Changed

  • mailradar 2026.43 and cookieradar 2026.43 are now the floor. The audit leans on their checks — SPF, DMARC and DKIM on the publisher's domain, the cookie sessions on its site — and both shipped the fixes from the same review today: a bare all read as +all, redirect= followed, the ten-lookup limit, TLS verified before the password is sent; batch surviving a 403, the accept selector no longer clicking "disagree". pip install -U apkradar brings them along.
  • The race with PyPI is closed rather than narrowed, and two stale defaults went with
    it.
    This is the repository ...
Read more

v2026.43

Choose a tag to compare

@github-actions github-actions released this 04 Oct 13:48
v2026.43
948a7aa

Full Changelog: v2026.42...v2026.43

v2026.42

Choose a tag to compare

@github-actions github-actions released this 03 Oct 16:30
v2026.42
e401e99

What's Changed

  • feat(hosts): report the endpoints an APK carries in its DEX by @maksimtech in #16
  • fix(ci): a fixed tag for the image Snyk scans by @maksimtech in #17
  • ci: pin the runners to ubuntu-26.04, benchmarks to 24.04 by @maksimtech in #18
  • fix(ci): mutation testing runs again — also_copy for what the tests read by @maksimtech in #19

Full Changelog: v2026.41...v2026.42

v2026.41

Choose a tag to compare

@github-actions github-actions released this 30 Sep 14:22
v2026.41
2d5f073

What's Changed

  • build(deps): bump actions/upload-artifact from 4 to 7 by @dependabot[bot] in #15

Full Changelog: v2026.40...v2026.41

v2026.40

Choose a tag to compare

@github-actions github-actions released this 26 Sep 12:33
v2026.40
befd1fd

Full Changelog: v2026.09.32...v2026.40

v2026.09.32

Choose a tag to compare

@github-actions github-actions released this 24 Sep 17:04
v2026.09.32
6e3b686

What's Changed

  • build(deps): bump docker/setup-qemu-action from 3 to 4 by @dependabot[bot] in #14
  • build(deps): bump docker/login-action from 3 to 4 by @dependabot[bot] in #13
  • build(deps): bump SonarSource/sonarqube-scan-action from 6 to 7 by @dependabot[bot] in #12
  • build(deps): bump actions/setup-python from 5 to 7 by @dependabot[bot] in #11
  • build(deps): bump codecov/codecov-action from 4 to 7 by @dependabot[bot] in #10

Full Changelog: v2026.09.31...v2026.09.32

v2026.09.31

Choose a tag to compare

@github-actions github-actions released this 19 Sep 15:03
v2026.09.31

v2026.09.30

Choose a tag to compare

@github-actions github-actions released this 18 Sep 17:35
v2026.09.30
af99ac2