[high] fix(chainsaw): index the detections, not just how many there were - #127
Draft
elhoim wants to merge 1 commit into
Draft
[high] fix(chainsaw): index the detections, not just how many there were#127elhoim wants to merge 1 commit into
elhoim wants to merge 1 commit into
Conversation
run_chainsaw indexed only a summary -- "Total findings: 3" and the per-level counts. The detections themselves (rule names, computers, event IDs, MITRE techniques, SRUM and timeline entries) went to tool_response but never to extract_and_index, and tool_response returns a compact preview once source is set. A later search() over the case could not find a single Chainsaw detection by rule name. The same defect had a second half: the parsers truncated their record lists -- detections[:500], srum_entries[:500], timeline_entries[:1000] -- before the caller ever saw them, so on a busy host the records past the cap could not be indexed even in principle. Format one searchable line per record and index all of them, then cap only what goes into the response, flagging it with <key>_truncated. The true total_findings / total_entries count is unchanged and still reflects everything Chainsaw produced. The formatter is a module-local helper on purpose: the fields worth indexing differ per tool, so this is not a shared cross-cutting abstraction. 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_chainsawindexed only a summary —Total findings: 3and the per-level counts — so a latersearch()cannot find a Chainsaw detection by rule name, host, event ID or MITRE technique.tool_response, but withsourceset that returns a compact preview and drops the rest. So the detections reached neither the case DB nor, in full, the agent.detections[:500],srum_entries[:500],timeline_entries[:1000]) before the caller saw them. On a busy host, records past the cap could not be indexed even in principle.<key>_truncated. The cap belongs on what is returned, never on what is stored.src/mulder/server/tools/chainsaw.pyonly. The formatter is a module-local helper, not a shared abstraction.The bug
Everything indexed is a count. A hunt that fires
Suspicious PowerShell Encoded CommandonWORKSTATION-07stores the sentence "Total findings: 1" and nothing an analyst would actually search for.And in the parsers, upstream of all of it:
The fix
Records are no longer truncated in the parsers. The caller formats and indexes every one, then caps the response:
_detection_linesis deliberately module-local. The fields worth indexing differ per tool — a Chainsaw detection is not shaped like a Zircolite one — so a shared formatter would be a cross-cutting abstraction with no second honest caller. Response size is unchanged at 500 records; the difference is that the other 100 of a 600-detection hunt are now in the case DB instead of discarded.Deliberately out of scope
tool_responseenvelope. Making a success response carry more than a preview is a separate concern and is not proposed here — indexing is the right place for the full records, which is exactly what this PR does.--mapping, and srum for a rejected--jsonplus a missing--software. Those are separate PRs against separate subcommands. This PR is about what happens to detections once Chainsaw does produce them.fix/chainsaw-exit-code) covers that; independent of this and also branched offmain.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→ 861 passed, nothing deselected, nothing skipped.chainsaw.pyrestored toorigin/mainand the new tests kept, 4 of 6 fail, all behaviourally (no import errors, no signature changes involved):The two that pass on both trees are the narrowness pins: the existing summary lines are still indexed, and a hunt with zero detections is still a successful, indexed result.
test_the_response_is_still_cappedis the one that guards against over-correcting — it asserts the response holds exactly 500 records andtotal_findings == 600, while detection 599 is present in the indexed text.Context
Recovered from closed PR #74, which made this change across chainsaw, zircolite, aleapp and ileapp at once behind a shared record-formatting helper. This is one tool, and the helper is module-local — the rejected outcome framework is not reintroduced, and there is no
classify_tool_exitor second status taxonomy here, per the review on #32:Branched fresh from current
main(2e5432c); the closed branch was not revised in place, and this uses a new branch name rather than reusing the closed one.🤖 Generated with Claude Code