[Git][security-tracker-team/security-tracker][master] 3 commits: security_db.py: support opening the database read-only

Emilio Pozuelo Monfort (@pochu) pochu at debian.org
Tue Aug 18 10:39:43 BST 2026



Emilio Pozuelo Monfort pushed to branch master at Debian Security Tracker / security-tracker


Commits:
85e765c4 by Helmut Grohne at 2026-08-12T18:22:22+02:00
security_db.py: support opening the database read-only

Most applications using the database know in advance whether they intend
to modify it. As a defensive mechanism, we can convey this intention and
make stuff fail when the intention is not met.

- - - - -
1a274edc by Helmut Grohne at 2026-08-12T18:23:26+02:00
tracker_service.py: open the security database read-only

The web service never modifies the database. All updates are performed
via other scripts. Convey this to the database class to defend against
unintended modification.

As a side effect, this slightly reduces the cost of creating the
connection, which may become relevant if we more frequently open
connections for scaling load. It also removes a possible race condition
if (read-only) connections were created concurrently.

Suggested-by: Enrico Zini <enrico at enricozini.org>

- - - - -
49e5fcb3 by Emilio Pozuelo Monfort at 2026-08-18T09:39:40+00:00
Merge branch 'helmutg/web-readonly' into 'master'

web service: open database connection read-only

See merge request security-tracker-team/security-tracker!320
- - - - -


2 changed files:

- bin/tracker_service.py
- lib/python/security_db.py


Changes:

=====================================
bin/tracker_service.py
=====================================
@@ -110,7 +110,7 @@ class TrackerService(WebServiceBase):
 
     def __init__(self, db_name):
         WebServiceBase.__init__(self)
-        self.db = security_db.DB(db_name)
+        self.db = security_db.DB(db_name, readonly=True)
 
         self.stable_releases = config.get_supported_releases()
         self.stable_releases.remove(config.get_release_codename('testing'))


=====================================
lib/python/security_db.py
=====================================
@@ -268,9 +268,12 @@ class DB:
     misnomer because these objects are quite versatile.
     """
 
-    def __init__(self, name, verbose=False):
+    def __init__(self, name, *, verbose=False, readonly=False):
         self.name = name
-        self.db = apsw.Connection(name)
+        flags = apsw.SQLITE_OPEN_READWRITE | apsw.SQLITE_OPEN_CREATE
+        if readonly:
+            flags = apsw.SQLITE_OPEN_READONLY
+        self.db = apsw.Connection(name, flags=flags)
         self.verbose = verbose
         c = self.cursor()
 
@@ -279,18 +282,26 @@ class DB:
 
         c.execute("PRAGMA page_size = 4096")
 
-        # Enable WAL.  This means that updates will not block readers.
-        c.execute("PRAGMA journal_mode = WAL")
+        if not readonly:
+            # Enable WAL.  This means that updates will not block readers.
+            c.execute("PRAGMA journal_mode = WAL")
 
         self.schema_version = 24
         self._initFunctions()
 
-        for (v,) in c.execute("PRAGMA user_version"):
-            if v == 0:
+        try:
+            user_version, = next(c.execute("PRAGMA user_version"))
+        except StopIteration:
+            raise RuntimeError("no user_version returned from sqlite")
+        if user_version != self.schema_version:
+            if readonly:
+                raise RuntimeError("DB: readonly open failed. expected schema version %d mismatches database schema version %d"
+                                   % (self.schema_version, user_version))
+            elif user_version == 0:
                 self.initSchema(c)
-            elif v == 20:
+            elif user_version == 20:
                 self._initSchema20(c)
-            elif v == 21:
+            elif user_version == 21:
                 # Remove legacy views.
                 for view in ('testing_status', 'stable_status',
                              'oldstable_status'):
@@ -299,18 +310,16 @@ class DB:
                     except apsw.SQLError:
                         pass
                 c.execute("PRAGMA user_version = 22")
-            elif v == 22:
+            elif user_version == 22:
                 self._initSchema22(c)
-            elif v == 23:
+            elif user_version == 23:
                 self._initSchema23(c)
-            elif v != self.schema_version:
+            else:
                 if self.verbose:
                     print("DB: schema version mismatch: expected %d, got %d"
-                          % (self.schema_version, v))
-                raise SchemaMismatch(repr(v))
-            self._initViews(c)
-            return
-        assert False
+                          % (self.schema_version, user_version))
+                raise SchemaMismatch(repr(user_version))
+        self._initViews(c)
 
     def __del__(self):
         self.db.close()
@@ -487,7 +496,6 @@ class DB:
     def _initSchema20(self, cursor):
         cursor.execute("PRAGMA user_version = 1")
         self._initNoDSA(cursor)
-        self._initViews(cursor)
         cursor.execute("DELETE FROM inodeprints WHERE file ='data/CVE/list'")
         cursor.execute("PRAGMA user_version = %d" % self.schema_version)
 



View it on GitLab: https://salsa.debian.org/security-tracker-team/security-tracker/-/compare/0f4fefb8bdf3bc880fcbb84fcbff129d58e2cec5...49e5fcb3d02a581e2f3df5948d7af1372f631f34

-- 
View it on GitLab: https://salsa.debian.org/security-tracker-team/security-tracker/-/compare/0f4fefb8bdf3bc880fcbb84fcbff129d58e2cec5...49e5fcb3d02a581e2f3df5948d7af1372f631f34
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-security-tracker-commits/attachments/20260818/77da613c/attachment-0001.htm>


More information about the debian-security-tracker-commits mailing list