Skip to content

APKRadar 2026.44

Choose a tag to compare

@github-actions github-actions released this 08 Oct 15:28
· 19 commits to main since this release
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 where the race was measured: on 2026-10-03 the Docker build
    failed with No matching distribution found at 16:31:36, fifteen seconds after the
    wait had reported the version available at 16:31:21. The poll runs on the runner; the
    multi-platform build resolves the index again, per platform, from whichever edge answers.
    Before the poll a fixed sleep 60 lost the same race twice, on 2026-09-24 and
    2026-09-30. Nothing that waits can close it. Not asking does, so the image is built from
    the source the tag points at.

    The Dockerfile could not do that — it only knew how to install from the index — so it
    gained the local/pypi switch the other Radar have, with local as the default,
    --only-binary :all: on both branches, and the source copied in. Nothing is compiled
    while building this image: it is built for amd64 and arm64, and a dependency without an
    aarch64 wheel would be compiled under QEMU, which in a release means tens of minutes or
    an out-of-memory.

    Two defaults had rotted, and both published August's code without anything looking
    wrong: ARG APKRADAR_VERSION=2026.9.1 in the Dockerfile, while this project was at
    2026.43, so docker build . built August; and default: 'v2026.09.8' on the rebuild
    dispatch, so the form arrived pre-filled with something that looked deliberate.

    The checkout now stands on the tag being built. That matters more here than in exeradar
    or cookieradar, where a smoke test compares the version in the image against the tag
    before anything is pushed: this workflow logs in, builds and pushes, so a checkout left
    on the default branch would have published main's code under an old release's tag
    rather than failing.

    And the image is built on every push and pull request, not only when asked.
    docker-build-check.yml could not run on its own before — the Dockerfile needed a
    published version handed to it — so the only thing that built this image automatically
    was the workflow that publishes it, and the first attempt at a build was the one that
    released. Given a version it still reproduces that one from the index, which keeps
    latest_pypi_version.py in use rather than orphaned.

    That the file on PyPI can be installed, which the old arrangement proved by accident, is
    now checked on purpose in publish.yml after the upload — where a slow index delays a
    check instead of failing a build, and with no margin, because there is a single resolver
    there.

    Ten mutations hold all of this, and all ten fail: the image back on the index, the
    checkout off the tag, either stale default restored, the published file unchecked, a
    margin where there is one resolver, the build check no longer running on pull requests,
    the Dockerfile defaulting to pypi, the local branch compiling dependencies, and the
    licence label back to singular.

  • release.sh runs the suite after the bump, and refuses before committing.
    The version is written as the script's first act, so a suite run before a
    release cannot see what the bump breaks. Twice — apkradar 2026.42 on 2026-10-03
    and 2026.43 on 2026-10-04 — test_the_readme_states_the_version_it_was_captured_with
    failed in CI, on main, with the tag already pushed, and was fixed by hand after
    the fact.

    The gate sits between writing the version and committing it, not after: a refusal
    then leaves the version file modified and nothing else touched, which is what
    somebody needs to see, and git checkout undoes it. A gate after the commit would
    have to undo a commit, and undoing is worse than not doing. A repository with no
    tests/ is not held up by a suite it does not have.

    Three cases hold it, and the first was checked against the script without the gate:
    a failing suite stops the release with nothing committed, tagged or pushed; a
    passing one lets it through; and no tests/ is not a failure.

Full Changelog: v2026.43...v2026.44