Skip to content

Commit 72fcd2f

Browse files
hawkffYour Name
andauthored
docs: dependency and maintenance audit (#1)
* docs: add dependency and maintenance audit * docs: address review (NDK count, Go mismatch, code fence lang) --------- Co-authored-by: Your Name <you@example.com>
1 parent 9154efc commit 72fcd2f

1 file changed

Lines changed: 210 additions & 0 deletions

File tree

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# Dependency Audit
2+
3+
Branch: `maintenance/dependency-audit`
4+
Date: 2026-06-15
5+
Base: `hawkff/NekoBoxForAndroid` (fork of `starifly/NekoBoxForAndroid`, itself a fork of `MatsuriDayo/NekoBoxForAndroid`)
6+
Scope: **documentation only — no functional changes.** This file identifies update targets and risky areas so that follow-up branches can be small and reversible.
7+
8+
> "Latest safe target" cells marked `VERIFY` could not be confirmed from inside this environment (no package-index access at audit time). Confirm the newest stable release before bumping, then record the exact version in the update branch PR.
9+
10+
---
11+
12+
## Current app identity
13+
14+
Source: `nb4a.properties`, `buildSrc/src/main/kotlin/Helpers.kt`
15+
16+
| Field | Value | Source |
17+
|---|---|---|
18+
| Package name | `com.nb4a` | `nb4a.properties:1` |
19+
| Version name | `1.4.2-mod-12` | `nb4a.properties:2` |
20+
| Pre-release version name | `pre-1.4.2-20260214-1` | `nb4a.properties:3` |
21+
| Version code (raw) | `46` | `nb4a.properties:4` |
22+
| Version code (effective) | `230` (raw × 5) | `Helpers.kt:146` |
23+
| Android namespace | `io.nekohasekai.sagernet` | `app/build.gradle.kts:30` |
24+
| Debug app id suffix | `.debug` | `Helpers.kt:97` |
25+
| ABI splits | armeabi-v7a, arm64-v8a, x86, x86_64 (no universal APK) | `Helpers.kt:169-177` |
26+
| Product flavors | oss, fdroid, play, preview | `Helpers.kt:180-191` |
27+
28+
### Signing behavior
29+
- Release keystore is `release.keystore` at repo root, configured only when `KEYSTORE_PASS` is present (`Helpers.kt:118-139`).
30+
- Credentials come from `local.properties` or env (`KEYSTORE_PASS`, `ALIAS_NAME`, `ALIAS_PASS`).
31+
- **Both** release and debug build types are signed with the release key when it exists (`Helpers.kt:133-138`).
32+
- CI injects `local.properties` via base64 `LOCAL_PROPERTIES` secret and passes keystore secrets per build (`.github/workflows/release.yml:64-68`).
33+
- **Caveat (inherited from starifly):** package name `com.nb4a` (upstream is `moe.nb4a`) and a different signing key mean APKs from this fork will not update over upstream/other-fork installs.
34+
35+
---
36+
37+
## Android build stack
38+
39+
Sources: `gradle/wrapper/gradle-wrapper.properties`, `buildSrc/build.gradle.kts`, `build.gradle.kts`, `Helpers.kt`, `.github/workflows/*`, `buildScript/init/*`
40+
41+
| Component | Current | Latest safe target | Risk | Branch |
42+
|---|---|---|---|---|
43+
| Gradle wrapper | 8.10.2 | VERIFY (8.10.x/8.11.x) | Low | update-build-toolchain |
44+
| Android Gradle Plugin | 8.8.1 | VERIFY | Medium (AGP majors change DSL) | update-build-toolchain |
45+
| Kotlin | 2.0.21 | VERIFY | Medium (KSP must match) | update-build-toolchain |
46+
| KSP | 2.0.21-1.0.27 | VERIFY (must pair with Kotlin) | Medium | update-build-toolchain |
47+
| compileSdk / targetSdk | 35 / 35 | 35 (or VERIFY 36) | Medium (targetSdk bump = behavior changes) | update-build-toolchain |
48+
| minSdk | 21 | keep 21 | n/a ||
49+
| buildToolsVersion | 35.0.1 | VERIFY | Low | update-build-toolchain |
50+
| NDK | 25.0.8775105 | VERIFY (pinned in CI + env) | Medium (core build) | update-build-toolchain / core branch |
51+
| Java (source/target) | 1.8 | keep 1.8 (desugar enabled) | Low ||
52+
| Java (CI runner / Go build) | 17 implied via runners | VERIFY | Low | update-build-toolchain |
53+
| core library desugaring | desugar_jdk_libs 2.0.3 | VERIFY | Low | update-build-toolchain |
54+
55+
NDK is pinned in three places that must stay in sync: `.github/workflows/build.yml:63`, `.github/workflows/release.yml:65` (`ndk/25.0.8775105`) and `buildScript/init/env_ndk.sh:13`.
56+
57+
---
58+
59+
## App dependencies
60+
61+
Source: `app/build.gradle.kts:41-87`
62+
63+
| Dependency | Current | Target | Reason | Risk |
64+
|---|---|---|---|---|
65+
| kotlinx-coroutines-android | 1.6.4 | VERIFY (1.8.x+) | Old; aligns with Kotlin 2.0 | Medium |
66+
| androidx.core:core-ktx | 1.9.0 | VERIFY | Routine | Low |
67+
| androidx.recyclerview | 1.3.0 | VERIFY | Routine | Low |
68+
| androidx.activity:activity-ktx | 1.10.1 | VERIFY | Already fairly new | Low |
69+
| androidx.fragment:fragment-ktx | 1.5.6 | VERIFY | Lags activity | Low |
70+
| androidx.browser | 1.5.0 | VERIFY | Routine | Low |
71+
| androidx.swiperefreshlayout | 1.1.0 | (latest stable) | Routine | Low |
72+
| androidx.constraintlayout | 2.1.4 | VERIFY | Routine | Low |
73+
| androidx.navigation:*-ktx | 2.5.3 | VERIFY | Routine | Low |
74+
| androidx.preference:preference-ktx | 1.2.0 | VERIFY | Routine | Low |
75+
| androidx.appcompat | 1.6.1 | VERIFY | Routine | Low |
76+
| androidx.work:work-* | 2.8.1 | VERIFY | Routine | Low |
77+
| com.google.android.material | 1.8.0 | VERIFY | Routine | Low |
78+
| com.google.code.gson | 2.9.0 | 2.11.0 (VERIFY) | Maintenance/security | Low |
79+
| zxing-lite | 2.1.1 | VERIFY | QR scanning | Low |
80+
| blacksquircle editorkit/language-* | 2.6.0 | VERIFY | Config editor | Low |
81+
| **com.squareup.okhttp3:okhttp** | **5.0.0-alpha.3** | **5.0.0 stable (VERIFY)** | **On an alpha; move to stable line** | **Medium** |
82+
| **org.yaml:snakeyaml** | **1.30** | **2.x (VERIFY)** | **Security (see below)** | **Medium** |
83+
| material-about-library | 3.2.0-rc01 | VERIFY | About screen | Low |
84+
| process-phoenix | 2.1.2 | VERIFY | Process restart | Low |
85+
| kryo | 5.2.1 | VERIFY (hold — profile serialization) | **Do NOT bump casually**; kryo wire format backs profile blobs | Medium |
86+
| guava | 31.0.1-android | VERIFY | Routine | Low |
87+
| ini4j | 0.5.4 | VERIFY | Config parse | Low |
88+
| recyclerview-fastscroll | 2.0.1 | VERIFY | UI | Low |
89+
| androidx.room:room-* | 2.6.1 | VERIFY (2.6.x) | DB; bump cautiously | Medium |
90+
| Roomigrant (MatrixDev) | 0.3.4 | hold | Schema migration generator | Medium |
91+
| desugar_jdk_libs | 2.0.3 | VERIFY | Desugaring | Low |
92+
93+
Notes:
94+
- `kryo` and `Room` touch persisted user data. Treat both as **migration-sensitive**, not routine bumps.
95+
- okhttp is on an **alpha** (`5.0.0-alpha.3`); prefer moving to a stable 5.0.0 once verified, in its own commit, because the API surface changed across the 5.0 alphas.
96+
97+
---
98+
99+
## Core dependencies
100+
101+
Sources: `libcore/go.mod`, `buildScript/lib/core/get_source.sh`, `buildScript/lib/core/get_source_env.sh`, `.github/workflows/*`
102+
103+
The core is **not a normal Go-module update**. `libcore/go.mod` replaces sing-box and libneko with local checkouts, and `get_source.sh` clones pinned commits of `starifly/sing-box` and `starifly/libneko`.
104+
105+
```text
106+
go.mod replaces (libcore/go.mod:93-97):
107+
github.com/matsuridayo/libneko => ../../libneko
108+
github.com/sagernet/sing-box => ../../sing-box
109+
github.com/sagernet/sing-vmess => github.com/starifly/sing-vmess v0.2.8-mod.1
110+
```
111+
112+
| Component | Current | Target | Reason | Risk |
113+
|---|---|---|---|---|
114+
| starifly/sing-box commit | `4998428a136368500428a6a35cdd466a7042c75b` | VERIFY (commit with HY2 Gecko) | **Required for Gecko obfs** | High |
115+
| starifly/libneko commit | `1c47a3af71990a7b2192e03292b4d246c308ef0b` | hold unless needed | Keep separate from sing-box bump | High |
116+
| starifly/sing-vmess | `v0.2.8-mod.1` | hold | Fork pin | Medium |
117+
| Go toolchain (go.mod) | go 1.24.7 / toolchain 1.24.9 | VERIFY | **Mismatch: go.mod declares 1.24.x, CI uses ^1.25 (see next row)** | Medium |
118+
| Go toolchain (CI) | `^1.25` (`build.yml:31`, `release.yml:33`) | align with go.mod | **Mismatch: CI uses ^1.25, go.mod declares 1.24.x** | Medium |
119+
| sagernet/sing | v0.7.18 | follow sing-box | Transitive via fork | Medium |
120+
| sagernet/quic-go | v0.52.0-sing-box-mod.3 | follow sing-box | QUIC/Hysteria path | High |
121+
| sagernet/sing-quic | v0.5.2 | follow sing-box | **Hysteria2 lives here** | High |
122+
| sagernet/sing-tun | v0.7.10 | follow sing-box | TUN | Medium |
123+
| dyhkwong/sing-juicity | v0.0.3 | hold | Juicity protocol | Medium |
124+
| reF1nd/sing-snell | v0.0.6 | hold | Snell protocol | Medium |
125+
| anytls/sing-anytls | v0.0.11 | hold | AnyTLS | Medium |
126+
| metacubex/mihomo | v1.19.13 | hold | Clash compat | Medium |
127+
128+
> **Gecko finding (confirmed):** at the pinned commit `4998428…`, `option/hysteria2.go` defines `Hysteria2Obfs` with only `Type` and `Password` fields — **no `min_packet_size` / `max_packet_size`.** Gecko obfs is therefore **not supported by the current core**. This validates the plan: `core/singbox-gecko-base` must land (pin bump or backport) before any Android Gecko UI work.
129+
130+
---
131+
132+
## Security-sensitive findings
133+
134+
### 1. Unsafe YAML deserialization (Medium/High)
135+
- `app/build.gradle.kts:69``org.yaml:snakeyaml:1.30`.
136+
- `app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt:255-259` — uses default `Yaml().loadAs(text, Map::class.java)` on **untrusted remote subscription content** (fetched at `RawUpdater.kt:80`).
137+
- The default constructor allows arbitrary Java type instantiation (CVE-2022-1471 class of gadget). The `Map` cast happens *after* dangerous construction, so it does not mitigate.
138+
- **Fix direction (separate security branch):** use `Yaml(SafeConstructor(LoaderOptions()))` and/or upgrade to SnakeYAML 2.x with a safe loader. Not part of the toolchain branch.
139+
140+
### 2. WebDAV credentials in plaintext + http:// allowed (Medium)
141+
- Keys: `Constants.kt:184-187`. Storage: `DataStore.kt:302-316``webdavPassword` stored as plain string in `configurationStore` (no EncryptedSharedPreferences/Keystore).
142+
- Basic auth sent at `BackupFragment.kt:237-239,267-269,287-289,361-363`; `WebDAVSettingsActivity.kt:142-144,180-182`.
143+
- `http://` accepted: `BackupFragment.kt:212-213,342-343` (scheme check accepts both); `WebDAVSettingsActivity.kt:130` uses `URL(server)` with no scheme restriction.
144+
- **Risk:** an `http://` endpoint transmits Basic-auth creds and the full secrets-bearing backup in cleartext, with no warning.
145+
- **Fix direction:** warn/deny on non-TLS; consider encrypting stored creds.
146+
147+
### 3. LAN-exposed inbound can be unauthenticated (Medium)
148+
- `ConfigBuilder.kt:143``bind = if (!forTest && DataStore.allowAccess) "0.0.0.0" else 127.0.0.1`.
149+
- Inbound auth gate `DataStore.kt:155-160``mixedInboundNeedsAuth` is false when `appendHttpProxy` is active on Android Q+.
150+
- **Risk:** `allowAccess=true` (bind `0.0.0.0`) **and** `appendHttpProxy` on Q+ ⇒ proxy exposed to LAN with no auth. The bind decision and auth decision are independent/uncoupled.
151+
- **Fix direction:** force auth whenever bind is `0.0.0.0`, regardless of `appendHttpProxy`. Note: starifly already added an `appendHttpProxy` confirmation prompt; this is a separate, deeper coupling fix.
152+
153+
### 4. Swapped geosite/geoip fallback URLs (Bug, Low-Medium)
154+
- `DataStore.kt:135-136`:
155+
- `rulesGeositeUrl` default → `…/sing-geoip/releases/latest/download/geoip.db` (wrong; points at geoip)
156+
- `rulesGeoipUrl` default → `…/sing-geosite/releases/latest/download/geosite.db` (wrong; points at geosite)
157+
- The two defaults are swapped; resetting to custom defaults would populate geosite with geoip data and vice-versa, breaking routing.
158+
- **Fix direction:** swap the two default URLs back. Small, isolated fix — good candidate for its own tiny PR.
159+
160+
### 5. DNS/routing defaults (informational)
161+
- `DataStore.kt`: `remoteDns` `https://dns.google/dns-query` (`:124`), `directDns` `https://223.5.5.5/dns-query` (`:125`), `enableDnsRouting=true` (`:126`), `enableFakeDns=true` (`:127`), `ipv6Mode=DISABLE` (`:177`), `bypass=true` (`:181`), `strictRoute=true` (`:188`). Consumed in `ConfigBuilder.kt:144-153`.
162+
163+
---
164+
165+
## Feature-readiness notes (for later branches)
166+
167+
### Hysteria2 Gecko obfs
168+
- Data model: single `HysteriaBean.obfuscation` String (`HysteriaBean.java:24`); **no obfs-type field**. kryo serialization version is `7` (`HysteriaBean.java:85`) → bumping requires deserialize handling.
169+
- Emission point: `HysteriaFmt.kt:328-333` hardcodes `type = "salamander"`.
170+
- URI parse/build: `HysteriaFmt.kt:95-97` (parse `obfs-password`), `:159-162` (emit `obfs=salamander`).
171+
- UI: `hysteria_preferences.xml:30-35` + `HysteriaSettingsActivity.kt` — single password field, **no type selector**.
172+
- SingBoxOptions Java mirror: `SingBoxOptions.java:588-594` `Hysteria2Obfs{ type, password }` — would need `min_packet_size`/`max_packet_size` added once core supports them.
173+
- **Blocker:** core pin lacks Gecko (see Core section). Do `core/singbox-gecko-base` first.
174+
175+
### Mieru (already plugin-based)
176+
- Existing: `MieruBean.java`, `MieruFmt.kt` (`buildMieruConfig` emits standalone mieru JSON), `MieruSettingsActivity.kt`, `mieru_preferences.xml`.
177+
- Launch path: external plugin `mieru-plugin` / package `moe.matsuri.exe.mieru` (`PluginEntry.kt:17-26`).
178+
- **Native goal = replace the external plugin** with a bundled binary/sidecar reusing the existing `buildMieruConfig` JSON. Bean fields today: `protocol` (TCP/UDP), `username`, `password`, `mtu` — fewer than the mbox outbound schema; extend as needed.
179+
180+
### MasterDnsVPN
181+
- Not present in the codebase; greenfield. Plan as a SOCKS5 sidecar (new `MasterDnsVpnBean` + config generator + process runner + packaged binaries).
182+
183+
---
184+
185+
## Recommended update branches (in order)
186+
187+
1. `maintenance/update-build-toolchain` — Gradle/AGP/Kotlin/KSP/AndroidX/CI only. No core, no protocol, no DB/serialization. Also fix the CI/go.mod Go-version mismatch here (or in the core branch).
188+
2. `fix/geo-fallback-urls` — swap the two default URLs in `DataStore.kt:135-136`. Tiny, isolated.
189+
3. `security/yaml-safe-load` — SnakeYAML safe loader + version bump.
190+
4. `security/webdav-tls-and-secrets` — warn/deny http://, consider credential encryption.
191+
5. `security/inbound-auth-coupling` — require auth when binding 0.0.0.0.
192+
6. `core/singbox-gecko-base` — bump or backport sing-box for HY2 Gecko (blocks Gecko UI).
193+
7. `feature/hy2-gecko-obfs` — model/parse/export/UI/config-builder (depends on #6).
194+
8. `feature/mieru-native-sidecar` — bundle mieru binary, drop external-plugin requirement.
195+
9. `feature/masterdnsvpn-client-sidecar` — new SOCKS5 sidecar client.
196+
197+
---
198+
199+
## Acceptance criteria for this audit branch
200+
- [x] No functional app changes (docs-only).
201+
- [x] Update targets identified; risky areas flagged.
202+
- [x] Core-source pins documented separately from Gradle/Kotlin/app deps.
203+
- [x] Gecko core gap confirmed against the actual pinned commit.
204+
- [ ] CI still builds (run after pushing; this branch only adds a markdown file).
205+
206+
## Open items to verify before bumping
207+
- Exact latest-stable versions for every `VERIFY` cell (no index access at audit time).
208+
- Whether targetSdk 36 is required by any store deadline.
209+
- The specific starifly/sing-box commit (or upstream sing-box ≥ 1.14.0) that introduces HY2 Gecko `min_packet_size`/`max_packet_size`.
210+
- Resolve CI Go `^1.25` vs `go.mod` `go 1.24.7` mismatch.

0 commit comments

Comments
 (0)