[Git][security-tracker-team/security-tracker][master] 2 commits: security_db.py: track source_package_status.vulnerable as an enum

Emilio Pozuelo Monfort (@pochu) pochu at debian.org
Thu Aug 6 22:41:13 BST 2026



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


Commits:
4ef7958d by Helmut Grohne at 2026-08-06T18:35:56+02:00
security_db.py: track source_package_status.vulnerable as an enum

The vulnerable column of source_package_status can take three possible
values:
 * 0 (fixed)
 * 1 (affected)
 * 2 (undetermined)

We can encapsulate this as an IntEnum on the Python side. Then we can
use those names in place of magic numbers. Additionally, we may add a
constraint to the database column. Since the old schema is compatible,
we do not incur a new schema version.

Reported-by: Emilio Pozuelo Monfort <pochu at debian.org>

- - - - -
1e700f1c by Emilio Pozuelo Monfort at 2026-08-06T21:41:08+00:00
Merge branch 'helmutg/vulnerable-enum' into 'master'

security_db.py: track source_package_status.vulnerable as an enum

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


2 changed files:

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


Changes:

=====================================
bin/tracker_service.py
=====================================
@@ -73,7 +73,7 @@ class BugFilter:
                     urg in ('medium', 'medium**')
         filterhigh = not self.params['high_urgency'] and \
                     urg in ('high', 'high**')
-        filterund = not self.params['undetermined_issues'] and vuln == 2
+        filterund = not self.params['undetermined_issues'] and vuln == security_db.Vulnerable.UNDETERMINED
         filteruni = not self.params['unimportant_urgency'] \
                     and urg == 'unimportant'
         filteruna = not self.params['unassigned_urgency'] \
@@ -424,10 +424,10 @@ data source.""")],
                         package = compose(
                             self.make_source_package_ref(url, package),
                             " (", self.make_pts_ref(url, package, 'PTS'), ")")
-                    if vulnerable == 1:
+                    if vulnerable == security_db.Vulnerable.AFFECTED:
                         vuln = self.make_red('vulnerable')
                         version = self.make_red(version)
-                    elif vulnerable == 2:
+                    elif vulnerable == security_db.Vulnerable.UNDETERMINED:
                         vuln = self.make_purple('undetermined')
                         version = self.make_purple(version)
                     else:
@@ -598,7 +598,7 @@ to improve our documentation and procedures, so feedback is welcome.""")])])
         def format_summary_entry(per_release):
             if per_release is None:
                 return self.make_purple('unknown')
-            if per_release.vulnerable == 1:
+            if per_release.vulnerable == security_db.Vulnerable.AFFECTED:
                 if per_release.state == 'no-dsa':
                     if per_release.reason:
                         text = 'vulnerable (no DSA, %s)' % per_release.reason
@@ -609,9 +609,9 @@ to improve our documentation and procedures, so feedback is welcome.""")])])
                                                text=hint)
                 else:
                     return self.make_red('vulnerable')
-            if per_release.vulnerable == 2:
+            if per_release.vulnerable == security_db.Vulnerable.UNDETERMINED:
                 return self.make_purple('undetermined')
-            assert per_release.vulnerable == 0
+            assert per_release.vulnerable == security_db.Vulnerable.FIXED
             return self.make_green('fixed')
 
         def gen_summary(bugs):
@@ -704,7 +704,7 @@ to improve our documentation and procedures, so feedback is welcome.""")])])
 
                 if urgency.startswith('high'):
                     urgency = self.make_red(urgency)
-                elif vulnerable == 2:
+                elif vulnerable == security_db.Vulnerable.UNDETERMINED:
                     urgency = self.make_purple(urgency)
                 else:
                     if no_dsa:
@@ -765,7 +765,7 @@ to improve our documentation and procedures, so feedback is welcome.""")])])
 
                 if urgency.startswith('high'):
                     urgency = self.make_red(urgency)
-                elif vulnerable == 2:
+                elif vulnerable == security_db.Vulnerable.UNDETERMINED:
                     urgency = self.make_purple(urgency)
 
                 yield (self.make_source_package_ref(url, pkg_name, title), self.make_xref(url, bug_name),
@@ -792,9 +792,10 @@ to improve our documentation and procedures, so feedback is welcome.""")])])
                 (SELECT range_remote FROM nvd_data
                  WHERE cve_name = st.bug_name)
                 FROM source_package_status AS st, source_packages AS sp
-                WHERE st.vulnerable AND sp.rowid = st.package
+                WHERE st.vulnerable != ? AND sp.rowid = st.package
                 AND sp.release = ?  AND sp.subrelease = ''
-                ORDER BY sp.name, st.bug_name COLLATE version""", (rel,)):
+                ORDER BY sp.name, st.bug_name COLLATE version""",
+                (security_db.Vulnerable.FIXED, rel,)):
                 if bf.urgencyFiltered(urgency, vulnerable):
                     continue
                 if bf.remoteFiltered(remote):
@@ -818,7 +819,7 @@ to improve our documentation and procedures, so feedback is welcome.""")])])
 
                 if urgency.startswith('high'):
                     urgency = self.make_red(urgency)
-                elif vulnerable == 2:
+                elif vulnerable == security_db.Vulnerable.UNDETERMINED:
                     urgency = self.make_purple(urgency)
 
                 yield self.make_source_package_ref(url, pkg_name, title), self.make_xref(url, bug_name), urgency, remote
@@ -900,7 +901,7 @@ to improve our documentation and procedures, so feedback is welcome.""")])])
 
                 if urgency.startswith('high'):
                     urgency = self.make_red(urgency)
-                elif vulnerable == 2:
+                elif vulnerable == security_db.Vulnerable.UNDETERMINED:
                     urgency = self.make_purple(urgency)
 
                 if stable_later:
@@ -952,10 +953,11 @@ checker to find out why they have not entered testing yet."""),
                     """SELECT DISTINCT sp.name, st.bug_name, sp.release,
                     bugs.description
                     FROM source_package_status AS st, source_packages AS sp, bugs
-                    WHERE st.vulnerable == 2 AND sp.rowid = st.package
+                    WHERE st.vulnerable == ? AND sp.rowid = st.package
                     AND sp.release IN (""" + ",".join("?" * len(releases)) + """)
                     AND sp.subrelease = '' AND st.bug_name == bugs.name
-                    ORDER BY sp.name, st.bug_name COLLATE version""", releases):
+                    ORDER BY sp.name, st.bug_name COLLATE version""",
+                    (security_db.Vulnerable.UNDETERMINED, *releases)):
 
                 if old_bug == '':
                     old_bug = bug_name
@@ -996,11 +998,12 @@ checker to find out why they have not entered testing yet."""),
                     """SELECT DISTINCT sp.name, st.bug_name, sp.release,
                     bugs.description
                     FROM source_package_status AS st, source_packages AS sp, bugs
-                    WHERE st.vulnerable > 0 AND sp.rowid = st.package
+                    WHERE st.vulnerable != ? AND sp.rowid = st.package
                     AND sp.release IN (""" + ",".join("?" * len(releases)) +  """)
                     AND st.urgency == 'unimportant'
                     AND sp.subrelease = '' AND st.bug_name == bugs.name
-                    ORDER BY sp.name, st.bug_name COLLATE version""", releases):
+                    ORDER BY sp.name, st.bug_name COLLATE version""",
+                    (security_db.Vulnerable.FIXED, *releases)):
 
                 if old_bug == '':
                     old_bug = bug_name
@@ -1237,8 +1240,8 @@ issue (or a bug has been created and is not recorded in this database).
 In the second kind of names, there is a Debian bug for the issue, and the "''',
                CODE("000000"), '''"part of the name is replaced with the
 Debian bug number.'''),
-             make_table(gen(1),title=H2('With unfixed issues'), caption=("Bug", "Description")),
-             make_table(gen(0),title=H2('The rest'), caption=("Bug", "Description")),
+             make_table(gen(security_db.Vulnerable.AFFECTED),title=H2('With unfixed issues'), caption=("Bug", "Description")),
+             make_table(gen(security_db.Vulnerable.FIXED),title=H2('The rest'), caption=("Bug", "Description")),
             ])
 
     def page_data_pts(self, path, params, url):
@@ -1248,10 +1251,10 @@ Debian bug number.'''),
                 (SELECT package, bug, urgency FROM stable_status
                  UNION ALL SELECT DISTINCT sp.name, st.bug_name, st.urgency
                    FROM source_package_status AS st, source_packages AS sp
-                   WHERE st.vulnerable AND st.urgency <> 'unimportant'
+                   WHERE st.vulnerable != ? AND st.urgency <> 'unimportant'
                    AND sp.rowid = st.package AND sp.release = 'sid'
                    AND sp.subrelease = '') x WHERE urgency <> 'unimportant'
-                GROUP BY package ORDER BY package"""):
+                GROUP BY package ORDER BY package""", (security_db.Vulnerable.FIXED,)):
             data.append(pkg)
             data.append(':')
             data.append(str(bugs))


=====================================
lib/python/security_db.py
=====================================
@@ -31,6 +31,7 @@ import apsw
 import bugs
 from collections import defaultdict, namedtuple
 import email.utils
+import enum
 import functools
 import json
 import pickle
@@ -61,6 +62,15 @@ class InsertError(Exception):
     def __str__(self):
         return self.errors[0] + ' [more...]'
 
+
+class Vulnerable(enum.IntEnum):
+    """vulnerable column value for source_package_status table"""
+
+    FIXED = 0
+    AFFECTED = 1
+    UNDETERMINED = 2
+
+
 def mergeLists(a, b):
     """Merges two lists."""
     if isinstance(a, str):
@@ -190,7 +200,7 @@ def getBugsForSourcePackage(cursor, pkg):
 
             # Compute state.  Update state-seen flags for global state
             # determination.
-            if best_row.vulnerable:
+            if best_row.vulnerable != Vulnerable.FIXED:
                 if best_row.urgency == 'unimportant':
                     state = 'unimportant'
                     unimportant_seen = True
@@ -207,7 +217,7 @@ def getBugsForSourcePackage(cursor, pkg):
 
             bug = BugForSourcePackageRelease(
                 best_row.release, best_row.subrelease, best_row.version,
-                best_row.vulnerable, state, comment, reason)
+                Vulnerable(best_row.vulnerable), state, comment, reason)
             releases[best_row.release] = bug
 
         # Compute global_state.
@@ -442,7 +452,7 @@ class DB:
         cursor.execute("""CREATE TABLE source_package_status
         (bug_name TEXT NOT NULL,
          package INTEGER NOT NULL REFERENCES source_packages(rowid),
-         vulnerable INTEGER NOT NULL,
+         vulnerable INTEGER NOT NULL CHECK (vulnerable IN (0, 1, 2)),
          urgency TEXT NOT NULL,
          PRIMARY KEY (bug_name, package))""")
         cursor.execute(
@@ -1432,12 +1442,12 @@ class DB:
             ORDER BY sp.name""",
             (bug_name,)):
             have_something = True
-            if vulnerable == 1:
+            if vulnerable == Vulnerable.AFFECTED:
                 if urgency == 'unimportant':
                     unimportant_packages.append( package )
                 else:
                     vulnerable_packages.append(package)
-            elif vulnerable == 2:
+            elif vulnerable == Vulnerable.UNDETERMINED:
                 undetermined_packages.append(package)
 
         if vulnerable_packages or undetermined_packages:
@@ -1485,7 +1495,7 @@ class DB:
         # here.
 
         status = {'' : {}, 'security' : {}, 'lts' : {}}
-        for (package, note, subrelease, vulnerable, urgency) in cursor.execute(
+        for (package, note, subrelease, vulnerable_int, urgency) in cursor.execute(
             """SELECT DISTINCT sp.name, n.id, sp.subrelease,
             st.vulnerable, n.urgency
             FROM source_package_status AS st,
@@ -1495,7 +1505,7 @@ class DB:
             AND n.bug_name = st.bug_name AND n.package = sp.name
             ORDER BY sp.name""",
             (bug_name, nickname)):
-            status[subrelease][(package, note)] = (vulnerable,urgency)
+            status[subrelease][(package, note)] = (Vulnerable(vulnerable_int), urgency)
 
         # Check if any packages in plain testing are vulnerable, and
         # if all of those have been fixed in the security archive.
@@ -1504,7 +1514,7 @@ class DB:
         undet_pkgs = {}
         unimp_pkgs = {}
         for ((package, note), (vulnerable, urgency)) in status[''].items():
-            if vulnerable == 1:
+            if vulnerable == Vulnerable.AFFECTED:
                 if urgency == 'unimportant':
                     unimp_pkgs[package] = True
                 else:
@@ -1513,7 +1523,7 @@ class DB:
                     fixed_in_security = False
                 elif status['lts'].get((package, note), True):
                     fixed_in_security = False
-            elif vulnerable == 2:
+            elif vulnerable == Vulnerable.UNDETERMINED:
                 undet_pkgs[package] = True
 
         unfixed_pkgs = list(unfixed_pkgs.keys())
@@ -1641,8 +1651,7 @@ class DB:
             if fix_available_sql:
                 for (v,) in c.execute(fix_available_sql,
                                       (package, release, name)):
-                    assert v is not None
-                    if not v:
+                    if Vulnerable(v) == Vulnerable.FIXED:
                         fix_available = 'F'
                     break
 
@@ -1907,7 +1916,7 @@ class DB:
         nodsa_reason = defaultdict(lambda: defaultdict(dict))
         next_point_update = defaultdict(lambda: defaultdict(set))
         supported_releases = config.get_supported_releases()
-        for (pkg, issue, desc, debianbug, release, subrelease, db_version, db_fixed_version, db_status, db_urgency, db_remote, db_nodsa, db_nodsa_reason, db_next_point_update) in self.cursor().execute(
+        for (pkg, issue, desc, debianbug, release, subrelease, db_version, db_fixed_version, db_vulnerable_int, db_urgency, db_remote, db_nodsa, db_nodsa_reason, db_next_point_update) in self.cursor().execute(
                 """SELECT sp.name, st.bug_name,
                 (SELECT cve_desc FROM nvd_data
                 WHERE cve_name = st.bug_name),
@@ -1937,9 +1946,10 @@ class DB:
                 AND sp.release IN (""" + ",".join("?" * len(supported_releases)) + """)
                 ORDER BY sp.name, st.bug_name, sp.release, sp.subrelease""" , supported_releases):
 
+            db_vulnerable = Vulnerable(db_vulnerable_int)
             ### to ease debugging...:
             #if issue in ('CVE-2012-6656','CVE-2014-8738','CVE-2013-6673') :
-            #    print pkg, issue, release, subrelease, db_version, db_fixed_version, db_status
+            #    print pkg, issue, release, subrelease, db_version, db_fixed_version, db_vulnerable
             if pkg not in packages:
                 packages.append(pkg)
             if issue not in issues[pkg]:
@@ -1958,7 +1968,7 @@ class DB:
                 repositories[pkg][issue].append(repository)
             version[pkg][issue][repository] = db_version
             fixed_version[pkg][issue][repository] = db_fixed_version
-            status[pkg][issue][repository] = db_status
+            status[pkg][issue][repository] = db_vulnerable
             urgency[pkg][issue][repository] = db_urgency
             if db_next_point_update:
                 next_point_update[pkg][issue].add(db_next_point_update)
@@ -2006,11 +2016,10 @@ class DB:
                                 # keep looking for a real winner...
                                 winner=suffix
                     repository=release+winner
-                    if status[pkg][issue][repository] == 0:
-                        # 1 = vulnerable, 2 = undetermined
+                    if status[pkg][issue][repository] == Vulnerable.FIXED:
                         state = "resolved"
                         suite_fixed_version = fixed_version[pkg][issue][repository]
-                    elif status[pkg][issue][repository] == 2:
+                    elif status[pkg][issue][repository] == Vulnerable.UNDETERMINED:
                         state = "undetermined"
                     else:
                         state = "open"
@@ -2130,7 +2139,7 @@ class DB:
         releases = config.get_supported_releases()
         values = [bug] + releases
 
-        for (package, releases, version, vulnerable) in cursor.execute(
+        for (package, releases, version, vulnerable_int) in cursor.execute(
             """SELECT package, string_list(release), version, vulnerable
             FROM (SELECT p.name AS package,
             release_name(p.release, p.subrelease, p.archive) AS release,
@@ -2141,7 +2150,7 @@ class DB:
             GROUP BY package, version, vulnerable
             ORDER BY package, releasepart_to_number(release), subreleasepart_to_number(release), version COLLATE version""",
             values):
-            yield package, releases.split(', '), version, vulnerable
+            yield package, releases.split(', '), version, Vulnerable(vulnerable_int)
 
     def getBugsFromDebianBug(self, cursor, number):
         """A generator which returns a list of tuples
@@ -2284,7 +2293,7 @@ class DB:
         if old_package:
             yield (old_package, bugs)
 
-    def getFakeBugs(self, cursor=None, vulnerability=0):
+    def getFakeBugs(self, cursor=None, vulnerability=Vulnerable.FIXED):
         """Returns a list of pairs (BUG-NAME, DESCRIPTION)."""
 
         if cursor is None:



View it on GitLab: https://salsa.debian.org/security-tracker-team/security-tracker/-/compare/e9427f9551b1b1aef927bd738e651b4b79a4f695...1e700f1cd4c21c02e96949f4a422dac12c4c8b6f

-- 
View it on GitLab: https://salsa.debian.org/security-tracker-team/security-tracker/-/compare/e9427f9551b1b1aef927bd738e651b4b79a4f695...1e700f1cd4c21c02e96949f4a422dac12c4c8b6f
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/20260806/a675af3e/attachment-0001.htm>


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