Compare commits

...

1 commit

Author SHA1 Message Date
45321b8495 Add APK binary analysis and correct audit findings 2026-08-17 23:25:37 +00:00
2 changed files with 400 additions and 21 deletions

272
APKANALYSIS.md Normal file
View file

@ -0,0 +1,272 @@
# Binary Artifact Analysis
## Tangem Android Wallet — Official Release APK vs. Build from Audited Source
---
| | |
|---|---|
| **Subject** | Comparison of Tangem's official v6.1.2 release APK against a build produced from the audited source |
| **Companion to** | [AUDIT.md](./AUDIT.md) — static source review of commit `358144ee61a` |
| **Analysis date** | 17 August 2026 |
| **Analysis type** | Static binary inspection (manifest, signing, package structure). No decompilation, no execution |
| **Objective** | Determine whether the officially distributed binary can be confirmed to originate from the audited source |
---
## 1. Executive Summary
**The official APK cannot be verified as originating from the audited source, and no available
technique would allow it.** This is a structural property of how Tangem builds and hardens its
release artifact, not a deficiency of this analysis or an indication of wrongdoing.
What *can* be established is weaker but not worthless: every externally observable property of the
official binary that could be compared against a build from the audited source **matches**. That
rules out crude substitution or repackaging. It does not, and cannot, rule out a targeted
modification concealed beneath the protection layer.
The comparison additionally surfaced **two factual errors in AUDIT.md**, both of which overstate the
security posture of the shipped application. They are recorded in §5 and have been corrected in the
audit report.
### Verdict
> **UNVERIFIABLE — CONSISTENT.** Provenance of the official binary cannot be established by any
> means available to a third party. All comparable attributes are consistent with the audited
> source. Absence of contradiction is not evidence of correspondence.
---
## 2. Artifacts Compared
| | Official release | Build from audited source |
|---|---|---|
| Filename | `app-release-protected-1819-6.1.2.apk` | `app-google-debug.apk` |
| Origin | `github.com/tangem/tangem-app-android` releases, tag `v6.1.2` | `./gradlew :app:assembleGoogleDebug` at `358144ee61a` |
| Size | 134,544,995 bytes | 176,908,283 bytes |
| SHA-256 | `6ed4ac317f2f5bdccd5bb1cec9d6733207261e6b4f823a4de883e646872166a1` | `5df4c81755bc423ed39e92499830d789b309561211c62091585fa9038adfe5cd` |
| Package | `com.tangem.wallet` | `com.tangem.wallet.debug` |
| Version | `6.1.2` (code `1819`) | `1.0.0-SNAPSHOT` (code `1`) |
| Build type | `release`, minified, DexProtector-hardened | `debug`, unminified, unprotected |
| Signer | `L=Zug, O=Tangem AG` | Committed dummy debug keystore |
| Archive entry dates | 2026-08-13 | 2026-08-17 |
The size difference is expected and runs counter to intuition: the debug build is *larger* because
it is not minified by R8, despite carrying no protection layer.
---
## 3. Why Provenance Cannot Be Established
Four independent obstacles apply. Any one alone would be sufficient to defeat verification.
### 3.1 The release is commercially hardened
The official APK ships Licel DexProtector:
```
lib/arm64-v8a/libdexprotector.so 446,117 bytes
lib/armeabi-v7a/libdexprotector.so 394,517 bytes
assets/assets.dat 229,499 bytes
assets/ict.dat 10,853 bytes
```
DexProtector encrypts and obfuscates DEX bytecode. The transformation is keyed per build and is
explicitly designed to prevent the analysis that verification would require. Comparing bytecode
between the two artifacts is not merely impractical — it is precluded by design.
### 3.2 The compared artifacts are different build types
The official build passes through R8 minification, obfuscation, and resource shrinking. The
reference build does not. Class names, method signatures, and the DEX file layout diverge for
reasons entirely unrelated to source content: 54 DEX files versus 68.
### 3.3 The source tree does not correspond to the released version
The repository carries **no version of its own**. `AppConfig.versionCode` is `1`, and
`VersionNameProvider` derives the name from the current git branch, falling back to
`1.0.0-SNAPSHOT`. Both values are injected by CI via `-PversionCode` / `-PversionName`.
Consequently nothing in the tree identifies which release a given commit produced. The evidence
indicates the audited commit is **newer** than v6.1.2: archive entries in the official APK are dated
2026-08-13, whereas the audited commits are dated 2026-08-14. The two artifacts therefore do not
represent the same source state, and could not be expected to match even in principle.
### 3.4 The build is not reproducible by a third party
Independent of the above, an outside party cannot reconstruct the official build:
- `core/ui/ds-tokens` is a submodule gitlink with **no `.gitmodules` mapping anywhere in the
repository's 16,087-commit history**, so it cannot be resolved (see AUDIT.md O-3)
- All third-party API keys in `config_prod.json` are `PLACEHOLDER`; the official build embeds real
values, compiled in at build time by `GenerateEnvironmentConfigTask`
- Release signing requires a keystore that is deliberately not published
Tangem publishes no reproducible-build attestation, and the artifact's construction is incompatible
with one.
---
## 4. Consistency Findings
The following were compared and **match**. Each is a property an attacker repackaging or
substituting the application would find difficult to preserve.
### 4.1 Permission set — identical
Both APKs declare **exactly 23 permissions**. After normalising the debug package prefix, a sorted
diff of the two sets is **empty**.
This is the most informative single result. The comparison was made against a binary built from the
audited source, so any permission present in Tangem's distributed build but not produced by that
source would be a direct indicator of divergence. There is none.
Both sets also **exclude** location and microphone permissions, confirming that the
`tools:node="remove"` stripping described in AUDIT.md §3.6 is genuine and survives into the shipped
artifact.
### 4.2 Native libraries — identical, plus protection runtime
| | Count | Detail |
|---|---|---|
| Build from audited source | 14 | see below |
| Official release | 16 | the same 14, plus `libdexprotector.so` and `libalice.so` |
```
libTrustWalletCore.so · libsecp256k1-jni.so · libTrezorCrypto.so · libargon2.so
libblst.so · libsqlcipher.so · libuniffi_yttrium.so · libtoolChecker.so
libtensorflowlite_jni.so · libbarhopper_v3.so · libjnidispatch.so
libimage_processing_util_jni.so · libandroidx.graphics.path.so · libsurface_util_jni.so
```
The audited source reproduces the official release's native library set exactly. The only additions
are the protection layer. `libalice.so` was not independently identified; it appears only in the
protected build alongside `libdexprotector.so`, which is consistent with it forming part of the
DexProtector runtime, but this was not confirmed.
### 4.3 Build configuration and identity
- `targetSdkVersion` 36 and `compileSdkVersion` 36 in both, matching the source configuration
- Package `com.tangem.wallet` (reference build carries the expected `.debug` suffix)
- Official APK signed by `L=Zug, O=Tangem AG`, certificate SHA-256
`e0fe798927cf94709b9d439c9ce728a90a2891698cdff31a4c4d63435b946748`, coherent with Tangem AG's
registration in Zug, Switzerland
**Note on the signing certificate:** this establishes only that the holder of that key signed the
artifact. It is a first-observation baseline, not an identity proof. Its value is comparative —
future releases can be checked against this digest to detect a signing-key change.
---
## 5. Discrepancies Against AUDIT.md
Both findings below arise from the same root cause: the audit examined the **source tree**, and its
conclusions were phrased as though they described the **shipped application**. Neither indicates
malicious behaviour. Both have been corrected in AUDIT.md.
### D-1 — Permission set understated (AUDIT.md §3.6)
The audit lists nine permissions as the "complete permission set across all manifests" and states
that external storage and `RECEIVE_BOOT_COMPLETED` are **absent**.
The nine are accurate for the hand-written manifests (`app/src/main/` and `app/src/huawei/`), which
were verified to declare exactly that set. However the **merged** manifest — in Tangem's release and
in the build from the audited source alike — contains 23. The fourteen not accounted for:
```
READ_EXTERNAL_STORAGE · WRITE_EXTERNAL_STORAGE · WRITE_SETTINGS
RECEIVE_BOOT_COMPLETED · FOREGROUND_SERVICE · USE_FINGERPRINT · POST_NOTIFICATIONS
BIND_GET_INSTALL_REFERRER_SERVICE · ACCESS_ADSERVICES_ATTRIBUTION
ACCESS_ADSERVICES_AD_ID · c2dm.RECEIVE · DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION
com.samsung.android.mapsagent.permission.READ_APP_INFO
com.huawei.appmarket.service.commondata.permission.GET_COMMON_DATA
```
These are introduced by manifest merging from bundled third-party SDKs — the same mechanism the
audit correctly credits Tangem for defending against with `tools:node="remove"`. The characterisation
"permissions MINIMISED" and "the set contains nothing a wallet does not require" is not supported by
the artifact as shipped. `WRITE_SETTINGS` and `RECEIVE_BOOT_COMPLETED` in particular warrant
explanation rather than omission.
### D-2 — "No native code" is true of the repository, false of the application (AUDIT.md §3.1)
The audit states "No `.so` libraries, no JNI, no NDK build inputs," and answers **No** to the
methodology question *"Does it ship native binaries or opaque blobs?"*
This is correct for the source tree — no `.so` file is committed. It is incorrect for the
application, which bundles fourteen native libraries even in the unprotected build (§4.2).
The distinction is material because several are security-critical: `libTrustWalletCore.so`,
`libsecp256k1-jni.so`, and `libTrezorCrypto.so` perform key derivation and signing; `libargon2.so`
provides key-stretching; `libsqlcipher.so` provides at-rest database encryption. These arrive
through the pre-built Maven dependencies that AUDIT.md §6.1 places outside the review's scope, so
the audit's scope statement does technically cover them. But a reader reaching "ships native
binaries: **No**" in the summary table will draw the opposite conclusion about the application they
are installing.
---
## 6. What This Analysis Does and Does Not Establish
**Does establish:**
- The official v6.1.2 APK is signed by Tangem AG and structured as the audited source predicts
- Its permission surface and native library set are exactly reproduced by building the audited
source, with no unexplained additions
- Gross tampering — repackaging, permission injection, substitution of a different application — is
ruled out
- The privacy hardening claimed in AUDIT.md §3.6 is present in the distributed artifact
**Does not establish:**
- That the official APK was built from the audited source, or from any particular source
- That the DEX bytecode is free of modification. DexProtector renders this unexaminable
- Anything about the pre-built Tangem SDKs, which perform mnemonic encryption and at-rest key
storage and remain unreviewed (AUDIT.md §6.1)
- That any *future* release will share these properties
A user installing the official APK is extending trust to Tangem's build pipeline and signing key.
Nothing available to a third party can substitute for that trust. This analysis narrows the range of
undetected substitutions; it does not eliminate it.
---
## 7. Method
All inspection used Android SDK build-tools 36.0.0.
```bash
# Manifest, version, permissions
aapt2 dump badging <apk>
# Signing certificate
apksigner verify --print-certs <apk>
# Package structure, native libraries, DEX count
unzip -l <apk>
```
Permission sets were extracted, normalised for the debug package prefix, sorted, and compared with
`diff`. Native library sets were compared with `comm` after stripping ABI directory components.
No decompilation, deobfuscation, or execution was performed. No attempt was made to defeat
DexProtector.
---
## 8. Limitations
1. **Static inspection of archive metadata only.** Application behaviour was not observed.
2. **Version mismatch.** The reference build derives from a commit dated one day after the official
artifact. Differences attributable to intervening source changes cannot be distinguished from
differences attributable to anything else.
3. **Reference build was not fully faithful.** It was produced with `verifyDesignTokens` skipped
(the `ds-tokens` submodule is unresolvable) and with `PLACEHOLDER` API keys. Neither affects the
manifest or native library comparisons on which the findings rest.
4. **Single point in time.** Applies to v6.1.2 build 1819 only. Re-verification is required for
every subsequent release, and the certificate digest in §4.3 is the recommended anchor.
---
*Binary analysis of `app-release-protected-1819-6.1.2.apk` against a reference build of commit
`358144ee61a`. Companion to AUDIT.md; findings D-1 and D-2 have been applied to that report.*

149
AUDIT.md
View file

@ -8,8 +8,10 @@
|---|---| |---|---|
| **Subject** | `tangem-app-android` — Tangem cryptocurrency wallet for Android | | **Subject** | `tangem-app-android` — Tangem cryptocurrency wallet for Android |
| **Upstream** | `https://github.com/tangem/tangem-app-android` | | **Upstream** | `https://github.com/tangem/tangem-app-android` |
| **Commit reviewed** | `358144ee61a` (branch `master`) | | **Commit reviewed** | `358144ee61a` (branch `master`) — confirmed identical to upstream `master` (§6.5) |
| **Report date** | 17 August 2026 | | **Report date** | 17 August 2026 |
| **Revised** | 17 August 2026 — §3.1, §3.6, O-3 corrected; §6.5 resolved. See "Revisions" below |
| **Companion report** | [APKANALYSIS.md](./APKANALYSIS.md) — binary comparison against Tangem's official v6.1.2 release APK |
| **Review type** | Static source code review (manual, targeted) | | **Review type** | Static source code review (manual, targeted) |
| **Codebase size** | ~292 Gradle modules · 7,719 Kotlin files · 641 XML · 5 Java | | **Codebase size** | ~292 Gradle modules · 7,719 Kotlin files · 641 XML · 5 Java |
| **Objective** | Detect malware, backdoors, information leaks, obfuscated code, and remote code-download behaviour | | **Objective** | Detect malware, backdoors, information leaks, obfuscated code, and remote code-download behaviour |
@ -52,6 +54,22 @@ Readers should note the scope boundaries in §6 — most importantly that Tangem
consumed as **pre-built binary artifacts** and were therefore outside the reach of this source consumed as **pre-built binary artifacts** and were therefore outside the reach of this source
review. review.
### Revisions (17 August 2026)
This report was amended after a build of the source was produced and compared against Tangem's
official release APK. **Three corrections narrow claims that were stated too broadly**; one closes a
limitation. All should be read before relying on the original text:
| Section | Change |
|---|---|
| §3.1 | "No native code" holds for the repository, **not** for the built application, which bundles 14 native libraries — including those performing key derivation, signing, and at-rest encryption |
| §3.6 | The nine permissions listed are the *source manifest* set. The **merged** manifest contains 23. "Permissions minimised" overstated the position |
| O-3 | A clean checkout **does not build at all** — the `ds-tokens` submodule is unresolvable outside Tangem. The prior text said it compiles |
| §6.5 | Commit provenance is now **verified** against upstream, closing a limitation rather than adding one |
None of these alters the §1 verdict on the source. §3.1 and §3.6 do mean the shipped application has
a larger native-code and permission surface than the original report conveyed.
--- ---
## 2. Methodology ## 2. Methodology
@ -65,7 +83,7 @@ of every candidate hit in context.
|---|---|---| |---|---|---|
| 1 | Can it load or execute code obtained at runtime? | **No** | | 1 | Can it load or execute code obtained at runtime? | **No** |
| 2 | Can it execute shell commands or spawn processes? | **No** | | 2 | Can it execute shell commands or spawn processes? | **No** |
| 3 | Does it ship native binaries or opaque blobs? | **No** | | 3 | Does the *repository* contain native binaries or opaque blobs? | **No** — but the built APK bundles 14 native libraries from binary dependencies; see §3.1 |
| 4 | Does key material reach logs, storage, or the network? | **No** | | 4 | Does key material reach logs, storage, or the network? | **No** |
| 5 | Does it contact hidden or hostile endpoints? | **No** | | 5 | Does it contact hidden or hostile endpoints? | **No** |
| 6 | Are live credentials committed to the repository? | **No** | | 6 | Are live credentials committed to the repository? | **No** |
@ -97,14 +115,31 @@ Runtime.getRuntime().exit(0)
This terminates the application's own process. It executes nothing. This terminates the application's own process. It executes nothing.
**Native code.** No `.so` libraries, no JNI, no NDK build inputs. Only two `.jar` files exist in **Native code — repository.** No `.so` libraries, no JNI, and no NDK build inputs are committed to
the tree: the tree. Only two `.jar` files exist:
| File | Size | Assessment | | File | Size | Assessment |
|---|---|---| |---|---|---|
| `gradle/wrapper/gradle-wrapper.jar` | 55.6 KB | Standard Gradle 8.14.1 wrapper | | `gradle/wrapper/gradle-wrapper.jar` | 55.6 KB | Standard Gradle 8.14.1 wrapper |
| `app/libs/dexprotector-annotations.jar` | 3.5 KB | Compile-time annotations only (§3.9) | | `app/libs/dexprotector-annotations.jar` | 3.5 KB | Compile-time annotations only (§3.9) |
**Native code — built application.** *(Correction, 17 Aug 2026.)* The statement above describes the
repository, not the artifact. A build of this source bundles **14 native libraries**, several of
them security-critical:
```
libTrustWalletCore.so · libsecp256k1-jni.so · libTrezorCrypto.so · libargon2.so
libblst.so · libsqlcipher.so · libuniffi_yttrium.so · libtoolChecker.so
libtensorflowlite_jni.so · libbarhopper_v3.so · libjnidispatch.so
libimage_processing_util_jni.so · libandroidx.graphics.path.so · libsurface_util_jni.so
```
Key derivation and signing (`libTrustWalletCore`, `libsecp256k1-jni`, `libTrezorCrypto`), key
stretching (`libargon2`), and at-rest database encryption (`libsqlcipher`) are all performed in
native code. These arrive through the pre-built Maven dependencies excluded from this review by §6.1
and were therefore **not** examined. Tangem's own release build adds `libdexprotector.so` and
`libalice.so` on top. See [APKANALYSIS.md](./APKANALYSIS.md) §4.2.
**Reflection.** Searches for `Class.forName`, `getDeclaredMethod`, `getDeclaredField`, and **Reflection.** Searches for `Class.forName`, `getDeclaredMethod`, `getDeclaredField`, and
`setAccessible` returned **zero** genuine results. There is no reflective dispatch to hide `setAccessible` returned **zero** genuine results. There is no reflective dispatch to hide
behaviour behind. behaviour behind.
@ -205,16 +240,36 @@ repository — a common weakness that is simply absent here.
`addJavascriptInterface` or `@JavascriptInterface` anywhere in the tree. The JavaScript-bridge `addJavascriptInterface` or `@JavascriptInterface` anywhere in the tree. The JavaScript-bridge
attack surface does not exist by construction. attack surface does not exist by construction.
### 3.6 Permissions and Exported Components — MINIMISED ### 3.6 Permissions and Exported Components — RESTRAINED IN SOURCE, WIDER AS SHIPPED
Complete permission set across all manifests: Permission set declared in the **hand-written manifests** (`app/src/main/`, `app/src/huawei/`):
`INTERNET` · `ACCESS_NETWORK_STATE` · `CAMERA` (QR scanning) · `NFC` (card scanning) · `INTERNET` · `ACCESS_NETWORK_STATE` · `CAMERA` (QR scanning) · `NFC` (card scanning) ·
`USE_BIOMETRIC` · `VIBRATE` · `WAKE_LOCK` · `HIDE_OVERLAY_WINDOWS` · `AD_ID` `USE_BIOMETRIC` · `VIBRATE` · `WAKE_LOCK` · `HIDE_OVERLAY_WINDOWS` · `AD_ID`
**Absent:** SMS, contacts, call log, location, microphone, external storage, accessibility Nothing there exceeds what a wallet requires.
services, `SYSTEM_ALERT_WINDOW`, `REQUEST_INSTALL_PACKAGES`, `RECEIVE_BOOT_COMPLETED`. The set
contains nothing a wallet does not require. **Correction (17 Aug 2026).** An earlier revision presented the nine above as the complete set and
listed external storage and `RECEIVE_BOOT_COMPLETED` among permissions that were *absent*. That was
wrong. The nine describe the source manifests; the **merged** manifest in the built APK contains
**23**. The additional fourteen, contributed by manifest merging from bundled third-party SDKs:
```
READ_EXTERNAL_STORAGE · WRITE_EXTERNAL_STORAGE · WRITE_SETTINGS
RECEIVE_BOOT_COMPLETED · FOREGROUND_SERVICE · USE_FINGERPRINT · POST_NOTIFICATIONS
BIND_GET_INSTALL_REFERRER_SERVICE · ACCESS_ADSERVICES_ATTRIBUTION
ACCESS_ADSERVICES_AD_ID · c2dm.RECEIVE · DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION
com.samsung.android.mapsagent.permission.READ_APP_INFO
com.huawei.appmarket.service.commondata.permission.GET_COMMON_DATA
```
`WRITE_SETTINGS` and `RECEIVE_BOOT_COMPLETED` are broader than a wallet needs and are not explained
by first-party code; they warrant enquiry with the SDK vendors that introduce them. This merged set
is **identical** in Tangem's official release APK and in a build from this source — see
[APKANALYSIS.md](./APKANALYSIS.md) §4.1 and D-1.
**Still genuinely absent** from the merged manifest: SMS, contacts, call log, location, microphone,
accessibility services, `SYSTEM_ALERT_WINDOW`, and `REQUEST_INSTALL_PACKAGES`.
Two measures are worth drawing out, as both are affirmative hardening rather than mere restraint: Two measures are worth drawing out, as both are affirmative hardening rather than mere restraint:
@ -231,7 +286,9 @@ SDKs would otherwise introduce through manifest merging:
``` ```
Denying location and microphone access to advertising and analytics SDKs is a privacy measure that Denying location and microphone access to advertising and analytics SDKs is a privacy measure that
argues directly against hostile intent. argues directly against hostile intent. **This control was verified in the shipped artifact:**
neither location nor `RECORD_AUDIO` appears in the merged manifest of Tangem's official release APK
(APKANALYSIS.md §4.1). The stripping is effective, not merely declared.
**Exported components.** Exactly one: `MainActivity`, necessarily exported as the launcher. Its **Exported components.** Exactly one: `MainActivity`, necessarily exported as the launcher. Its
intent filters are tightly scoped — the `LAUNCHER` category, NFC NDEF discovery restricted to intent filters are tightly scoped — the `LAUNCHER` category, NFC NDEF discovery restricted to
@ -404,14 +461,49 @@ is pseudonymised (§3.7).
SurveySparrow, and drop the `AD_ID` permission. The Huawei flavour already differs in SDK SurveySparrow, and drop the `AD_ID` permission. The Huawei flavour already differs in SDK
composition. composition.
### O-3 — A clean checkout builds but does not fully function ### O-3 — A clean checkout does not build, and does not fully function once it does
Because all configuration values are `PLACEHOLDER` (§3.10) and only a dummy debug keystore is *(Revised 17 Aug 2026 after an actual build was attempted. The previous text stated a clean checkout
committed, a build from a clean checkout compiles but its third-party integrations — price quotes, compiles; it does not.)*
buy/sell providers, and some RPC endpoints — will not operate until real API keys are supplied.
Release signing requires a keystore that is deliberately not in the repository.
This is correct and expected practice for a public source release, not a defect. **The build fails outright.** `core/ui/ds-tokens` is registered in the tree as a submodule gitlink
(commit `a59c6a36`), but **no `.gitmodules` file exists anywhere in the repository's 16,087-commit
history**. There is no URL from which the submodule can be resolved. `:core:ui:verifyDesignTokens`
runs during `preBuild` and aborts before any module compiles, emitting the instruction
`git submodule update --init --recursive` — which cannot succeed outside Tangem's own environment.
The task only *verifies*: it hashes the submodule's JSON and SVG sources and compares the digest
against a committed `.tokens-hash`. It generates nothing, and the 380 generated files it guards are
all committed. Building with `-x :core:ui:verifyDesignTokens` therefore produces a functionally
complete APK, at the cost of losing the assurance that the committed generated sources still
correspond to the design tokens they derive from.
**Once building, integrations remain partly inert.** All configuration values are `PLACEHOLDER`
(§3.10) and are compiled into the APK at build time by `GenerateEnvironmentConfigTask`. No runtime
check detects the sentinel, so the application transmits `PLACEHOLDER` as a live credential and
receives authentication failures.
The practical effect is uneven. `providers_order.json` lists RPC providers per chain as either
`public` (no key) or `private` (keyed), and the SDK falls through the list in order. Of 97 chains,
**87 retain at least one public provider and will synchronise without any key**. The 10 that do not
are:
```
bitcoin · ethereum · solana · cardano · litecoin
dogecoin · bitcoin-cash · dash · the-open-network · chia
```
— that is, the chains most users actually hold. Fiat prices and the swap aggregator additionally
depend on Tangem-issued credentials that cannot be self-provisioned.
**Consequence for downstream forks:** a `PLACEHOLDER` build presents as a working wallet while
silently failing to report balances on the major chains. A user scanning a funded card may see a
zero balance and conclude their funds are lost. Such a build should not be distributed to
non-technical users, and any release of one should say so explicitly.
Publishing a source tree without private keys or internal submodules is correct practice, not a
defect — but the resulting checkout is neither buildable nor safely usable as-is, and that should be
stated plainly rather than left to be discovered.
--- ---
@ -437,9 +529,17 @@ obtained.
*leaked*. It does not assess whether the encryption schemes protecting that material at rest are *leaked*. It does not assess whether the encryption schemes protecting that material at rest are
correctly chosen or parameterised. correctly chosen or parameterised.
5. **Commit provenance could not be verified.** The reviewed checkout carries squashed commit 5. **Commit provenance — resolved.** *(Updated 17 Aug 2026.)* An earlier revision recorded this as
messages (`Updated on 2026-08-14`), so upstream authorship and per-change history were not an open limitation. It has since been closed: `git ls-remote` confirms that `master` at
available for inspection. `github.com/tangem/tangem-app-android` is `358144ee61a9f65e0f39578e8c943226abb5d996` — the exact
commit reviewed — and `git diff` against it is empty. The reviewed tree is the published upstream
tree, unmodified.
The `Updated on 2026-08-14` subject line appears on **all 16,087 commits**, not merely recent
ones, so it is an artefact of how the upstream repository is published rather than evidence of
rewriting. Per-change authorship and history remain unavailable for inspection, so the *content*
of individual upstream changes could not be reviewed; the *identity* of the tree is now
established.
6. **Point-in-time.** Findings apply to commit `358144ee61a` only. 6. **Point-in-time.** Findings apply to commit `358144ee61a` only.
@ -464,8 +564,15 @@ or negligent application:
- `FAIL_ON_PROJECT_REPOS` constraining the build supply chain - `FAIL_ON_PROJECT_REPOS` constraining the build supply chain
- Commercial runtime tamper and root detection - Commercial runtime tamper and root detection
Subject to the scope boundaries in §6 — in particular the unreviewed binary SDKs — the application Subject to the scope boundaries in §6 — in particular the unreviewed binary SDKs and the native
is assessed as safe to build and distribute from this source. libraries they contribute (§3.1) — **nothing in the reviewed first-party source is an obstacle to
building and distributing this application.**
That is a narrower statement than "safe to distribute", and deliberately so. A build produced from
this source alone carries `PLACEHOLDER` credentials and will not report balances on the ten most
widely held chains while otherwise presenting as a functioning wallet (O-3). Anyone distributing
such a build must say so explicitly. Assurance over the binary that Tangem itself ships is a
separate question, addressed — and largely left open — in [APKANALYSIS.md](./APKANALYSIS.md).
--- ---