Tracing JavaScript Value Origins with Breakpoint-Driven Heap Search (BDHS) and Live Object Search
Modern JavaScript applications make a deceptively simple question extremely hard to answer:
Where does this value come from?
Between frameworks, bundlers, minifiers, async flows, closures, and virtual DOM abstractions, the origin and lifecycle of a value become opaque. You might see:
- a token inside a request header,
- a corrupted component state,
- a tainted DOM string,
- a suspicious flag affecting behavior,
…with no visibility into where it was created or modified.
Traditional debugging tools weren’t designed for this reality.
Manual breakpoints, stepping, logs, or a single heap snapshot rarely help.
This writeup presents the techniques implemented in Wirebrowser, an open-source CDP-based toolkit:
- Breakpoint-Driven Heap Search (BDHS / Origin Trace) – a multi-snapshot temporal analysis technique for finding where objects and primitives first appear.
- Live Object Search – a runtime heap explorer capable of scanning and patching live objects.
- Hybrid Structural Similarity Search – a similarity engine that compares objects by structure and shape, available across all memory subsystems.
1. Why Tracing Value Origins Is Hard in Modern JavaScript
Several factors obscure causality:
• Framework abstractions
React, Vue, Angular, Svelte, and others wrap user-land logic and hide real callsites.
• Bundlers and minifiers
Applications ship as giant anonymous bundles; stack traces are meaningless.
• Async flows
Promises, microtasks, events, timers, schedulers — traditional linear stepping cannot follow these cross-task transitions.
• Closures and hidden state
Values can live inside unreachable closures or internal structures.
A snapshot shows what exists now, not where or when it appeared.
Even trivial questions like “Where was this object created?” become difficult.
2. Breakpoint-Driven Heap Search (BDHS / Origin Trace)
BDHS turns heap snapshots into a temporal search space.
Instead of capturing one snapshot at an arbitrary moment, BDHS:
- Pauses execution automatically at meaningful boundaries.
- Captures a heap snapshot at each pause.
- Searches every snapshot for the target value or structure.
- Identifies the first snapshot containing it (origin in time).
- (Future) Detects when the structure or value changes across snapshots.
- Maps the origin back to the user-land function responsible, ignoring framework noise.
BDHS answers:
“When did this value first appear, and which user-land function introduced it?”
Even inside minified, async-heavy SPAs.
2.1 Event → Heuristic Handler Detection → Step-Out Execution
BDHS does not pause randomly or on every async boundary.
Instead, it follows this sequence:
- Install an initial breakpoint on a real user event (e.g., click).
- Attempt to identify the actual event handler for the event target using heuristics
(Handler Detection). This is not fully deterministic but works in most real-world SPAs.
- Attach a breakpoint to the resolved handler.
- Execute with step-out stepping, pausing only at function returns.
- Capture a snapshot at each pause.
This yields a clean, semantically meaningful timeline aligned with:
- real UX-driven control flow,
- user-land logic boundaries,
- places where values are typically created or transformed.
Unlike traditional linear stepping, BDHS does not attempt to follow every instruction or every async boundary.
Instead, it uses semantic step-out execution: it pauses only at meaningful user-land function boundaries,
creating a sparse but highly informative temporal timeline.
Even though BDHS issues step-out commands sequentially, it does not produce a linear execution trace.
Traditional stepping tries to follow the real instruction flow, including async jumps and framework internals.
BDHS instead samples only meaningful user-land boundaries, creating a sparse, semantic timeline rather than
a faithful step-by-step trace.
2.2 Snapshot Capture & Search
At each pause, BDHS calls HeapProfiler.takeHeapSnapshot.
Snapshots include:
- all objects,
- all primitives (including strings),
- closure-captured values,
- internal framework data structures,
- unreachable nodes.
BDHS searches each snapshot for:
- object identity,
- nested keys/values,
- strings/primitives,
- structural shapes,
- hybrid structural similarity.
Since snapshots contain everything, BDHS can locate:
- closure-bound values DevTools cannot reach,
- ephemeral objects that live briefly,
- deeply nested cache entries,
- primitives unreachable from the global scope.
2.3 Identifying Origins
When a match is found:
- the first snapshot containing the object/value = its origin,
- (future) later snapshots that differ structurally = mutation points.
BDHS maps these events to the user-land function via:
- script URLs,
- byte offsets,
- framework/vendor blackboxing.
BDHS identifies the function where the value was introduced—
not the exact line, due to CDP granularity and snapshot timing.
BDHS can also provide the snapshots immediately before and after the first match through the tolerance window, offering contextual insight into how the value is created or mutated.
BDHS therefore complements both static snapshot search and live object search: it provides the temporal dimension that neither of them can offer.
2.4 Tolerance Window (Snapshot Sampling Window)
The tolerance window expands BDHS by providing not only the snapshot where the target value first appears, but also the immediate execution context around it.
When BDHS detects the first match, it automatically:
- samples a configurable number of snapshots before the match (e.g., 5), and
- a configurable number of snapshots after the match (e.g., 15).
Wirebrowser displays these snapshots in a chronological table, highlighting:
- steps where the value does not yet exist,
- the exact snapshot where it first appears,
- subsequent steps that show how the object evolves.
This contextual timeline makes it possible to understand:
- what functions prepare or influence the creation,
- whether precursor data structures were set up earlier,
- how the value mutates immediately after being introduced,
- whether multiple functions participate in its lifecycle.
The tolerance window dramatically increases BDHS’s diagnostic power, especially in asynchronous, framework-heavy SPAs where objects may be assembled over several steps.
3. Hybrid Structural Similarity Search
Wirebrowser includes a hybrid similarity engine for comparing JavaScript objects by structure and shape.
Pure SimHash performs poorly on small or sparse objects.
To address this, Wirebrowser uses a hybrid similarity metric combining:
- Jaccard similarity between structural tokens
- fuzzy similarity between nested keys
The result is robust similarity detection for:
- small objects,
- deeply nested structures,
- state objects,
- request/response payloads,
- objects that vary slightly between runs.
Similarity search is fully integrated across:
✔️ Live Object Search
Find structurally similar objects in the live heap.
✔️ Static Heap Snapshot Search
Identify clusters of similar objects inside snapshots.
✔️ BDHS (Origin Trace)
Use structural similarity as a search key across temporal snapshots.
This enables queries like:
- “Find all objects shaped like this one.”
- “Trace the first appearance of this object family.”
- “Locate objects structurally similar to this payload.”
Because the same similarity engine runs across live memory, snapshots, and BDHS timelines, investigations can seamlessly pivot between the three without changing query semantics.
4. Static Snapshot Search vs BDHS vs Live Objects
Wirebrowser offers three memory analysis modes:
Static Heap Snapshot Search
- Captures a single snapshot.
- Finds objects and primitives.
- Read-only.
- Useful to inspect a single moment in time.
BDHS / Origin Trace
- Captures multiple snapshots over time.
- Tracks first appearance and (future) structural deltas.
- Maps origins to the user-land function.
- Works with objects and primitives.
Live Object Search
- Scans the live heap via CDP.
- Finds objects by keys, values, structure, or similarity.
- Supports runtime patching (mutating live objects).
- Cannot detect primitives (V8 constraint).
5. Live Object Search and Runtime Patching
Live Object Search enables:
- finding objects unreachable from globals,
- scanning nested structures,
- pattern and similarity-based matching.
And supports live object patching:
- modifying fields,
- toggling flags,
- replacing handlers or callbacks,
- altering state/config values,
- injecting instrumentation.
Useful for:
- reproducing bugs without UI interaction,
- exploring edge cases,
- bypassing internal checks,
- security investigations,
- manipulating state machines in-flight.
6. Combined Workflow
The intended analysis workflow:
- Find a suspicious value (Live Object Search).
- Trace its origin (BDHS / function-level origin).
2b. Use the tolerance window to inspect the snapshots before and after the origin, gaining contextual understanding of the value’s creation or mutation.
- Patch related objects to test hypotheses (Live Object Search).
- Validate by rerunning the scenario.
A cycle that traditional debugging cannot achieve:
find → trace → patch → validate
7. Concrete Examples
These scenarios show how BDHS, Live Object Search, and Hybrid Similarity Search work together in real investigations.
Example 1 — Tracing a JWT-like Value in a Request
A token-like string appears in an outgoing request. It doesn’t exist in globals, and DevTools shows only framework internals.
Workflow
- Trigger the UI action that generates the request.
- Use Live Object Search (regex) to find the value in memory.
- Reload the page to remove the value from memory.
- Start BDHS using the found value.
- BDHS performs heuristic handler detection, step-out stepping, and snapshot capture/search.
- The first snapshot containing the token maps to the user-land function assembling it.
Result
BDHS reveals logic hidden inside minified SPA bundles.
Example 2 — Finding Where a React Component State Becomes Invalid
A component occasionally enters an impossible state (loading: true + error: true).
Workflow
- Trigger the problematic UI action once.
- BDHS:
- attempts heuristic handler detection
- executes step-out stepping
- captures snapshots at each boundary
- BDHS searches snapshot-by-snapshot using patterns and similarity.
- The earliest snapshot with the invalid structure reveals the responsible reducer function.
Result
Something stepping cannot catch becomes visible immediately.
With the tolerance window, BDHS also reveals the snapshots preceding the invalid state, making it clear which function prepares the inconsistent data before the reducer sets the final state.
Example 3 — Tracing a Tainted String in a DOM-Based XSS Case
A dangerous string appears in a DOM sink.
Workflow
- Copy the tainted string.
- Use Live Object Search to find all occurrences in memory.
- Run BDHS on one instance.
- BDHS identifies when the string first appears in snapshots.
- The origin maps to the user-land function that introduced it.
Result
Effective for DOM XSS incident response.
Example 4 — Reverse Engineering a Minified SPA
A hidden boolean flag controls anti-bot behavior.
Workflow
- Use Live Object Search to locate the flag and similar objects.
- Start BDHS.
- BDHS traces the first appearance of the flag.
- Mapping reveals a small region in a huge minified file.
Result
The investigation narrows from ~200k lines to ~20.
Example 5 — Using Structural Similarity to Trace Complex Objects
A component generates slightly varying nested configuration objects.
Workflow
- Locate an instance via Live Object Search.
- Use Similarity Search to find structurally similar objects.
- Run BDHS using the similarity signature as a search key.
- The first snapshot containing any object of that “shape family” reveals the origin.
Result
BDHS + similarity find origins even for evolving object families.
Example 6 — Tracing Runtime-Generated Code Paths
Some SPAs generate handlers dynamically with closures.
Workflow
- Find the handler via Live Object Search (by pattern or similarity).
- Run BDHS on the function object.
- BDHS finds the first snapshot containing the function (including closure environments).
- Mapping identifies the factory function responsible.
Result
BDHS handles what DevTools cannot.
8. Future Work
Building on the current foundation:
• Object Lifecycle Diffing
Compare similarity across BDHS snapshots to visualize evolution.
• Mutation Tracking in Snapshots
Detect meaningful structural changes over time.
• Flow Debugger
A temporal visualization combining BDHS, snapshots, and similarity deltas.
• Cross-Modal Correlation
Correlate network payloads with memory objects via similarity.
• Automated SPA Crawling
Explore SPAs while attaching BDHS and Live Object Search to each action.
• Deeper Framework Blackboxing
More robust heuristics for ignoring framework internals in single-file bundles.
• Adaptive tolerance window
Heuristics that automatically adjust the sampling range based on object complexity or execution patterns.
9. Closing Notes
Breakpoint-Driven Heap Search, Hybrid Similarity Search, and Live Object Search provide a new way to analyze JavaScript:
- automatic pauses instead of manual breakpoints,
- temporal heap search instead of static snapshots,
- function-level origin tracing instead of stack inspection,
- full-heap live search instead of globals-only visibility,
- runtime patching instead of passive observation.
Wirebrowser makes these techniques practical in real-world, framework-heavy, async-intensive applications.
Contributions and feedback are welcome.
10. Resources
• Wirebrowser Repository
https://github.com/fcavallarin/wirebrowser
• BDHS + Live Object Search Demo Video
https://www.youtube.com/watch?v=WA5nHk-6UJc
• Documentation / Writeups
This document is part of the Wirebrowser technical documentation inside the docs/ directory.