Skip to content

Security

This project downloads untrusted binaries from the internet, runs third-party tools on them, signs the result, and publishes it. This document states what is defended, what is deliberately not, and where the real trust boundaries are.

Threat model

BoundaryMain threatsControls
Google Play downloaderstolen tokens, malformed metadata, partial or substituted APKsexternal goopdl process pinned by commit; redacted errors; size, digest, manifest, and signature verification; atomic publication
Fallback providershostile URLs, mirror metadata, oversized files, downgrade attemptsHTTPS host and path validation, bounded retries/timeouts/sizes, mirror metadata treated as untrusted, full APK verification before publication
Tool and patch downloadsrelease substitution, ambiguous assets, concurrent cache corruptionGitHub release validation, exact asset selection, upstream checksum when available, local SHA-256 provenance, bounded downloads, exclusive cache locks
APK and module archivestraversal, symlinks, zip bombs, malformed manifestsno archive extraction; safe entry names, entry-count, uncompressed-size, and compression-ratio limits enforced before archive reads
Keystoresigner impersonation, key loss, incompatible future updatesdocumented public template key for ready-to-run forks; optional private override; passwords outside argv and logs; certificate fingerprint in provenance
Subprocessesshell injection, secret-bearing argv, hangsargument arrays with shell=False, explicit timeouts, sanitized output, passwords through environment or stdin
CI and releasessecret exfiltration, poisoned caches, partial publicationsecret-free default workflow, public-tool-only cache, pinned actions, pre-upload hash verification, draft release rollback, attestations

Explicit non-goals

Being honest about what is not protected matters more than the control list:

  • The public template key proves nothing about identity. Anyone can sign an APK with it. It provides update compatibility across forks, not publisher authenticity.
  • Unpinned apps trust the provider's signer. Without expected-signer, a valid and internally consistent signer is accepted and logged, not verified against a known-good value.
  • Patch behavior is not audited. The builder verifies that the Morphe CLI and patch bundle are the exact releases it recorded. It does not analyze what those patches do to the app.
  • Mirror content is not attested. APKMirror and Uptodown downloads are verified against the requested identity and a valid signer, which does not make the mirror trustworthy, only its output checkable.
  • Module runtime behavior is not proven by the build. Static generation cannot establish mount or root-manager behavior; see modules.md.

Limits and concurrency

LimitValue
Network timeouts30 to 60 seconds, explicit per call
Download retries3 maximum
Provider file size300 MiB
Provider files per result20
GitHub asset sizebounded by release-reported size
ZIP entries100,000 maximum
ZIP uncompressed size2 GiB maximum
ZIP compression ratio1000:1 maximum

Symlinks and traversal paths in archives are rejected outright.

Tool cache writes use exclusive lock files and atomic replacement. A crashed process may leave a stale lock; remove it only after confirming no Morphe Builder process is still using that cache.

Supported platforms

RoleSupport
Builder hostWindows 10/11 and current Ubuntu Linux on x86-64, Python 3.11–3.13, Java 21+
CIUbuntu GitHub-hosted runner, Python 3.11–3.13, Java 21
OutputsAndroid ARM64 and ARMv7
Not validatedmacOS and non-x86 builder hosts

Device, Magisk, and KernelSU compatibility still requires the final validation matrix.

Keystore policy

The default workflow key .github/template-release.p12 is intentionally public. Alias and password are both morphe-builder, and its certificate SHA-256 is:

text
A4FBBB098E4A9F23C45BFA34A85EBC2B6FF7BC24BD745331B2DA5DD89B448942

Because anyone can sign with it, it gives cross-fork update compatibility and no publisher identity. Trust a release only when GitHub provenance identifies the expected repository and workflow.

Maintainers who need an independent signing identity configure a private replacement through workflow secrets.

Warning: keep a replacement keystore and its password in separate encrypted backups. Losing the key makes future releases signature-incompatible, which forces users to uninstall the app and lose its data. Rotation requires a deliberate package and signing migration, and must never silently replace an existing release key.

Google Play and historical versions

Anonymous dispenser capacity is rate-limited and can return HTTP 429, and it is frequently blocked from datacenter ranges entirely. Configure account credentials for reliable access; see releases.md.

Auth tokens, caches, cookies, dispenser responses, and authenticated URLs must stay outside the repository and outside CI caches.

Google cohorts can present the same requested version code with different display metadata. The trusted APK manifest remains the authority, not store metadata.

For versions Google no longer serves, resolution order is: confirmed local mapping cache, then an APKMirror candidate version code used for a Google download, then a verified fallback download. Version codes are never derived arithmetically and candidate ranges are never brute-forced.

Integrity and signature failures are terminal. Only provider or version unavailability advances the fallback chain; see providers.md.

Clean-machine validation

On a fresh Windows or Ubuntu host with Git, uv, and Java 21:

bash
uv sync --locked
uv run python -m unittest discover -s tests
uv lock --check
uv run morphe-builder tools
uv run morphe-builder list-patches morphe.toml

A live build uses the public goopdl dispenser and the committed template keystore by default. Account credentials, a custom dispenser, and a private replacement keystore are all optional. Final YouTube and YouTube Music ARM64/ARMv7 device or emulator validation is still required before declaring stable releases.

Public repository review

The repository and its forks may be public. Never commit downloaded APKs, private replacement keystores, auth material, or live screenshots. The documented template keystore is the sole intentional exception, because it is public signing material rather than a secret.

Repository-wide rules, including the never-commit list: rules/README.md.

Released under the MIT License.