Summary
After the cached GeoIP database becomes older than 30 days, every GeoIP-enabled browser launch downloads the approximately 70 MB database again.
The download does not successfully replace the existing database on Windows, so the cache remains stale and the same download is triggered indefinitely.
Launching many profiles can therefore cause many repeated or concurrent downloads of the same shared file.
Root cause
The issue is caused by the combination of two problems in geoip.py.
1. The existing database cannot be replaced on Windows
When the database is older than GEOIP_UPDATE_INTERVAL, _maybe_trigger_update() starts a background download:
_download_geoip_db(db_path)
After downloading, _download_geoip_db() attempts to update the existing database with:
On Windows, Path.rename() cannot overwrite an existing destination file. It raises FileExistsError/WinError 183 when GeoLite2-City.mmdb already exists.
The background thread catches this exception and logs it only at debug level:
except Exception:
logger.debug("Background GeoIP update failed", exc_info=True)
As a result:
- The full database is downloaded.
- Replacing the existing database fails.
- The temporary download is deleted.
- The old database modification time remains unchanged.
- The database is still considered older than 30 days.
- The next browser launch starts another full download.
This creates an endless download loop after the cache first expires.
2. There is no update-in-progress guard
Every call to _maybe_trigger_update() creates a new thread when the database is stale:
threading.Thread(target=_bg, daemon=True).start()
There is no lock or shared "update in progress" state.
If many profiles are launched while the cache is stale, every launch can start another approximately 70 MB download.
The initial download has a similar race: multiple callers can all see that the database does not exist and begin downloading it simultaneously.
Environment
- OS: Windows
- CloakBrowser tested:
0.3.31
- The same affected logic appears to still be present in
0.4.12
- Cache file:
~/.cloakbrowser/geoip/GeoLite2-City.mmdb
Steps to reproduce
- Enable
geoip=True and configure a proxy.
- Ensure
GeoLite2-City.mmdb exists and is older than 30 days.
- Launch a browser profile.
- Observe the GeoIP database download.
- After the download finishes, check the database modification time.
- Launch the profile again.
The database modification time remains unchanged and the complete download starts again.
Example logs:
cloakbrowser INFO GeoIP download: 79 %
cloakbrowser INFO GeoIP download: 89 %
cloakbrowser INFO GeoIP download: 99 %
When multiple profiles are launched, multiple copies of these download sequences can appear.
Expected behavior
- Only one refresh should run when the database becomes stale.
- Other launches should continue using the existing database.
- The downloaded database should atomically replace the existing file on Windows.
- Concurrent launches should not start duplicate downloads.
Suggested fix
Use os.replace() or Path.replace() instead of Path.rename():
Also add a process-wide lock or update state so only one initial download or background refresh can run at a time.
For stale databases, the existing database should remain usable while one background refresh is running. If the refresh fails, the existing database should be retained and retries should be rate-limited.
Summary
After the cached GeoIP database becomes older than 30 days, every GeoIP-enabled browser launch downloads the approximately 70 MB database again.
The download does not successfully replace the existing database on Windows, so the cache remains stale and the same download is triggered indefinitely.
Launching many profiles can therefore cause many repeated or concurrent downloads of the same shared file.
Root cause
The issue is caused by the combination of two problems in
geoip.py.1. The existing database cannot be replaced on Windows
When the database is older than
GEOIP_UPDATE_INTERVAL,_maybe_trigger_update()starts a background download:After downloading,
_download_geoip_db()attempts to update the existing database with:On Windows,
Path.rename()cannot overwrite an existing destination file. It raisesFileExistsError/WinError 183whenGeoLite2-City.mmdbalready exists.The background thread catches this exception and logs it only at debug level:
As a result:
This creates an endless download loop after the cache first expires.
2. There is no update-in-progress guard
Every call to
_maybe_trigger_update()creates a new thread when the database is stale:There is no lock or shared "update in progress" state.
If many profiles are launched while the cache is stale, every launch can start another approximately 70 MB download.
The initial download has a similar race: multiple callers can all see that the database does not exist and begin downloading it simultaneously.
Environment
0.3.310.4.12~/.cloakbrowser/geoip/GeoLite2-City.mmdbSteps to reproduce
geoip=Trueand configure a proxy.GeoLite2-City.mmdbexists and is older than 30 days.The database modification time remains unchanged and the complete download starts again.
Example logs:
When multiple profiles are launched, multiple copies of these download sequences can appear.
Expected behavior
Suggested fix
Use
os.replace()orPath.replace()instead ofPath.rename():Also add a process-wide lock or update state so only one initial download or background refresh can run at a time.
For stale databases, the existing database should remain usable while one background refresh is running. If the refresh fails, the existing database should be retained and retries should be rate-limited.