Skip to content

[BUG] A failed Geo database refresh deletes the last known good copy, so country blocking fails open until restart #78

Closed

Description

@RomanPszonka

ipinfo_handler.py, in initialize():

except Exception as e:
    ...
    if self.db_path.exists():
        self.db_path.unlink()
    self.reader = None
    return

The download that failed is a refresh — the file on disk was working a
moment ago. Deleting it and clearing the reader means every subsequent
get_country() returns nothing, so blocked_countries and whitelist_countries
both stop matching. Country blocking is off until a later refresh succeeds, and
nothing retries within the process.

An outage at ipinfo.io, a token that expired, or a proxy that briefly refuses
egress all take a working geo policy off. That is the wrong direction for a
transient failure: a stale MMDB is a far better answer than no MMDB.

Fix: keep the existing file and the existing reader on download failure. Delete
only when the file is genuinely unusable — maxminddb.open_database raising is
the signal — and log the staleness so last_refreshed in the status route
reflects it.



Found while re-checking a private audit against the current release. Verified on guard-core 3.13.0, CPython 3.14.

Activity

  1. rennf93 commented on Aug 25, 2026

    @rennf93
    Member

    Thanks @RomanPszonka for opening the issue! By @rennf93's directive:

    Reproduced. It is two defects, not one. initialize() unlinks the good file on any download exception, and since the download reads the whole body before it opens db_path, the file is provably untouched when the common failures (HTTP error, timeout, expired token) fire. Separately, refresh() sets reader = None before it even attempts the download and never reopens the existing file when the download fails, so a failed refresh loses the reader regardless of what happens to the file. Both since the first commit.

    Fix in 3.14.0: keep the last-known-good file and reader on download failure; delete only when maxminddb.open_database raises (currently unguarded, and the actual corruption signal); last_refreshed stops advancing on failure so the status route shows staleness.

  2. self-assigned this
    on Aug 25, 2026
  3. added
    bugConfirmed defect
    securitySecurity-related issue (use private reporting first!)
    area: handlersTouches guard_core/handlers/
    on Aug 26, 2026
  4. linked a pull request that will close this issuerelease(v3.14.0): post-3.13.0 hardening #82on Aug 26, 2026
  5. rennf93 commented on Aug 26, 2026

    @rennf93
    Member

    Shipped in guard-core 3.14.0 (PyPI), merged in #82 (500d4ef).

    A failed GeoIP refresh or download keeps the last known good database and reader; downloads go to a temporary file that is swapped in atomically, and only a database that fails to open is discarded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: handlersTouches guard_core/handlers/bugConfirmed defectsecuritySecurity-related issue (use private reporting first!)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions