def map(self, record_json): if not record_json: return None # Handle a 'Vulnerability' wrapper around the specific record. If not present, assume a direct record if len(record_json.keys()) == 1 and record_json.get('Vulnerability'): vuln = record_json['Vulnerability'] else: vuln = record_json db_rec = Vulnerability() db_rec.id = vuln['Name'] db_rec.namespace_name = self.group db_rec.severity = vuln.get('Severity', 'Unknown') db_rec.link = vuln.get('Link') description = vuln.get("Description", "") if description: db_rec.description = vuln.get('Description', '') if len( vuln.get('Description', '')) < self.MAX_STR_LEN else ( vuln.get('Description')[:self.MAX_STR_LEN - 8] + '...') else: db_rec.description = "" db_rec.fixed_in = [] db_rec.vulnerable_in = [] db_rec.metadata_json = json.dumps( vuln.get('Metadata')) if 'Metadata' in vuln else None cvss_data = vuln.get('Metadata', {}).get('NVD', {}).get('CVSSv2') if cvss_data: db_rec.cvss2_vectors = cvss_data.get('Vectors') db_rec.cvss2_score = cvss_data.get('Score') # Process Fixes if 'FixedIn' in vuln: for f in vuln['FixedIn']: fix = FixedArtifact() fix.name = f['Name'] fix.version = f['Version'] fix.version_format = f['VersionFormat'] fix.epochless_version = re.sub(r'^[0-9]*:', '', f['Version']) fix.vulnerability_id = db_rec.id fix.namespace_name = self.group db_rec.fixed_in.append(fix) if 'VulnerableIn' in vuln: for v in vuln['VulnerableIn']: v_in = VulnerableArtifact() v_in.name = v['Name'] v_in.version = v['Version'] v_in.version_format = v['VersionFormat'] v_in.epochless_version = re.sub(r'^[0-9]*:', '', v['Version']) v_in.vulnerability_id = db_rec.id v_in.namespace_name = self.group db_rec.vulnerable_in.append(v_in) return db_rec
def map(self, record_json): advisory = record_json["Advisory"] db_rec = Vulnerability() db_rec.id = advisory["ghsaId"] db_rec.name = advisory["ghsaId"] db_rec.namespace_name = advisory["namespace"] db_rec.description = advisory["Summary"] db_rec.severity = advisory.get("Severity", "Unknown") or "Unknown" db_rec.link = advisory["url"] db_rec.metadata_json = advisory["Metadata"] references = [ "https://nvd.nist.gov/vuln/detail/{}".format(i) for i in advisory["CVE"] ] db_rec.references = references # Set the `FixedArtifact` to an empty list so that a cascade deletion # gets rid of the associated fixes. If the advisory has been withdrawn, # this field will a string with a date. if advisory["withdrawn"] is not None: db_rec.fixed_in = [] return db_rec for f in advisory["FixedIn"]: fix = FixedArtifact() fix.name = f["name"] # this is an unfortunate lie, 'version' has to be a range in order # to be processed correctly. If there is a real fix version, it # will be set in the `fix_metadata`. fix.version = f.get("range", "None") fix.version_format = "semver" fix.vulnerability_id = db_rec.id fix.namespace_name = f["namespace"] fix.vendor_no_advisory = False # the advisory summary is the same as db_rec.description, do we need to do this again? fix.fix_metadata = {"first_patched_version": f["identifier"]} db_rec.fixed_in.append(fix) return db_rec
def map(self, record_json): advisory = record_json['Advisory'] db_rec = Vulnerability() db_rec.id = advisory['ghsaId'] db_rec.name = advisory['ghsaId'] db_rec.namespace_name = advisory['namespace'] db_rec.description = advisory['Summary'] db_rec.severity = advisory.get('Severity', 'Unknown') or 'Unknown' db_rec.link = advisory['url'] db_rec.metadata_json = advisory['Metadata'] references = [ "https://nvd.nist.gov/vuln/detail/{}".format(i) for i in advisory['CVE'] ] db_rec.references = references # Set the `FixedArtifact` to an empty list so that a cascade deletion # gets rid of the associated fixes. If the advisory has been withdrawn, # this field will a string with a date. if advisory['withdrawn'] is not None: db_rec.fixed_in = [] return db_rec for f in advisory['FixedIn']: fix = FixedArtifact() fix.name = f['name'] # this is an unfortunate lie, 'version' has to be a range in order # to be processed correctly. If there is a real fix version, it # will be set in the `fix_metadata`. fix.version = f.get('range', 'None') fix.version_format = 'semver' fix.vulnerability_id = db_rec.id fix.namespace_name = f['namespace'] fix.vendor_no_advisory = False # the advisory summary is the same as db_rec.description, do we need to do this again? fix.fix_metadata = {'first_patched_version': f['identifier']} db_rec.fixed_in.append(fix) return db_rec
test_package.size = 1000 test_package.origin = 'upstream' test_package.arch = 'x86_64' test_package.image = test_image test_cve = Vulnerability(id='CVE123', namespace_name='centos:7') test_cve.severity = 'High' test_cve.description = 'some test cve' test_cve.cvss2_score = '1.0' test_cve.metadata_json = {} test_cve.cvss2_vectors = '' test_cve.link = 'http://mitre.com/cve123' test_fixedin = FixedArtifact(vulnerability_id=test_cve.id) test_fixedin.name = 'testpackage' test_fixedin.version = '1.1' test_fixedin.version_format = 'rpm' test_fixedin.epochless_version = '1.1' test_fixedin.include_later_versions = True test_fixedin.parent = test_cve test_cve.fixed_in = [test_fixedin] test_vulnin = VulnerableArtifact(vulnerability_id=test_cve.id) test_vulnin.name = 'testpackage' test_vulnin.version = '0.9' test_vulnin.epochless_version = '0.9' test_vulnin.namespace_name = 'centos:7' test_vulnin.version_format = 'rpm' test_vulnin.include_previous_versions = False test_vulnin.parent = test_cve test_cve.vulnerable_in = [test_vulnin]
def map(self, record_json): if not record_json: return None # Handle a 'Vulnerability' wrapper around the specific record. If not present, assume a direct record if len(list( record_json.keys())) == 1 and record_json.get("Vulnerability"): vuln = record_json["Vulnerability"] else: vuln = record_json db_rec = Vulnerability() db_rec.id = vuln["Name"] db_rec.namespace_name = self.group db_rec.severity = vuln.get("Severity", "Unknown") db_rec.link = vuln.get("Link") description = vuln.get("Description", "") if description: db_rec.description = ( vuln.get("Description", "") if len(vuln.get("Description", "")) < self.MAX_STR_LEN else (vuln.get("Description")[:self.MAX_STR_LEN - 8] + "...")) else: db_rec.description = "" db_rec.fixed_in = [] # db_rec.vulnerable_in = [] # db_rec.metadata_json = json.dumps(vuln.get('Metadata')) if 'Metadata' in vuln else None db_rec.additional_metadata = vuln.get("Metadata", {}) cvss_data = vuln.get("Metadata", {}).get("NVD", {}).get("CVSSv2") if cvss_data: db_rec.cvss2_vectors = cvss_data.get("Vectors") db_rec.cvss2_score = cvss_data.get("Score") # Process Fixes if "FixedIn" in vuln: for f in vuln["FixedIn"]: fix = FixedArtifact() fix.name = f["Name"] fix.version = f["Version"] fix.version_format = f["VersionFormat"] fix.epochless_version = re.sub(r"^[0-9]*:", "", f["Version"]) fix.vulnerability_id = db_rec.id fix.namespace_name = self.group fix.vendor_no_advisory = f.get("VendorAdvisory", {}).get("NoAdvisory", False) fix.fix_metadata = ({ "VendorAdvisorySummary": f["VendorAdvisory"]["AdvisorySummary"] } if f.get("VendorAdvisory", {}).get("AdvisorySummary", []) else None) db_rec.fixed_in.append(fix) # if 'VulnerableIn' in vuln: # for v in vuln['VulnerableIn']: # v_in = VulnerableArtifact() # v_in.name = v['Name'] # v_in.version = v['Version'] # v_in.version_format = v['VersionFormat'] # v_in.epochless_version = re.sub(r'^[0-9]*:', '', v['Version']) # v_in.vulnerability_id = db_rec.id # v_in.namespace_name = self.group # # db_rec.vulnerable_in.append(v_in) return db_rec
def test_cve_updates(test_data_env): test_env = test_data_env test_env.init_feeds() test_user_id = 'test1' test_img_id = 'img1' test_image = Image(user_id=test_user_id, id=test_img_id, distro_name='centos', distro_version='7') test_image.familytree_json = [test_img_id] test_image.layers_json = [test_img_id] test_image.layer_info_json = ['somelayer_here'] test_image.like_distro = 'centos' test_image.state = 'analyzed' test_image.digest = 'digest1' test_image.anchore_type = 'undefined' test_image.dockerfile_mode = 'Guessed' test_image.docker_history_json = ['line1', 'line2'] test_image.docker_data_json = {'Config': {}, 'ContainerConfig': {}} test_image.dockerfile_contents = 'FROM BLAH' test_package = ImagePackage(image_user_id=test_user_id, image_id=test_img_id, name='testpackage', version='1.0', pkg_type='RPM') test_package.src_pkg = 'testpackage' test_package.distro_name = 'centos' test_package.distro_version = '7' test_package.like_distro = 'centos' test_package.license = 'apache2' test_package.fullversion = '1.0' test_package.normalized_src_pkg = '1.0' test_package.release = '' test_package.size = 1000 test_package.origin = 'upstream' test_package.arch = 'x86_64' test_package.image = test_image test_cve = Vulnerability(id='CVE123', namespace_name='centos:7') test_cve.severity = 'High' test_cve.description = 'some test cve' test_cve.cvss2_score = '1.0' test_cve.metadata_json = {} test_cve.cvss2_vectors = '' test_cve.link = 'http://mitre.com/cve123' test_fixedin = FixedArtifact(vulnerability_id=test_cve.id) test_fixedin.name = 'testpackage' test_fixedin.version = '1.1' test_fixedin.version_format = 'rpm' test_fixedin.epochless_version = '1.1' test_fixedin.include_later_versions = True test_fixedin.parent = test_cve test_cve.fixed_in = [test_fixedin] test_vulnin = VulnerableArtifact(vulnerability_id=test_cve.id) test_vulnin.name = 'testpackage' test_vulnin.version = '0.9' test_vulnin.epochless_version = '0.9' test_vulnin.namespace_name = 'centos:7' test_vulnin.version_format = 'rpm' test_vulnin.include_previous_versions = False test_vulnin.parent = test_cve test_cve.vulnerable_in = [test_vulnin] db = get_session() try: db.add(test_image) db.add(test_package) db.commit() except sqlalchemy.exc.IntegrityError: db.rollback() except Exception: logger.exception('Unexpected failure') raise db = get_session() try: db.add(test_cve) feeds.process_updated_vulnerability(db, test_cve) db.commit() except sqlalchemy.exc.IntegrityError: logger.exception('Failed!') db.rollback() finally: db = get_session() i = db.query(Image).get((test_img_id, test_user_id)) print(('Vulns: {}'.format(i.vulnerabilities()))) db.commit() test_cve2 = Vulnerability(id='CVE123', namespace_name='centos:7') test_cve2.severity = 'Medium' test_cve2.description = 'some test cve' test_cve2.cvss2_score = '1.0' test_cve2.metadata_json = {} test_cve2.cvss2_vectors = '' test_cve2.link = 'http://mitre.com/cve123' fix2 = FixedArtifact(name='pkg2', version='1.2', epochless_version='1.2') fix2.namespace_name = 'centos:7' fix2.vulnerability_id = test_cve2.id test_cve2.fixed_in = [fix2] db = get_session() try: t2 = db.merge(test_cve2) db.add(t2) feeds.process_updated_vulnerability(db, t2) db.commit() except sqlalchemy.exc.IntegrityError: logger.exception('Failed!') db.rollback() finally: db = get_session() i = db.query(Image).get((test_img_id, test_user_id)) print(('Vulns: {}'.format(i.vulnerabilities()))) db.commit()
def test_github_advisory_fixed_in(test_data_env): test_env = test_data_env test_env.init_feeds() test_user_id = 'test1' test_img_id = 'img1' test_image = Image( user_id=test_user_id, id=test_img_id, distro_name='centos', distro_version='7' ) test_image.familytree_json = [test_img_id] test_image.layers_json = [test_img_id] test_image.layer_info_json = ['somelayer_here'] test_image.like_distro = 'centos' test_image.state = 'analyzed' test_image.digest = 'digest1' test_image.anchore_type = 'undefined' test_image.dockerfile_mode = 'Guessed' test_image.docker_history_json = ['line1', 'line2'] test_image.docker_data_json = {'Config': {}, 'ContainerConfig': {}} test_image.dockerfile_contents = 'FROM BLAH' test_package = ImagePackage( image_user_id=test_user_id, image_id=test_img_id, name='testpackage', version='1.0', pkg_type='python' ) test_package.src_pkg = 'testpackage' test_package.distro_name = 'centos' test_package.distro_version = '7' test_package.like_distro = 'centos' test_package.license = 'apache2' test_package.fullversion = '1.0' test_package.normalized_src_pkg = '1.0' test_package.release = '' test_package.size = 1000 test_package.origin = 'upstream' test_package.arch = 'x86_64' test_package.image = test_image test_cve = Vulnerability(id='GHSA-rpch-cqj9-h65r', namespace_name='github:python') test_cve.severity = 'High' test_cve.description = 'some advisory ghsa' test_cve.link = 'http://mitre.com/cve123' test_fixedin = FixedArtifact(vulnerability_id=test_cve.id) test_fixedin.name = 'testpackage' test_fixedin.version = 'None' test_fixedin.fix_metadata = {'first_patched_version': '1.2'} test_fixedin.version_format = 'semver' test_fixedin.parent = test_cve test_cve.fixed_in = [test_fixedin] db = get_session() try: db.add(test_image) db.add(test_package) db.commit() except sqlalchemy.exc.IntegrityError: db.rollback() except Exception: logger.exception('Unexpected failure') raise db = get_session() # XXX This needs to be a fixture try: db.add(test_cve) feeds.process_updated_vulnerability(db, test_cve) db.commit() except sqlalchemy.exc.IntegrityError: logger.exception('Failed!') db.rollback() db = get_session() image_vuln = db.query(Image).get((test_img_id, test_user_id)) # should be one vulnerability vulnerabilities = image_vuln.vulnerabilities() assert len(vulnerabilities) == 1 img_pkg_vuln = vulnerabilities[0] assert img_pkg_vuln.fixed_in() == '1.2'
def test_FixedArtifact_fix_observed_at_behavior(anchore_db): try: with session_scope() as session: testV = Vulnerability() testV.id = "FA_TEST-1234" testV.namespace_name = "fa_testnamespace:1" testV.severity = "Unknown" # this indicates a fix available testFA_wfix = FixedArtifact() testFA_wfix.name = "fa_testpkg1" testFA_wfix.version = "1.0" testV.fixed_in.append(testFA_wfix) # this indicates a vulnerable package (no fix available) testFA_wofix = FixedArtifact() testFA_wofix.name = "fa_testpkg2" testFA_wofix.version = "None" testV.fixed_in.append(testFA_wofix) session.add(testV) timestamps = {} logger.info("TESTING: state after initial insert") with session_scope() as session: records = ( session.query(FixedArtifact) .filter(FixedArtifact.vulnerability_id == "FA_TEST-1234") .all() ) for record in records: p = record.name logger.info( "FOUND: record {} - version {} - fix_observed_at - {}".format( record.vulnerability_id, record.version, record.fix_observed_at ) ) if record.version == "None" and record.fix_observed_at is not None: logger.info( "FAIL: record shows no fix version, but fix_observed_at set" ) assert False elif ( record.version is not None and record.version != "None" and record.fix_observed_at is None ): logger.info( "FAIL: record shows fix version, but fix_observed_at is null" ) assert False else: logger.info( "SUCCESS: record {} is correct at this phase ({} - {})".format( p, record.version, record.fix_observed_at ) ) timestamps[record.name] = record.fix_observed_at logger.info("TESTING: state after update of elements unrelated to fix version") with session_scope() as session: records = ( session.query(FixedArtifact) .filter(FixedArtifact.vulnerability_id == "FA_TEST-1234") .all() ) for record in records: record.version_format = "testformat" session.add(record) with session_scope() as session: records = ( session.query(FixedArtifact) .filter(FixedArtifact.vulnerability_id == "FA_TEST-1234") .all() ) for record in records: p = record.name logger.info( "FOUND: record {} - version {} - fix_observed_at - {}".format( record.vulnerability_id, record.version, record.fix_observed_at ) ) if record.version == "None" and record.fix_observed_at is not None: logger.info( "FAIL: record shows no fix version, but fix_observed_at set" ) assert False elif ( record.version is not None and record.version != "None" and record.fix_observed_at is None ): logger.info( "FAIL: record shows fix version, but fix_observed_at is null" ) assert False else: logger.info( "SUCCESS: record {} is correct at this phase ({} - {})".format( p, record.version, record.fix_observed_at ) ) logger.info("TESTING: state after update of elements related to fix version") with session_scope() as session: records = ( session.query(FixedArtifact) .filter(FixedArtifact.vulnerability_id == "FA_TEST-1234") .all() ) for record in records: record.version = "1.0" session.add(record) record = ( session.query(FixedArtifact) .filter( FixedArtifact.vulnerability_id == "FA_TEST-1234", FixedArtifact.name == "fa_testpkg2", ) .one() ) timestamps["fa_testpkg2"] = record.fix_observed_at with session_scope() as session: for p in ["fa_testpkg1", "fa_testpkg2"]: record = ( session.query(FixedArtifact) .filter( FixedArtifact.vulnerability_id == "FA_TEST-1234", FixedArtifact.name == p, ) .one() ) if record.fix_observed_at != timestamps[p]: logger.info( "FAIL: {} timestamp in DB is not equal to original set ({} != {})".format( p, record.fix_observed_at, timestamps[p] ) ) assert False else: logger.info( "SUCCESS: record {} is correct at this phase ({} - {})".format( p, record.version, record.fix_observed_at ) ) logger.info( "TESTING: state after further update of elements related to fix version" ) with session_scope() as session: records = ( session.query(FixedArtifact) .filter(FixedArtifact.vulnerability_id == "FA_TEST-1234") .all() ) for record in records: record.version = "2.0" session.add(record) with session_scope() as session: for p in ["fa_testpkg1", "fa_testpkg2"]: record = ( session.query(FixedArtifact) .filter( FixedArtifact.vulnerability_id == "FA_TEST-1234", FixedArtifact.name == p, ) .one() ) if record.fix_observed_at != timestamps[p]: logger.info( "FAIL: {} timestamp in DB is not equal to original set ({} != {})".format( p, record.fix_observed_at, timestamps[p] ) ) assert False else: logger.info( "SUCCESS: record {} is correct at this phase ({} - {})".format( p, record.version, record.fix_observed_at ) ) except Exception as err: logger.error("FAIL: exception - {}".format(err)) raise (err) finally: tearDown()
def test_cve_updates(test_data_env): test_env = test_data_env test_env.init_feeds() test_user_id = "test1" test_img_id = "img1" test_image = Image(user_id=test_user_id, id=test_img_id, distro_name="centos", distro_version="7") test_image.familytree_json = [test_img_id] test_image.layers_json = [test_img_id] test_image.layer_info_json = ["somelayer_here"] test_image.like_distro = "centos" test_image.state = "analyzed" test_image.digest = "digest1" test_image.anchore_type = "undefined" test_image.dockerfile_mode = "Guessed" test_image.docker_history_json = ["line1", "line2"] test_image.docker_data_json = {"Config": {}, "ContainerConfig": {}} test_image.dockerfile_contents = "FROM BLAH" test_package = ImagePackage( image_user_id=test_user_id, image_id=test_img_id, name="testpackage", version="1.0", pkg_type="RPM", ) test_package.src_pkg = "testpackage" test_package.distro_name = "centos" test_package.distro_version = "7" test_package.like_distro = "centos" test_package.license = "apache2" test_package.fullversion = "1.0" test_package.normalized_src_pkg = "1.0" test_package.release = "" test_package.size = 1000 test_package.origin = "upstream" test_package.arch = "x86_64" test_package.image = test_image test_cve = Vulnerability(id="CVE123", namespace_name="centos:7") test_cve.severity = "High" test_cve.description = "some test cve" test_cve.cvss2_score = "1.0" test_cve.metadata_json = {} test_cve.cvss2_vectors = "" test_cve.link = "http://mitre.com/cve123" test_fixedin = FixedArtifact(vulnerability_id=test_cve.id) test_fixedin.name = "testpackage" test_fixedin.version = "1.1" test_fixedin.version_format = "rpm" test_fixedin.epochless_version = "1.1" test_fixedin.include_later_versions = True test_fixedin.parent = test_cve test_cve.fixed_in = [test_fixedin] test_vulnin = VulnerableArtifact(vulnerability_id=test_cve.id) test_vulnin.name = "testpackage" test_vulnin.version = "0.9" test_vulnin.epochless_version = "0.9" test_vulnin.namespace_name = "centos:7" test_vulnin.version_format = "rpm" test_vulnin.include_previous_versions = False test_vulnin.parent = test_cve test_cve.vulnerable_in = [test_vulnin] db = get_session() try: db.add(test_image) db.add(test_package) db.commit() except sqlalchemy.exc.IntegrityError: db.rollback() except Exception: logger.exception("Unexpected failure") raise db = get_session() try: db.add(test_cve) feeds.process_updated_vulnerability(db, test_cve) db.commit() except sqlalchemy.exc.IntegrityError: logger.exception("Failed!") db.rollback() finally: db = get_session() i = db.query(Image).get((test_img_id, test_user_id)) print(("Vulns: {}".format(i.vulnerabilities()))) db.commit() test_cve2 = Vulnerability(id="CVE123", namespace_name="centos:7") test_cve2.severity = "Medium" test_cve2.description = "some test cve" test_cve2.cvss2_score = "1.0" test_cve2.metadata_json = {} test_cve2.cvss2_vectors = "" test_cve2.link = "http://mitre.com/cve123" fix2 = FixedArtifact(name="pkg2", version="1.2", epochless_version="1.2") fix2.namespace_name = "centos:7" fix2.vulnerability_id = test_cve2.id test_cve2.fixed_in = [fix2] db = get_session() try: t2 = db.merge(test_cve2) db.add(t2) feeds.process_updated_vulnerability(db, t2) db.commit() except sqlalchemy.exc.IntegrityError: logger.exception("Failed!") db.rollback() finally: db = get_session() i = db.query(Image).get((test_img_id, test_user_id)) print(("Vulns: {}".format(i.vulnerabilities()))) db.commit()
def test_github_advisory_fixed_in(test_data_env): test_env = test_data_env test_env.init_feeds() test_user_id = "test1" test_img_id = "img1" test_image = Image(user_id=test_user_id, id=test_img_id, distro_name="centos", distro_version="7") test_image.familytree_json = [test_img_id] test_image.layers_json = [test_img_id] test_image.layer_info_json = ["somelayer_here"] test_image.like_distro = "centos" test_image.state = "analyzed" test_image.digest = "digest1" test_image.anchore_type = "undefined" test_image.dockerfile_mode = "Guessed" test_image.docker_history_json = ["line1", "line2"] test_image.docker_data_json = {"Config": {}, "ContainerConfig": {}} test_image.dockerfile_contents = "FROM BLAH" test_package = ImagePackage( image_user_id=test_user_id, image_id=test_img_id, name="testpackage", version="1.0", pkg_type="python", ) test_package.src_pkg = "testpackage" test_package.distro_name = "centos" test_package.distro_version = "7" test_package.like_distro = "centos" test_package.license = "apache2" test_package.fullversion = "1.0" test_package.normalized_src_pkg = "1.0" test_package.release = "" test_package.size = 1000 test_package.origin = "upstream" test_package.arch = "x86_64" test_package.image = test_image test_cve = Vulnerability(id="GHSA-rpch-cqj9-h65r", namespace_name="github:python") test_cve.severity = "High" test_cve.description = "some advisory ghsa" test_cve.link = "http://mitre.com/cve123" test_fixedin = FixedArtifact(vulnerability_id=test_cve.id) test_fixedin.name = "testpackage" test_fixedin.version = "None" test_fixedin.fix_metadata = {"first_patched_version": "1.2"} test_fixedin.version_format = "semver" test_fixedin.parent = test_cve test_cve.fixed_in = [test_fixedin] db = get_session() try: db.add(test_image) db.add(test_package) db.commit() except sqlalchemy.exc.IntegrityError: db.rollback() except Exception: logger.exception("Unexpected failure") raise db = get_session() # XXX This needs to be a fixture try: db.add(test_cve) feeds.process_updated_vulnerability(db, test_cve) db.commit() except sqlalchemy.exc.IntegrityError: logger.exception("Failed!") db.rollback() db = get_session() image_vuln = db.query(Image).get((test_img_id, test_user_id)) # should be one vulnerability vulnerabilities = image_vuln.vulnerabilities() assert len(vulnerabilities) == 1 img_pkg_vuln = vulnerabilities[0] assert img_pkg_vuln.fixed_in() == "1.2"