|
| 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