CodeVetter · Repo Unpack · Risk map
2dust/v2rayN
The biggest risk surface is OS-level mutation: registry/WinInet proxy changes, autostart registration, sudo shell scripts carrying a GUI-entered sudo password, and a self-updater that extracts a network-downloaded zip over the install directory. SQL built by string interpolation in ProfileModels is only partially sanitized. The GlobalHotKeys submodule and ServiceLib.UdpTest project reference are easy build breakers. Heavy catch-all swallowing and a 3000-line ConfigHandler concentrate blast radius, and the ProfileItem ConfigVersion migration chain is delicate and untested.
- Linux/macOS TUN mode stores the user's sudo password in memory (AppManager.LinuxSudoPwd) and pipes it into 'sudo -S' via generated run_as_sudo.sh / kill_as_sudo.sh scripts under binConfigs — a plaintext credential-in-shell path that also leaves a world-visible generated script.
- AppManager.ProfileModels builds raw SQL with interpolated subid and a user-supplied filter; the only sanitization is stripping single quotes from filter, so other injection-adjacent characters pass through into SQLite QueryAsync.
- Self-update downloads a zip from GitHub over (optionally proxied) HTTPS and AmazTool extracts it over the install dir with only a 'bin/' skip rule — no in-app signature/hash verification is performed on the archive (GPG verification is offered manually per README).
- AmazTool.Upgrade kills every process named 'v2rayN' whose MainModule path starts with the app dir, then extracts entries by stripping the first path segment — a maliciously crafted zip entry name could still resolve outside intended paths (no explicit traversal check on entry.FullName). (inferred)
- ProxySettingWindows uses raw WinInet P/Invoke with manually allocated marshaled buffers (five 'remember to deallocate' sites); the code frees them but any early-return/exception path between alloc and free leaks unmanaged memory, and failure falls back to direct registry writes under 'HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings'.
- Custom server configs read arbitrary file paths: node.Address is used directly as a filesystem path (GenerateClientCustomConfig copies it; RegisterSingleNodeAsync reads it into CustomOutboundContent) — a crafted profile could point at any readable file.
- PacManager.RunListener creates a TcpListener on the PAC port and answers every connection with the PAC bytes without request parsing or binding check — the listener inherits default binding behavior and any local (or LAN, if bound broadly) client can fetch the PAC file.
- The ProfileItem schema migration chain (ConfigVersion <3 -> MigrateProfileExtraV2ToV3 rewriting per-protocol fields into ProtocolExtra JSON; =3 -> MigrateProfileTransportV3ToV4 reshaping transport fields) runs synchronously-blocked at startup and has no test coverage — a bad batch permanently corrupts stored profiles.
- ConfigHandler.cs is ~3000 lines containing config load/save, profile CRUD, batch import of five formats, sub/group management, routing/DNS/template init and regional presets — a single static god-class where a mistake in one feature can break unrelated persistence.
- v2rayN/GlobalHotKeys is a git submodule that is empty in a non-recursive clone; v2rayN.Desktop.csproj references GlobalHotKeys/src/GlobalHotKeys/GlobalHotKeys.csproj so the Desktop build fails outright without 'submodules: recursive' (which the workflows do set).
- Exceptions are swallowed broadly: AppExitAsync wraps the whole shutdown in catch{}, ProcessService.StopAsync/Dispose have nested empty catches, SysProxyHandler.UpdateSysProxy catches and returns true on failure — masking cleanup failures that leave cores running or the system proxy stuck.
- Subscription download supports http:// URLs and an optional third-party subconvert proxy chain; subscription content is base64-decoded and imported wholesale into the profile DB — a hostile/compromised subscription can inject arbitrary outbound definitions or Custom nodes pointing to local files. (inferred)
- WPF OnExit force-kills the current process (Process.GetCurrentProcess().Kill()) after logging — any async persistence still in flight at that point is truncated.
- Timeouts for outbound fetches are asymmetric and short (DirectFetch 10s, ProxyFetch 30s, LocalFetch 5s); silent empty-string failures propagate as 'IsNullOrEmpty -> return null' so download errors frequently surface only as a generic failure message. (inferred)