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.
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_indiceswhere 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, useflat_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)
calibre-0004 (NEW) — HTMLFile.find_links list dedup O(L^2)
- 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:
-
calibre/spell/break_iterator.py—PerThreadIteratorsholds ICU break iterator objects per thread. These are per-thread ICU resources (not request-scoped identity). Correct use: thread pools reuse iterators. -
calibre/utils/icu.py—ThreadLocalCollatorCachecaches 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 |