Repository navigation
Add snapshots and a diff that only confirms what it can - #52
Merged
Merged
Conversation
The canvas could only add a node's neighbors, one hop and untyped, with no sign when the fixed limit cut the result. An investigator following a suspected chain needs direction, types and caps, and needs to know when the answer is incomplete. Graph::explore walks breadth-first with one parameterized query per step, so caps and cancellation are enforced between queries, and says which cap cut the result. khonsu explore offers it to agents and scripts, and the inspector gets a panel with the hops to the selected node. gpui-graph cannot highlight a set of nodes, so showing a path narrows the canvas to it, and Back returns to earlier views. Refs #32 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A relationship on the canvas said nothing about where it came from. An investigator needs the source, the time and whether it was reported or worked out, and needs missing values stated rather than filled in. Each provenance field is explicitly known or unknown. khonsu evidence and explore --to give stable IDs, and the inspector shows the same record with buttons to copy it as JSON. Exports hold no source configuration, and secret-looking names and values are masked and listed. A sync does not rewrite an unchanged relationship, so its timestamp is named record_written_at, separate from the source's last successful sync. Refs #33 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add evidence records: what supports a node, relationship or path
An open security group rule or a walkable path isn't an actionable finding. An investigator needs to know why a resource was flagged, which fields say so, and what was never checked. khonsu exposure evaluates three documented rules. Each condition is met, not met or unknown and names the fields it read. The verdict follows from those alone: potential_exposure, insufficient_data or not_indicated. There is no score, and no verdict says reachable. The AWS plugin now reads route tables and IPv6 addresses, because a public address and an open rule mean little without a route. A record that should exist and doesn't gives unknown, not a clear result, since a missing saved response reads as empty (#38). A finding carries its path as a derived evidence record with its rule ID, plus the arguments to reopen it in khonsu explore. The new cases live in their own fixture, so the shop fixture's answers and the HawDB snapshot stay as they were. Refs #35, #33, #36 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rules could only be read as JSON, so someone investigating in the window had to leave it to learn why an instance was flagged. Selecting an instance now offers a check. Each finding shows its verdict, rule, conditions with their AWS fields, candidate path and what wasn't checked. Showing a path narrows the canvas to it as a walk, so Back, Path Explorer and each record's evidence carry on working from there. Refs #35 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A line of inquiry was lost when the window closed, and nothing would have said whether the data underneath it had moved on. khonsu investigation save writes a walk's starts and filters, a chosen path, a query and a dashboard to a versioned file of its own, by stable IDs and with no source configuration. The graph isn't touched. khonsu investigation reopen runs the walk again and compares a fingerprint of the graph that leaves out internal IDs, sync times and plugin versions, so a re-sync that changes nothing stays unchanged and a rebuilt graph matches. It names records that have gone and doesn't substitute others. Refs #34 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full sync removes the nodes stamped before its own start, in whole seconds. Two syncs in one second shared a stamp, so a node gone from the source stayed in the graph while its relationships were removed, with nothing to say so. Each sync's stamp is now later than any its source has written. A stamp can run a second or so ahead of the clock after quick repeat syncs. An older test slept to avoid this, and no longer needs to. Refs #34 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saved investigations could only be used from the command line, and only while the window didn't hold the graph. Path Explorer can now save the current filters, walk and chosen path, and File > Open investigation or khonsu open reopens one in a new tab, opening its graph first if needed. The query goes in the editor without running. Everything that differs from when it was saved shows as warnings on the canvas, since a lost path mustn't wait for someone to select a node. At startup the last graph is still opening, and a second open was dropped without a word, so reopening waits for it. Showing a finding's path now also sets the filters that find it, so saving from there means the same thing. Refs #34 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A saved response that was missing read as an empty one, so a re-sync deleted what it used to describe and nothing said so. An investigation could then read an unfinished import as an all-clear. A plugin can now finish with complete: false and say what it didn't enumerate. The AWS plugin does so for a response that isn't saved. A full sync removes records and moves the cursor only when it was complete. Each source keeps its latest runs, with ID, times, status, plugin version and gaps. Evidence, exposure findings and reopened investigations state the coverage they rest on. A finding on an incomplete source can't be not_indicated, since what was missed could be what would have met a condition; what the source did report still stands. Values of a source's secrets are masked in what its plugin says. The shop fixture gains an explicitly empty route tables response, so its answers stay as they were. Refs #38 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An investigator couldn't ask what changed, because a graph only holds its latest state. khonsu snapshot save keeps a graph's records in a file by stable IDs, and khonsu snapshot diff compares two. A record gone from the later snapshot counts as removed only when its source's last sync was complete there. Otherwise it's listed as not seen, since it may still exist. A saved investigation pins the source runs it was evaluated against, and names them when reopened on a graph that has changed. Evidence's source_snapshot now gives the last complete run. Refs #37 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KhonsuGraph is a general graph workbench, but its core held AWS semantics: EC2, security group, load balancer and route table rules, and a general `khonsu exposure` command for them. Every new provider would have added more. Plugins can now offer analyses, an optional part of the protocol. The host reads a bounded part of the graph and sends it with stable references to each record. The plugin answers with findings that point at those records. The host checks every reference, looks up field values itself, and rebuilds each path from the graph, so a plugin can't misquote a record or claim a connection that isn't there. A bad answer is refused whole, since dropping a finding would read as that resource having none. Nothing in the host names a provider's labels, rules or verdicts. The three rules, their field mappings, assumptions and verdict policy are now in plugins/aws/src/exposure.rs, run with `khonsu plugin analyze aws exposure`. Finding IDs, rule IDs, verdicts, order and condition text are unchanged. In the output, `aws_field` is now `source_field`, `instances` is now `subjects`, and a relationship's end reads as the field `@to`. Three false readings found in review are fixed, with regression tests: - Missing balancer group data read as "the target's rules don't allow it", clearing the finding. It's now unknown. - A TCP_UDP target group was treated as TCP only, so a UDP rule didn't count. - The target's security group membership wasn't in the rule's evidence. An example analyzer with nothing to do with AWS shows the interface, and tests how the host handles an answer that misbehaves. Refs #55, #35 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The inspector called the exposure evaluator in core, matched AWS rule constants to find its titles, and only appeared on nodes labelled Instance. It now asks plugins what they offer and shows each analysis on nodes with a label it declares. Titles, verdicts, rules and condition text all come from the plugin, including their Chinese, and a verdict's tone sets how it is shown. The graph is held only while its records are read, so a plugin that takes a while doesn't block the window, and Stop ends one. A plugin not approved as it is now isn't started just to ask what it offers. "Show path on canvas" draws what the host rebuilt from the plugin's references, which also replaces the fields that were skipped in JSON. A test checks that the command and the library the window calls give the same findings, and another that the window's code names no provider's labels or rules. Refs #55, #35 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # app/tests/investigation.rs # crates/khonsu-cli/src/lib.rs # crates/khonsu-core/src/lib.rs # docs/investigation-gates.md
Saving after "Show path on canvas" stored only the walk's filters, and reopening walked again. That finds the fewest hops, which isn't always the finding's path: a lateral finding's five hops came back as three, with nothing saying so. A saved path can now hold the stable references a finding's path crossed the process boundary as. Reopening looks those records up instead of walking, and if one has gone or they no longer connect, path_same is false and the walk runs instead. The window saves them for the finding path on the canvas, and `khonsu investigation save --path-refs` takes a finding's path_refs. Older files and walks are read and saved as before. Refs #55, #34 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test deleted a relationship with a query that matched nothing, so it never saw a changed graph. Change the saved responses and sync instead, as a real account would change. Refs #55 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oundary Coverage used to be handled inside the exposure evaluator in core, which this merge removes. It now goes both ways through the analysis: - The request says which source wrote each record and how much of it the last sync covered, so the AWS plugin can apply its own policy: an incomplete source can't clear anything, and what it did report still stands. - The host works out the incomplete sources behind each finding and marks the report incomplete itself, so this stays visible even for a plugin that ignores coverage. A source of the graph with nothing in it still counts. Against the golden file from before the merge, every finding's ID, verdict, unknowns and incomplete sources are unchanged, and so is the report's coverage. Refs #55, #38 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # crates/khonsu-cli/src/lib.rs # crates/khonsu-core/src/investigation.rs
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.
Adds
khonsu snapshot save,diffandcheck, so "what changed?" has an answer. The window has no snapshot or diff view yet, so #37 stays open.Merge order: #47, #48, #49, #50, #51, then this. This branch is built on all five, so its diff shows their work until they land. It targets
main. Please merge these with merge commits, not squash or rebase, or each later branch stops containing the one before it (#55).Updated for #55: snapshots and diffs are unchanged. Tests and docs now use the new analysis command.
What it does
savewrites a graph's records to a file by stable IDs, along with each source's last run and plugin version. HawDB and the graph are untouched.diffcompares two snapshots and lists, for nodes and relationships:addedremovedcompletein the later snapshotnot_seenchangedchecksays whether a graph still holds what a snapshot does.Saved investigations now also pin the source runs they were evaluated against. Reopening on a changed graph names the run when saved and the run now, which the window shows through its existing warnings. Evidence's
source_snapshotnow gives the last complete run, where it was always unknown before.docs/snapshots.mdhas the details.Choices to review
not_seenis its own list. A source that's been removed, or has no record of its runs, also lands there, so old data never produces a confirmed deletion.Limits
app/tests/fixtureshold saved-response data from the test fixture, and are written once. If the format changes, the old ones must keep working or the test fails.Testing
cargo test --locked --workspacepasses on Windows, and CI will cover macOS.source_snapshot: unknown, which are now known.Issue status
For #37, the fixed diff, pinning, no false deletions and machine-readable stale warnings have tests. Still to do: a window view of a diff, and path-level or exposure-level changes.
Refs #37
🤖 Generated with Claude Code