diff --git a/external-import/cisa-known-exploited-vulnerabilities/README.md b/external-import/cisa-known-exploited-vulnerabilities/README.md index 425ee2e360a..17ba3d046ef 100644 --- a/external-import/cisa-known-exploited-vulnerabilities/README.md +++ b/external-import/cisa-known-exploited-vulnerabilities/README.md @@ -65,7 +65,7 @@ There are a number of configuration options, which are set either in `docker-com | Parameter | config.yml | Docker environment variable | Default | Mandatory | Description | |-------------------------|----------------------------|---------------------------------|-------------------------------------------------------------------------------|-----------|------------------------------------------------------------------------| | Catalog URL | cisa.catalog_url | `CISA_CATALOG_URL` | https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json | No | URL of the CISA KEV catalog JSON feed. | -| Create Infrastructures | cisa.create_infrastructures| `CISA_CREATE_INFRASTRUCTURES` | true | No | Create Infrastructure entities for affected products. | +| Create Infrastructures | cisa.create_infrastructures| `CISA_CREATE_INFRASTRUCTURES` | false | No | Also create Infrastructure entities for affected products. Disabled by default — KEV entries are software products and do not map to any valid STIX 2.1 `infrastructure-type-ov` value, so enabling this produces Infrastructure objects with an empty `infrastructure_types`. Left opt-in for legacy workflows. | | KEV Flag Only | cisa.kev_flag_only | `CISA_KEV_FLAG_ONLY` | false | No | When enabled, the connector only sets the `x_opencti_cisa_kev` flag on Vulnerability objects without modifying any other attribute (description, dates, markings) and without creating additional entities or relationships. See [KEV Flag Only Mode](#kev-flag-only-mode). | | TLP | cisa.tlp | `CISA_TLP` | TLP:CLEAR | No | TLP marking for imported data (`TLP:CLEAR`, `TLP:GREEN`, `TLP:AMBER`, `TLP:AMBER+STRICT`, `TLP:RED`). | | Interval (deprecated) | cisa.interval | `CISA_INTERVAL` | 7 | No | **[DEPRECATED]** Interval in days between runs. Use `CONNECTOR_DURATION_PERIOD` instead. | @@ -94,7 +94,7 @@ Configure the connector in `docker-compose.yml`: - CONNECTOR_LOG_LEVEL=info - CONNECTOR_DURATION_PERIOD=P1D - CISA_CATALOG_URL=https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json - - CISA_CREATE_INFRASTRUCTURES=true + - CISA_CREATE_INFRASTRUCTURES=false - CISA_KEV_FLAG_ONLY=false - CISA_TLP=TLP:CLEAR restart: always diff --git a/external-import/cisa-known-exploited-vulnerabilities/__metadata__/CONNECTOR_CONFIG_DOC.md b/external-import/cisa-known-exploited-vulnerabilities/__metadata__/CONNECTOR_CONFIG_DOC.md index 84a66d70e31..cdc1765a0b9 100644 --- a/external-import/cisa-known-exploited-vulnerabilities/__metadata__/CONNECTOR_CONFIG_DOC.md +++ b/external-import/cisa-known-exploited-vulnerabilities/__metadata__/CONNECTOR_CONFIG_DOC.md @@ -14,7 +14,7 @@ Below is an exhaustive enumeration of all configurable parameters available, eac | CONNECTOR_LOG_LEVEL | `string` | | `debug` `info` `warn` `warning` `error` | | `"error"` | Determines the verbosity of the logs. | | CONNECTOR_DURATION_PERIOD | `string` | | Format: [`duration`](https://json-schema.org/understanding-json-schema/reference/string#built-in-formats) | | `"P2D"` | Duration between two scheduled runs of the connector (ISO 8601 format). | | CISA_CATALOG_URL | `string` | | string | | `"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"` | The URL that hosts the KEV Catalog https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json. | -| CISA_CREATE_INFRASTRUCTURES | `boolean` | | boolean | | `true` | Allows you to create or not create an infrastructure in OpenCTI. | +| CISA_CREATE_INFRASTRUCTURES | `boolean` | | boolean | | `false` | Also emit a STIX Infrastructure SDO for each affected product, alongside the Software SCO. Disabled by default because CISA KEV entries (e.g. PHP, Joomla, Laravel) are software products and do not map cleanly to any value in the STIX 2.1 infrastructure-type-ov vocabulary; enabling this produces Infrastructure objects with an empty infrastructure_types field. Left opt-in for workflows that depend on the legacy behaviour. | | CISA_KEV_FLAG_ONLY | `boolean` | | boolean | | `false` | When enabled, the connector only sets the x_opencti_cisa_kev flag on Vulnerability objects without creating additional entities (vendors, software, infrastructures) or relationships. | | CISA_TLP | `string` | | `TLP:WHITE` `TLP:CLEAR` `TLP:GREEN` `TLP:AMBER` `TLP:AMBER+STRICT` `TLP:RED` | | `"TLP:CLEAR"` | Traffic Light Protocol (TLP) level to apply on objects imported into OpenCTI. Possible values: TLP:CLEAR, TLP:GREEN, TLP:AMBER, TLP:AMBER+STRICT, TLP:RED. | | CISA_INTERVAL | `integer` | | integer | ⛔️ | `7` | [DEPRECATED] Interval in days between two scheduled runs of the connector. | diff --git a/external-import/cisa-known-exploited-vulnerabilities/__metadata__/connector_config_schema.json b/external-import/cisa-known-exploited-vulnerabilities/__metadata__/connector_config_schema.json index b08abbc355f..9aca441fc52 100644 --- a/external-import/cisa-known-exploited-vulnerabilities/__metadata__/connector_config_schema.json +++ b/external-import/cisa-known-exploited-vulnerabilities/__metadata__/connector_config_schema.json @@ -58,8 +58,8 @@ "type": "string" }, "CISA_CREATE_INFRASTRUCTURES": { - "default": true, - "description": "Allows you to create or not create an infrastructure in OpenCTI.", + "default": false, + "description": "Also emit a STIX Infrastructure SDO for each affected product, alongside the Software SCO. Disabled by default because CISA KEV entries (e.g. PHP, Joomla, Laravel) are software products and do not map cleanly to any value in the STIX 2.1 infrastructure-type-ov vocabulary; enabling this produces Infrastructure objects with an empty infrastructure_types field. Left opt-in for workflows that depend on the legacy behaviour.", "type": "boolean" }, "CISA_KEV_FLAG_ONLY": { diff --git a/external-import/cisa-known-exploited-vulnerabilities/src/models/configs/cisakev_configs.py b/external-import/cisa-known-exploited-vulnerabilities/src/models/configs/cisakev_configs.py index 41356714d82..048383d1cf6 100644 --- a/external-import/cisa-known-exploited-vulnerabilities/src/models/configs/cisakev_configs.py +++ b/external-import/cisa-known-exploited-vulnerabilities/src/models/configs/cisakev_configs.py @@ -28,8 +28,16 @@ class _ConfigLoaderCISAKEV(ConfigBaseSettings): description="The URL that hosts the KEV Catalog https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json.", ) create_infrastructures: bool = Field( - default=True, - description="Allows you to create or not create an infrastructure in OpenCTI.", + default=False, + description=( + "Also emit a STIX Infrastructure SDO for each affected product, " + "alongside the Software SCO. Disabled by default because CISA KEV " + "entries (e.g. PHP, Joomla, Laravel) are software products and do " + "not map cleanly to any value in the STIX 2.1 infrastructure-type-ov " + "vocabulary; enabling this produces Infrastructure objects with an " + "empty infrastructure_types field. Left opt-in for workflows that " + "depend on the legacy behaviour." + ), ) kev_flag_only: bool = Field( default=False, diff --git a/external-import/cisa-known-exploited-vulnerabilities/tests/__init__.py b/external-import/cisa-known-exploited-vulnerabilities/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/external-import/cisa-known-exploited-vulnerabilities/tests/conftest.py b/external-import/cisa-known-exploited-vulnerabilities/tests/conftest.py new file mode 100644 index 00000000000..9f4c9c10901 --- /dev/null +++ b/external-import/cisa-known-exploited-vulnerabilities/tests/conftest.py @@ -0,0 +1,58 @@ +"""Test fixtures for the CISA KEV connector. + +Adds `src/` to sys.path so tests can `import main` and +`from models.configs.cisakev_configs import _ConfigLoaderCISAKEV` +without packaging the connector. Mirrors how the connector itself +is invoked at runtime (`python3 main.py` from `src/`). +""" + +import sys +from pathlib import Path + +import pytest + +SRC = Path(__file__).resolve().parent.parent / "src" +sys.path.insert(0, str(SRC)) + + +@pytest.fixture +def sample_kev_entry(): + """A representative single-product CISA KEV entry. + + Field shape mirrors the upstream catalog: + https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json + """ + return { + "cveID": "CVE-2024-12345", + "vendorProject": "Acme Corp", + "product": "Acme Widget Server", + "vulnerabilityName": "Acme Widget Server RCE", + "dateAdded": "2024-01-15", + "shortDescription": "Acme Widget Server contains a remote code execution vulnerability.", + "requiredAction": "Apply mitigations per vendor instructions.", + "dueDate": "2024-02-05", + "knownRansomwareCampaignUse": "Unknown", + "notes": "", + "cwes": ["CWE-78"], + } + + +@pytest.fixture +def kev_entry_software_product(): + """A KEV entry that is unambiguously a software product (not infrastructure). + + Real example from the CISA KEV catalog. + """ + return { + "cveID": "CVE-2017-9805", + "vendorProject": "Apache", + "product": "Struts", + "vulnerabilityName": "Apache Struts Deserialization Vulnerability", + "dateAdded": "2021-11-03", + "shortDescription": "Apache Struts contains an unsafe deserialization vulnerability.", + "requiredAction": "Apply updates per vendor instructions.", + "dueDate": "2022-05-03", + "knownRansomwareCampaignUse": "Known", + "notes": "", + "cwes": ["CWE-502"], + } diff --git a/external-import/cisa-known-exploited-vulnerabilities/tests/test_bundle.py b/external-import/cisa-known-exploited-vulnerabilities/tests/test_bundle.py new file mode 100644 index 00000000000..f7cc6ae616e --- /dev/null +++ b/external-import/cisa-known-exploited-vulnerabilities/tests/test_bundle.py @@ -0,0 +1,308 @@ +"""Tests for `Cisa.build_bundle` — the entity-emission heart of the connector. + +The previous default (`create_infrastructures=True`) produced an +Infrastructure SDO alongside the Software SCO for every CISA KEV entry, +with `infrastructure_types` left empty (no valid STIX 2.1 vocabulary +value fits a software product). These tests pin the new default +behaviour and the legacy opt-in. + +We bypass `Cisa.__init__` entirely (it tries to connect to OpenCTI) +and drive `build_bundle` as a bound method against a hand-built instance. +""" + +from types import SimpleNamespace +from typing import List + +import pytest +from main import Cisa + +# -------------------------------------------------------------------------- +# Helpers +# -------------------------------------------------------------------------- + + +def _make_connector(create_infrastructures: bool, kev_flag_only: bool = False) -> Cisa: + """Build a Cisa instance suitable for calling `build_bundle`. + + Bypasses `__init__` (which connects to OpenCTI) and stitches in + only the attributes that `build_bundle` reads. + """ + conn = object.__new__(Cisa) + + captured: List = [] + + def fake_create_bundle(objs): + # Capture the list so tests can inspect it. Real helper returns + # a serialised JSON string; we return the list directly because + # the tests need to introspect the STIX object types. + captured.extend(objs) + return objs + + conn.helper = SimpleNamespace( + log_info=lambda *a, **kw: None, + stix2_create_bundle=fake_create_bundle, + ) + conn._captured = captured # convenience handle for test inspection + + conn.cisa_create_infrastructures = create_infrastructures + conn.cisa_kev_flag_only = kev_flag_only + conn.org = "Cybersecurity and Infrastructure Security Agency" + + # set_created_by_stix is pure stix2/pycti — no API call. + conn.set_created_by_stix(org=conn.org) + + # set_tlp_marking would call self.helper.api.marking_definition.read. + # We short-circuit by setting the marking directly. + conn.tlp_marking = "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9" + + return conn + + +def _types(objs) -> List[str]: + """Return STIX `type` values for the bundle's objects.""" + return [getattr(o, "type", None) or o.get("type") for o in objs] + + +# -------------------------------------------------------------------------- +# The behaviour change +# -------------------------------------------------------------------------- + + +class TestCreateInfrastructuresFalse: + """Default path: emit Software SCO, do not emit Infrastructure SDO.""" + + def test_no_infrastructure_object_emitted(self, sample_kev_entry): + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(sample_kev_entry) + types = _types(objs) + assert "infrastructure" not in types, ( + f"Bundle must not contain an Infrastructure SDO when " + f"create_infrastructures=False. Got types: {types}" + ) + + def test_software_object_still_emitted(self, sample_kev_entry): + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(sample_kev_entry) + types = _types(objs) + assert ( + "software" in types + ), f"Bundle must still contain a Software SCO. Got types: {types}" + + def test_vulnerability_object_emitted(self, sample_kev_entry): + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(sample_kev_entry) + types = _types(objs) + assert "vulnerability" in types + + def test_vendor_identity_emitted(self, sample_kev_entry): + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(sample_kev_entry) + identities = [o for o in objs if getattr(o, "type", None) == "identity"] + names = {i.name for i in identities} + assert "Acme Corp" in names + + def test_no_infrastructure_relationships(self, sample_kev_entry): + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(sample_kev_entry) + rels = [o for o in objs if getattr(o, "type", None) == "relationship"] + for r in rels: + assert ( + "infrastructure" not in r.source_ref + ), f"Relationship {r.id} sources an Infrastructure" + assert ( + "infrastructure" not in r.target_ref + ), f"Relationship {r.id} targets an Infrastructure" + + def test_software_has_vulnerability_relationship(self, sample_kev_entry): + """Software→has→Vulnerability is the canonical edge for KEV.""" + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(sample_kev_entry) + has_rels = [ + o + for o in objs + if getattr(o, "type", None) == "relationship" + and o.relationship_type == "has" + ] + assert len(has_rels) >= 1 + assert any( + "software" in r.source_ref and "vulnerability" in r.target_ref + for r in has_rels + ), "Expected a Software→has→Vulnerability relationship" + + +class TestCreateInfrastructuresTrue: + """Backward-compat path: legacy users who explicitly opt in still get + the old behaviour (Infrastructure SDO + its relationships).""" + + def test_infrastructure_object_emitted(self, sample_kev_entry): + conn = _make_connector(create_infrastructures=True) + objs = conn.build_bundle(sample_kev_entry) + types = _types(objs) + assert "infrastructure" in types + + def test_infrastructure_has_vulnerability_relationship(self, sample_kev_entry): + conn = _make_connector(create_infrastructures=True) + objs = conn.build_bundle(sample_kev_entry) + rels = [o for o in objs if getattr(o, "type", None) == "relationship"] + infra_to_vuln = [ + r + for r in rels + if r.relationship_type == "has" + and "infrastructure" in r.source_ref + and "vulnerability" in r.target_ref + ] + assert len(infra_to_vuln) == 1, ( + "Expected exactly one Infrastructure→has→Vulnerability when " + "create_infrastructures=True" + ) + + def test_software_still_emitted(self, sample_kev_entry): + """The Software SCO is unchanged regardless of the flag.""" + conn = _make_connector(create_infrastructures=True) + objs = conn.build_bundle(sample_kev_entry) + types = _types(objs) + assert "software" in types + + +class TestKevFlagOnlyMode: + """Existing kev_flag_only mode is independent of our change.""" + + def test_only_emits_vulnerability(self, sample_kev_entry): + conn = _make_connector(create_infrastructures=False, kev_flag_only=True) + objs = conn.build_bundle(sample_kev_entry) + types = _types(objs) + assert types == [ + "vulnerability" + ], f"kev_flag_only mode must emit only the Vulnerability. Got: {types}" + + def test_kev_flag_only_overrides_create_infrastructures(self, sample_kev_entry): + """Even if someone sets both flags True, kev_flag_only wins.""" + conn = _make_connector(create_infrastructures=True, kev_flag_only=True) + objs = conn.build_bundle(sample_kev_entry) + types = _types(objs) + assert "infrastructure" not in types + assert types == ["vulnerability"] + + def test_x_opencti_cisa_kev_flag_set(self, sample_kev_entry): + conn = _make_connector(create_infrastructures=False, kev_flag_only=True) + objs = conn.build_bundle(sample_kev_entry) + vuln = [o for o in objs if getattr(o, "type", None) == "vulnerability"][0] + assert vuln.x_opencti_cisa_kev is True + + +class TestRealKevSample: + """Pin the behaviour against a real KEV catalog entry shape.""" + + def test_apache_struts_no_infrastructure_by_default( + self, kev_entry_software_product + ): + """Apache Struts is unambiguously software — no Infrastructure must leak.""" + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(kev_entry_software_product) + types = _types(objs) + assert "infrastructure" not in types + software = [o for o in objs if getattr(o, "type", None) == "software"][0] + assert software.name == "Struts" + assert software.vendor == "Apache" + + +class TestKnownBadKevEntries: + """Pin the fix against the real product names that previously produced + bogus Infrastructure SDOs in the wild. Each of these was observed in + production with `entity_type=infrastructure` and `infrastructure_types=None`.""" + + @pytest.mark.parametrize( + "vendor,product", + [ + ("PHP", "PHP"), + ("Joomla", "Joomla!"), + ("Laravel", "Laravel Framework"), + ("Apache", "Struts"), + ("Atlassian", "October CMS"), + ("GNU", "GNU C Library"), + ("Microsoft", "Skype for Business"), + ], + ) + def test_software_product_emits_no_infrastructure(self, vendor, product): + entry = { + "cveID": "CVE-0000-0000", + "vendorProject": vendor, + "product": product, + "vulnerabilityName": f"{product} test", + "dateAdded": "2024-01-01", + "shortDescription": f"{product} contains a vulnerability.", + "requiredAction": "Apply mitigations.", + "dueDate": "2024-02-01", + "knownRansomwareCampaignUse": "Unknown", + "notes": "", + "cwes": [], + } + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(entry) + types = _types(objs) + assert "infrastructure" not in types, ( + f"Regression: {product!r} produced an Infrastructure SDO. " + f"This is exactly the bug we set out to fix. Got types: {types}" + ) + # And the product DOES exist as software. + software = [o for o in objs if getattr(o, "type", None) == "software"] + assert len(software) == 1 + assert software[0].name == product + + +class TestBundleSerialization: + """The real helper.stix2_create_bundle returns a JSON string. Verify + the objects we produce are JSON-serialisable end-to-end.""" + + def test_bundle_objects_are_stix2_serialisable(self, sample_kev_entry): + """Round-trip through stix2.Bundle.serialize() and assert no + Infrastructure object survives. (Note: substring-checking the + raw JSON is wrong — "Infrastructure" appears in CISA's name.)""" + import json + + import stix2 + + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(sample_kev_entry) + bundle = stix2.Bundle(objects=objs, allow_custom=True) + data = json.loads(bundle.serialize()) + emitted_types = [o["type"] for o in data["objects"]] + assert "infrastructure" not in emitted_types + + def test_bundle_with_infra_serialises(self, sample_kev_entry): + import json + + import stix2 + + conn = _make_connector(create_infrastructures=True) + objs = conn.build_bundle(sample_kev_entry) + bundle = stix2.Bundle(objects=objs, allow_custom=True) + data = json.loads(bundle.serialize()) + emitted_types = [o["type"] for o in data["objects"]] + assert "infrastructure" in emitted_types + # The known-bad shape: the Infrastructure SDO has no infrastructure_types. + infra = [o for o in data["objects"] if o["type"] == "infrastructure"][0] + assert "infrastructure_types" not in infra, ( + "If this assertion ever flips, the connector is now setting " + "infrastructure_types — which is good, but the test pins the " + "current legacy behaviour. Update the test if so." + ) + + +class TestObjectCount: + """Exact bundle composition — guards against accidental new emissions.""" + + def test_bundle_size_without_infrastructures(self, sample_kev_entry): + """When create_infrastructures=False, expect 6 objects: + CISA Identity (author), Vulnerability, Vendor Identity, Software, + Software→Vendor, Software→Vulnerability.""" + conn = _make_connector(create_infrastructures=False) + objs = conn.build_bundle(sample_kev_entry) + assert len(objs) == 6, f"Expected 6 objects, got {len(objs)}: {_types(objs)}" + + def test_bundle_size_with_infrastructures(self, sample_kev_entry): + """With the flag, +3 objects: Infrastructure, Infrastructure→Vuln, + Vendor→Infrastructure.""" + conn = _make_connector(create_infrastructures=True) + objs = conn.build_bundle(sample_kev_entry) + assert len(objs) == 9, f"Expected 9 objects, got {len(objs)}: {_types(objs)}" diff --git a/external-import/cisa-known-exploited-vulnerabilities/tests/test_config.py b/external-import/cisa-known-exploited-vulnerabilities/tests/test_config.py new file mode 100644 index 00000000000..fcaf5363e8d --- /dev/null +++ b/external-import/cisa-known-exploited-vulnerabilities/tests/test_config.py @@ -0,0 +1,69 @@ +"""Tests for `_ConfigLoaderCISAKEV` defaults and overrides. + +The connector previously defaulted `create_infrastructures` to True, +producing duplicate Infrastructure SDOs alongside Software SCOs for +every CISA KEV entry. The flag stays for backward compatibility but +defaults to False — see commit message for context. +""" + +import pytest +from models.configs.cisakev_configs import _ConfigLoaderCISAKEV +from models.configs.config_loader import ConfigLoader + + +@pytest.fixture +def required_opencti_env(monkeypatch): + """`ConfigLoader` requires OpenCTI URL + token; stub them so we can + exercise the cisa.* fields through the real env-var pipeline.""" + monkeypatch.setenv("OPENCTI_URL", "http://localhost:8080") + monkeypatch.setenv("OPENCTI_TOKEN", "test-token") + + +class TestCreateInfrastructuresDefault: + """The behaviour change at the heart of this PR.""" + + def test_default_is_false_on_inner_config(self): + """Fast unit-level check on the dedicated config class.""" + cfg = _ConfigLoaderCISAKEV() + assert cfg.create_infrastructures is False, ( + "Default must be False so that fresh deployments don't generate " + "STIX-non-compliant Infrastructure SDOs for software products." + ) + + def test_default_is_false_via_full_loader(self, required_opencti_env): + """Sanity check end-to-end through the real ConfigLoader.""" + cfg = ConfigLoader() + assert cfg.cisa.create_infrastructures is False + + def test_explicit_true_still_honoured(self, required_opencti_env, monkeypatch): + """Existing users who relied on the old behaviour can opt back in + with the documented `CISA_CREATE_INFRASTRUCTURES=true` env var.""" + monkeypatch.setenv("CISA_CREATE_INFRASTRUCTURES", "true") + cfg = ConfigLoader() + assert cfg.cisa.create_infrastructures is True + + def test_explicit_false_honoured(self, required_opencti_env, monkeypatch): + monkeypatch.setenv("CISA_CREATE_INFRASTRUCTURES", "false") + cfg = ConfigLoader() + assert cfg.cisa.create_infrastructures is False + + +class TestOtherDefaultsUnchanged: + """Sanity check — the PR should only flip create_infrastructures.""" + + def test_kev_flag_only_default_false(self): + cfg = _ConfigLoaderCISAKEV() + assert cfg.kev_flag_only is False + + def test_tlp_default_clear(self): + cfg = _ConfigLoaderCISAKEV() + assert cfg.tlp == "TLP:CLEAR" + + def test_catalog_url_default(self): + cfg = _ConfigLoaderCISAKEV() + assert "known_exploited_vulnerabilities.json" in cfg.catalog_url + assert cfg.catalog_url.startswith("https://") + + def test_interval_default_seven_days(self): + cfg = _ConfigLoaderCISAKEV() + assert cfg.interval == 7