Skip to content

chrome_extension: detachRecordListeners() never removes listeners, so leaving record mode does not stop recording #72

Description

@spo0nman

What happened

detachRecordListeners() in chrome_extension/content.js only flips the
recordListenersAttached flag; the comment says as much:

function detachRecordListeners() {
  // Note: In practice we'd need to store handler references to properly remove them
  // For now, just mark as detached
  recordListenersAttached = false;
}

attachRecordListeners() registers click, keydown, keyup, mousemove and scroll
handlers plus three trackMouseEvent handlers with inline/anonymous references, so there is
nothing to pass to removeEventListener. Because setMode calls detachRecordListeners() when
switching to replay or idle, the effect is that the extension keeps capturing after the
user stops recording
, and each captured event still goes through
sendMessageToBackgroundScript. Setting record mode again is guarded by
if (!recordListenersAttached), so a stop/start cycle also leaves the original handlers in
place — and a second start attaches a second set only after an intervening reload.

The equivalent replay-side function, disconnectReplayObservers(), does this correctly
(removeEventListener with named handlers, observers disconnected), so the asymmetry looks
like an oversight rather than a decision.

Worth noting the consequence given what the extension captures: keydown/keyup are recorded
per keystroke and every event carries visibleHTMLString (the visible DOM). So "stopped"
still streams keystrokes and page content — including anything typed into a password field,
since the extension has no secret handling — to whatever is listening on the WebSocket. We are
filing this publicly as a functional bug because it is visible in the source and there is no
concealed exploit, but if you would rather have it as a private advisory under SECURITY.md, say
so and we will move it.

Suggested fix

Hoist the handlers to module-level named functions (as the replay path already does) and keep a
list of [target, type, handler, options] triples at attach time to replay in reverse at
detach time. Options must match: the listeners are registered with capture true, and scroll
is on document rather than document.body.

Steps to reproduce

  1. Load chrome_extension unpacked and point background.js at a WebSocket listener on
    ws://localhost:8765 (a ~10 line echo server is enough).
  2. Set record mode, click and type on any page — messages arrive.
  3. Set idle mode.
  4. Click and type again: messages continue to arrive, and the frames keep carrying
    visibleHTMLString.

Environment

Chrome (unpacked, MV3), chrome_extension @ 431fd37, macOS 14.6 arm64.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions