Skip to content

Mark the leaking turn in the timeline (#336) - #337

Merged
mando merged 2 commits into
mando-issue-327-secret-scanningfrom
mando-issue-336-timeline-secret-markers
Aug 18, 2026
Merged

Mark the leaking turn in the timeline (#336)#337
mando merged 2 commits into
mando-issue-327-secret-scanningfrom
mando-issue-336-timeline-secret-markers

Conversation

@mando

@mando mando commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Stacked on #332 (mando-issue-327-secret-scanning) — retarget to main once 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 interactionSeq and chunkType; 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 SecretFindingsBanner is now a button that
opens 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/:id already returns secretFindings, so SessionDetail passes
them to SessionTimeline as a prop. Nothing new crosses the wire, and findings stay local-only.

Three behaviors worth calling out

  • Markers don't need retained text. With retainText off the timeline shows no prompt or
    response body, and the marker still says which turn and which kind of credential. That's the part
    the user acts on.
  • A marker shows the first place a credential appeared, not every place. The scanner dedupes
    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.
  • Markers survive dismissal. Dismissing silences the banner ("I know about this"); the marker is
    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 a
task (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 the
    timeline marks turns with. The banner's copies of the labels and the ordering move here.
  • web/src/lib/timeline.ts — chapter grouping (lifted out of SessionTimeline unchanged) plus focus
    resolution.

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 a
careful "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, focus
    resolution including the stale-target and no-request cases, and the stale-link note).
  • bun run typecheck and bun run build:web clean.
  • Ran serve against 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/:id carries the findings through to the session payload the timeline reads.
  • Not verified: the rendered marker in a browser. No agent-browser on 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

mando and others added 2 commits August 18, 2026 15:50
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
mando force-pushed the mando-issue-336-timeline-secret-markers branch from aeadb45 to 953316b Compare August 18, 2026 20:50
@mando
mando merged commit c3cff70 into main Aug 18, 2026
2 checks passed
@mando
mando deleted the mando-issue-336-timeline-secret-markers branch August 18, 2026 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mark the leaking turn in the Timeline so users see where and how a credential was exposed

1 participant