java-topology/defects/calibre/TICKET.md
russell@unturf.com fac760d8f1 calibre: 5-MOAD scan; calibre-0003 CWE-407 depth_first O(L*F) 38x, calibre-0004 CWE-407 find_links O(L^2) 124x
MOAD-0001 CWE-407 — 2 new defects:
- calibre-0003: depth_first() in src/calibre/ebooks/html/input.py calls flat.index(link)
  inside a while loop over all ebook links. O(L * F) where L = links, F = flat list size.
  Fix: pre-build flat_map = {hf: hf for hf in flat} before loop, O(1) lookup.
  Measured: 38x at F=500 files (linear chain), 13.5x at F=200.
- calibre-0004: HTMLFile.find_links() uses list membership for dedup: `if link not in
  self.links`. O(L^2) per HTML chapter file. Fix: parallel _links_seen set.
  Measured: 124x at L=1000 unique links, 68x at L=500.

MOAD-0002/0003/0004/0005 — CLEAN. See TICKET.md for full analysis.
2026-04-03 13:49:57 -04:00

4 KiB

Calibre 5-MOAD Scan

Target: Calibre (kovidgoyal/calibre) — Python ebook manager
Scan date: 2026-04-03
Scanner: agent blackops


MOAD-0001 CWE-407 Results

calibre-0001 (pre-existing) — series index scan O(S)

  • File: src/calibre/db/__init__.py
  • Function: _get_next_series_num_for_list
  • Pattern: if i not in series_indices where series_indices is a list, scanned up to 10,000 times
  • Severity: MEDIUM (250x at S=500)
  • Fix: convert to set before the scan loop
  • Patch: patch/calibre-0001-series-index-list-membership.patch

calibre-0002 (pre-existing) — Google metadata tag dedup O(T^2)

  • File: src/calibre/ebooks/metadata/sources/google.py
  • Function: to_metadata
  • Pattern: if tag not in tags: tags.append(tag) — tags is a list
  • Severity: LOW-MEDIUM (250x at T=500)
  • Fix: maintain tags_seen set alongside the list
  • Patch: patch/calibre-0002-google-metadata-tag-dedup.patch

calibre-0003 (NEW) — HTML ebook traversal flat.index() O(L*F)

  • File: src/calibre/ebooks/html/input.py
  • Function: depth_first
  • Pattern: flat.index(link) inside a while loop — flat is the full list of HTMLFile objects
  • Total complexity: O(L * F) where L = links traversed, F = flat list size
  • Severity: MEDIUM (38x at F=500 files, linear chain)
  • Fix: pre-build flat_map = {hf: hf for hf in flat} before loop, use flat_map.get(link) for O(1) lookup
  • Patch: patch/calibre-0003-html-depth-first-flat-index.patch
  • Test: test/test_calibre_0003.py — PASS (38x at n=500)
  • File: src/calibre/ebooks/html/input.py
  • Class: HTMLFile
  • Method: find_links
  • Pattern: if link not in self.links: self.links.append(link) — self.links is a list
  • Severity: LOW-MEDIUM (124x at L=1000 unique links)
  • Fix: add self._links_seen = set(), check/add to set instead of list
  • Note: Link.hash is defined via path, so it is safely hashable
  • Patch: patch/calibre-0004-htmlfile-links-dedup.patch
  • Test: test/test_calibre_0004.py — PASS (124x at n_unique=1000)

MOAD-0002 Intertangle — CLEAN

Our customize/ui.py uses global plugin registries (_initialized_plugins, _on_import, etc.) but these are write-once at startup and read-only afterward. No cross-subsystem coupling through shared mutable global state during request processing.

Our db/cache.py properly separates read/write APIs with lock decorators (@read_api, @write_api) and uses vls_cache_lock for our VLS cache.


MOAD-0003 Leaked Context — CLEAN

Two threading.local usages found:

  1. calibre/spell/break_iterator.pyPerThreadIterators holds ICU break iterator objects per thread. These are per-thread ICU resources (not request-scoped identity). Correct use: thread pools reuse iterators.

  2. calibre/utils/icu.pyThreadLocalCollatorCache caches ICU collator objects per thread. Same pattern, correct use.

No ThreadLocal holding request-scoped user identity or session context. No MOAD-0003 defect.


MOAD-0004 Logged Secret — CLEAN

Our content server auth (srv/auth.py) does not log our Authorization header content. Failed login attempts log only our client IP address. Our AI backends pass API keys in HTTP headers but exception messages from urllib do not include request headers. Our admin CLI tool (srv/manage_users_cli.py) has an intentional show_password action in an interactive TUI — this is by design for admin tooling, not a logging defect.


MOAD-0005 Thundering Herd — CLEAN

Our db/cache.py VLS cache uses self.vls_cache_lock = Lock() protecting vls_for_books_cache. Our read/write API decorators use RLock-based locking throughout. Our ebooks/unihandecode/jadecoder.py uses correct double-checked locking with an explicit with self._lock. No unprotected lazy-init cache patterns found.


Summary

MOAD Result
0001 CWE-407 4 defects (2 pre-existing, 2 new)
0002 Intertangle CLEAN
0003 Leaked Context CLEAN
0004 Logged Secret CLEAN
0005 Thundering Herd CLEAN