[high] fix(tsk): stop every fls-driven extractor skipping deleted files - #121
Draft
elhoim wants to merge 1 commit into
Draft
[high] fix(tsk): stop every fls-driven extractor skipping deleted files#121elhoim wants to merge 1 commit into
elhoim wants to merge 1 commit into
Conversation
fls marks a deleted entry with a `*` token placed after the type pair and
before the inode:
r/r * 22: Windows/System32/winevt/Logs/Deleted.evtx
Eleven copies of a row regex across six modules spelled that as
`[rd]/[rd*]`, treating `*` as a character inside the type pair. The pattern
therefore expected a digit where real output has `*`, and every deleted
entry failed to match. Undeleted files in the same directory matched fine,
so the loss was silent -- a cleared Security.evtx recoverable as a deleted
entry was never handed to icat.
The same spelling also restricted the type characters to r and d, dropping
symlinks (l/l) and the virtual $OrphanFiles node (V/V) that holds recovered
deleted content.
Add one parse_fls_rows to mulder.patterns, mirroring the parse_mmls_rows
introduced in calebevans#68, and point all eleven call sites at it. artifacts.py's
_find_inodes_by_pattern becomes _find_inodes_by_path: callers now supply
only a path pattern, so the row grammar is stated once.
Verified against The Sleuth Kit 4.12.1 on a purpose-built ext4 image with
two deleted files; the old regex misses both, the new parser reads all
fourteen rows.
Also removes a dead size filter in app_files.py. It searched fls output for
a size field between two tabs, but `fls -r -p` emits no size column, so
max_file_size_kb could never take effect. The threshold now applies to the
bytes icat returns, where the size is actually known.
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
Security.evtx, recoverable as a deleted entry, was never handed toicat.flsprints the deleted marker as a separate*token after the type pair, before the inode. Eleven copies of a row regex across six modules spelled that as[rd]/[rd*], treating*as a character inside the type pair — so the pattern expected a digit where real output has*.randd, dropping symlinks (l/l) and the virtual$OrphanFilesnode (V/V) that holds recovered deleted content.parse_fls_rowsinmulder/patterns.py, mirroring theparse_mmls_rowsmerged in [high] fix(tsk): correct mmls partition-row regex and share one parser #68, with all eleven call sites pointed at it.The bug
Genuine
fls -r -poutput, The Sleuth Kit 4.12.1, from a purpose-built ext4 image with two deleted files:The regex, repeated eleven times:
r"^[rd]/[rd*]\s+(\d+(?:-\d+-\d+)?):\s+(.+)\s*$"Run against that output, it matches ten of the fourteen rows and misses:
Security.evtxandSystem.evtxin the same directory matched, which is why nothing ever looked wrong.The eleven copies, all identical in substance:
The fix
One parser in
mulder/patterns.py, directly alongsideparse_mmls_rows:FlsEntrycarriesdeletedas data rather than discarding it, and exposesbase_inode(the NTFS6083-128-1form reduced to6083, which is whaticatwants).fls_file_entriesis the extractor-facing view: directories dropped, everything else kept — including-(unknown) types, which is how orphaned files present.The separator between inode and path is a literal tab, which is what makes paths containing spaces safe to parse.
In
artifacts.py,_find_inodes_by_patternbecomes_find_inodes_by_path— callers now pass only a path pattern:The row grammar is stated once, so a future fix lands in one place.
One adjacent dead check, removed
app_files.pyfiltered by size using_FLS_SIZE_RE = re.compile(r"\t(\d+)\t")against the fls listing.fls -r -pemits no size column — every row has exactly one tab — somax_file_size_kbcould never fire. Verified against the real output above: zero matches.Removing the duplicated row regex forces the issue, so leaving a provably-dead filter in place was not an option. The threshold now applies to the bytes
icatreturns, where the size is actually known. The repo's own test documented the inertness —test_size_limit_with_tab_delimited_sizesasserted all three files matched despite its name — and is renamed to say what it checks.Deliberately out of scope
fls -r -mbodyfile output (run_mactime) is a different, pipe-delimited format and is untouched.disk.pyandserver/tools/tsk.pyneeded no change here.Verification
mkfs.ext4 -d, deleted two files withdebugfs(no mount, no root), and ran the realflsfrom The Sleuth Kit 4.12.1. Both the broken and fixed patterns were run against that captured output; the fixture intests/test_fls_parsing.pyis that output verbatim.uvx pre-commit run --all-files— ruff, ruff-format, mypy all pass. (Staged first:--all-filesenumerates viagit ls-filesand cannot see untracked files.)pytest tests/ -q— 874 passed, 0 failed. No deselect was needed;test_disk_pcap.py::...::test_size_filtering_skips_large_pcapspasses here, so this branch is green on the full suite.src/mulder/server/tools/extract/evtx.pyrestored toorigin/mainand the new tests kept, the behavioural tests fail on the assertion (not on an import or signature mismatch, because_extract_evtx_from_image's signature is unchanged):The two that pass on both trees are the narrowness tests — the undeleted sibling still extracts, directories still are not — so they pin the fix rather than the bug.
test_the_old_spelling_really_did_miss_themembeds the previous regex directly, so the premise stays pinned even after the old spelling is gone from the tree.Context
Recovered from closed PR #77. That branch was stacked on #68 and carried its mmls work; #68 has since merged, so this is rebuilt fresh from current
main(2e5432c) with only the fls half — and re-verified against the real tool rather than cherry-picked.This is the same shape as #68, which was merged: one shared parser replacing duplicated regexes that had each inherited the same misreading of a TSK output format. It is a single concern, not a framework. Checked against all fifteen open PRs: only #86 touches
artifacts.py, for read-only SQLite URI escaping — a disjoint concern, different lines, no conflict.🤖 Generated with Claude Code