Repository navigation
0.2.0: hold the Obtainium parity work to Tern's bar - #3
Merged
Merged
Conversation
The token went in as a plain Authorization header, which the network door drops on purpose, so a private Forgejo or Gitea project never showed its page. It now goes the way the GitHub and GitLab ones do, to that host only.
Only Tern's own messages went through Scrub. Other entries kept whole addresses with their queries. The crash report kept every cause message. A signed link or a key in a query left the phone when the person shared either one. Now every message is scrubbed on its way out and the crash report is scrubbed a line at a time. A report an older build wrote is scrubbed before it is shown. Scrub also takes out Cookie headers, session ids and short passwords.
The settings link already kept only User-Agent, Accept, Accept-Language and Referer. Exports, the kept export and a file shared with another app wrote every stored header, so an X-Api-Key went along when someone shared one app. The rule now lives in RequestHeaders and both export formats use it. The export summary says so.
Any app could take one of the four verifier package names and get the checked file of every first install, then show whatever verdict it liked. Each verifier is now pinned to the certificate its makers publish, which Privacy Guides' verified list also has. The hand-off and the "Check with" button both skip an app that fails the check, and the hand-off is off until the person turns it on.
The window was made at the size the header names before a byte came out. That is 64 MiB for xz -9, and a TV with a small heap threw OutOfMemoryError on a tiny file. The window now starts at 1 MiB and doubles until it wraps. When the device has no room left the data is refused like any other it cannot read.
The check looked for openConnection, sockets and name lookups only. URL.readText, DownloadManager, HttpEngine, a web view or DnsResolver all went through it clean. It now fails on those and on the usual HTTP libraries. tools/tests/network-doors holds one file per way, and its runner proves in CI that each one fails the check and the tree passes.
The docs and the setting said only that a hubproxy sees the requests. It serves the release list, the digest and the file, and Tern passes them on as GitHub's. So the first install of an app through it trusts the hubproxy, and pinning does not reach its host. The setting and SECURITY-MODEL.md now say so. A digest that came through one is shown as passed on by that host.
Until !50599 is merged it pins one version and one commit. A release that skips it leaves F-Droid building an older Tern. The step names the fields to change and wants the full commit hash, since the build has to reproduce the signed APK.
In v0.1.0 those two switches put their limits on the periodic check. PR #1 kept the keys but made them hold only installs, so someone who chose Wi-Fi only started checking over mobile data after updating. Checks and installs now have a switch each. Settings v0.1.0 wrote (it alone kept checkEveryHours) carry their old value into the check switches too.
The job JobScheduler runs once Wi-Fi or the charger is there asked the device again and could get another answer. A charger paused at 80 % counts as charging for JobScheduler and not for BatteryManager. The job then held the installs, set itself again at once and checked the whole list each time. It now installs from the last check without asking again, never sets itself, and waits 15 minutes at least.
With Let Me Downgrade and the setting on, the background check and Update all could also install an older file. A store that names a higher versionCode than it serves could then roll an app back to a signed but older build. Only a release picked from an app's history may go back now. Even then the gate refuses a file older than the versionCode its source named.
LiteAPKs, Apk4Free, RockMods and Farsroid offer unlocked "mod" builds. Farsroid mixes them with the originals under names like ADM-Pro and CapCut-Pro. No rule could pick the real file there. RuStore, Uptodown and CoolApk answer only their own apps. Reading them meant shipping keys from those apps and signing requests as them. All seven are gone. Their hosts are refused, also as plain web pages. An import that names them keeps every other app and lists these as skipped with the reason. The Russian state root CA and the rustore.ru block without Certificate Transparency go with RuStore. A test parses both network configs. The stores that stay get the same User-Agent as every other request. APKPure is read from its site now, since its API wants the id of the APKPure app. Huawei takes the handshake without the AppGallery package or a phone model. The Galaxy Store gets one fixed model and region. A custom User-Agent header is refused. HonestIdentityTest fails if any of this comes back.
A check asked for every Android upload of a game as the download button does, then read its first byte for the name and size. That is up to 17 requests per check, and itch.io may count each one as a download. A check now reads the page alone and lists each upload under the name the page gives. resolve() already asks for a fresh address just before the download, and the size comes from that one-byte read on the app page.
PR #1 changed what a web page app does but kept schema 1. In 0.1.0 a step took the first match in page order, the last page gave the highest version, and the version pattern read the guessed version. Every row also held a wait of 0 that nobody chose. A schema 1 row with no release order is from 0.1.0. Its steps now keep page order and take the first link, its wait follows the setting, and a web page app gets a highestVersion switch. The schema is now 2.
Four rows of chips were plain FlowRows: the categories and the sources in the filter dialog, the categories on the page of an app and the places a search looks in. Once they wrapped, right from the end of a line jumped to whatever lay to the right further down. On the television image it landed on the tenth chip. ChoiceChips already handled this. Its key handling is now ChipLines. It draws any list it is given and all four use it. The search places are ChoiceChips now too, so they draw focus like the others.
"Remove apps uninstalled elsewhere" deleted the row with its pins and repository key. Someone who uninstalled to reinstall had the next file served pinned as if it were new. Android 15 archiving also counted as an uninstall. Archived apps now stay. A dropped app's pins are kept by source and come back when the same source is added again. Removing by hand in Tern still forgets them.
The eight stores Tern still reads serve their own copies of apps through interfaces made for their own apps and sites. Nothing said so before an app was added, and nothing could turn them off. The new switch sits under Settings and Network. While it is off none of their hosts is asked. A pasted store address shows a card that says what the stores are and offers one button to turn them on. Search and Read as leave them out. Apps from them stay in the list as paused with a note on the row and the page. An export does not carry the switch, so no file or link turns it on. itch.io and Telegram and Neutron Code are the developers' own channels and stay open. The app page now has the origin note of the Add preview. Sites Tern refuses get their reason in the person's language.
Built-in pins were looked up by the address of the app. Aegis added from APKPure got none. Its first install pinned whatever the store served. Now an app from a store, a web page or a direct link is held to every certificate the starter list carries for its package. That is set when the app is added or imported, or when a check first learns the package. A mismatch blocks the first install with the built-in pin message. The pin can still be removed on the app page. Forges keep the old rule because a fork may keep the package. F-Droid repositories do too: their signed index names the signer.
The origin note was only on the Add preview. An Obtainium export full of APKPure apps went in without a word about them. Now the import summary lists the apps that came from third-party stores. It says what their first install decides, and while stores are off it says the apps wait and offers the switch. A checksum from a store was called the checksum of the publisher. It belongs to the store. A match says the file arrived as the store has it, not that the developer made it, and the line now says that.
"Share a link that opens it in Tern" put the address in the query of tern.munzzyy.dev/add. The host and any CDN in front of it saw which app was shared. The settings link went through Obtainium's redirect page, and each recipient's browser sent that server the whole config. Both links now carry what they share after '#'. A browser never sends that part. The add page reads '#url=' first and still takes '?url=' from older links. It hands '#app=' on to Tern as a tern://app/ link. Tern reads its own add page links when they are pasted or shared in. The Obtainium link is still there, named "Share as an Obtainium link". The README badges use the hash too, and check-site.js covers all of it.
StoresLiveTest reads a real app from each store and developer channel with the User-Agent the app sends. It follows each one to the first bytes of its file and reads the package out of every APK. It fails when a request carries another client's headers or goes to a host the source does not name, so the host list in PRIVACY.md stays checked. Like the other live tests it runs only with -Dtern.live=true. JvmHttp now sends a request body and leaves a redirect alone when asked to. The POST to Huawei and the vivo download needed both.
MainActivity is exported for the launcher. Any app could send it UPDATE_ALL and get every pending update installed, with no prompt under Shizuku or root. Any web page could open tern://refresh and make Tern ask every source. The shortcuts now go through an alias that is not exported. Only intents that came through it run Update all, Add or a check. A refresh link from outside asks the person first, once a minute at most. check-apk.sh fails if the alias is ever exported.
The source check cannot see into a dependency. R8 keeps the names of platform types in the dex. check-apk.sh now fails when it finds a DownloadManager request or a web view there. It does the same for HttpEngine, Cronet, OkHttp and DnsResolver.
ShizukuProvider called Sui.init at every start of Tern, before App.onCreate. That reads ServiceManager through a hidden API and sends a private call to the activity service, even for people who never chose Shizuku. App.attachBaseContext now turns that off, and Tern calls Sui.init itself the first time it asks about Shizuku as the installer.
With Shizuku, root or another app not ready, an install quietly went to Android's installer and only Settings showed it. SECURITY-MODEL.md says Tern says so. The verified entry of such an install now names the installer that was chosen and says Android's was used.
"Keep Tern itself up to date" always added GitHub. A release there, with the same key, then went over the F-Droid copy before F-Droid had built and checked it, and the row said nothing about that. Now a copy an F-Droid client installed follows the F-Droid entry. GitHub is still offered below it, and says it skips the checks F-Droid makes.
SECURITY-MODEL.md named the widget and the shortcuts but not who else could reach them. It now says both go to parts no other app can start, and that a refresh link from outside asks first and never installs.
PRIVACY.md lists every host by what makes Tern ask it. The eight stores get a table of their own with what each is told about the device. A new PrivacyHostsTest fails when a store or an icon gets a host the page does not name. Gradle now reruns it when PRIVACY.md changes. The store listing names the hosts in groups and points to PRIVACY.md. It no longer lists sources Tern dropped. The short description stays true with the stores on. The README no longer says Tern talks to nobody but your sources, and the language picker works on every version. COMPARISON.md says which of the places Obtainium reads Tern leaves out, and why. The security model loses the Russian root and gains the store switch, the pins by package and the config test.
Rows inside a group had no focus slot, because an app filed under two categories is drawn twice and two slots of one key would fight. So a grouped list opened on Check all instead of its first app, and back from an app's page lost the row. Each place in the list is now keyed by its group as well as the app, so back returns to the very row that was opened. With the first group folded, focus lands on its header.
Select all also picked the apps inside folded groups. Remove after it would then take apps nobody could see, and the dialog shows only a count. An app shown under two categories counts once.
Machine-translated with the terms each language already uses in Tern, then back-translated by a second model, whose corrections were taken where they kept every placeholder, plural form and name.
A person can no longer set one, so the export tests kept a header that cannot exist. The Galaxy Store option names no real model in its comment either, which the identity check reads for.
The downloader keeps a file under its release and address together. Four engine tests asked for it by the address alone. Three of them checked that no file was kept and so could never fail. The fourth checks that the file stays after a No in the system installer. It failed on every device although the file was there. With the key, a cancel that deletes the file fails that test.
A cut download is tried again by itself now, so the first fetch in this test resumed and finished before the test could look for its partial file. The other download tests already take away the second try; this one does too.
Android answers an abandoned session as aborted, just as it answers a No in its installer. Tern takes that as a cancel with no problem. The test expected a failed install and failed on the phone and the television. It now waits for that answer before it reopens the install. A session Android never answered for still settles as not finished, and the test checks that too.
The main button on the page of an app changes with what the app is doing: Cancel while it installs, Open once it is in. The old button went with the focus on it and left a remote on the Apps tab of the rail. Add and install on the television ended there every time. Now a button of that row that goes with the focus lands it on the row again.
With large text in a narrow pane the title of a screen was squeezed between the back button and the actions until it broke inside a word. On the television at double size the list read "App" over "s". The title now stays on the line of the buttons only while its longest word fits there. Otherwise it goes on a line of its own under them.
The list beside the page of an app is at most 400 dp wide. At double size on a television that is less than a phone at normal size. The waiting banner and the first row filled it and pushed Update all out of sight. From one and a half times the text size two panes need more width in step with the text. A television then shows the list alone, as a phone does.
The remote tests expected Settings to open on the minus button of how often. Only a device without touch has that button; a phone with keys lands on the slider. A phone also has more rows, so Import apps lies further than 40 presses down. The walks only ran on the television before.
The chip test asked for focus once, on the first frame. On a phone that does not take: the window is still in touch mode and not yet focused. Tern's screens leave touch mode and try again for a few frames, and the test now does the same.
On a phone the middle of a row is its link. A tap there asks whether to open the link, and the test pressed Open and left Tern. It now uses the action of the row itself, as TalkBack does.
The places a search looks in now stand between the field and the results, and Wren was below the fold. The test clicked where it was not shown and nothing happened.
Rows say when the release came out now. The test compared the whole description and failed on that; it still checks that the version is shown without its v.
The Add screen looked a pasted address up only after cutting it down to its host, path and query. A vivo address such as h5.appstore.vivo.com.cn/#/details?appId=40413 lost its app that way and was read as a web page with no releases. The registry already takes an address as typed for the stores that route after a '#'. Detection now hands it the address as typed. Found by running every source through the engine on a phone: the JVM test of the vivo source passes the address straight to the source.
When a project publishes only pre-releases, the Add screen turns them on for it and says so. The app that was added did not keep that when no file fitted the device, as with an ARM-only APKCombo release on an x86 phone. Its row then said only pre-releases are published and asked for the switch that was meant to be on. The detection now says it turned them on, and the app is added with them.
The live source tests run on the JVM. That cannot see what the runtime of Android does differently. This test runs on a device and only with -e live true. It detects, adds and checks one real app of each source through the real engine. Every request must carry the User-Agent of Tern. A refused site or a store while stores are off must make none.
The reason an app of a third-party store waits began with the word Paused, right after the status that already says it. TalkBack read the row as "Paused. Paused." and the page of the app showed the word twice. The reason now starts with the stores being off, under a new key, and the old key is gone.
Tern chooses its language itself on every Android version now, and the Look page shows the style of its own colours wherever Android's wallpaper colours are not in use. Two tests still expected the older screens and failed on Android 10: one counted no Language row there, and the other found two choices called Standard.
Shizuku 13.6 and Dhizuku 2.12 installed and updated without a prompt on an Android 13 emulator. Root was not tried: the su of the emulator images answers only adb. No emulator image can archive an app either. The verifier and handoff tests that were marked as not run on a device have now run on Android 10, 14 and 16. The emulator list in the README now names the ones this version ran on.
A television at one and a half times the text size and more shows the list and the page of an app one at a time now, and a title that does not fit beside its buttons goes under them. The changelog and the README say so.
A new source now gets one real app in LiveSourcesTest, run on a device, since the JVM cannot catch a pattern that Android's ICU refuses. The README says what that test covers and what it found. CONTRIBUTING no longer says that every store is out of scope: stores go behind their setting, while sites of modified apps and stores that need Tern to pretend to be their app stay out.
A person with apps from Play or F-Droid saw the well known apps offered again and read it as Tern wanting to install a second copy. Each row now says when the app is in the list or on the phone, and when the copy on the phone carries a key other than the developer's, so this address could never update it.
Gemini translated them. The usual second model had no quota left, so the four lines were read back by hand in each language, and the Polish note said "creator" where it meant signer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The first draft of Obtainium parity (#1) is now held to the rest of Tern. This is 0.2.0.
Sources
Safety
Proof