From 237a6d77043e7aef191e2a810ee5aad5ec81ef4e Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:30:33 +0100 Subject: [PATCH 01/17] Use new sqlite for mcafee --- dissect/target/plugins/apps/av/mcafee.py | 105 +++++++++++------------ 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/dissect/target/plugins/apps/av/mcafee.py b/dissect/target/plugins/apps/av/mcafee.py index 8ac9b8b47c..174ed54497 100644 --- a/dissect/target/plugins/apps/av/mcafee.py +++ b/dissect/target/plugins/apps/av/mcafee.py @@ -96,56 +96,55 @@ def msc(self) -> Iterator[McAfeeMscLogRecord]: len_marker = len(self.MARKER_SUSPICIOUS_UDP_CONNECTION) for log_file in self.get_log_files(): - with log_file.open() as open_log: - database = SQLite3(open_log) - fields = defaultdict(dict) - fields_table = database.table(self.TABLE_FIELD) - - for field in fields_table.rows(): - fields[field.fkey][field.field_id] = field.data - log_table = database.table(self.TABLE_LOG) - - for entry in log_table.rows(): - fkey = entry.fkey - log_fields = fields[fkey] - ip = None - protocol = None - port = None - threat = None - - for key, log_field in log_fields.items(): - try: - ipaddress.ip_address(log_field) - ip = log_field - continue - except ValueError: - pass - - if log_field.startswith( - (self.MARKER_SUSPICIOUS_TCP_CONNECTION, self.MARKER_SUSPICIOUS_UDP_CONNECTION) - ): - port = int(log_field[len_marker:]) - protocol = log_field[:3] - continue - - if key == self.TEMPLATE_ID_INFECTION and entry.details_info.find(self.MARKER_INFECTION) > -1: - threat = log_field - - if threat: - yield McAfeeMscLogRecord( - ts=from_unix(entry.date), - threat=threat, - message=self._clean_message(entry.details_info), - keywords=",".join(log_fields.values()), - fkey=entry.fkey, - ) - else: - yield McAfeeMscFirewallRecord( - ts=from_unix(entry.date), - ip=ip, - protocol=protocol, - port=port, - message=self._clean_message(entry.details_info), - keywords=",".join(log_fields.values()), - fkey=entry.fkey, - ) + database = SQLite3(log_file) + fields = defaultdict(dict) + fields_table = database.table(self.TABLE_FIELD) + + for field in fields_table.rows(): + fields[field.fkey][field.field_id] = field.data + log_table = database.table(self.TABLE_LOG) + + for entry in log_table.rows(): + fkey = entry.fkey + log_fields = fields[fkey] + ip = None + protocol = None + port = None + threat = None + + for key, log_field in log_fields.items(): + try: + ipaddress.ip_address(log_field) + ip = log_field + continue + except ValueError: + pass + + if log_field.startswith( + (self.MARKER_SUSPICIOUS_TCP_CONNECTION, self.MARKER_SUSPICIOUS_UDP_CONNECTION) + ): + port = int(log_field[len_marker:]) + protocol = log_field[:3] + continue + + if key == self.TEMPLATE_ID_INFECTION and entry.details_info.find(self.MARKER_INFECTION) > -1: + threat = log_field + + if threat: + yield McAfeeMscLogRecord( + ts=from_unix(entry.date), + threat=threat, + message=self._clean_message(entry.details_info), + keywords=",".join(log_fields.values()), + fkey=entry.fkey, + ) + else: + yield McAfeeMscFirewallRecord( + ts=from_unix(entry.date), + ip=ip, + protocol=protocol, + port=port, + message=self._clean_message(entry.details_info), + keywords=",".join(log_fields.values()), + fkey=entry.fkey, + ) From 79a65b9309c92fd4909fa11e23f694043c9b689a Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:36:13 +0100 Subject: [PATCH 02/17] Use new sqlite for sophos --- dissect/target/plugins/apps/av/sophos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/plugins/apps/av/sophos.py b/dissect/target/plugins/apps/av/sophos.py index 32cf893fc2..0004e70606 100644 --- a/dissect/target/plugins/apps/av/sophos.py +++ b/dissect/target/plugins/apps/av/sophos.py @@ -78,7 +78,7 @@ def hitmanlogs(self) -> Iterator[HitmanAlertRecord]: """ if self.target.fs.path(self.LOG_SOPHOS_HITMAN).exists(): try: - fh = self.target.fs.path(self.LOG_SOPHOS_HITMAN).open("rb") + fh = self.target.fs.path(self.LOG_SOPHOS_HITMAN) db = SQLite3(fh) alerts = next(filter(lambda t: t.name == "Alerts", db.tables())) for alert in alerts.rows(): From e167a8d8aa2506b226d03ca10019c87d2b93fb29 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:43:28 +0100 Subject: [PATCH 03/17] Use new sqlite for chromium --- dissect/target/plugins/apps/browser/chromium.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/plugins/apps/browser/chromium.py b/dissect/target/plugins/apps/browser/chromium.py index 24641e0a1f..7fe228b18d 100644 --- a/dissect/target/plugins/apps/browser/chromium.py +++ b/dissect/target/plugins/apps/browser/chromium.py @@ -158,7 +158,7 @@ def _iter_db(self, filename: str, subdirs: list[str] | None = None) -> Iterator[ seen.add(db_file) try: - yield user, db_file, SQLite3(db_file.open()) + yield user, db_file, SQLite3(db_file) except FileNotFoundError: self.target.log.warning("Could not find %s file: %s", filename, db_file) except DBError as e: From ed14abb56d9ccd80711484270a5a7da303447d85 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:45:52 +0100 Subject: [PATCH 04/17] Use new sqlite for firefox --- .../target/plugins/apps/browser/firefox.py | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/dissect/target/plugins/apps/browser/firefox.py b/dissect/target/plugins/apps/browser/firefox.py index a1d33cb1d2..fb17ca82ab 100644 --- a/dissect/target/plugins/apps/browser/firefox.py +++ b/dissect/target/plugins/apps/browser/firefox.py @@ -170,7 +170,7 @@ def _iter_db(self, filename: str) -> Iterator[tuple[UserDetails | None, Path, SQ db_file = profile_dir.joinpath(filename) try: - yield user_details, db_file, SQLite3(db_file.open()) + yield user_details, db_file, SQLite3(db_file) except FileNotFoundError: self.target.log.info("Could not find %s file: %s", filename, db_file) except DBError as e: @@ -673,25 +673,24 @@ def decrypt_master_key(key4_file: Path, primary_password: bytes) -> bytes: # Extract neccesary information from the key4.db file. Multiple values might exist for the # values we are interested in. Generally the last entry will be the currently active value, # which is why we need to iterate every row in the table to get the last entry. - with key4_file.open("rb") as fh: - db = SQLite3(fh) - - # Get the last ``item`` (global salt) and ``item2`` (password check) values. - if table := db.table("metadata"): - for row in table.rows(): - if row.get("id") == "password": - global_salt = row.get("item1", b"") - password_check = row.get("item2", b"") - else: - raise ValueError(f"Missing table 'metadata' in key4.db {key4_file}") - - # Get the last ``a11`` (master key) and ``a102`` (cka) values. - if table := db.table("nssPrivate"): - *_, last_row = table.rows() - master_key = last_row.get("a11") - master_key_cka = last_row.get("a102") - else: - raise ValueError(f"Missing table 'nssPrivate' in key4.db {key4_file}") + db = SQLite3(key4_file) + + # Get the last ``item`` (global salt) and ``item2`` (password check) values. + if table := db.table("metadata"): + for row in table.rows(): + if row.get("id") == "password": + global_salt = row.get("item1", b"") + password_check = row.get("item2", b"") + else: + raise ValueError(f"Missing table 'metadata' in key4.db {key4_file}") + + # Get the last ``a11`` (master key) and ``a102`` (cka) values. + if table := db.table("nssPrivate"): + *_, last_row = table.rows() + master_key = last_row.get("a11") + master_key_cka = last_row.get("a102") + else: + raise ValueError(f"Missing table 'nssPrivate' in key4.db {key4_file}") if not master_key: raise ValueError(f"Password master key is not defined in key4.db {key4_file}") From ad2fe6da9877b6f4d9d8dafaf483879b06ad8903 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:47:38 +0100 Subject: [PATCH 05/17] Use new sqlite for podman --- dissect/target/plugins/apps/container/podman.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/plugins/apps/container/podman.py b/dissect/target/plugins/apps/container/podman.py index 4b493a6b99..a5cd11bdf0 100755 --- a/dissect/target/plugins/apps/container/podman.py +++ b/dissect/target/plugins/apps/container/podman.py @@ -175,7 +175,7 @@ def _find_containers_sqlite(self, path: Path) -> Iterator[PodmanContainerRecord] """ try: - db = SQLite3(path.open("rb")) + db = SQLite3(path) except (ValueError, DBError) as e: self.target.log.warning("Unable to read Podman database %s: %s", path, e) self.target.log.debug("", exc_info=e) From 234c0ffdeddb847dd6d01dc4186f0963b6bf88c8 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:51:15 +0100 Subject: [PATCH 06/17] Use new sqlite for esxi configstore --- dissect/target/plugins/os/unix/esxi/configstore.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dissect/target/plugins/os/unix/esxi/configstore.py b/dissect/target/plugins/os/unix/esxi/configstore.py index 12b0328188..9bc519e5da 100644 --- a/dissect/target/plugins/os/unix/esxi/configstore.py +++ b/dissect/target/plugins/os/unix/esxi/configstore.py @@ -1,7 +1,7 @@ from __future__ import annotations import json as jsonlib -from typing import TYPE_CHECKING, Any, BinaryIO +from typing import TYPE_CHECKING, Any from dissect.database.sqlite3 import SQLite3 @@ -9,6 +9,8 @@ from dissect.target.plugin import Plugin, internal if TYPE_CHECKING: + from pathlib import Path + from dissect.target.target import Target @@ -25,8 +27,7 @@ def __init__(self, target: Target): # It's made available at /etc/vmware/configstore/current-store-1 during boot, but stored at # the path used below in local.tgz if (path := target.fs.path("/var/lib/vmware/configstore/backup/current-store-1")).exists(): - with path.open("rb") as fh: - self._configstore = parse_config_store(fh) + self._configstore = parse_config_store(path) def check_compatible(self) -> None: if self.target.os != "esxi": @@ -41,7 +42,7 @@ def get(self, key: str, default: Any = None) -> dict[str, Any]: return self._configstore.get(key, default) -def parse_config_store(fh: BinaryIO) -> dict[str, Any]: +def parse_config_store(fh: Path) -> dict[str, Any]: db = SQLite3(fh) store = {} From 55369b560cc0d6175177aec210cca0b3573729d2 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:53:53 +0100 Subject: [PATCH 07/17] Use new sqlite for proxmox --- dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py b/dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py index e559254fec..29e2f63a06 100644 --- a/dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py +++ b/dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py @@ -18,6 +18,8 @@ from dissect.target.plugins.os.unix.linux.debian._os import DebianPlugin if TYPE_CHECKING: + from pathlib import Path + from typing_extensions import Self from dissect.target.target import Target @@ -37,8 +39,7 @@ def create(cls, target: Target, sysvol: Filesystem) -> Self: obj = super().create(target, sysvol) if (config_db := target.fs.path("/var/lib/pve-cluster/config.db")).exists(): - with config_db.open("rb") as fh: - vfs = _create_pmxcfs(fh, obj.hostname) + vfs = _create_pmxcfs(config_db, obj.hostname) target.fs.mount("/etc/pve", vfs) @@ -63,7 +64,7 @@ def os(self) -> str: DT_REG = 8 -def _create_pmxcfs(fh: BinaryIO, hostname: str | None = None) -> VirtualFilesystem: +def _create_pmxcfs(fh: Path, hostname: str | None = None) -> VirtualFilesystem: # https://pve.proxmox.com/wiki/Proxmox_Cluster_File_System_(pmxcfs) db = SQLite3(fh) From a5d26bad82ea6eebf04746efc3c63554b28b135e Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:03:59 +0100 Subject: [PATCH 08/17] Use new sqlite for windows cam --- dissect/target/plugins/os/windows/cam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/plugins/os/windows/cam.py b/dissect/target/plugins/os/windows/cam.py index 7a8222ad51..11f7fab752 100644 --- a/dissect/target/plugins/os/windows/cam.py +++ b/dissect/target/plugins/os/windows/cam.py @@ -159,7 +159,7 @@ def _find_db(self) -> TargetPath | None: ) def _open_db(self) -> SQLite3 | None: - return SQLite3(self.camdb_path.open("rb")) if self.camdb_path else None + return SQLite3(self.camdb_path) if self.camdb_path else None def _build_context_dict(self) -> defaultdict[str, dict] | None: MAPDB = defaultdict(dict) From ef81d23d3ae6eeccca1f8e5b60310e07ae3902d3 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:12:05 +0100 Subject: [PATCH 09/17] Use new sqlite for itunes --- dissect/target/loaders/itunes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/loaders/itunes.py b/dissect/target/loaders/itunes.py index 4b0831d053..1635ebb1cb 100644 --- a/dissect/target/loaders/itunes.py +++ b/dissect/target/loaders/itunes.py @@ -146,7 +146,7 @@ def open(self, password: str | None = None, kek: bytes | None = None) -> None: def _open_manifest_db(self) -> SQLite3: path = self.root.joinpath("Manifest.db") if not self.encrypted or self.manifest["Lockdown"]["ProductVersion"] < "10.2": - fh = path.open("rb") + fh = path else: key = self.key_bag.unwrap(self.manifest["ManifestKey"]) fh = BytesIO(aes_decrypt(path.read_bytes(), key)) From 7567a4b4952347eefea281fba28b94108c9fee39 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:06:58 +0100 Subject: [PATCH 10/17] Use new sqlite for windows activitiescache --- dissect/target/plugins/os/windows/activitiescache.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dissect/target/plugins/os/windows/activitiescache.py b/dissect/target/plugins/os/windows/activitiescache.py index 237c20fa99..1530afe576 100644 --- a/dissect/target/plugins/os/windows/activitiescache.py +++ b/dissect/target/plugins/os/windows/activitiescache.py @@ -120,8 +120,7 @@ def activitiescache(self) -> Iterator[ActivitiesCacheRecord]: clipboard_payload (string): ClipboardPayload field. """ for user, cache_file in self.cachefiles: - fh = cache_file.open() - db = SQLite3(fh) + db = SQLite3(cache_file) if table := db.table("Activity"): for r in table.rows(): From 345033722e7e93114a2f81ae5b36f6a8c2ff98f5 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:07:20 +0100 Subject: [PATCH 11/17] Use new sqlite for windows search --- dissect/target/plugins/os/windows/search.py | 59 ++++++++++----------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/dissect/target/plugins/os/windows/search.py b/dissect/target/plugins/os/windows/search.py index dd1bf8c100..6f3e66ba5d 100644 --- a/dissect/target/plugins/os/windows/search.py +++ b/dissect/target/plugins/os/windows/search.py @@ -166,36 +166,35 @@ def parse_esedb(self, path: Path, user_details: UserDetails | None) -> Iterator[ def parse_sqlite(self, path: Path, user_details: UserDetails | None) -> Iterator[SearchIndexRecords]: """Parse the SQLite3 ``SystemIndex_1_PropertyStore`` table.""" - with path.open("rb") as fh: - db = SQLite3(fh) - - # ``ColumnId`` is translated using the ``SystemIndex_1_PropertyStore_Metadata`` table. - columns = { - row.get("Id"): row.get("UniqueKey", "").split("-", maxsplit=1)[-1] - for row in db.table("SystemIndex_1_PropertyStore_Metadata").rows() - } - - if not (table := db.table("SystemIndex_1_PropertyStore")): - self.target.log.warning("Database %s does not have a table called 'SystemIndex_1_PropertyStore'", path) - return - - current_work_id = None - values = {} - - for row in table.rows(): - work_id = row.get("WorkId") - if current_work_id is None: - current_work_id = work_id - if work_id != current_work_id: - yield from self.build_record(values, user_details, path) - current_work_id = work_id - values = {} - - if value := row.get("Value"): - column_name = columns[row.get("ColumnId")] - values[column_name] = value - - yield from self.build_record(values, user_details, path) + db = SQLite3(path) + + # ``ColumnId`` is translated using the ``SystemIndex_1_PropertyStore_Metadata`` table. + columns = { + row.get("Id"): row.get("UniqueKey", "").split("-", maxsplit=1)[-1] + for row in db.table("SystemIndex_1_PropertyStore_Metadata").rows() + } + + if not (table := db.table("SystemIndex_1_PropertyStore")): + self.target.log.warning("Database %s does not have a table called 'SystemIndex_1_PropertyStore'", path) + return + + current_work_id = None + values = {} + + for row in table.rows(): + work_id = row.get("WorkId") + if current_work_id is None: + current_work_id = work_id + if work_id != current_work_id: + yield from self.build_record(values, user_details, path) + current_work_id = work_id + values = {} + + if value := row.get("Value"): + column_name = columns[row.get("ColumnId")] + values[column_name] = value + + yield from self.build_record(values, user_details, path) def build_record( self, values: dict[str, Any] | TableRecord, user_details: UserDetails | None, db_path: Path From 7353ba4f51a200c104bd8bc1f5c7e7ac1cb5e440 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Wed, 7 Jan 2026 12:22:05 +0100 Subject: [PATCH 12/17] Use new sqlite for windows notifications --- dissect/target/plugins/os/windows/notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/plugins/os/windows/notifications.py b/dissect/target/plugins/os/windows/notifications.py index f079eed39c..e789246c73 100644 --- a/dissect/target/plugins/os/windows/notifications.py +++ b/dissect/target/plugins/os/windows/notifications.py @@ -444,7 +444,7 @@ def wpndatabase(self) -> Iterator[WpnDatabaseNotificationRecord | WpnDatabaseNot target_tz = self.target.datetime.tzinfo for user, wpndatabase in self.wpndb_files: - db = SQLite3(wpndatabase.open()) + db = SQLite3(wpndatabase) handlers = {} if table := db.table("NotificationHandler"): From d9e977e54c1e0eb5f40b802a1fb2883ace4c1aef Mon Sep 17 00:00:00 2001 From: Pim <36573021+PimSanders@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:23:40 +0100 Subject: [PATCH 13/17] Apply suggestions from code review Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/target/plugins/apps/av/sophos.py | 3 +-- dissect/target/plugins/os/unix/esxi/configstore.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dissect/target/plugins/apps/av/sophos.py b/dissect/target/plugins/apps/av/sophos.py index 0004e70606..2cb6377484 100644 --- a/dissect/target/plugins/apps/av/sophos.py +++ b/dissect/target/plugins/apps/av/sophos.py @@ -78,8 +78,7 @@ def hitmanlogs(self) -> Iterator[HitmanAlertRecord]: """ if self.target.fs.path(self.LOG_SOPHOS_HITMAN).exists(): try: - fh = self.target.fs.path(self.LOG_SOPHOS_HITMAN) - db = SQLite3(fh) + db = SQLite3(self.target.fs.path(self.LOG_SOPHOS_HITMAN)) alerts = next(filter(lambda t: t.name == "Alerts", db.tables())) for alert in alerts.rows(): yield HitmanAlertRecord( diff --git a/dissect/target/plugins/os/unix/esxi/configstore.py b/dissect/target/plugins/os/unix/esxi/configstore.py index 9bc519e5da..b842d98282 100644 --- a/dissect/target/plugins/os/unix/esxi/configstore.py +++ b/dissect/target/plugins/os/unix/esxi/configstore.py @@ -42,8 +42,8 @@ def get(self, key: str, default: Any = None) -> dict[str, Any]: return self._configstore.get(key, default) -def parse_config_store(fh: Path) -> dict[str, Any]: - db = SQLite3(fh) +def parse_config_store(path: Path) -> dict[str, Any]: + db = SQLite3(path) store = {} From ea60a55b2645cb7f5f130f9cb8b04d314107a246 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:40:26 +0100 Subject: [PATCH 14/17] Use context managers --- dissect/target/plugins/apps/av/mcafee.py | 104 +++++++++--------- .../target/plugins/apps/browser/firefox.py | 79 +++++++------ .../plugins/os/unix/esxi/configstore.py | 77 +++++++------ .../os/unix/linux/debian/proxmox/_os.py | 55 +++++---- .../plugins/os/windows/activitiescache.py | 67 ++++++----- .../plugins/os/windows/notifications.py | 94 ++++++++-------- dissect/target/plugins/os/windows/search.py | 57 +++++----- 7 files changed, 264 insertions(+), 269 deletions(-) diff --git a/dissect/target/plugins/apps/av/mcafee.py b/dissect/target/plugins/apps/av/mcafee.py index 174ed54497..2ede58e7e5 100644 --- a/dissect/target/plugins/apps/av/mcafee.py +++ b/dissect/target/plugins/apps/av/mcafee.py @@ -96,55 +96,55 @@ def msc(self) -> Iterator[McAfeeMscLogRecord]: len_marker = len(self.MARKER_SUSPICIOUS_UDP_CONNECTION) for log_file in self.get_log_files(): - database = SQLite3(log_file) - fields = defaultdict(dict) - fields_table = database.table(self.TABLE_FIELD) - - for field in fields_table.rows(): - fields[field.fkey][field.field_id] = field.data - log_table = database.table(self.TABLE_LOG) - - for entry in log_table.rows(): - fkey = entry.fkey - log_fields = fields[fkey] - ip = None - protocol = None - port = None - threat = None - - for key, log_field in log_fields.items(): - try: - ipaddress.ip_address(log_field) - ip = log_field - continue - except ValueError: - pass - - if log_field.startswith( - (self.MARKER_SUSPICIOUS_TCP_CONNECTION, self.MARKER_SUSPICIOUS_UDP_CONNECTION) - ): - port = int(log_field[len_marker:]) - protocol = log_field[:3] - continue - - if key == self.TEMPLATE_ID_INFECTION and entry.details_info.find(self.MARKER_INFECTION) > -1: - threat = log_field - - if threat: - yield McAfeeMscLogRecord( - ts=from_unix(entry.date), - threat=threat, - message=self._clean_message(entry.details_info), - keywords=",".join(log_fields.values()), - fkey=entry.fkey, - ) - else: - yield McAfeeMscFirewallRecord( - ts=from_unix(entry.date), - ip=ip, - protocol=protocol, - port=port, - message=self._clean_message(entry.details_info), - keywords=",".join(log_fields.values()), - fkey=entry.fkey, - ) + with SQLite3(log_file) as database: + fields = defaultdict(dict) + fields_table = database.table(self.TABLE_FIELD) + + for field in fields_table.rows(): + fields[field.fkey][field.field_id] = field.data + log_table = database.table(self.TABLE_LOG) + + for entry in log_table.rows(): + fkey = entry.fkey + log_fields = fields[fkey] + ip = None + protocol = None + port = None + threat = None + + for key, log_field in log_fields.items(): + try: + ipaddress.ip_address(log_field) + ip = log_field + continue + except ValueError: + pass + + if log_field.startswith( + (self.MARKER_SUSPICIOUS_TCP_CONNECTION, self.MARKER_SUSPICIOUS_UDP_CONNECTION) + ): + port = int(log_field[len_marker:]) + protocol = log_field[:3] + continue + + if key == self.TEMPLATE_ID_INFECTION and entry.details_info.find(self.MARKER_INFECTION) > -1: + threat = log_field + + if threat: + yield McAfeeMscLogRecord( + ts=from_unix(entry.date), + threat=threat, + message=self._clean_message(entry.details_info), + keywords=",".join(log_fields.values()), + fkey=entry.fkey, + ) + else: + yield McAfeeMscFirewallRecord( + ts=from_unix(entry.date), + ip=ip, + protocol=protocol, + port=port, + message=self._clean_message(entry.details_info), + keywords=",".join(log_fields.values()), + fkey=entry.fkey, + ) diff --git a/dissect/target/plugins/apps/browser/firefox.py b/dissect/target/plugins/apps/browser/firefox.py index fb17ca82ab..655ddecedd 100644 --- a/dissect/target/plugins/apps/browser/firefox.py +++ b/dissect/target/plugins/apps/browser/firefox.py @@ -673,59 +673,58 @@ def decrypt_master_key(key4_file: Path, primary_password: bytes) -> bytes: # Extract neccesary information from the key4.db file. Multiple values might exist for the # values we are interested in. Generally the last entry will be the currently active value, # which is why we need to iterate every row in the table to get the last entry. - db = SQLite3(key4_file) - - # Get the last ``item`` (global salt) and ``item2`` (password check) values. - if table := db.table("metadata"): - for row in table.rows(): - if row.get("id") == "password": - global_salt = row.get("item1", b"") - password_check = row.get("item2", b"") - else: - raise ValueError(f"Missing table 'metadata' in key4.db {key4_file}") + with SQLite3(key4_file) as db: + # Get the last ``item`` (global salt) and ``item2`` (password check) values. + if table := db.table("metadata"): + for row in table.rows(): + if row.get("id") == "password": + global_salt = row.get("item1", b"") + password_check = row.get("item2", b"") + else: + raise ValueError(f"Missing table 'metadata' in key4.db {key4_file}") - # Get the last ``a11`` (master key) and ``a102`` (cka) values. - if table := db.table("nssPrivate"): - *_, last_row = table.rows() - master_key = last_row.get("a11") - master_key_cka = last_row.get("a102") - else: - raise ValueError(f"Missing table 'nssPrivate' in key4.db {key4_file}") + # Get the last ``a11`` (master key) and ``a102`` (cka) values. + if table := db.table("nssPrivate"): + *_, last_row = table.rows() + master_key = last_row.get("a11") + master_key_cka = last_row.get("a102") + else: + raise ValueError(f"Missing table 'nssPrivate' in key4.db {key4_file}") - if not master_key: - raise ValueError(f"Password master key is not defined in key4.db {key4_file}") + if not master_key: + raise ValueError(f"Password master key is not defined in key4.db {key4_file}") - if master_key_cka != CKA_ID: - raise ValueError( - f"Password master key CKA_ID '{master_key_cka}' is not equal to expected value '{CKA_ID}' in {key4_file}" - ) + if master_key_cka != CKA_ID: + raise ValueError( + f"Password master key CKA_ID '{master_key_cka}' is not equal to expected value '{CKA_ID}' in {key4_file}" + ) - decoded_password_check: core.Sequence = core.load(password_check) - decoded_master_key: core.Sequence = core.load(master_key) + decoded_password_check: core.Sequence = core.load(password_check) + decoded_master_key: core.Sequence = core.load(master_key) - try: - decrypted_password_check, algorithm = _decrypt_master_key(decoded_password_check, primary_password, global_salt) + try: + decrypted_password_check, algorithm = _decrypt_master_key(decoded_password_check, primary_password, global_salt) - except EOFError: - raise ValueError("No primary password provided") + except EOFError: + raise ValueError("No primary password provided") - except ValueError as e: - raise ValueError(f"Unable to decrypt Firefox password check: {e!s}") from e + except ValueError as e: + raise ValueError(f"Unable to decrypt Firefox password check: {e!s}") from e - if not decrypted_password_check: - raise ValueError(f"Encountered unknown algorithm {algorithm} while decrypting Firefox master key") + if not decrypted_password_check: + raise ValueError(f"Encountered unknown algorithm {algorithm} while decrypting Firefox master key") - expected_password_check = b"password-check\x02\x02" + expected_password_check = b"password-check\x02\x02" - if decrypted_password_check != expected_password_check: - log.debug("Expected %s but got %s", expected_password_check, decrypted_password_check) - raise ValueError("Master key decryption failed. Provided password could be missing or incorrect") + if decrypted_password_check != expected_password_check: + log.debug("Expected %s but got %s", expected_password_check, decrypted_password_check) + raise ValueError("Master key decryption failed. Provided password could be missing or incorrect") - decrypted, algorithm = _decrypt_master_key(decoded_master_key, primary_password, global_salt) + decrypted, algorithm = _decrypt_master_key(decoded_master_key, primary_password, global_salt) - block_size = 16 if algos.EncryptionAlgorithmId.map(algorithm) == "pbes2" else 8 + block_size = 16 if algos.EncryptionAlgorithmId.map(algorithm) == "pbes2" else 8 - return unpad(decrypted, block_size) + return unpad(decrypted, block_size) def decrypt_value(b64_ciphertext: str, key: bytes) -> bytes | None: diff --git a/dissect/target/plugins/os/unix/esxi/configstore.py b/dissect/target/plugins/os/unix/esxi/configstore.py index b842d98282..3f3210529a 100644 --- a/dissect/target/plugins/os/unix/esxi/configstore.py +++ b/dissect/target/plugins/os/unix/esxi/configstore.py @@ -43,42 +43,41 @@ def get(self, key: str, default: Any = None) -> dict[str, Any]: def parse_config_store(path: Path) -> dict[str, Any]: - db = SQLite3(path) - - store = {} - - if table := db.table("Config"): - for row in table.rows(): - component_name = row.Component - config_group_name = row.ConfigGroup - value_group_name = row.Name - identifier_name = row.Identifier - - if component_name not in store: - store[component_name] = {} - component = store[component_name] - - if config_group_name not in component: - component[config_group_name] = {} - config_group = component[config_group_name] - - if value_group_name not in config_group: - config_group[value_group_name] = {} - value_group = config_group[value_group_name] - - if identifier_name not in value_group: - value_group[identifier_name] = {} - identifier = value_group[identifier_name] - - identifier["modified_time"] = row.ModifiedTime - identifier["creation_time"] = row.CreationTime - identifier["version"] = row.Version - identifier["success"] = row.Success - identifier["auto_conf_value"] = jsonlib.loads(row.AutoConfValue) if row.AutoConfValue else None - identifier["user_value"] = jsonlib.loads(row.UserValue) if row.UserValue else None - identifier["vital_value"] = jsonlib.loads(row.VitalValue) if row.VitalValue else None - identifier["cached_value"] = jsonlib.loads(row.CachedValue) if row.CachedValue else None - identifier["desired_value"] = jsonlib.loads(row.DesiredValue) if row.DesiredValue else None - identifier["revision"] = row.Revision - - return store + with SQLite3(path) as db: + store = {} + + if table := db.table("Config"): + for row in table.rows(): + component_name = row.Component + config_group_name = row.ConfigGroup + value_group_name = row.Name + identifier_name = row.Identifier + + if component_name not in store: + store[component_name] = {} + component = store[component_name] + + if config_group_name not in component: + component[config_group_name] = {} + config_group = component[config_group_name] + + if value_group_name not in config_group: + config_group[value_group_name] = {} + value_group = config_group[value_group_name] + + if identifier_name not in value_group: + value_group[identifier_name] = {} + identifier = value_group[identifier_name] + + identifier["modified_time"] = row.ModifiedTime + identifier["creation_time"] = row.CreationTime + identifier["version"] = row.Version + identifier["success"] = row.Success + identifier["auto_conf_value"] = jsonlib.loads(row.AutoConfValue) if row.AutoConfValue else None + identifier["user_value"] = jsonlib.loads(row.UserValue) if row.UserValue else None + identifier["vital_value"] = jsonlib.loads(row.VitalValue) if row.VitalValue else None + identifier["cached_value"] = jsonlib.loads(row.CachedValue) if row.CachedValue else None + identifier["desired_value"] = jsonlib.loads(row.DesiredValue) if row.DesiredValue else None + identifier["revision"] = row.Revision + + return store diff --git a/dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py b/dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py index 29e2f63a06..915d335d81 100644 --- a/dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py +++ b/dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py @@ -66,39 +66,38 @@ def os(self) -> str: def _create_pmxcfs(fh: Path, hostname: str | None = None) -> VirtualFilesystem: # https://pve.proxmox.com/wiki/Proxmox_Cluster_File_System_(pmxcfs) - db = SQLite3(fh) - - entries = {row.inode: row for row in db.table("tree")} - - vfs = VirtualFilesystem() - for entry in entries.values(): - if entry.type == DT_DIR: - cls = ProxmoxConfigDirectoryEntry - elif entry.type == DT_REG: - cls = ProxmoxConfigFileEntry - else: - raise ValueError(f"Unknown pmxcfs file type: {entry.type}") - - parts = [] - current = entry - while current.parent != 0: + with SQLite3(fh) as db: + entries = {row.inode: row for row in db.table("tree")} + + vfs = VirtualFilesystem() + for entry in entries.values(): + if entry.type == DT_DIR: + cls = ProxmoxConfigDirectoryEntry + elif entry.type == DT_REG: + cls = ProxmoxConfigFileEntry + else: + raise ValueError(f"Unknown pmxcfs file type: {entry.type}") + + parts = [] + current = entry + while current.parent != 0: + parts.append(current.name) + current = entries[current.parent] parts.append(current.name) - current = entries[current.parent] - parts.append(current.name) - path = "/".join(parts[::-1]) - vfs.map_file_entry(path, cls(vfs, path, entry)) + path = "/".join(parts[::-1]) + vfs.map_file_entry(path, cls(vfs, path, entry)) - if hostname: - node_root = vfs.path(f"nodes/{hostname}") - vfs.symlink(str(node_root), "local") - vfs.symlink(str(node_root / "lxc"), "lxc") - vfs.symlink(str(node_root / "openvz"), "openvz") - vfs.symlink(str(node_root / "qemu-server"), "qemu-server") + if hostname: + node_root = vfs.path(f"nodes/{hostname}") + vfs.symlink(str(node_root), "local") + vfs.symlink(str(node_root / "lxc"), "lxc") + vfs.symlink(str(node_root / "openvz"), "openvz") + vfs.symlink(str(node_root / "qemu-server"), "qemu-server") - # TODO: .version, .members, .vmlist, maybe .clusterlog and .rrd? + # TODO: .version, .members, .vmlist, maybe .clusterlog and .rrd? - return vfs + return vfs class ProxmoxConfigFileEntry(VirtualFile): diff --git a/dissect/target/plugins/os/windows/activitiescache.py b/dissect/target/plugins/os/windows/activitiescache.py index 1530afe576..ebdbad3319 100644 --- a/dissect/target/plugins/os/windows/activitiescache.py +++ b/dissect/target/plugins/os/windows/activitiescache.py @@ -120,40 +120,39 @@ def activitiescache(self) -> Iterator[ActivitiesCacheRecord]: clipboard_payload (string): ClipboardPayload field. """ for user, cache_file in self.cachefiles: - db = SQLite3(cache_file) - - if table := db.table("Activity"): - for r in table.rows(): - yield ActivitiesCacheRecord( - start_time=mkts(r["[StartTime]"]), - end_time=mkts(r["[EndTime]"]), - last_modified_time=mkts(r["[LastModifiedTime]"]), - last_modified_on_client=mkts(r["[LastModifiedOnClient]"]), - original_last_modified_on_client=mkts(r["[OriginalLastModifiedOnClient]"]), - expiration_time=mkts(r["[ExpirationTime]"]), - activity_id=r["[Id]"].hex(), - app_id=r["[AppId]"], - enterprise_id=r["[EnterpriseId]"] or None, - app_activity_id=r["[AppActivityId]"], - group_app_activity_id=r["[GroupAppActivityId]"] or None, - group=r["[Group]"], - activity_type=r["[ActivityType]"], - activity_status=r["[ActivityStatus]"], - activity_priority=r["[Priority]"], - match_id=r["[MatchId]"], - etag=r["[ETag]"], - tag=r["[Tag]"], - is_local_only=r["[IsLocalOnly]"], - created_in_cloud=r["[CreatedInCloud]"], - platform_device_id=r["[PlatformDeviceId]"], - package_id_hash=r["[PackageIdHash]"], - payload=r["[Payload]"], - original_payload=r["[OriginalPayload]"], - clipboard_payload=r["[ClipboardPayload]"], - source=cache_file, - _target=self.target, - _user=user, - ) + with SQLite3(cache_file) as db: + if table := db.table("Activity"): + for r in table.rows(): + yield ActivitiesCacheRecord( + start_time=mkts(r["[StartTime]"]), + end_time=mkts(r["[EndTime]"]), + last_modified_time=mkts(r["[LastModifiedTime]"]), + last_modified_on_client=mkts(r["[LastModifiedOnClient]"]), + original_last_modified_on_client=mkts(r["[OriginalLastModifiedOnClient]"]), + expiration_time=mkts(r["[ExpirationTime]"]), + activity_id=r["[Id]"].hex(), + app_id=r["[AppId]"], + enterprise_id=r["[EnterpriseId]"] or None, + app_activity_id=r["[AppActivityId]"], + group_app_activity_id=r["[GroupAppActivityId]"] or None, + group=r["[Group]"], + activity_type=r["[ActivityType]"], + activity_status=r["[ActivityStatus]"], + activity_priority=r["[Priority]"], + match_id=r["[MatchId]"], + etag=r["[ETag]"], + tag=r["[Tag]"], + is_local_only=r["[IsLocalOnly]"], + created_in_cloud=r["[CreatedInCloud]"], + platform_device_id=r["[PlatformDeviceId]"], + package_id_hash=r["[PackageIdHash]"], + payload=r["[Payload]"], + original_payload=r["[OriginalPayload]"], + clipboard_payload=r["[ClipboardPayload]"], + source=cache_file, + _target=self.target, + _user=user, + ) def mkts(ts: int) -> datetime | None: diff --git a/dissect/target/plugins/os/windows/notifications.py b/dissect/target/plugins/os/windows/notifications.py index e789246c73..c1fffdb839 100644 --- a/dissect/target/plugins/os/windows/notifications.py +++ b/dissect/target/plugins/os/windows/notifications.py @@ -444,50 +444,50 @@ def wpndatabase(self) -> Iterator[WpnDatabaseNotificationRecord | WpnDatabaseNot target_tz = self.target.datetime.tzinfo for user, wpndatabase in self.wpndb_files: - db = SQLite3(wpndatabase) - handlers = {} - - if table := db.table("NotificationHandler"): - for row in table.rows(): - handlers[row["[RecordId]"]] = WpnDatabaseNotificationHandlerRecord( - created_time=datetime.datetime.strptime(row["[CreatedTime]"], "%Y-%m-%d %H:%M:%S").replace( - tzinfo=target_tz - ), - modified_time=datetime.datetime.strptime(row["[ModifiedTime]"], "%Y-%m-%d %H:%M:%S").replace( - tzinfo=target_tz - ), - id=row["[RecordId]"], - primary_id=row["[PrimaryId]"], - wns_id=row["[WNSId]"], - handler_type=row["[HandlerType]"], - wnf_event_name=row["[WNFEventName]"], - system_data_property_set=row["[SystemDataPropertySet]"], - _target=self.target, - _user=user, - ) - - if table := db.table("Notification"): - for row in table.rows(): - record = WpnDatabaseNotificationRecord( - arrival_time=wintimestamp(row["[ArrivalTime]"]), - expiry_time=wintimestamp(row["[ExpiryTime]"]), - order=row["[Order]"], - id=row["[Id]"], - handler_id=row["[HandlerId]"], - activity_id=UUID(bytes=row["[ActivityId]"]), - type=row["[Type]"], - payload_data=row["[Payload]"], - payload_type=row["[PayloadType]"], - tag=row["[Tag]"], - group=row["[Group]"], - boot_id=row["[BootId]"], - expires_on_reboot=row["[ExpiresOnReboot]"] != "FALSE", - _target=self.target, - _user=user, - ) - handler = handlers.get(row["[HandlerId]"]) - - if handler: - yield GroupedRecord("windows/notification/wpndatabase/grouped", [record, handler]) - else: - yield record + with SQLite3(wpndatabase) as db: + handlers = {} + + if table := db.table("NotificationHandler"): + for row in table.rows(): + handlers[row["[RecordId]"]] = WpnDatabaseNotificationHandlerRecord( + created_time=datetime.datetime.strptime(row["[CreatedTime]"], "%Y-%m-%d %H:%M:%S").replace( + tzinfo=target_tz + ), + modified_time=datetime.datetime.strptime(row["[ModifiedTime]"], "%Y-%m-%d %H:%M:%S").replace( + tzinfo=target_tz + ), + id=row["[RecordId]"], + primary_id=row["[PrimaryId]"], + wns_id=row["[WNSId]"], + handler_type=row["[HandlerType]"], + wnf_event_name=row["[WNFEventName]"], + system_data_property_set=row["[SystemDataPropertySet]"], + _target=self.target, + _user=user, + ) + + if table := db.table("Notification"): + for row in table.rows(): + record = WpnDatabaseNotificationRecord( + arrival_time=wintimestamp(row["[ArrivalTime]"]), + expiry_time=wintimestamp(row["[ExpiryTime]"]), + order=row["[Order]"], + id=row["[Id]"], + handler_id=row["[HandlerId]"], + activity_id=UUID(bytes=row["[ActivityId]"]), + type=row["[Type]"], + payload_data=row["[Payload]"], + payload_type=row["[PayloadType]"], + tag=row["[Tag]"], + group=row["[Group]"], + boot_id=row["[BootId]"], + expires_on_reboot=row["[ExpiresOnReboot]"] != "FALSE", + _target=self.target, + _user=user, + ) + handler = handlers.get(row["[HandlerId]"]) + + if handler: + yield GroupedRecord("windows/notification/wpndatabase/grouped", [record, handler]) + else: + yield record diff --git a/dissect/target/plugins/os/windows/search.py b/dissect/target/plugins/os/windows/search.py index 6f3e66ba5d..1f76e94509 100644 --- a/dissect/target/plugins/os/windows/search.py +++ b/dissect/target/plugins/os/windows/search.py @@ -166,35 +166,34 @@ def parse_esedb(self, path: Path, user_details: UserDetails | None) -> Iterator[ def parse_sqlite(self, path: Path, user_details: UserDetails | None) -> Iterator[SearchIndexRecords]: """Parse the SQLite3 ``SystemIndex_1_PropertyStore`` table.""" - db = SQLite3(path) - - # ``ColumnId`` is translated using the ``SystemIndex_1_PropertyStore_Metadata`` table. - columns = { - row.get("Id"): row.get("UniqueKey", "").split("-", maxsplit=1)[-1] - for row in db.table("SystemIndex_1_PropertyStore_Metadata").rows() - } - - if not (table := db.table("SystemIndex_1_PropertyStore")): - self.target.log.warning("Database %s does not have a table called 'SystemIndex_1_PropertyStore'", path) - return - - current_work_id = None - values = {} - - for row in table.rows(): - work_id = row.get("WorkId") - if current_work_id is None: - current_work_id = work_id - if work_id != current_work_id: - yield from self.build_record(values, user_details, path) - current_work_id = work_id - values = {} - - if value := row.get("Value"): - column_name = columns[row.get("ColumnId")] - values[column_name] = value - - yield from self.build_record(values, user_details, path) + with SQLite3(path) as db: + # ``ColumnId`` is translated using the ``SystemIndex_1_PropertyStore_Metadata`` table. + columns = { + row.get("Id"): row.get("UniqueKey", "").split("-", maxsplit=1)[-1] + for row in db.table("SystemIndex_1_PropertyStore_Metadata").rows() + } + + if not (table := db.table("SystemIndex_1_PropertyStore")): + self.target.log.warning("Database %s does not have a table called 'SystemIndex_1_PropertyStore'", path) + return + + current_work_id = None + values = {} + + for row in table.rows(): + work_id = row.get("WorkId") + if current_work_id is None: + current_work_id = work_id + if work_id != current_work_id: + yield from self.build_record(values, user_details, path) + current_work_id = work_id + values = {} + + if value := row.get("Value"): + column_name = columns[row.get("ColumnId")] + values[column_name] = value + + yield from self.build_record(values, user_details, path) def build_record( self, values: dict[str, Any] | TableRecord, user_details: UserDetails | None, db_path: Path From 2b48e825561db34be4962b58e48ac1ff43858728 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:47:01 +0100 Subject: [PATCH 15/17] Update pyproject.toml --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5a8afef192..ba068b4612 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ dependencies = [ "defusedxml", "dissect.cstruct>=4,<5", - "dissect.database>=1,<2", + "dissect.database>=1.1.dev3,<2", # TODO: update on release! "dissect.eventlog>=3,<4", "dissect.evidence>=3.13.dev2,<4", # TODO: update on release! "dissect.hypervisor>=3.20,<4", @@ -84,7 +84,7 @@ dev = [ "dissect.clfs[dev]>=1.0.dev,<2.0.dev", "dissect.cramfs[dev]>=1.0.dev,<2.0.dev", "dissect.cstruct>=4.0.dev,<5.0.dev", - "dissect.database[dev]>=1.0.dev,<2.0.dev", + "dissect.database[dev]>=1.1.dev3,<2.0.dev", # TODO: update on release! "dissect.etl[dev]>=3.0.dev,<4.0.dev", "dissect.eventlog[dev]>=3.0.dev,<4.0.dev", "dissect.evidence[dev]>=3.13.dev2,<4.0.dev", From 107e29dbe8d14256ab9c9fdaa5c9e1cbdbc052a8 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 12 Jan 2026 10:24:32 +0100 Subject: [PATCH 16/17] fix linter --- dissect/target/plugins/apps/browser/firefox.py | 7 +++++-- dissect/target/plugins/os/windows/notifications.py | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/dissect/target/plugins/apps/browser/firefox.py b/dissect/target/plugins/apps/browser/firefox.py index 655ddecedd..3bf5eeee38 100644 --- a/dissect/target/plugins/apps/browser/firefox.py +++ b/dissect/target/plugins/apps/browser/firefox.py @@ -696,14 +696,17 @@ def decrypt_master_key(key4_file: Path, primary_password: bytes) -> bytes: if master_key_cka != CKA_ID: raise ValueError( - f"Password master key CKA_ID '{master_key_cka}' is not equal to expected value '{CKA_ID}' in {key4_file}" + f"Password master key CKA_ID '{master_key_cka}' " + f"is not equal to expected value '{CKA_ID}' in {key4_file}" ) decoded_password_check: core.Sequence = core.load(password_check) decoded_master_key: core.Sequence = core.load(master_key) try: - decrypted_password_check, algorithm = _decrypt_master_key(decoded_password_check, primary_password, global_salt) + decrypted_password_check, algorithm = _decrypt_master_key( + decoded_password_check, primary_password, global_salt + ) except EOFError: raise ValueError("No primary password provided") diff --git a/dissect/target/plugins/os/windows/notifications.py b/dissect/target/plugins/os/windows/notifications.py index c1fffdb839..6ccc35ce37 100644 --- a/dissect/target/plugins/os/windows/notifications.py +++ b/dissect/target/plugins/os/windows/notifications.py @@ -453,9 +453,9 @@ def wpndatabase(self) -> Iterator[WpnDatabaseNotificationRecord | WpnDatabaseNot created_time=datetime.datetime.strptime(row["[CreatedTime]"], "%Y-%m-%d %H:%M:%S").replace( tzinfo=target_tz ), - modified_time=datetime.datetime.strptime(row["[ModifiedTime]"], "%Y-%m-%d %H:%M:%S").replace( - tzinfo=target_tz - ), + modified_time=datetime.datetime.strptime( + row["[ModifiedTime]"], "%Y-%m-%d %H:%M:%S" + ).replace(tzinfo=target_tz), id=row["[RecordId]"], primary_id=row["[PrimaryId]"], wns_id=row["[WNSId]"], From 10d0cb99b22252c5291226f2266d727c893252c2 Mon Sep 17 00:00:00 2001 From: Pim Sanders <36573021+PimSanders@users.noreply.github.com> Date: Mon, 12 Jan 2026 10:57:56 +0100 Subject: [PATCH 17/17] use noqa --- dissect/target/plugins/apps/browser/firefox.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dissect/target/plugins/apps/browser/firefox.py b/dissect/target/plugins/apps/browser/firefox.py index 3bf5eeee38..f40fd6dd27 100644 --- a/dissect/target/plugins/apps/browser/firefox.py +++ b/dissect/target/plugins/apps/browser/firefox.py @@ -696,8 +696,7 @@ def decrypt_master_key(key4_file: Path, primary_password: bytes) -> bytes: if master_key_cka != CKA_ID: raise ValueError( - f"Password master key CKA_ID '{master_key_cka}' " - f"is not equal to expected value '{CKA_ID}' in {key4_file}" + f"Password master key CKA_ID '{master_key_cka}' is not equal to expected value '{CKA_ID}' in {key4_file}" # noqa: E501 ) decoded_password_check: core.Sequence = core.load(password_check)