Add APK binary analysis and correct audit findings
This commit is contained in:
parent
672a3f7938
commit
45321b8495
2 changed files with 400 additions and 21 deletions
272
APKANALYSIS.md
Normal file
272
APKANALYSIS.md
Normal 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
149
AUDIT.md
|
|
@ -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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue