Skip to content

Repository files navigation

android-release-contract

Compare Android APK and App Bundle releases for permission, exported-component, deep-link, application-policy, SDK, and module-delivery changes.

What it does

android-release-contract reads two local Android release artifacts, builds a neutral contract from their manifests and bundle metadata, and emits a stable semantic diff. Review-level changes produce exit code 1 without turning a successful comparison into an execution error.

The tool answers focused release questions: which permissions changed, which components became externally reachable, whether deep links or application policies changed, whether SDK compatibility moved, and whether an App Bundle's module delivery contract changed.

Why it exists

APK Analyzer and bundletool expose artifact information. android-release-contract turns selected release metadata into a stable semantic diff and CI decision. It is not a decompiler, malware scanner, vulnerability scanner, Play Console replacement, or general binary diff.

Supported artifacts

  • APK → APK
  • AAB → AAB
  • APK → AAB
  • AAB → APK

Content structure, ZIP magic, and required manifest paths determine the format; the filename extension is not trusted by itself. APK set (.apks) archives are not supported.

Download a release

Java 17 or newer is required.

Expand-Archive .\android-release-contract-0.1.0.zip
.\android-release-contract-0.1.0\bin\android-release-contract.bat --version
Get-FileHash .\android-release-contract-0.1.0-all.jar -Algorithm SHA256
tar -xf android-release-contract-0.1.0.tar
./android-release-contract-0.1.0/bin/android-release-contract --version
java -jar android-release-contract-0.1.0-all.jar --version
sha256sum -c SHA256SUMS.txt

Installation from source

Java 17 or newer is required. The build does not require Android Studio or a locally installed Android SDK.

git clone https://github.com/cagrikacmaz/android-release-contract.git
Set-Location android-release-contract
.\gradlew.bat installDist shadowJar

Generated entry points:

build/install/android-release-contract/bin/android-release-contract
build/install/android-release-contract/bin/android-release-contract.bat
build/libs/android-release-contract-all.jar

The standard JAR, fat JAR, install tree, ZIP, and TAR all contain the generated THIRD-PARTY-NOTICES.txt for the exact resolved runtime dependency set.

Quick start

android-release-contract compare app-100.apk app-101.apk
android-release-contract compare app-100.aab app-101.aab --format json
android-release-contract compare app-100.apk app-101.aab --format markdown --output report.md
java -jar android-release-contract-all.jar compare app-100.apk app-101.apk

Options:

--format text|json|markdown
--output PATH
--no-color
--max-changes N
--include-info / --no-include-info
--version
--help

Example output

Android Release Contract

Before  io.github.cagrikacmaz.deeplinkfixture  10 / 1.0  APK
After   io.github.cagrikacmaz.deeplinkfixture  11 / 1.1  APK
Scope   APK full comparison

REVIEW  app-link.auto-verify.disabled
        io.github.cagrikacmaz.deeplinkfixture.LinkActivity intent-filter[1]
        true → false
        Android App Link autoVerify declaration changed

REVIEW  deep-link.port.changed
        io.github.cagrikacmaz.deeplinkfixture.LinkActivity intent-filter[1]
        ["443","<absent>"] → ["8443","<absent>"]
        Deep-link port matching changed

REVIEW  sdk.min.increased
        minimum SDK
        23 → 26
        Minimum SDK increased

INFO    version.name.changed
        version name
        "1.0" → "1.1"
        Version name changed

Summary  3 review, 1 info
Result   REVIEW REQUIRED

APK vs AAB comparison semantics

An APK represents one installable, merged manifest contract. Module provenance is normally unavailable.

An AAB is a publishing artifact whose base, feature, and other modules remain separate. AAB comparisons match modules by canonical module name and compare delivery metadata without pretending every module is installed for every user.

APK↔AAB comparisons are structurally asymmetric. Only comparable APK/base-AAB fields are compared directly; AAB-only modules are reported separately. JSON sets comparisonScope to cross-format-base and includes the comparison.cross-format.limited diagnostic. This is useful for release review but is not equivalent to comparing two AABs.

If package names differ, detailed component/module comparison is suppressed to avoid a misleading flood of renamed subjects.

How it works

APK manifests are compiled Android binary XML, not plain XML. android-release-contract reads them in-process through maintained Android binary-resource parser artifacts, then converts parser-specific values into a neutral release-contract model. Normal analysis does not shell out to Android Studio, apkanalyzer, aapt2, or a locally installed Android SDK.

Android App Bundles use protobuf-encoded module manifests. The AAB reader uses bundletool and AAPT2 protobuf models in-process to parse the base manifest and each dynamic feature manifest separately. Module names, provenance, delivery conditions, instant and fusing settings, and component ownership remain explicit instead of being flattened into one synthetic APK.

Both artifact readers feed the same deterministic semantic differ. It emits stable change IDs, review impact, deterministic ordering, and text, JSON, or Markdown output. APK↔AAB comparisons intentionally cover only comparable APK/base-manifest fields; AAB-only modules remain separate.

What is compared

  • application ID, version, minimum SDK, and target SDK
  • requested and custom-defined permissions
  • required and optional device features
  • debuggable, test-only, backup, cleartext, and related application attributes
  • activities, aliases, services, receivers, and providers
  • declared and safely derived enabled/exported state
  • component permissions, provider authorities, and URI-grant settings
  • intent filters, deep-link declarations, and App Link auto-verification
  • AAB module inventory, delivery mode, conditions, instant, fusing, and removable state

Resource-backed manifest references such as @xml/network_security_config are compared as references. Referenced resource-file content is not inspected.

SDK transitions

Known numeric minSdk values are classified as increased or decreased. A missing/unknown value becoming known is sdk.min.introduced; a known value becoming missing/unknown is sdk.min.removed. Unknown values are never given a numeric ordering. For compatibility, sdk.target.increased also covers an unknown targetSdk becoming known, while sdk.target.missing covers the reverse.

Deep-link filter semantics

Intent-filter boundaries and the ordered structure of their <data> declarations are preserved. Exact filters are matched first; remaining filters are paired only through an unambiguous scheme/host or action/category identity. Ambiguous cases become one filter removal and one filter addition rather than a guessed field-level diff. Whole-filter additions/removals do not also emit noisy field additions/removals.

Matched filters compare schemes, hosts, ports, paths, MIME types, actions, categories, priority, and autoVerify per filter. Multiple <data> elements remain separate; the tool does not synthesize Cartesian URL combinations. autoVerify=true is only a manifest declaration and is not proof that Android successfully verified a domain.

Review policy

The built-in v0.1 policy is deliberately narrow: a result requires review when at least one change has impact review. Review means that a person should assess the release change; it does not assert a vulnerability.

Examples include a permission addition, an effective exported state becoming true, target SDK movement, application debuggable becoming true, a new external deep-link host, or module delivery changing. Informational changes remain in the report by default and can be hidden with --no-include-info.

The stable change-ID registry is documented in docs/change-ids.md.

Exit codes

Code Meaning
0 Comparison completed with no review-level change
1 Comparison completed and review is required
2 Usage, input, parsing, output, or internal analysis error

Exit code 1 is a valid comparison result. CI scripts must handle it separately from execution failures.

JSON schema

JSON output uses schema version 1. The schema is stored at schema/android-release-contract.schema.json. Default JSON contains no timestamps, random IDs, temporary paths, usernames, or absolute input paths.

CI usage example

- name: Build release contract tool
  run: ./gradlew shadowJar

- name: Compare releases
  shell: bash
  run: |
    set +e
    java -jar build/libs/android-release-contract-all.jar \
      compare previous.aab candidate.aab --format json --output contract.json
    code=$?
    set -e
    test "$code" -eq 0 -o "$code" -eq 1
    test "$code" -eq 0

The first assertion distinguishes a completed review result from an execution error; the second enforces this example project's no-review policy.

Security model

Analysis is local and performs no network calls or artifact upload. Artifact code, DEX, and native libraries are never loaded or executed. Archives are read with random access, are never fully extracted, and are subject to bounded entry, module, manifest, recursion, and diagnostic limits. See SECURITY.md.

Limitations

  • Signing certificates and signatures are not compared or verified.
  • DEX/bytecode, native libraries, resource sizes, and policy resource contents are not analyzed.
  • Complex deep-link patterns are compared structurally; arbitrary regex-language containment is not inferred.
  • Effective values remain unknown when Android behavior cannot be derived safely.
  • Cross-format results cannot represent every split APK configuration Google Play might serve.
  • Unknown future module forms are preserved conservatively and diagnosed.

Relationship to APK Analyzer and bundletool

APK Analyzer is excellent for inspecting APK composition, manifests, resources, DEX, and file-size changes. AAPT2 compiles and links Android resources. Bundletool is Google's reference utility for constructing and transforming App Bundles and APK sets. Diffuse provides broad APK/AAB/JAR structural and size diffs.

This tool uses maintained Android parser artifacts in-process, then adds a narrow release-contract model, stable changes, review policy, deterministic text/JSON/Markdown output, and CI-oriented exit behavior. It does not replace those adjacent tools.

Development

.\gradlew.bat spotlessApply
.\gradlew.bat spotlessCheck
.\gradlew.bat detekt
.\gradlew.bat test
.\gradlew.bat koverVerify
.\gradlew.bat verifyThirdPartyNotices
.\gradlew.bat build installDist shadowJar

Fixture source and regeneration helpers are under src/test/fixtures. Normal tests consume committed minimal APK/AAB binaries and do not require an Android SDK.

License

The project is licensed under the MIT License. Bundled third-party runtime components retain their own licenses; see NOTICE and the generated THIRD-PARTY-NOTICES.txt included in every binary distribution.

About

Compare Android APK and App Bundle releases for manifest, security, compatibility, and module-delivery changes.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages