Repository navigation
APKRadar 2026.44
Added
-
The files the build is told to include are checked to be there. apkradar lost its
LICENSEout of the working tree on 2026-10-04 and the loss reachedmain: pyproject
names the file, sopython -m buildfailed withLicense 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.shthat the tests did not actually measure. Found by
mutating the script rather than by reading it.Replacing the existing-tag check's
failwith anechoof the same words left every
case green: the script carried on, bumped, committed, and only then didgit 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 --atomicwas not measured at all; two separate pushes passed. With two
pushesmainarrives 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". Apre-receivehook 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 withNo matching distribution foundfifteen 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 60that 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 forandroid:hostwas a str pattern, and the TypeError
went into anexcept Exception: pass— somanifest_domainswas empty on every APK,
and a host likewhere.areu.lombardia.itnever reachedaudit --fullor
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.1and192.*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 theCONNECTline, 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_tunnelrefuses anything
outside[A-Za-z0-9.-]before connecting. -
batch-excel --augmentwrites 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-excelno 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 --outputreports 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.brcounted as a
mention ofexample.com, andhttps://example.com.attacker.net/privacyor
https://example.com@attacker.net/privacyas 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'shttps://www.example.comis normalised to
example.com, and the samewww.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 enthe art. 9 reasons and the TLP block were still written in English inside
it. The language is now the template's.--langis also matched against the
templates that exist before it reaches a path: on Windows/../..used to load any
.txtas a Jinja template. -
Smaller ones.
BODY_SENSORS_BACKGROUNDis 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.subjectdoes not takeTLP:AMBER+STRICTfor
TLP:AMBER. Report names inbatchare compared casefolded, soApp.apkand
app.apkno longer overwrite each other on Windows and macOS. A failed extraction
from an XAPK or APKM removes its temporary directory.com.my_company.appmaps to
my-company.com, not to a name with an underscore, anddomain_to_urlno longer
takeshttpbin.orgfor a URL.python -m apkradar.cli batch-excelexists: 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 throughenv:now and has to look like a
release tag; the unusedNORMALIZEDoutput 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.shcame out asset -euo pipefail\rand 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
allread as+all,redirect=followed, the ten-lookup limit, TLS verified before the password is sent;batchsurviving a 403, the accept selector no longer clicking "disagree".pip install -U apkradarbrings 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 withNo matching distribution foundat 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 fixedsleep 60lost 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 thelocal/pypiswitch the other Radar have, withlocalas 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.1in the Dockerfile, while this project was at
2026.43, sodocker build .built August; anddefault: '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 publishedmain'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.ymlcould 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.pyin use rather than orphaned.That the file on PyPI can be installed, which the old arrangement proved by accident, is
now checked on purpose inpublish.ymlafter 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 topypi, the local branch compiling dependencies, and the
licence label back to singular. -
release.shruns 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, onmain, 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, andgit checkoutundoes 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 notests/is not a failure.
Full Changelog: v2026.43...v2026.44