[high] fix(mvt): index every detection MVT counted, not just the first 100 - #126
Draft
elhoim wants to merge 1 commit into
Draft
[high] fix(mvt): index every detection MVT counted, not just the first 100#126elhoim wants to merge 1 commit into
elhoim wants to merge 1 commit into
Conversation
_collect_mvt_results reported module_counts[module] = len(data) -- the number surfaced to the analyst as "detections" and "total_indicators" -- but appended only the first 100 records to the indexed text, and only those that were dicts. Records past that ceiling, and any non-dict record, were counted but never reached the case DB, so search() could not surface them and nothing in the response said they had been dropped. A device reported as having 250 spyware detections held 100 that an examiner could actually find. The ceiling was an oversight rather than a resource control: the timeline CSVs collected in the same function are indexed whole, uncapped, so the only capped data was the detection records themselves. Index every counted record. module_counts and the indexed content now agree by construction, which is the invariant the tests assert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BLUF
run_mvt_android/run_mvt_iosreport a detection count the case DB cannot honour. A device reported as having 250 spyware detections holds 100 that an examiner can actually find withsearch()._collect_mvt_resultssetsmodule_counts[module] = len(data)— the number surfaced asdetectionsandtotal_indicators— but appends onlydata[:100]to the indexed text, and only records that happen to bedicts.module_countsand the indexed content agree by construction.src/mulder/server/tools/mvt.py, one function, +5/-3. No shared helper, no new abstraction.The bug
raw_outputthen goes straight toextract_and_indexwith no further cap, sodata[:100]is the only place evidence is lost — and it is upstream of the index, not a display limit.Two distinct ways a counted record fails to arrive:
Immediately below, in the same function, the timeline CSVs are indexed with no cap at all:
That asymmetry is what identifies
[:100]as an accident.The fix
The invariant is now testable directly, and one test asserts exactly it:
sum(module_counts.values()) == len(raw_output.splitlines()).Deliberately out of scope
format_recordsrefactor from closed PR [high] fix(index): index the detections, not just how many there were #74. That branch added a shared formatter inextract_helpers.pyand rewired nine tool modules through it. That is the cross-cutting shape the maintainer declined in [critical][feature] 0.2 — Introduce typed tool outcomes and coverage metadata #32, so this PR keeps the change insidemvt.pyand touches no shared helper.fix/mvt-exit-code) handles a failed MVT run — it errors when MVT exits non-zero having produced nothing, and stops MVT's stderr being indexed as device evidence. This PR is the success path: MVT ran, produced findings, and the findings were partly discarded. The two are complementary and touch different code — [high] fix(mvt): report a failed MVT run instead of a clean spyware scan #92 adds a guard at the call sites, this changes the body of_collect_mvt_results. Verified conflict-free withgit merge-tree --write-tree.run_photorecinextract/carving.py, the other module named in [high] fix(index): index the detections, not just how many there were #74. Checked and not defective for this concern: it indexes the fullreport.xmlwhen present, and otherwise indexes real file paths, capping at_FILE_LIST_CAP = 500with the truncation disclosed in the indexed text (... and N more). No silent loss, so no PR.Verification
uvx pre-commit run --all-files(new test staged first) → ruff, ruff-format, mypy all pass.uv run --locked --extra dev pytest tests/ -q→ 862 passed, 1 deselected (test_disk_pcap.py::...::test_size_filtering_skips_large_pcaps, which writes a real 200 MB file and fails identically on unmodifiedmainin this environment — a disk quota, not a regression).mvt.pyrestored toorigin/mainand the new tests kept, 4 of 8 fail and the 4 narrowness tests pass:The narrowness tests pin that the fix does not over-reach: a clean device still yields no output, timeline CSVs are still indexed whole, a malformed module file is still skipped, and records stay one-per-line so an embedded newline cannot split one across index windows.
Context
Recovered from closed PR #74 (
fix/summary-only-indexing). That branch was stacked on the exit-code work, so itsmvt.pydiff contained onlyclassify_tool_exitplumbing — already delivered separately as #92. This defect was found by reading today'smainrather than by replaying the closed diff, and is narrower than #74's framing: MVT does not index a bare summary, it indexes real records and then silently truncates them.No outcome framework and no
classify_tool_exitis reintroduced, per the review on #32:Branched fresh from current
main(2e5432c); the closed branch was not revised in place.🤖 Generated with Claude Code