Proxy God — Audit Findings
Generated: 2026-05-22
Tests: 47/47 pass · compileall clean
Severity summary: P0: 12 · P1: 28 · P2: 22 · Total: 62
Executive Summary
| Severity |
Count |
| Critical (P0) |
12 |
| High (P1) |
28 |
| Medium (P2) |
22 |
| Total |
62 |
Already remediated (prior passes): CI workflow, GOST SHA256 pin, kill-switch emergency_disengage + atexit, settings migration/backup, log rotation, MIT LICENSE, GOST SHA256 verified correct.
Top P0 Issues
- Wrong Chrome/Edge WebRTC policy key/value —
fingerprint.py:21,177 writes DWORD 2 which is default_public_and_private_interfaces, NOT disable_non_proxied_udp (DWORD 3). Modern Chrome also requires REG_SZ WebRtcIPHandling = disable_non_proxied_udp. Audits treat 2 as success → false green.
BrowserSession.stop() kills ALL Firefox on the machine — browser_launcher.py:297-305 runs taskkill /F /IM firefox.exe with no PID scope. Operator loses unrelated browser sessions.
- Credentials plaintext at rest —
config.py:19-22, signup_prep.py:252-325 store proxy auth + signup passwords in JSON. cryptography==48.0.0 is in requirements but unused.
- Preflight race condition —
app.py:2001-2009 uses fixed root.after(200, _final) which can report "All passed" before async network checks finish.
- Preflight IP compare is exact-match only — ignores the VPN-aware
/16 logic used in production leak detection (leak_detect.py:24-38).
- HTTPS CONNECT failure doesn't stop chain —
service.py:573-580 logs warning only; chain stays "healthy" and system proxy remains set.
- Pinned manual chain never validates hops at runtime — dead chain retries every 10s forever (
service.py:393-403).
emergency_disengage() runs on ALL exits — even if kill-switch never engaged; can mask other apps' firewall state (service.py:105, firewall.py:165-177).
- Signup extension runs on all URLs —
signup_extension/manifest.json:13-19 uses <all_urls>.
- Account auto-saved before signup completes — password saved on browser launch before user registers (
app.py:2147-2174).
- GOST exe not re-hashed on reuse — zip is verified but extracted
gost.exe on disk is not re-checked (gost_util.py:61-64).
asyncio.run() from worker threads — app.py:1860-1863 nested event loop risk if caller context changes.
1. Critical Bugs
| ID |
Finding |
File:Line |
| C-01 |
WebRTC policy wrong key/type/value |
fingerprint.py:18-21,162-177, webrtc_check.py:37-96, leak_audit.py:138-157 |
| C-02 |
Browser stop kills all firefox.exe globally |
browser_launcher.py:297-305 |
| C-03 |
Plaintext settings.json / signup JSON credentials |
config.py:19-22, signup_prep.py:252-325 |
| C-04 |
Preflight root.after(200) race — final verdict before checks complete |
app.py:2001-2009 |
| C-05 |
Preflight IP compare exact-match only (ignores /16 VPN logic) |
app.py:1886-1893, leak_detect.py:24-38 |
| C-06 |
HTTPS CONNECT failure keeps chain "healthy" |
service.py:573-580 |
| C-07 |
Pinned chain never validates hops at runtime |
service.py:393-403 |
| C-08 |
emergency_disengage() fires on ALL exits |
service.py:105, firewall.py:165-177 |
| C-09 |
Signup extension <all_urls> permissions |
signup_extension/manifest.json:13-19 |
| C-10 |
Account saved before signup completes |
app.py:2147-2174 |
| C-11 |
GOST exe not re-hashed on disk reuse |
gost_util.py:61-64 |
| C-12 |
asyncio.run() from worker threads |
app.py:1860-1863 |
2. Edge Cases / Failure Modes
| ID |
Finding |
File:Line |
| E-01 |
real_ip captured once at start; VPN reconnect mid-session not reflected |
service.py:339-340,551-558 |
| E-02 |
Sticky exit suppresses leak rotation but not manual rotate/GOST death |
service.py:607-619 |
| E-03 |
Sticky + health leak: status forced "healthy" while leaking |
service.py:610-618 |
| E-04 |
is_chain_leak(real_ip=None) returns False (no leak) — fail-open |
leak_detect.py:32-33 |
| E-05 |
Same /16 ISP neighbors never flagged — some leak modes missed by design |
leak_detect.py:36-37 |
| E-06 |
Empty pinned chain + use_pinned_chain=True falls through to pool silently |
service.py:393, config.py:201 |
| E-07 |
chain_length=1 + manual exit only — no mid-hop redundancy |
service.py:407-415 |
| E-08 |
Obfuscation random_mix shuffles list but takes prefix slice — not random per hop |
service.py:641-644,717 |
| E-09 |
Pool exhausted → reshuffle; blacklisted proxies stay excluded until refresh |
service.py:409-417,768-771 |
| E-10 |
Fixed exit validation failure still used in chains |
service.py:822-839 |
| E-11 |
socket.setdefaulttimeout() global mutation — thread race in DNS helpers |
dns_leak.py:140-151, ban_tester.py:149-150 |
| E-12 |
DNS leak "proxy path" uses Google DoH through HTTP proxy — not equivalent to system DNS |
dns_leak.py:154-167 |
| E-13 |
DNS all_match logic can false-flag or miss due to CDN/geo DNS answers |
dns_leak.py:221-252 |
| E-14 |
Kill-switch allows outbound DNS globally — DNS exfil channel exists |
firewall.py:136-140 |
| E-15 |
Kill-switch skipped when non-admin with only log line |
service.py:350-357 |
| E-16 |
IPv6 disable skips virtual adapters by name heuristics only |
fingerprint.py:122-137 |
| E-17 |
Hostname rename may require reboot; partial apply possible |
fingerprint.py:73-103 |
| E-18 |
MAC spoof restore depends on in-memory originals — crash mid-session may leave spoofed MAC |
service.py:96-97 |
| E-19 |
BrowserSession.is_running() treats any firefox.exe within 5 min as "ours" |
browser_launcher.py:153-157 |
| E-20 |
Auto-relaunch can loop if profile locked/corrupt |
app.py:2866-2871 |
| E-21 |
task_exists() logon task runs LIMITED — no admin kill-switch at boot |
windows_task.py:47-48 |
| E-22 |
Group Policy proxy locks detected but never remediated |
sysproxy.py:298-317, service.py:586-594 |
| E-23 |
load_settings auto-rewrites sanitized file — can surprise operators |
config.py:436-443 |
| E-24 |
_is_safe_https_url allows http:// sources — MITM on pool fetch |
config.py:266-271, fetcher.py:13-16 |
| E-25 |
Ban tester DNS leak helper checks x-real-ip header (usually absent) — nonsense results |
ban_tester.py:169-219 |
| E-26 |
Ban tester _looks_banned_body matches "captcha" on normal pages → false bans |
ban_tester.py:71-89,133-134 |
| E-27 |
run_ban_tests(categories=["All"]) duplicates sites |
ban_tester.py:237-243 |
| E-28 |
Exit intel/geo fetch via third-party APIs through proxy — telemetry surface |
exit_intel.py, chain_map.py:152-179 |
| E-29 |
World map geo resolution uses direct socket.getaddrinfo — DNS leak for hop lookup |
chain_map.py:86-112,160 |
| E-30 |
GOST immediate exit blacklists hops but pinned/manual exit exempt |
service.py:515-522,557-562 |
3. Missing Features / README Gaps
| ID |
Finding |
Reference |
| M-01 |
README "self-healing forever" — pinned mode doesn't rotate pool |
README.md:9, service.py:393-403 |
| M-02 |
README "fail closed" — non-admin + kill-switch skipped |
README.md:25, service.py:350-357 |
| M-03 |
README 1–8 hops; UI/config says 2–8 in places |
README.md:48, config.py:167 |
| M-04 |
No in-GUI emergency kill-switch disengage button |
firewall.py, app.py |
| M-05 |
audit_device() is OS-only but UI shows it under "Browser Fingerprint" |
fingerprint.py:299-383, app.py:2310-2313 |
| M-06 |
Preflight card mentions "fingerprint" but no fingerprint check in _PF_CHECKS |
app.py:1775-1793 |
| M-07 |
browser_auto_relaunch not tied to chain health flag |
app.py:2866-2871, config.py:205 |
| M-08 |
No export/import encrypted settings/signup vault |
— |
| M-09 |
No persisted ban-test / audit history |
app.py |
| M-10 |
prefer_elite can empty pool with no GUI recovery wizard |
config.py:183, service.py:754-761 |
| M-11 |
SOCKS5 remote DNS disabled in Firefox profile |
browser_profile.py:125 |
| M-12 |
Signup presets have no ToS/disclaimer in UI |
signup_prep.py:37-160 |
| M-13 |
No IPv6 chain path — IPv6 disable adapter-level only |
fingerprint.py:106-138 |
| M-14 |
Tray red/yellow/green with no click-through to failed check detail |
tray.py, app.py:2841-2844 |
| M-15 |
CHANGELOG.md claims DPAPI/Fernet — not implemented |
CHANGELOG.md:35-36 |
4. Build / Packaging / Deployment
| ID |
Finding |
Reference |
| B-01 |
No Authenticode signing |
ProxyChainManager.spec:70-71 |
| B-02 |
UPX enabled — AV false positives risk |
ProxyChainManager.spec:66-67 |
| B-03 |
uac_admin=True — every launch elevates |
ProxyChainManager.spec:72 |
| B-04 |
PyInstaller not pinned in requirements.txt |
scripts/setup_and_build.ps1:39 |
| B-05 |
Build script doesn't emit SHA256 sidecar |
scripts/setup_and_build.ps1:50-58 |
| B-06 |
GOST not bundled — first run requires GitHub access |
gost_util.py:61-86 |
| B-07 |
__version__ static 1.0.0, not tied to git tag |
__init__.py:3 |
| B-08 |
CI does not run PyInstaller build |
.github/workflows/test.yml |
| B-09 |
No SBOM / pip freeze in release artifacts |
— |
| B-10 |
Defender exclusion adds whole %LOCALAPPDATA%\ProxyChainManager folder |
gost_util.py:29-45 |
5. Security / Privacy
| ID |
Finding |
Reference |
| S-01 |
Plaintext secrets at rest (proxy creds + signup passwords) |
config.py, signup_prep.py |
| S-02 |
WebRTC mis-hardening (wrong policy value) |
fingerprint.py |
| S-03 |
All httpx clients use verify=False — MITM risk |
validator.py:142, ban_tester.py:119, dns_leak.py:159 |
| S-04 |
Signup passwords can appear in UI/log via clipboard actions |
app.py:2059-2062 |
| S-05 |
Signup extension content script on all URLs |
signup_extension/manifest.json |
| S-06 |
Public proxy list sources — hostile infrastructure by design |
config.py:218-223 |
| S-07 |
cryptography dependency unused — attack surface without benefit |
requirements.txt:5 |
| S-08 |
Kill-switch + DNS allow rule = DNS bypass channel |
firewall.py:136-140 |
| S-09 |
Forensic wipe deletes Recycle Bin without extra confirmation |
artifact_wipe.py:208-216 |
| S-10 |
No secure deletion of signup JSON on uninstall |
— |
6. UI/UX Issues
| ID |
Finding |
Reference |
| U-01 |
Window close minimizes to tray — chain/firewall active silently |
app.py:2908 |
| U-02 |
"Run as Admin" relaunch closes app — unsaved UI state lost |
app.py:347-350 |
| U-03 |
No version/build SHA in About UI |
__init__.py:3 |
| U-04 |
Cookie/persona menus reverse-lookup by label — fragile if labels change |
app.py:1321-1325 |
| U-05 |
Hardened toggles visible even when blend persona overrides them |
browser_profile.py:42-65 |
| U-06 |
Preflight "fingerprint" in subtitle but not in checklist |
app.py:1775-1793 |
| U-07 |
Ban tester has no progress bar for 30+ sites |
ban_tester.py:222-260 |
| U-08 |
Saved accounts list shows email + password in plain text |
app.py:2046-2049 |
| U-09 |
Chain map silently skips hops when geo lookup fails — no unknown marker |
chain_map.py:156-166,322-328 |
| U-10 |
verbose_logs_var affects UI handler only; file log stays INFO |
app.py:707-713 |
7. Test Coverage Gaps
| ID |
Finding |
| T-01 |
No tests for service.py (rotation, sticky, kill-switch) |
| T-02 |
No tests for firewall.py / emergency_disengage |
| T-03 |
No tests for sysproxy.py registry blob encoding |
| T-04 |
No tests for GOST SHA256 mismatch path |
| T-05 |
No tests for dns_leak.run_dns_leak_test verdict logic |
| T-06 |
No tests for webrtc_check / apply_webrtc_hardening |
| T-07 |
No tests for BrowserSession |
| T-08 |
No tests for signup_prep.install_signup_extension |
| T-09 |
No tests for config.load_settings/migrate/sanitize round-trip |
| T-10 |
No tests for ban_tester heuristics |
| T-11 |
No GUI/smoke tests for app.py (~2900 lines) |
| T-12 |
test_fetch_smoke is network-dependent — can skip silently in CI |
| T-13 |
No test that WebRTC policy value matches Chrome documentation |
| T-14 |
No frozen-bundle (_MEIPASS) path tests in CI |
8. Recommended Fix Priority
P0 — Fix immediately:
C-01, C-02, C-03, C-04, C-05, C-06, S-01, S-02, S-03, B-01
P1 — Next sprint:
C-07, C-10, E-01–E-06, E-11, E-22, E-25–E-27, M-04–M-07, M-11, B-04–B-06, T-01–T-05, T-09, U-01–U-03
P2 — Backlog:
All remaining E/M/B/U/T items, installer signing, metrics, coverage gates
9. Remediation log (2026-05-22 passes)
| ID |
Status |
Notes |
| C-01 |
Fixed |
fingerprint.py, webrtc_check.py — DWORD 3 + WebRtcIPHandling REG_SZ |
| C-02 |
Fixed |
browser_launcher.py — scoped taskkill /PID |
| C-04 |
Fixed |
app.py — _final scheduled after all after(0) callbacks |
| C-05 |
Fixed |
app.py preflight uses is_chain_leak() + VPN-aware /16 |
| C-06 |
Fixed |
service.py — HTTPS CONNECT failure rotates chain (not healthy) |
| C-08 |
Fixed |
firewall.py — emergency_disengage() no-op unless is_engaged() |
| C-10 |
Fixed |
app.py — auto-save on launch is pending with empty password |
| E-06 |
Fixed |
service.py — log when pinned chain enabled but empty |
| E-11 |
Fixed |
dns_leak.py, ban_tester.py — restore socket timeout in finally |
| E-24 |
Fixed |
config.py — pool sources must be https:// only |
| E-26 |
Fixed |
ban_tester.py — removed bare "captcha" hint; stricter phrases |
| E-27 |
Fixed |
ban_tester.py — dedupe by URL |
| M-06 / U-06 |
Fixed |
Preflight subtitle no longer claims “fingerprint” check |
| M-15 |
Fixed |
CHANGELOG.md — Fernet/DPAPI not implemented (clarified) |
| B-02 |
Fixed |
ProxyChainManager.spec — upx=False |
| leak_audit |
Fixed |
_check_webrtc_policy() accepts DWORD 3 or REG_SZ |
| (prior) |
Fixed |
CI, GOST zip SHA256, settings migration/backup, LICENSE, log rotation |
10. Still needs to be fixed
Items below were not changed because they need design decisions, external tooling (certs, infra), or behavior that is not safe to guess.
Critical / security (P0)
| ID |
Why left open |
Suggested direction |
| C-09 |
Narrowing signup extension off <all_urls> breaks custom signup URLs the operator types in |
Dynamic host permissions API or per-session host approval prompt |
| C-12 |
asyncio.run() in worker threads (app.py preflight) needs a dedicated event-loop policy refactor |
Move all preflight network ops to a single asyncio runner thread |
| S-03 |
verify=False on httpx is intentional for the broken-cert reality of public proxies |
Per-setting toggle with security warning, documented trade-off |
| B-01 |
Authenticode signing needs an actual code-signing certificate + CI secrets |
Sign dist\ProxyChainManager.exe in release pipeline once cert is provisioned |
High (P1) — design / scope debt
| ID |
Why left open |
| E-01 |
Refresh real_ip when VPN state changes mid-session — needs interval policy |
| E-02–E-05 |
Sticky exit / leak semantics need a product rule (rotate vs warn) |
| E-07–E-10 |
Pool/pinned/exit edge cases need operator UX |
| E-12–E-13 |
DNS leak methodology (DoH vs system DNS) needs spec |
| E-14 / S-08 |
Kill-switch DNS allow rule is required for GOST hostname resolution |
| E-16–E-18 |
OS adapter / MAC spoof crash recovery |
| E-21 |
Boot task LIMITED vs admin kill-switch |
| E-22 |
Group Policy proxy-lock remediation (destructive — needs explicit operator consent) |
| E-23 |
Auto-rewrite of sanitized settings — needs UI toggle |
| E-28–E-30 |
Third-party intel APIs / map geo / GOST blacklist rules |
| M-01–M-03, M-05, M-07–M-14 |
README alignment, encrypted vault export, ban history, IPv6 chain path, tray click-through |
| U-01, U-02, U-04, U-05, U-07–U-10 |
Tray/minimize UX, admin-relaunch save state, cookie menu keys, password masking |
| T-01–T-14 |
Larger test-coverage program |
| B-03, B-06–B-10 |
UAC manifest tradeoff, GOST bundling, SBOM, Defender scope |
Medium (P2)
Installer polish, metrics, coverage gates, IPv6 chain path, SOCKS5 remote DNS policy (browser_profile.py:125 — likely intentional for Firefox+GOST).
12. Decisions held — accepted trade-offs
These items are closed by design choice (operator approved), not because they're hidden bugs.
| ID |
Decision |
Rationale |
| C-09 |
Keep signup extension on <all_urls> |
Custom signup URLs entered at runtime require dynamic host match; operator isolates the profile per session. |
| S-03 |
Keep verify=False on httpx probes |
Public proxies routinely ship broken / self-signed TLS; turning verification on would drop ~half the working pool. |
| E-14 / S-08 |
Keep DNS pass-through in kill-switch |
GOST needs system DNS to resolve proxy hostnames; locking port 53 would break pool fetching and exit verification. |
| B-01 |
Code-signing deferred |
Requires an Authenticode certificate (commercial or self-signed); will wire signtool sign into the build script once the cert is provided. |
| B-08 |
CI builds the exe — pending operator approval |
Adds ~2 min per CI run; not enabled yet. |
11. Remediation log — 2026-05-22 (round 2)
| ID |
Status |
Notes |
| C-03 / S-01 |
Fixed |
secrets_store.py (Windows DPAPI via ctypes — no new deps). Signup passwords + draft passwords now encrypted on disk; legacy plaintext migrates automatically on first save |
| C-07 |
Fixed |
New validate_pinned_on_start setting + _probe_tcp helper in service.py; dead pinned hops logged but used |
| C-11 |
Fixed |
gost_util.py writes gost.exe.sha256 sidecar on extract and re-verifies on every reuse; mismatch triggers re-download |
| E-15 |
Fixed |
app.py _start() confirms with operator before starting chain without admin when kill-switch is enabled |
| E-19 |
Fixed |
browser_launcher.py is_running() walks the spawned PID's descendants via WMIC instead of pattern-matching any firefox.exe |
| E-20 |
Fixed |
max_browser_relaunches setting + counter in app.py; auto-relaunch self-disables after N failures |
| E-25 |
Fixed |
Dead ban_tester.check_dns_leak() removed (use dns_leak.run_dns_leak_test) |
| M-04 |
Fixed |
Settings tab now has “⚠ Emergency disengage firewall now” button with confirm |
| U-03 |
Fixed |
Window title now reads Proxy God v{__version__} |
| B-04 |
Fixed |
New dev-requirements.txt pins pyinstaller==6.10.0; build script installs it |
| B-05 |
Fixed |
Build script emits ProxyChainManager.exe.sha256 next to the exe and copies it to Desktop |
Test suite: 47 → 53 passing (added 6 round-trip tests for secrets_store).
Proxy God Audit — last updated 2026-05-22 (round 2)
13. Remediation log — 2026-05-22 (round 3)
| ID |
Status |
Notes |
| E-04 |
Fixed (fail-closed) |
leak_detect.is_chain_leak() now treats unknown real_ip as a leak; service.py retries direct-IP lookup 3× with 2 s back-off as warm-up before the verdict applies. New tests/test_fail_closed.py enforces the contract. |
| C-12 |
Fixed |
Preflight worker thread now creates ONE asyncio event loop, runs all coroutines on it via _run_async(), and closes it at the end. No more nested-loop risk. |
| U-01 |
Fixed |
X button asks the operator (Yes=tray / No=full quit / Cancel) when chain or firewall is still active. |
| U-02 |
Fixed |
"Run as Admin" calls _save_settings() before relaunching so unsaved Chain Builder / Settings edits persist. |
| U-04 |
Fixed |
Persona / cookie OptionMenus now reverse-lookup via a precomputed label→key dict — copy changes can no longer break the round-trip. |
| B-06 |
Fixed |
scripts/prepare_bundled_gost.ps1 stages proxy_chain_manager/_bundled/gost.exe (SHA-verified) before PyInstaller; the spec bundles it; ensure_gost() installs from bundle first and only network-downloads as fallback. First run works fully offline. |
| T-coverage |
Fixed |
New tests: test_config_round_trip.py (sanitize / migrate / save-load / corrupt backup), test_ban_tester.py (banned-body heuristics, categories), test_gost_util.py (zip + exe hash sidecar), test_fail_closed.py (leak contract), test_firewall_helpers.py (emergency_disengage guard). 47 → 82 tests passing (+35 since session start). |
No-action documented (§12): C-09, S-03, E-14/S-08, B-01, B-08.
Proxy God Audit — last updated 2026-05-22 (round 3)