Skip to content

Security & stability fixes (local proxy, crashes, import)#44

Merged
hawkff merged 8 commits into
mainfrom
security-patches
Jun 20, 2026
Merged

Security & stability fixes (local proxy, crashes, import)#44
hawkff merged 8 commits into
mainfrom
security-patches

Conversation

@hawkff

@hawkff hawkff commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Summary

Backports and original security/stability fixes for upstream issues that are not
addressed in our base. All changes verified to build/review clean locally on Android.

Changes

Testing

  • CodeRabbit CLI review: No findings.
  • Verified on Android (build via CI).

Notes

  • The three local-proxy options share one coherent gate; defaults preserve current
    behavior except VPN-mode inbound, which is now closed unless opted in.
  • Removing the keystore from HEAD does not purge it from prior history (separate decision).

Greptile Summary

This PR delivers six targeted security and stability fixes across the local proxy inbound, YAML subscription parsing, per-app filter threading, screen-rotation crash, and the About screen detachment crash, plus untracking a committed signing key.

  • Local proxy hardening (ConfigBuilder.kt, DataStore.kt, Constants.kt, global_preferences.xml, strings.xml): adds a keepMixedInbound gate that closes the SOCKS/HTTP inbound by default in VPN/TUN mode (new opt-in requireProxyInVPN), and an opt-in proxyModeInboundAuth flag that extends credential enforcement to Proxy-mode loopback. Both inbound and route rule are consistently guarded; the mixedInboundNeedsAuth property is updated to match.
  • Thread-safety and crash fixes (AppManagerActivity.kt, ConfigurationFragment.kt, AboutFragment.kt, AndroidManifest.xml): replaces android.widget.Filter (one pthread per call) with a coroutine-based, debounced, cancellable filter; guards both dialog.dismiss() sites with a targeted catch (IllegalStateException); adds isAdded checks before async UI callbacks in AboutFragment; and registers orientation|screenSize|keyboardHidden config-changes to prevent Activity restart during URL tests.
  • Import fixes (Formats.kt, RawUpdater.kt): defers SubscriptionFoundException so stray promo URLs in a multi-profile file no longer abort the entire import; raises maxAliasesForCollections from 50 → 200 to cover large Clash/Mihomo configs while keeping alias-expansion memory bounded.

Confidence Score: 5/5

Safe to merge; all changes are well-scoped fixes with consistent logic across DataStore, ConfigBuilder, and the preference UI.

Each fix addresses a discrete, verified issue (OOM thread exhaustion, fragment detachment crash, rotation crash, subscription parse abort, local proxy exposure). The new keepMixedInbound flag and mixedInboundNeedsAuth extension are correctly threaded through every consumer. The coroutine filter replacement is sound with appropriate @volatile annotations. No regressions were identified in the changed paths.

No files require special attention. The most behaviorally significant change — closing the VPN-mode local proxy by default — is intentional and documented.

Important Files Changed

Filename Overview
app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt Adds keepMixedInbound gate so the local SOCKS/HTTP inbound is omitted in VPN/TUN mode by default; both the inbound and its route rule are correctly guarded by the flag.
app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt Extends mixedInboundNeedsAuth with a Proxy-mode auth clause; adds requireProxyInVPN and proxyModeInboundAuth delegated boolean properties. Logic is consistent with ConfigBuilder and BoxInstance usage.
app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt Defers SubscriptionFoundException so stray http(s) promo URLs no longer abort a mixed import; subscription is only triggered when no profiles were parsed at all.
app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt Replaces android.widget.Filter (one thread per call) with a coroutine-based, debounced, cancellable filter; @volatile on apps and sysApps provides correct cross-thread visibility.
app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt Guards async update-check callbacks with isAdded checks; uses the passed activityContext instead of Fragment's getString to avoid IllegalStateException when detached.
app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt Wraps both dialog.dismiss() sites in a targeted catch (e: IllegalStateException) so post-rotation window teardown doesn't crash the test flow.
app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt Sets maxAliasesForCollections = 200 (up from SnakeYAML's default 50) to accommodate large Clash/Mihomo configs while keeping alias-expansion amplification bounded.
app/src/main/AndroidManifest.xml Adds orientation

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[buildConfig called] --> B{forTest?}
    B -- yes --> C[keepMixedInbound = false\nNo local inbound added]
    B -- no --> D{isVPN?}
    D -- no\nProxy mode --> E[keepMixedInbound = true]
    D -- yes\nVPN/TUN mode --> F{requireProxyInVPN?}
    F -- yes --> E
    F -- no --> G{appendHttpProxy\n+ Android Q+?}
    G -- yes --> E
    G -- no --> C
    E --> H[Add mixed inbound to config]
    H --> I{mixedInboundNeedsAuth?}
    I -- LAN exposed\nallowAccess=true --> J[Auth mandatory]
    I -- VPN mode\nno appendHttpProxy --> J
    I -- Proxy mode\nproxyModeInboundAuth=true --> J
    I -- Proxy mode\nproxyModeInboundAuth=false --> K[Auth disabled\nopen loopback proxy]
    J --> L[Inbound with username/password]
    K --> M[Inbound with no credentials]
    C --> N[No local proxy port opened\nReduced attack surface]
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[buildConfig called] --> B{forTest?}
    B -- yes --> C[keepMixedInbound = false\nNo local inbound added]
    B -- no --> D{isVPN?}
    D -- no\nProxy mode --> E[keepMixedInbound = true]
    D -- yes\nVPN/TUN mode --> F{requireProxyInVPN?}
    F -- yes --> E
    F -- no --> G{appendHttpProxy\n+ Android Q+?}
    G -- yes --> E
    G -- no --> C
    E --> H[Add mixed inbound to config]
    H --> I{mixedInboundNeedsAuth?}
    I -- LAN exposed\nallowAccess=true --> J[Auth mandatory]
    I -- VPN mode\nno appendHttpProxy --> J
    I -- Proxy mode\nproxyModeInboundAuth=true --> J
    I -- Proxy mode\nproxyModeInboundAuth=false --> K[Auth disabled\nopen loopback proxy]
    J --> L[Inbound with username/password]
    K --> M[Inbound with no credentials]
    C --> N[No local proxy port opened\nReduced attack surface]
Loading

Reviews (3): Last reviewed commit: "review: address CR/Greptile feedback (al..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds two new configurable preferences (requireProxyInVPN, proxyModeInboundAuth) that gate the local mixed SOCKS/HTTP inbound inclusion and authentication in VPN/Proxy modes, wired through constants, DataStore, ConfigBuilder, strings, and preferences XML. It also refactors AppManagerActivity app filtering from android.widget.Filter to a coroutine-based pipeline with debounce support, defers SubscriptionFoundException in proxy parsing to allow mixed import inputs, raises the YAML alias limit for Clash/Mihomo configs, hardens AboutFragment/ConfigurationFragment against detached-fragment crashes, broadens MainActivity config-change handling, and excludes keystore files from git.

Changes

Mixed Inbound Gating Feature

Layer / File(s) Summary
Keys, DataStore properties, and UI strings/prefs
app/src/main/java/io/nekohasekai/sagernet/Constants.kt, app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt, app/src/main/res/values/strings.xml, app/src/main/res/xml/global_preferences.xml
Declares REQUIRE_PROXY_IN_VPN and PROXY_MODE_INBOUND_AUTH key constants, adds requireProxyInVPN and proxyModeInboundAuth DataStore properties, extends mixedInboundNeedsAuth for proxy-mode auth control, adds four string resources for labels/summaries, and inserts two SwitchPreference entries into the inbound settings category.
ConfigBuilder keepMixedInbound logic
app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
Imports android.os.Build, computes keepMixedInbound from forTest/isVPN/requireProxyInVPN/appendHttpProxy combined with Android Q+ version check, conditionally adds the Inbound_MixedOptions mixed inbound to the configuration, and conditionally adds its global-mode routing rule.

AppManagerActivity Coroutine Filter Refactor

Layer / File(s) Summary
AppsAdapter: remove Filterable, add computeFiltered/publishFiltered
app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt
Removes Filter/Filterable imports and interface declaration, replaces the Filter implementation with computeFiltered(constraint) for off-thread list building and system-app suppression, and adds publishFiltered(result) to update filteredApps and trigger adapter refresh.
applyFilter coroutine pipeline and call-site updates
app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt
Introduces filterJob property and applyFilter(constraint, debounceMs) that cancels in-flight jobs, applies optional debounce on Dispatchers.Default, executes computeFiltered off-thread, and dispatches publishFiltered on main; updates all six call sites (initial load, search text changes, system-apps toggle, invert/clear/proxy-app menu actions) from filter.filter() to applyFilter().

Bug Fixes and Infrastructure

Layer / File(s) Summary
Deferred SubscriptionFoundException in parseProxies
app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt
Introduces subscriptionCandidate local variable to defer subscription detection; when an http(s) URL fails to parse as a proxy, the generated clash://install-config?url=... is stored instead of immediately throwing; after processing all links, throws SubscriptionFoundException only if no profiles were parsed, allowing mixed imports to proceed when valid profiles exist.
YAML alias cap increase
app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt
Raises LoaderOptions.maxAliasesForCollections from the SnakeYAML default of 50 to 200 so Clash/Mihomo subscription configs with heavy anchor/alias reuse can parse successfully, while the codePointLimit input-size guard remains unchanged.
AboutFragment lifecycle guards and ConfigurationFragment dialog safety
app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt, app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt
Switches version string formatting from getString() to activityContext.getString() to avoid context-detachment issues; adds isAdded guards before UI updates and error toasts on the main dispatcher after async work in AboutFragment; wraps dialog.dismiss() in try/catch for both pingTest and urlTest cancellation flows in ConfigurationFragment.
Manifest configChanges and .gitignore keystore entries
app/src/main/AndroidManifest.xml, .gitignore
Broadens MainActivity android:configChanges declaration to handle orientation, screenSize, and keyboardHidden in addition to uiMode; adds .gitignore patterns for signing keystores (release.keystore, *.keystore, *.jks) with a comment that these should never be committed.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (Settings)
    participant GlobalPrefs as global_preferences.xml
    participant DataStore as DataStore
    participant ConfigBuilder as ConfigBuilder
    participant SingBox as Sing-box Inbounds

    User->>GlobalPrefs: toggle requireProxyInVPN / proxyModeInboundAuth
    GlobalPrefs->>DataStore: persist preference
    Note over DataStore: mixedInboundNeedsAuth reads proxyModeInboundAuth<br/>when serviceMode == PROXY
    DataStore->>ConfigBuilder: requireProxyInVPN, appendHttpProxy, isVPN
    ConfigBuilder->>ConfigBuilder: compute keepMixedInbound
    alt keepMixedInbound == true
        ConfigBuilder->>SingBox: add Inbound_MixedOptions("mixed-in")
        ConfigBuilder->>SingBox: add routing rule TAG_MIXED → mainProxyTag
    else keepMixedInbound == false
        ConfigBuilder->>SingBox: skip mixed inbound and routing rule
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

  • hawkff/NekoBoxForAndroid#4: Directly modifies DataStore.mixedInboundNeedsAuth at the same conditional logic point, tying auth enforcement to allowAccess rather than the new proxy-mode/VPN gating flags added in this PR.

Poem

🐇 Hop, hop, the inbound gate swings wide,
No longer open when VPN's your guide!
A coroutine cancels what Filter once spun,
And subscriptions wait 'til all links are done.
The YAML aliases soar, no more denial—
This rabbit secured the keys with a smile! 🔑

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% 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 'Security & stability fixes (local proxy, crashes, import)' clearly and specifically summarizes the main changes: security hardening, crash fixes, and import improvements across three key areas.
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 PR description accurately describes the changeset, detailing security/stability fixes for six upstream issues with clear explanations of each change and their rationale.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

Comment thread app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt Outdated
Comment thread app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt
Comment thread app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt Outdated

@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: 1

🧹 Nitpick comments (1)
app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt (1)

163-165: ⚡ Quick win

Consider @Volatile for thread-safe visibility of shared state.

apps is read on Dispatchers.Default in computeFiltered but written from multiple threads (Default in invert/clear actions, IO in reload, Main in selectProxyApp). Similarly, sysApps is read on Default but written on Main. Without @Volatile, the JVM memory model doesn't guarantee that writes are visible to other threads.

In practice this likely works due to dispatch latency, but the worst case is filtering against a stale list. Adding @Volatile provides proper visibility guarantees.

♻️ Suggested fix
 private var filterJob: Job? = null
-private var apps = emptyList<ProxiedApp>()
+@Volatile private var apps = emptyList<ProxiedApp>()
 private val appsAdapter = AppsAdapter()

And for sysApps at line 270:

-private var sysApps = true
+@Volatile private var sysApps = true
🤖 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/AppManagerActivity.kt` around
lines 163 - 165, The apps and sysApps properties are being accessed and modified
from multiple threads (Dispatchers.Default, Dispatchers.IO, and
Dispatchers.Main) without proper synchronization guarantees. Add the `@Volatile`
annotation to both the apps property (currently at line 163) and the sysApps
property (at line 270) to ensure that writes to these shared state variables are
properly visible to other threads. This prevents potential issues where
filtering operations in computeFiltered could work with stale cached values.
🤖 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 `@app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt`:
- Line 269: Fix the typo in the comment on line 269 where it says "unparyable" -
change it to the correct spelling "unparsable" to fix the misspelled word in the
comment describing the behavior when no profile links are parsed but an
unparsable URL is encountered.

---

Nitpick comments:
In `@app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt`:
- Around line 163-165: The apps and sysApps properties are being accessed and
modified from multiple threads (Dispatchers.Default, Dispatchers.IO, and
Dispatchers.Main) without proper synchronization guarantees. Add the `@Volatile`
annotation to both the apps property (currently at line 163) and the sysApps
property (at line 270) to ensure that writes to these shared state variables are
properly visible to other threads. This prevents potential issues where
filtering operations in computeFiltered could work with stale cached values.
🪄 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: fdd727f1-0478-48fb-8258-2492014d6a20

📥 Commits

Reviewing files that changed from the base of the PR and between 8082f8d and 0672bb6.

📒 Files selected for processing (13)
  • .gitignore
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/io/nekohasekai/sagernet/Constants.kt
  • app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
  • app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
  • app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt
  • app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt
  • app/src/main/res/values/strings.xml
  • app/src/main/res/xml/global_preferences.xml
  • release.keystore

Comment thread app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt Outdated
@hawkff

hawkff commented Jun 20, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@hawkff hawkff merged commit d391cba into main Jun 20, 2026
8 checks passed
@hawkff hawkff deleted the security-patches branch June 20, 2026 17:53
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