T09 · Insecure Skill Coding Practices
- Location
scripts/write_items.py:24- Finding
Backup Validation Does Not Protect the Database Being Modified
- Content
View full analysis
Vulnerability Details
File Location:
scripts/write_items.py, lines 24–26 and 312–322
Vulnerability Type: Backup safeguard bypass due to unbound source and target paths
Risk Level: MediumVulnerable Code
python parser.add_argument("--db", default=DEFAULT_DB, help="Path to zotero.sqlite") parser.add_argument("--backup", metavar="PATH", help="Backup the database to BACKUP_DIR before writing")python # All write operations require backup has_write = any([args.add_tag, args.add_note, args.set_field, args.new_item]) if has_write and not args.backup: print("ERROR: --backup DB_PATH is required before making any write changes.") print("Example: --backup \"E:\\Refer.Hub\\zotero.sqlite\"") sys.exit(1) # Backup if args.backup: backup_path = backup_db(args.backup) print(f"[WRITE MODE] Database: {args.db}") conn = sqlite3.connect(args.db, timeout=60)The backup function copies the independently supplied path:
python def backup_db(db_path): os.makedirs(BACKUP_DIR, exist_ok=True) ts = datetime.now().strftime("%Y%m%d_%H%M%S") backup_path = os.path.join(BACKUP_DIR, f"zotero_{ts}.sqlite") shutil.copy2(db_path, backup_path) print(f"[BACKUP] Created: {backup_path}") return backup_pathTechnical Analysis
The script requires the presence of
--backupbefore a write operation, but it does not bind that argument to the database specified by--db. The file copied bybackup_db(args.backup)can therefore differ from the file subsequently opened and modified bysqlite3.connect(args.db).This is a fail-open validation flaw. The safeguard verifies only that a backup argument was supplied, not that a recoverable copy of the write target was created. Path aliases, symbolic links, relative paths, or completely unrelated readable files can satisfy the check.
The behavior contradicts the documented guarantee that write operations always back up the affected Zotero databa ...[truncated 1689 chars]
- Remediation
View remediation
Remediation Suggestions
Make the database selected by
--dbthe sole authoritative backup source. The safest interface is to change--backupinto a Boolean flag:python parser.add_argument( "--backup", action="store_true", help="Back up the database selected by --db before writing" )Then back up
args.dbdirectly:python if has_write and not args.backup: parser.error("--backup is required for write operations") if has_write: backup_path = backup_db(args.db)Additional hardening should include:
- Resolve the source path with
os.path.realpath()orpathlib.Path.resolve()before both backup and connection operations. - If backward compatibility requires
--backup PATH, resolve bothargs.backupandargs.db, compare them, and reject any mismatch. - Verify that the source is a regular file and that the completed backup exists with a nonzero size before allowing writes.
- Prefer SQLite's
Connection.backup()API overshutil.copy2()so the backup remains transactionally consistent if the database is in WAL mode or another process has it open. - Generate collision-resistant backup names by including microseconds or using an exclusive temporary filename; the existing one-second timestamp can overwrite a backup created during the same second.
- Abort before opening the write connection if backup creation or verification fails.
- Add automated tests confirming that mismatched paths, aliases, symbolic links, and backup failures prevent all write operations.
- Resolve the source path with
