The vulnerability is caused by trusting attacker-controlled snapshot paths restored from backup files.
The vulnerable flow starts in the backup restore logic. When a backup ZIP is restored, the application extracts the archive and copies each restored watch UUID directory directly into the live datastore using shutil.copytree(entry.path, dst_dir). This preserves attacker-controlled files inside the restored watch directory, including history.txt.
Relevant code:
After restore, the application parses history.txt in the watch history property. This is the core trust-boundary issue.
Relevant code:
The relevant logic is effectively:
if os.sep not in v and '/' not in v and '\\' not in v:
v = os.path.join(self.data_dir, v)
else:
snapshot_fname = os.path.basename(v)
proposed_new_path = os.path.join(self.data_dir, snapshot_fname)
if not os.path.exists(v) and os.path.exists(proposed_new_path):
v = proposed_new_path
This has the following security consequence:
history.txt value is only a filename, it is resolved safely under self.data_dir.0.55.1Exploitability
AV:NAC:LPR:NUI:NScope
S:UImpact
C:HI:NA:N7.5/CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N