Mark the leaking turn in the timeline (#336) - #337
Merged
mando merged 2 commits intoAug 18, 2026
Merged
Conversation
6 tasks
A credential warning said what leaked but not where, so the user had to scroll the whole session to find it. The findings already carry the interaction and the half they matched; this uses them. The timeline now marks the turn a credential was found in with a shield chip naming the kind and the redacted hint, on the prompt or the response half it matched, and each finding in the warning banner is a link that opens the timeline on that interaction and outlines it. The details rail numbers each interaction so the banner's "Interaction 4" points somewhere the user can see. No new endpoint: /api/session/:id already returns the findings, so they ride in as a prop. Markers don't need retained text (with retention off the turn still says which kind of credential it was), they mark the first place a credential appeared (the scanner dedupes across the session), and they survive dismissal, which silences the banner rather than the annotation. Two pure modules come out of the components so this is testable without a DOM: lib/secret-findings.ts (labels, order, per-interaction grouping) and lib/timeline.ts (chapter grouping, moved out of SessionTimeline, plus focus resolution, now that a focus request can name a task or a single interaction). Closes #336 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gs6LqsJLsH1dg71YFQXp8r
The session detail and the timeline are two separate fetches, so indexing a session again under an open tab can leave a credential warning pointing at an interaction the timeline no longer has. The link then switched to the Timeline tab and highlighted nothing, which reads as a broken link (with several tasks the timeline opens collapsed, so there was nothing to see at all). An unresolvable focus request now says so instead. The store can't disagree with itself here (findings and the interaction spine are written from the same array in one transaction), so this is only about the client-side staleness window. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gs6LqsJLsH1dg71YFQXp8r
mando
force-pushed
the
mando-issue-336-timeline-secret-markers
branch
from
August 18, 2026 20:50
aeadb45 to
953316b
Compare
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.
Stacked on #332 (
mando-issue-327-secret-scanning) — retarget tomainonce that merges.The credential warning from #327 says what leaked but not where, so finding it means scrolling
the whole session. The findings already carry
interactionSeqandchunkType; this uses them.What changed
The timeline marks the leaking turn. A turn where the scanner found a likely credential gets a
red shield chip naming the category and the redacted hint, on the prompt half or the response half
it actually matched. The card carrying a finding also gets a tinted edge so it's spottable while
scrolling.
The banner deep-links to the turn. Each finding in
SecretFindingsBanneris now a button thatopens the Timeline tab focused on that interaction and outlines the card. Findings read
"Interaction 4: AWS access key (AKIA…WXYZ) in your prompt", and the timeline's details rail now
numbers each interaction so that reference points at something visible. The list still stops at
five, and the overflow line now says the rest are marked on their turns.
No new endpoint.
/api/session/:idalready returnssecretFindings, soSessionDetailpassesthem to
SessionTimelineas a prop. Nothing new crosses the wire, and findings stay local-only.Three behaviors worth calling out
retainTextoff the timeline shows no prompt orresponse body, and the marker still says which turn and which kind of credential. That's the part
the user acts on.
across the whole session, so a key pasted once and echoed in three later replies is one finding at
its first location. Unchanged from Scan session content for exposed secrets/API keys and surface findings to the user #327, but it's now visible, so it's documented.
an annotation on a turn the user navigated to on purpose, so it stays.
Implementation
The focus mechanism used to mean "a task chapter"; it now carries a
kind, so a request can name atask (the task list's timeline links, unchanged) or a single interaction (a finding).
A target that isn't in the timeline scrolls nowhere rather than somewhere arbitrary, and says so.
That case is narrower than it sounds: findings and the interaction spine are written from the same
array in one transaction, so the store can't disagree with itself. But the banner and the timeline
are two separate fetches, so indexing a session again under an open tab can leave a finding pointing
at an interaction the newer timeline data no longer has. Without the note the click switched tabs and
highlighted nothing, and with several tasks the timeline opens collapsed, so there was nothing to see
at all.
Two pure modules come out of the components so the logic is testable without a DOM:
web/src/lib/secret-findings.ts— category labels, display order, the per-interaction grouping thetimeline marks turns with. The banner's copies of the labels and the ordering move here.
web/src/lib/timeline.ts— chapter grouping (lifted out ofSessionTimelineunchanged) plus focusresolution.
Not done
The issue's optional enhancement (re-scan text at serve time to return transient match offsets for
in-place highlighting,
SearchSnippet-style) is left out. It needs a serve-side re-scan and acareful "offsets in, secrets out" contract, and the marker answers "which turn and what kind"
without it. Worth its own issue if we want the highlight.
Verification
bun test— 788 pass, 0 fail (32 new: finding labels/order/grouping, chapter grouping, focusresolution including the stale-target and no-request cases, and the stale-link note).
bun run typecheckandbun run build:webclean.serveagainst a throwaway store seeded with demo data plus two obviously-fake credentials(the same non-functional constructions the scanner's unit tests use) and confirmed
/api/session/:idcarries the findings through to the session payload the timeline reads.agent-browseron this machine, so the chip,the outline and the scroll are unphotographed — worth a look before merge.
Design notes updated in
docs/internals/secret-scanning.md.Closes #336