Skip to content

Commit d7c14c5

Browse files
iamaeroplaneclaude
andcommitted
fix: validate top-level registry structure in get() and restore()
- get() now validates self.data["presets/extensions"] is a dict before accessing - restore() ensures presets/extensions dict exists before writing - Prevents crashes when registry JSON is parseable but has corrupted structure - Applied same fixes to both PresetRegistry and ExtensionRegistry for parity Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 71dd58a commit d7c14c5

2 files changed

Lines changed: 14 additions & 2 deletions

File tree

src/specify_cli/extensions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ def restore(self, extension_id: str, metadata: dict):
302302
"""
303303
if metadata is None or not isinstance(metadata, dict):
304304
raise ValueError(f"Cannot restore '{extension_id}': metadata must be a dict")
305+
# Ensure extensions dict exists (handle corrupted registry)
306+
if not isinstance(self.data.get("extensions"), dict):
307+
self.data["extensions"] = {}
305308
self.data["extensions"][extension_id] = copy.deepcopy(metadata)
306309
self._save()
307310

@@ -327,7 +330,10 @@ def get(self, extension_id: str) -> Optional[dict]:
327330
Returns:
328331
Deep copy of extension metadata, or None if not found or corrupted
329332
"""
330-
entry = self.data["extensions"].get(extension_id)
333+
extensions = self.data.get("extensions")
334+
if not isinstance(extensions, dict):
335+
return None
336+
entry = extensions.get(extension_id)
331337
# Return None for missing or corrupted (non-dict) entries
332338
if entry is None or not isinstance(entry, dict):
333339
return None

src/specify_cli/presets.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,9 @@ def restore(self, pack_id: str, metadata: dict):
322322
"""
323323
if metadata is None or not isinstance(metadata, dict):
324324
raise ValueError(f"Cannot restore '{pack_id}': metadata must be a dict")
325+
# Ensure presets dict exists (handle corrupted registry)
326+
if not isinstance(self.data.get("presets"), dict):
327+
self.data["presets"] = {}
325328
self.data["presets"][pack_id] = copy.deepcopy(metadata)
326329
self._save()
327330

@@ -337,7 +340,10 @@ def get(self, pack_id: str) -> Optional[dict]:
337340
Returns:
338341
Deep copy of preset metadata, or None if not found or corrupted
339342
"""
340-
entry = self.data["presets"].get(pack_id)
343+
packs = self.data.get("presets")
344+
if not isinstance(packs, dict):
345+
return None
346+
entry = packs.get(pack_id)
341347
# Return None for missing or corrupted (non-dict) entries
342348
if entry is None or not isinstance(entry, dict):
343349
return None

0 commit comments

Comments
 (0)