Skip to content

[kernel-1116] Add CDP Monitor#213

Open
archandatta wants to merge 50 commits intomainfrom
archand/kernel-1116/cdp-foundation
Open

[kernel-1116] Add CDP Monitor#213
archandatta wants to merge 50 commits intomainfrom
archand/kernel-1116/cdp-foundation

Conversation

@archandatta
Copy link
Copy Markdown
Contributor

@archandatta archandatta commented Apr 13, 2026

Introduces the foundational layer of the CDP monitor as a standalone reviewablechunk. No Monitor struct wiring, just the primitives that everything else builds on.

  • types.go: CDP wire format (cdpMessage), all event type constants, internal state structs (networkReqState, targetInfo, CDP param shapes).

  • util.go: Console arg extraction, MIME allow-list (isCapturedMIME), resource type filter (isTextualResource), per-MIME body size caps (bodyCapFor), UTF-8-safe body truncation (truncateBody).

  • computed.go: State machine for the three derived events: network_idle (500ms debounce after all requests finish), layout_settled (1s after page_load with no layout shifts), navigation_settled (fires once all three flags converge). Timer invalidation via navSeq prevents stale AfterFunc callbacks from publishing for a previous navigation.

  • domains.go: isPageLikeTarget predicate (pages and iframes get Page.* / PerformanceTimeline.*; workers don't), bindingName constant, interaction.js embed.

  • interaction.js: Injected script tracking clicks, keydowns, and scroll-settled events via the __kernelEvent CDP binding.


Note

Medium Risk
Adds a new long-lived CDP monitoring subsystem that captures network/console data (including headers/bodies) and manages reconnect/timers/concurrency, so bugs could impact event fidelity, memory use, or sensitive-data exposure.

Overview
Adds a new cdpmonitor package that maintains a CDP WebSocket connection with auto-attach to targets, dispatches Runtime/Network/Page/PerformanceTimeline events into the capture pipeline, and injects interaction.js to emit click/key/scroll events via Runtime.addBinding (with per-session rate limiting and click/key privacy suppression for sensitive fields).

Implements derived page lifecycle signals (network_idle, layout_settled, navigation_settled) via debounced timers, captures truncated textual response bodies (skipping binary), and adds periodic screenshot capture via ffmpeg with downscaling + rate limiting.

Adds reconnection handling on upstream DevTools URL changes (disconnect/reconnect/failure events, backoff retries, cleanup of pending commands/requests), plus extensive unit tests and JSON fixtures to enforce CDP PDL field fidelity.

Updates the API service to construct the monitor with a slog logger and makes ApiService.cdpMonitor an interface to allow stubbing in tests.

Reviewed by Cursor Bugbot for commit 5465e59. Bugbot is set up for automated code reviews on this repo. Configure here.

This binary is tracked on main and was incidentally deleted earlier on
this branch. Restoring it keeps the 13.4MB binary out of this PR's diff.
Removing the tracked binary from main should be done in a separate PR.
Comment thread server/lib/cdpmonitor/handlers.go
Comment thread server/lib/cdpmonitor/monitor.go Outdated
Sayan-
Sayan- previously approved these changes Apr 18, 2026
Copy link
Copy Markdown
Contributor

@Sayan- Sayan- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good! Reading through #216 was super helpful to ground the motivation of some of these changes.

I do think the pending bot comments should be addressed.

From a scalability perspective a few properties I noticed:

  • Publish is synchronous and serialized. Every handler blocks on CaptureSession.Publish, which holds a session-wide mutex and writes to disk inline.
  • Per-response goroutine fanout for body fetches. handleResponseReceived spawns one unbounded goroutine per textual response to call Network.getResponseBody.
  • Screenshots are inline base64 PNGs carried through the same publish path.

I think the units have reasonable coverage. As we get closer to shipping, I'd love to see a consistent stress test against a real page so we can understand latency, memory, and perf implications.

Comment thread server/lib/cdpmonitor/cdp_proto.go
Comment thread server/lib/cdpmonitor/monitor.go
Comment thread server/lib/cdpmonitor/monitor.go
@archandatta archandatta force-pushed the archand/kernel-1116/cdp-foundation branch from 092a265 to 7550bc1 Compare April 22, 2026 14:35
Comment thread server/lib/cdpmonitor/interaction.js Outdated
Comment thread server/lib/cdpmonitor/monitor.go
Comment thread server/lib/cdpmonitor/interaction.js
Comment thread server/lib/cdpmonitor/screenshot.go
Comment thread server/lib/cdpmonitor/interaction.js
_ = m.injectScript(ctx, p.SessionID)
}
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Injected script never runs on already-loaded pages

Medium Severity

handleAttachedToTarget calls injectScript, which only uses Page.addScriptToEvaluateOnNewDocument. This registers interaction.js for future navigations but never evaluates it on the current document. Pages already loaded when the monitor attaches (via attachExistingTargets or after reconnect) won't have click, keydown, or scroll-settled tracking until their next navigation. A Runtime.evaluate call with the same script source is needed alongside the addScriptToEvaluateOnNewDocument registration to cover the current page.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bf4b04c. Configure here.

Comment thread server/lib/cdpmonitor/interaction.js
Comment thread server/lib/cdpmonitor/monitor.go
}
// name/id/aria-label heuristic as fallback, covers custom controls that use ARIA.
var name = (el.name || el.id || (el.getAttribute && el.getAttribute('aria-label')) || '');
return SENSITIVE_NAME_RE.test(name);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sensitive field regex only checks first non-empty attribute

Medium Severity

The isSensitiveInput function uses a || chain (el.name || el.id || ... aria-label ...) that short-circuits on the first truthy value, so only one attribute is ever tested against SENSITIVE_NAME_RE. If el.name is non-empty but non-sensitive (e.g. "field1"), a sensitive el.id like "password_input" or aria-label is never checked. Keystrokes in such fields are captured with the actual e.key value. Notably, shouldSuppressClickText already handles this correctly by testing id and aria-label independently — the same approach is needed here.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4feef7e. Configure here.

Base automatically changed from archand/kernel-1116/cdp-pipeline to main April 22, 2026 17:41
@archandatta archandatta dismissed Sayan-’s stale review April 22, 2026 17:41

The base branch was changed.

@archandatta archandatta requested a review from Sayan- April 22, 2026 17:43
_, err := m.send(ctx, "Page.addScriptToEvaluateOnNewDocument", map[string]any{
"source": injectedJS,
}, sessionID)
return err
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interaction script not injected into current document

Medium Severity

injectScript only calls Page.addScriptToEvaluateOnNewDocument, which registers the interaction-tracking JS for future navigations. The already-loaded document in an attached target never receives the script. When the monitor attaches to existing pages (via attachExistingTargets at startup or after reconnect), clicks, keydowns, and scroll events on those pages won't be captured until the user navigates away. A companion Runtime.evaluate call is needed to inject into the current document.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8e94162. Configure here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 6 total unresolved issues (including 5 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5465e59. Configure here.

key: e.key,
selector: sel(t), tag: t.tagName || ''
}));
}, true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sensitive input detection bypassed by shadow DOM retargeting

Medium Severity

The keydown handler uses e.target to check isSensitiveInput, but e.target is retargeted across shadow DOM boundaries. When a <input type="password"> lives inside a web component's shadow DOM (common with Material UI, Lit, Shoelace, etc.), e.target at the document level resolves to the shadow host custom element — not the inner password input. Since the shadow host typically isn't an INPUT/TEXTAREA, isEditable returns false and isSensitiveInput returns false, allowing the actual e.key character to be captured. Using e.composedPath()[0] instead of e.target would resolve this, as it returns the real originating element even across shadow boundaries.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5465e59. Configure here.

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.

4 participants