Skip to content

chore: improve reliability and backup handling#108

Merged
hawkff merged 2 commits into
mainfrom
chore/improvements
Jun 28, 2026
Merged

chore: improve reliability and backup handling#108
hawkff merged 2 commits into
mainfrom
chore/improvements

Conversation

@hawkff

@hawkff hawkff commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • harden subscription/background update paths, notification permission handling, and profile-switch UI state
  • bound network/file handling for HTTP, assets, WebDAV logging, and downloaded build artifacts
  • add versioned Parcel-free backup export/import while preserving legacy imports
  • add Depot APK build workflow and keep the existing CI workflow available for manual dispatch

Local verification

  • ./gradlew :app:compileOssDebugKotlin --no-configuration-cache
  • ./gradlew :app:testOssDebugUnitTest --tests "io.nekohasekai.sagernet.bg.SubscriptionUpdaterScheduleTest" --tests "io.nekohasekai.sagernet.ui.*FileName*" --tests "io.nekohasekai.sagernet.ui.WebDAVSecurityTest" --tests "io.nekohasekai.sagernet.ui.BackupFormatV2Test" --no-configuration-cache
  • ./gradlew spotlessKotlinCheck --no-configuration-cache
  • bash -n buildScript/lib/naive.sh buildScript/lib/assets.sh
  • local CodeRabbit CLI review

Greptile Summary

This PR hardens several reliability and correctness paths: HTTP/file I/O gets request-level timeouts and per-call byte limits; subscription scheduling is extracted into a pure, testable function and gains null safety; the profile database update is wrapped in a single transaction with an integrity check; and backup export migrates to an explicit JSON v2 schema that removes the Parcel dependency while retaining v1 import compatibility.

  • Reliability: NewHttpClient now sets DialContext, TLSHandshakeTimeout, ResponseHeaderTimeout, and an overall Client.Timeout; WriteToLimited/ReadAllLimited cap body reads; WriteTo and Unxz are rewritten with atomic temp-file rename and cleanup guards.
  • Backup v2: BackupFormatV2 replaces opaque Parcel blobs with typed JSON fields and URL-safe Base64 for the Kryo bytes, paired with round-trip unit tests; v1 files continue to import via the legacy Parcel path.
  • UI / scheduler fixes: GroupPreference moves the DB query off the main thread; ProfileSettingsActivity caches group data and fixes a null-dereference on the menu inflate path; ConfigurationFragment fixes a use-before-assign on oldProfile and wraps the reloadAccess lock in try/finally.

Confidence Score: 5/5

Safe to merge — all changed paths have tightly scoped fixes backed by new unit tests, and no regressions were found in the reviewed logic.

The DB transaction in RawUpdater is correct: the count check fires inside the transaction so a rollback leaves the group consistent, and lastUpdated is only written on success. The Go temp-file/rename pattern in WriteToLimited and Unxz is correct: defers run LIFO so the close+rename defer executes before the remove defer, meaning a rename failure still triggers cleanup. The subscription scheduler correctly uses coerceAtLeast(0) for initial delay and coerceAtLeast(15) for interval, removing the old unclamped-negative-delay bug. BackupFormatV2 round-trips are validated by the new test suite.

buildScript/lib/assets.sh — the GEOIP_SHA256/GEOSITE_SHA256 defaults lack the co-update comment that naive.sh has, making future version bumps slightly error-prone.

Important Files Changed

Filename Overview
libcore/http.go Adds HTTP timeouts and bounded-read helpers; WriteToLimited uses temp-file/rename for atomicity. Previous review threads note the cached re-read path returns an error when a second caller passes a smaller limit than the first read used — still present in the new mutex+contentRead design.
libcore/io.go Unxz rewritten with deferred i.Close(), 256 MB copyLimited cap, and atomic temp-file rename; fixes the pre-existing input-file leak on xz.NewReader failure.
app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt Extracts computeSubscriptionWorkSchedule for unit-testability; adds null-safety for lastUpdated/autoUpdateDelay; guards notifications with permission checks; returns Result.retry() on failure; removes the old 60-second initial-delay cap.
app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt Wraps all insert/update/delete/lastUpdated writes in a single Room transaction with a post-write count integrity check; limits subscription HTTP response to 10 MB.
app/src/main/java/io/nekohasekai/sagernet/ui/BackupFormatV2.kt New v2 backup codec using explicit typed JSON fields and URL-safe Base64 for Kryo bytes; all public encode/decode functions are exercised by BackupFormatV2Test round-trips.
app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt Switches export to v2 format; redacts WebDAV URLs in logs; replaces unbounded response.body?.string() with bounded readBytesBounded() for PROPFIND responses; fixes locale-sensitive Date().toLocaleString() filename.
app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt Adds per-call byte limits for HTTP JSON and file downloads; uses writeToLimited and Os.rename for atomic asset replacement; handles both .xz and plain asset variants; adds cleanup in finally blocks.
app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt Fixes use-before-assign bug on oldProfile (was captured after the map update); wraps reloadAccess.tryLock() in try/finally to prevent lock leak if reloadService throws.
app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt Moves SagerDatabase.groupDao.allGroups() off the main thread; caches moveTargetGroups to avoid a second DB query at menu-inflate time; fixes proxyEntity!! to a local val to avoid double-null-checked access.
app/src/main/java/io/nekohasekai/sagernet/widget/GroupPreference.kt Moves allGroups() DB query to a background dispatcher; stores group name map locally for summary lookup; disables the preference while loading and restores prior enabled state after.
buildScript/lib/assets.sh Replaces dynamic GitHub API version lookup with pinned versions and SHA256 verification; adds pipefail and retries; version/hash pairs must be updated together when bumping.
buildScript/lib/naive.sh Adds per-ABI SHA256 verification of downloaded naiveproxy APKs before extraction; hash values are pinned to NAIVE_VERSION and must be co-updated on version bumps.
.depot/workflows/build-apk.yml New Depot workflow that builds a signed OSS debug APK on every branch push; KEYSTORE_B64 is consumed via the env: block and decoded inline.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[SubscriptionUpdater.registerSubscriptions] --> B[computeSubscriptionWorkSchedule\nminInterval + initialDelay]
    B --> C[PeriodicWorkRequest\ninterval = min delay >= 15 min]
    C --> D[UpdateTask.doWork]
    D --> E{for each subscription\nlastUpdated + delay <= now?}
    E -- skip --> F[log: not updating]
    E -- update --> G[notifyProgress\ncheck permission]
    G --> H[GroupUpdater.executeUpdate]
    H --> I[RawUpdater.doUpdate]
    I --> J[HTTP GET subscription\ngetContentStringLimited 10 MB]
    J --> K[parseRaw proxies list]
    K --> L[runInTransaction\ninsert / update / delete\ncountByGroup == proxies.size?]
    L -- mismatch throw --> M[transaction rollback]
    L -- ok --> N[lastUpdated = now\nupdateGroup]
    N --> O[executeUpdate true]
    M --> P[executeUpdate false]
    O --> Q{attempted and failed?}
    P --> Q
    Q -- yes --> R[Result.retry]
    Q -- no --> S[Result.success]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[SubscriptionUpdater.registerSubscriptions] --> B[computeSubscriptionWorkSchedule\nminInterval + initialDelay]
    B --> C[PeriodicWorkRequest\ninterval = min delay >= 15 min]
    C --> D[UpdateTask.doWork]
    D --> E{for each subscription\nlastUpdated + delay <= now?}
    E -- skip --> F[log: not updating]
    E -- update --> G[notifyProgress\ncheck permission]
    G --> H[GroupUpdater.executeUpdate]
    H --> I[RawUpdater.doUpdate]
    I --> J[HTTP GET subscription\ngetContentStringLimited 10 MB]
    J --> K[parseRaw proxies list]
    K --> L[runInTransaction\ninsert / update / delete\ncountByGroup == proxies.size?]
    L -- mismatch throw --> M[transaction rollback]
    L -- ok --> N[lastUpdated = now\nupdateGroup]
    N --> O[executeUpdate true]
    M --> P[executeUpdate false]
    O --> Q{attempted and failed?}
    P --> Q
    Q -- yes --> R[Result.retry]
    Q -- no --> S[Result.success]
Loading

Comments Outside Diff (1)

  1. app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt, line 564-576 (link)

    P2 Post-unxz size check is unreachable — Libcore.unxz already enforces the same limit

    Libcore.unxz (Go’s Unxz) uses copyLimited(o, r, defaultUnxzFileLimit) where defaultUnxzFileLimit = 256 * 1024 * 1024. MAX_RULE_ASSET_BYTES in Kotlin is also 256L * 1024 * 1024. If the decompressed stream exceeds the limit, Go returns an error and Libcore.unxz throws before unpackedFile is ever written. The if (unpackedFile.length() > MAX_RULE_ASSET_BYTES) check is therefore dead code.

Reviews (2): Last reviewed commit: "fix(libcore): drop unused fmt import (bu..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds bounded HTTP and file handling in libcore and Android callers, introduces versioned backup serialization with WebDAV log redaction, adds notification permission checks and subscription scheduling changes, and updates workflow cache keys plus APK build automation.

Changes

Libcore hardening and Android callers

Layer / File(s) Summary
HTTP and I/O limits
libcore/http.go, libcore/io.go
Extends HTTPResponse with bounded content and write methods, adds client timeouts, rewrites cached body handling, and changes Unxz to decompress through a temp file with a size limit.
Android callers use bounded reads
app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt, app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt, app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt
Updates AssetsActivity, RawUpdater, and AboutFragment to read capped response bodies, and changes asset replacement to use temp files and rename-based finalization.
Pinned download checksums
buildScript/lib/assets.sh, buildScript/lib/naive.sh
Pins GEOIP, GEOSITE, and NaïveProxy release inputs with SHA-256 verification in the build scripts, replacing latest-release fetches and unverified downloads.

Backup format v2 and WebDAV logging

Layer / File(s) Summary
BackupFormatV2 encoding
app/src/main/java/io/nekohasekai/sagernet/ui/BackupFormatV2.kt
Defines version 2 backup serialization for profiles, groups, rules, and settings, including Base64-url and Kryo-backed payload fields plus JSON helper utilities.
BackupFragment versioning and WebDAV handling
app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt
Generates timestamped backup filenames, writes versioned backup JSON, decodes imports by version, and changes WebDAV backup and restore logging and body handling.
WebDAV log redaction
app/src/main/java/io/nekohasekai/sagernet/ui/WebDAVSecurity.kt, app/src/test/java/io/nekohasekai/sagernet/ui/WebDAVSecurityTest.kt
Adds a helper that redacts WebDAV paths for logging while preserving scheme, host, and non-default ports.
Backup tests
app/src/test/java/io/nekohasekai/sagernet/ui/BackupFormatV2Test.kt, app/src/test/java/io/nekohasekai/sagernet/ui/BackupFileNameTest.kt
Covers version 2 backup round-trips for profiles, groups, rules, and settings, plus filename formatting and unsafe-character checks.
Profile move target caching
app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt
Caches BASIC group move targets in ProfileSettingsActivity and uses the cached state for menu visibility and dialog content.

Notification guards and UI fixes

Layer / File(s) Summary
Subscription scheduling
app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt, app/src/test/java/io/nekohasekai/sagernet/bg/SubscriptionUpdaterScheduleTest.kt
Introduces subscription schedule data classes and a helper that computes periodic work timing from auto-update delays and last-updated timestamps, with tests covering the timing cases.
Notification permission checks
app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt, app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt, app/lint-baseline.xml
Checks notification enablement and POST_NOTIFICATIONS before notifying, wraps notification calls in SecurityException handling, and adjusts retry behavior in the subscription worker.
Configuration and group preference fixes
app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt, app/src/main/java/io/nekohasekai/sagernet/widget/GroupPreference.kt
Captures the previous profile before replacement, guarantees reload lock release with try/finally, and makes group summaries come from an async cache instead of synchronous DAO reads.

CI and workflow cache updates

Layer / File(s) Summary
Depot APK build workflow
.depot/workflows/build-apk.yml
Adds the build-apk workflow, Android/Java setup, deterministic cache keys, conditional native builds, APK assembly, and artifact upload.
libcore cache-key refresh
.depot/workflows/android-instrumented.yml, .depot/workflows/lint.yml, .depot/workflows/unit-tests.yml
Changes libcore status hashing in the Android instrumented, lint, and unit-test workflows to use git ls-files with SHA-256 output.
CI trigger change
.github/workflows/ci.yml
Updates the GitHub Actions CI workflow trigger block to enable workflow_dispatch and remove the previous push and pull_request triggers.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I nibbled the edges of bytes and of time,
Then packed them in backups all tidy and fine.
With checksums and redactions, the burrow feels neat,
And APKs hop out with a very safe beat.
Hooray for the warren, both swift and secure!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the PR’s main focus on reliability and backup handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description accurately summarizes the PR’s reliability, backup, workflow, and parsing changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libcore/http.go (1)

133-144: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the SOCKS handshake and close failed sockets. defaultHTTPDialTimeout only covers the TCP dial to the local SOCKS port. socks.ClientHandshake5 can still stall on that connection unless a deadline is set, and failed handshakes should close socksConn before falling back.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libcore/http.go` around lines 133 - 144, The SOCKS proxy path in the http
transport setup still leaves `socks.ClientHandshake5` unbounded and can leak a
failed `socksConn` before retrying. In `libcore/http.go`, update the
`c.h1h2Transport.DialContext` logic to set a deadline or use the existing `ctx`
timeout around the handshake, and ensure any handshake error closes `socksConn`
before the fallback/continue path. Keep the fix localized to the `DialContext`
closure and preserve the existing `tryH3Direct` behavior.
🧹 Nitpick comments (2)
buildScript/lib/naive.sh (1)

35-43: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

exit 1 for an unsupported ABI won't propagate from the command substitution.

expected_sha256_for_abi is invoked as "$(expected_sha256_for_abi "$abi")" (Line 61), so its *) ... exit 1 (Line 41) only terminates the subshell. The parent continues with an empty expected value; verify_sha256 then reports a misleading "checksum mismatch ... expected , got " instead of "unsupported ABI". The build still halts, but the diagnostic is wrong.

Resolve the expected checksum into a variable first so the failure surfaces directly:

♻️ Suggested fix
   echo ">> fetching libnaive.so for $abi ($apk)"
   curl -fL --retry 3 --retry-delay 2 --max-time 300 "$BASE/$apk" -o "$apk_path"
-  verify_sha256 "$apk_path" "$(expected_sha256_for_abi "$abi")"
+  local expected
+  expected="$(expected_sha256_for_abi "$abi")" || exit 1
+  verify_sha256 "$apk_path" "$expected"

Also applies to: 55-66

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@buildScript/lib/naive.sh` around lines 35 - 43, The unsupported-ABI failure
in expected_sha256_for_abi is getting swallowed by the command substitution used
at the verify_sha256 call site, so the script reports a checksum mismatch
instead of the real error. Update the flow around expected_sha256_for_abi and
verify_sha256 in naive.sh to resolve the checksum into a variable before calling
verify_sha256, and explicitly detect the unsupported ABI case so the script
exits with the correct diagnostic rather than continuing with an empty expected
value.
app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt (1)

690-724: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for both import versions.

This branch is now the compatibility boundary, but the new coverage only exercises BackupFormatV2 helpers and backupFileName() (app/src/test/java/io/nekohasekai/sagernet/ui/BackupFormatV2Test.kt, Lines 22-193; app/src/test/java/io/nekohasekai/sagernet/ui/BackupFileNameTest.kt, Lines 10-26). A small finishImport() test for one v1 payload and one v2 payload would catch dispatch regressions before the destructive reset path runs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt` around lines
690 - 724, Add regression coverage in the import flow to verify both backup
formats are dispatched correctly before the destructive reset path. Extend the
tests around BackupFragment.finishImport() so one case imports a v1 payload and
another imports a BackupFormatV2.VERSION payload, asserting the correct decode
path is used for profiles/groups/rules/settings. This should complement the
existing BackupFormatV2Test and BackupFileNameTest coverage and protect the
version switch logic in finishImport.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.depot/workflows/build-apk.yml:
- Around line 42-64: The cache fingerprint logic for libcore and sidecars only
hashes file contents, so renames/moves or same-content swaps can reuse stale
artifacts. Update the status-generation steps in build-apk.yml so the
fingerprints also incorporate each file’s path alongside its content, then keep
the existing cache keys in libcore-cache and the sidecar cache aligned with the
new status files. Use the existing libcore status and sidecars status steps as
the place to make this change.

In `@app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt`:
- Around line 113-117: In SubscriptionUpdater.computeSubscriptions(), switch the
due-check window to Long arithmetic instead of Int. Use a Long value for the
current epoch seconds and ensure the auto-update delay is converted and
multiplied as Long before comparing against subscription.lastUpdated, so the
check cannot overflow or flip for large timestamps or delays. Keep the logic in
sync with computeSubscriptionWorkSchedule(), which already uses Long.

In `@app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt`:
- Around line 374-381: In the WebDAV restore flow inside BackupFragment’s
PROPFIND handling, only proceed to parse the multistatus body when the response
is exactly 207. Tighten the success check around the existing response
validation and body parsing so that any other 2xx responses (such as 200 or 204)
are treated as unexpected server responses and fail before reaching the
responseBody parsing path. Keep the fix localized to the PROPFIND branch and
preserve the existing logging/error handling in this restore logic.

In `@libcore/http.go`:
- Around line 91-96: The HTTP clients used by GetContentLimited and
WriteToLimited only set connect and header timeouts, so body reads can still
hang. Update the h1h2Client setup in the http client initialization to use a
full client deadline, and apply the same per-request deadline behavior to the
HTTP/3 path where the separate http.Client is created. Make the change in the
shared client setup and the request-building code so both transport paths
enforce a body-read timeout.
- Around line 436-466: The WriteToLimited flow is making downloaded files
world-writable by calling Chmod on the temp file before rename, which bypasses
the default private permissions from os.CreateTemp. Remove the explicit 0666
chmod in httpResponse.WriteToLimited and keep the temp file permissions as
created, so the final os.Rename preserves private access unless broader
permissions are intentionally needed.

In `@libcore/io.go`:
- Around line 38-42: The temp file permission change in the file-writing path is
too permissive and should be removed or narrowed. In the code around the
temporary output handling in io.go, drop the unconditional o.Chmod(0666) before
rename so the os.CreateTemp default private mode is preserved, and make the same
change in the analogous temp-file flow in http.go. If a custom mode is required,
apply a caller-supplied, restrictive mode after the final rename instead of
making the temp file world-writable.

---

Outside diff comments:
In `@libcore/http.go`:
- Around line 133-144: The SOCKS proxy path in the http transport setup still
leaves `socks.ClientHandshake5` unbounded and can leak a failed `socksConn`
before retrying. In `libcore/http.go`, update the `c.h1h2Transport.DialContext`
logic to set a deadline or use the existing `ctx` timeout around the handshake,
and ensure any handshake error closes `socksConn` before the fallback/continue
path. Keep the fix localized to the `DialContext` closure and preserve the
existing `tryH3Direct` behavior.

---

Nitpick comments:
In `@app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt`:
- Around line 690-724: Add regression coverage in the import flow to verify both
backup formats are dispatched correctly before the destructive reset path.
Extend the tests around BackupFragment.finishImport() so one case imports a v1
payload and another imports a BackupFormatV2.VERSION payload, asserting the
correct decode path is used for profiles/groups/rules/settings. This should
complement the existing BackupFormatV2Test and BackupFileNameTest coverage and
protect the version switch logic in finishImport.

In `@buildScript/lib/naive.sh`:
- Around line 35-43: The unsupported-ABI failure in expected_sha256_for_abi is
getting swallowed by the command substitution used at the verify_sha256 call
site, so the script reports a checksum mismatch instead of the real error.
Update the flow around expected_sha256_for_abi and verify_sha256 in naive.sh to
resolve the checksum into a variable before calling verify_sha256, and
explicitly detect the unsupported ABI case so the script exits with the correct
diagnostic rather than continuing with an empty expected value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef980a58-1f67-4a6d-bbb3-be2bb4d956ef

📥 Commits

Reviewing files that changed from the base of the PR and between 2c2e9ad and 3633950.

📒 Files selected for processing (22)
  • .depot/workflows/build-apk.yml
  • .github/workflows/ci.yml
  • app/lint-baseline.xml
  • app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt
  • app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt
  • app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/BackupFormatV2.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/WebDAVSecurity.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/widget/GroupPreference.kt
  • app/src/test/java/io/nekohasekai/sagernet/bg/SubscriptionUpdaterScheduleTest.kt
  • app/src/test/java/io/nekohasekai/sagernet/ui/BackupFileNameTest.kt
  • app/src/test/java/io/nekohasekai/sagernet/ui/BackupFormatV2Test.kt
  • app/src/test/java/io/nekohasekai/sagernet/ui/WebDAVSecurityTest.kt
  • buildScript/lib/assets.sh
  • buildScript/lib/naive.sh
  • libcore/http.go
  • libcore/io.go
💤 Files with no reviewable changes (1)
  • app/lint-baseline.xml

Comment thread .depot/workflows/build-apk.yml Outdated
Comment thread app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt Outdated
Comment thread app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt Outdated
Comment thread libcore/http.go
Comment thread libcore/http.go
Comment thread libcore/io.go Outdated
Comment thread .github/workflows/ci.yml
Comment on lines 1 to 6
name: CI
on:
push:
tags-ignore:
- 'v*'
branches:
- '*'
pull_request:
workflow_dispatch:
env:
MIERU_VERSION: v3.34.0
HYSTERIA_VERSION: v2.9.2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unit tests no longer run automatically

ci.yml previously triggered on every push and pull_request; it now only fires via workflow_dispatch. The new Depot workflow (build-apk.yml) runs on every branch push but only assembles the APK — it contains no ./gradlew test step. The four new test files added by this PR (SubscriptionUpdaterScheduleTest, BackupFileNameTest, BackupFormatV2Test, WebDAVSecurityTest) will never run in CI unless the workflow is triggered manually. A future commit that breaks these tests will pass CI silently.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread libcore/http.go
Comment on lines +389 to 406
func (h *httpResponse) GetContentLimited(limit int64) ([]byte, error) {
h.contentMu.Lock()
defer h.contentMu.Unlock()
if h.contentRead {
if h.contentError != nil {
return nil, h.contentError
}
if int64(len(h.content)) > limit {
return nil, fmt.Errorf("HTTP response body exceeds %d bytes", limit)
}
return h.content, nil
}
defer h.Body.Close()
h.contentRead = true
h.contentLimit = limit
h.content, h.contentError = readAllLimited(h.Body, limit)
return h.content, h.contentError
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cached re-read with a smaller limit silently rejects valid content

When GetContentLimited(bigLimit) has already run and cached n bytes, a subsequent call with GetContentLimited(smallLimit) where n > smallLimit returns fmt.Errorf("HTTP response body exceeds %d bytes", smallLimit) — even though n was within the original limit and the body is gone. The check uses the caller's limit rather than the limit enforced during the read. The contentLimit field stores the original limit but is never consulted in the re-read path, making it dead data.

Comment thread libcore/http.go
Comment thread .depot/workflows/build-apk.yml Outdated
The unused "fmt" import in libcore/io.go failed gomobile compilation,
which broke all four Depot CI jobs (build/test/lint/instrumented depend
on libcore-from-source).

Also addresses CodeRabbit/Greptile findings on PR #108:
- http.go: bound the full request (incl. body reads) with a client
  timeout on the h1h2 client and both doH3Direct clients (ECH + H3).
- http.go/io.go: drop world-writable Chmod(0666) on temp files;
  os.CreateTemp already creates them 0600 and they are atomically
  renamed (over-permissive on per-app-UID Android).
- http.go: remove dead contentLimit field.
- SubscriptionUpdater: use Long arithmetic in the due-check to avoid
  Int overflow, matching computeSubscriptionWorkSchedule.
- BackupFragment: require WebDAV PROPFIND 207 before parsing the
  multistatus body (a 200/204 proxy/login page no longer falls through).
- AssetsActivity: drop dead post-unxz size check (Libcore.unxz already
  enforces the same 256 MB cap and fails before rename).
- .depot/workflows: make libcore/sidecars cache fingerprints content-
  and path-aware via 'git ls-files -s | sha256sum' (symlink-safe; the
  old find|cat masked content moves and a dangling buildScript/nkmr
  symlink), normalize the libcore-status command identically across all
  four workflows (shared cache key), and pass KEYSTORE_B64 via env:.
@hawkff

hawkff commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

Pushed d6978c5 — root-cause fix + review follow-ups

Root cause of the failing checks: libcore/io.go had an unused fmt import (io.go:5:2: "fmt" imported and not used), which gomobile treats as a hard compile error. That broke the libcore build step, so every job depending on the built library failed at the same point. Fixed.

Review findings addressed:

  • http.go full response deadline: added a client Timeout to the h1h2 client and to both doH3Direct clients (ECH + H3) so body reads can't hang indefinitely, on top of the existing dial/TLS/header timeouts.
  • World-writable temp files: dropped Chmod(0666) in WriteToLimited (http.go) and Unxz (io.go); os.CreateTemp already creates files privately and both paths atomically rename.
  • Dead contentLimit field: removed.
  • Int overflow in due-check: SubscriptionUpdater.doWork() now uses Long arithmetic, matching computeSubscriptionWorkSchedule.
  • WebDAV PROPFIND: require 207 before parsing the multistatus body so a 200/204 non-multistatus response surfaces the real error instead of "No backup found".
  • Dead post-unxz size check: removed; the native unxz already enforces the same 256 MB cap and fails before rename.
  • Cache fingerprints + signing secret: libcore/sidecars status now uses git ls-files -s | sha256sum (content- and path-aware, and symlink-safe), normalized identically across the CI workflows so the shared cache key stays consistent; the keystore secret now goes through env: like the other signing secrets.

Note on the new test classes: they run on push/PR via the unit-test workflow (:app:testOssDebugUnitTest), alongside lint+spotless, the instrumented Room-migration test, and the APK build. They surface as the PR checks. The earlier red was the libcore build break above, now fixed.

Local gates: spotlessKotlinCheck + :app:compileOssDebugKotlin green; SubscriptionUpdaterScheduleTest + WebDAVSecurityTest pass; gofmt/go vet clean on the edited Go files.

@hawkff hawkff merged commit 8bbadde into main Jun 28, 2026
7 checks passed
@hawkff hawkff deleted the chore/improvements branch June 28, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant