Skip to content

feat: professional trace viewer, Bedrock decoder, real-IP interception#10

Open
GeiserX wants to merge 2 commits intomainfrom
feat/professional-overhaul
Open

feat: professional trace viewer, Bedrock decoder, real-IP interception#10
GeiserX wants to merge 2 commits intomainfrom
feat/professional-overhaul

Conversation

@GeiserX
Copy link
Copy Markdown
Owner

@GeiserX GeiserX commented Apr 16, 2026

Summary

  • Bedrock EventStream decoder — AWS application/vnd.amazon.eventstream binary frames are now parsed, extracting base64 JSON payloads into SSEChunk[] for token counting and response display. Previously ALL Bedrock streaming responses had NULL response bodies.
  • Real-IP PF interception — Resolves target domains to real IPs via DoH and creates PF rdr rules on the physical network interface. Fixes the critical issue where clients using dns.resolve4() (c-ares, used by Claude Code's AWS SDK) bypass /etc/hosts sentinel IPs entirely. Periodic 60s re-resolution handles DNS round-robin.
  • Health monitor fix — "Interception Rules Degraded" notification now fires once per degradation episode instead of every 60s cooldown cycle.
  • Capture start notification — macOS notification advising to restart IDE/CLI tools in a new terminal after enabling capture.
  • Purge button fix — Replaced confirm() (silently returns false in WKWebView) with HTML <dialog>.
  • Tray icon template fix — Recreate tray on icon change to preserve macOS NSImage template flag (ElectroBun setImage() bug workaround).
  • Professional trace viewer overhaul:
    • Resizable panes (sidebar + detail panel) with drag handles and localStorage persistence
    • Column sorting (click headers, direction indicators) and column resizing
    • JSON syntax highlighting with collapsible/expandable tree view and copy-on-click
    • Keyboard navigation (↑↓, j/k, Enter, Escape, Cmd+F)
    • Status code color coding (2xx/3xx/4xx/5xx) and duration performance indicators
    • Auto-scroll toggle for live traces
    • Streaming progress indicator (pulsing dot for in-flight requests)
    • Professional polish: alternating rows, sticky headers, trace count, custom scrollbars, responsive breakpoints, ARIA roles

Test plan

  • Build passes (npm run build)
  • 118 tests pass (bun test)
  • Enable capture → macOS notification appears advising to restart CLI
  • Open trace viewer → resizable panes, column sorting, keyboard nav all functional
  • Click Purge → HTML dialog appears (not native confirm)
  • Tray icon adapts to menu bar color (template image)
  • Bedrock streaming traces show response body/chunks in viewer
  • "Interception Rules Degraded" notification fires at most once (not repeating)
  • Claude Code traces captured when started after AgentTap (real-IP interception)

Summary by CodeRabbit

  • New Features

    • Added trace sorting by column with persistent sort direction
    • Enabled auto-scroll to latest traces with manual toggle
    • Implemented interactive JSON tree view with expand/collapse and copy functionality for request/response details
    • Added resizable sidebar and detail panels with localStorage persistence
    • Introduced keyboard navigation (Cmd+F filter, arrow keys, Enter to select, Esc to close)
    • Added Bedrock AI model event stream decoding and token tracking
    • Real-IP DNS interception support for improved traffic routing
  • Bug Fixes

    • Fixed response body preservation for streaming responses with decoded chunks
    • Suppressed repeated degraded interception notifications
  • Tests

    • Added comprehensive Bedrock event stream decoder test coverage

GeiserX added 2 commits April 17, 2026 00:10
Add macOS notification after capture starts advising to restart IDE/CLI.
Replace broken confirm() with HTML dialog for purge confirmation.
Recreate tray on icon change to preserve template flag.
Bedrock EventStream decoder:
- Parse AWS binary event stream frames (application/vnd.amazon.eventstream)
- Extract base64-encoded JSON payloads into SSEChunk[] for token counting
- Fallback: preserve raw response body when chunks not parsed

Real-IP PF interception (fixes c-ares DNS bypass):
- Resolve target domains to real IPs via DoH
- Create PF rdr rules on physical interface for resolved IPs
- Periodic 60s re-resolution for DNS round-robin
- Complements existing /etc/hosts sentinel approach

Health monitor fix:
- Fire degraded notification once per episode, not every cooldown cycle

Trace viewer professional overhaul:
- Resizable panes with drag handles (sidebar, detail panel)
- Column sorting (click headers) with direction indicators
- Column resizing with drag handles
- JSON syntax highlighting with collapsible tree view
- Keyboard navigation (arrows, j/k, Enter, Escape, Cmd+F)
- Status code color coding (2xx green, 4xx orange, 5xx red)
- Duration performance indicators with mini timing bars
- Auto-scroll toggle for live traces
- Streaming progress indicator
- Professional polish (alternating rows, sticky headers, trace count,
  custom scrollbars, responsive layout, ARIA roles)
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

Extends PF anchor validation and cleanup to support real-IP DNS interception; adds Bedrock EventStream decoding; introduces user notifications for capture activation; implements real-IP DNS resolution and PF rules management; deduplicates health monitor alerts; and significantly enhances the UI with trace sorting, resizable panes, auto-scroll, and interactive JSON tree view.

Changes

Cohort / File(s) Summary
PF/Native Security
native/Helper/HelperTool.swift, src/bun/system/pf-manager.ts
Extended PF anchor allowlist to include com.apple/300.AgentTap.realip and .../pf-anchor-realip.conf; added new exported functions to resolve domain IPs via DNS-over-HTTPS, apply/clear real-IP PF redirect rules, and manage periodic refresh cycles.
Bedrock EventStream Decoding
src/bun/sse/bedrock-decoder.ts, src/bun/sse/bedrock-decoder.test.ts, src/bun/proxy/proxy-server.ts
Introduced new module to parse AWS EventStream binary frames with per-frame header extraction and base64 payload decoding; replaced empty-body behavior for Bedrock responses with parsed chunks; comprehensive test suite covering single/concatenated frames and edge cases.
Notification & System
src/bun/system/log-manager.ts, src/bun/system/health-monitor.ts, src/bun/index.ts
Added notifyInfo() API for macOS user notifications; refactored notifyCritical to use internal helper; introduced module-level flag to suppress repeated degraded-health notifications; wired capture-activation notification in CLI entry point.
Trace Storage & Interception
src/bun/storage/trace-writer.ts, src/bun/system/redirect-manager.ts
Updated response-body handling to preserve body when chunks exist; integrated real-IP rules application and lifecycle (start/stop/clear) alongside existing DNS/hosts interception.
UI Enhancements
src/views/mainview/index.html, src/views/mainview/index.css, src/views/mainview/index.ts
Added client-side trace sorting, auto-scroll toggle, resizable sidebar/detail panels, trace column resizing; implemented interactive JSON tree view with per-node copy; enhanced keyboard navigation (arrow keys, Escape, Cmd/Ctrl+F); updated row rendering with duration bars and streaming indicators.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #7 — Modifies the same core system modules (log-manager.ts, health-monitor.ts, pf-manager.ts) with overlapping health reporting and notification logic.
  • PR #5 — Introduces Bedrock EventStream decoding and real-IP PF rules management, directly overlapping with the streaming decoder and redirect-manager changes in this PR.
  • PR #3 — Establishes the initial PF real-IP anchor validation and cleanup patterns extended in this PR's native helper and pf-manager updates.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately summarizes the three major features: Bedrock decoder, real-IP interception, and professional trace viewer overhaul.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/professional-overhaul

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
src/bun/system/redirect-manager.ts (1)

18-22: Remove unused stopRealIpRefresh import.

clearRealIpRules calls stopRealIpRefresh internally (pf-manager.ts:357), so the explicit import is redundant.

♻️ Remove unused import
 import {
   initPfManager,
   ensureHelperInstalled,
   applyRules,
   clearRules,
   cleanupStaleRules,
   verifyDnsInterception,
   verifyRulesActive,
   applyRealIpRules,
   clearRealIpRules,
   startRealIpRefresh,
-  stopRealIpRefresh,
 } from "./pf-manager";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bun/system/redirect-manager.ts` around lines 18 - 22, Remove the
redundant import of stopRealIpRefresh from redirect-manager.ts: the function
clearRealIpRules already calls stopRealIpRefresh internally, so delete
stopRealIpRefresh from the import list (leaving applyRealIpRules,
clearRealIpRules, startRealIpRefresh). Ensure no other references to
stopRealIpRefresh remain in this module; if there are, either keep a single
needed import or adjust those calls to use clearRealIpRules/startRealIpRefresh
as intended.
src/views/mainview/index.html (1)

75-75: Consider adding role="option" to trace rows for ARIA compliance.

The listbox has role="listbox", but child trace rows should have role="option" and aria-selected for proper assistive technology support. This would need to be set dynamically in index.ts when rendering rows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/views/mainview/index.html` at line 75, The trace list container with id
"trace-rows" is marked role="listbox" but each rendered trace row must be given
role="option" and a corresponding aria-selected attribute; update the
row-rendering logic in index.ts (where trace rows are created/updated) to set
role="option" on each row element and toggle aria-selected ("true"/"false")
based on the currently focused/selected item, and ensure keyboard/mouse
selection handlers update aria-selected accordingly.
src/bun/sse/bedrock-decoder.ts (2)

63-75: Redundant JSON.parse calls.

The decoded payload is parsed twice: once at line 64 (discarded) and again at line 70. Parse once and reuse.

♻️ Proposed fix
          const decoded = Buffer.from(payload.bytes, "base64").toString("utf-8");
-          // Try parsing to validate JSON; store the raw string as data
-          JSON.parse(decoded);
-
-          // Map :event-type to SSE event name — Bedrock uses "chunk" for data frames
-          // The decoded JSON itself contains the Anthropic event type
-          let sseEvent: string | undefined;
-          try {
-            const decodedJson = JSON.parse(decoded);
-            sseEvent = decodedJson.type;
-          } catch {
-            // Use the EventStream event type as fallback
-            sseEvent = eventType;
-          }
+          // Parse once to validate and extract event type
+          const decodedJson = JSON.parse(decoded);
+          const sseEvent = decodedJson.type ?? eventType;

          chunks.push({
            timestamp: Date.now(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bun/sse/bedrock-decoder.ts` around lines 63 - 75, The code is parsing the
same JSON twice (first JSON.parse(decoded) then again when assigning
decodedJson); update the Bedrock decoder to parse decoded only once and reuse
the result: remove the initial discarded JSON.parse(decoded) and instead call
JSON.parse(decoded) once into a variable (e.g., decodedJson) then derive
sseEvent = decodedJson.type with a try/catch or check for valid object, falling
back to eventType if parsing fails or type is missing; adjust references to
decodedJson and sseEvent in the surrounding logic so no duplicate parsing
occurs.

156-160: Minor: Missing bounds check for Bytes type.

For type 6 (Bytes), pos += 2 + valueLen advances without verifying pos + 2 + valueLen <= headersEnd. If valueLen is malformed/large, this could skip past valid data. The loop will exit safely on the next iteration's bounds check, but an explicit guard would be cleaner.

♻️ Proposed fix
    } else if (valueType === 6) {
      // Bytes
      if (pos + 2 > headersEnd) break;
      const valueLen = buffer.readUInt16BE(pos);
-      pos += 2 + valueLen;
+      if (pos + 2 + valueLen > headersEnd) break;
+      pos += 2 + valueLen;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bun/sse/bedrock-decoder.ts` around lines 156 - 160, In the valueType ===
6 (Bytes) branch, add an explicit bounds check before advancing pos: after
reading valueLen via buffer.readUInt16BE(pos) ensure that pos + 2 + valueLen <=
headersEnd (using the existing pos, headersEnd, and valueLen variables); if the
check fails, break (or return/throw consistent with surrounding logic) instead
of blindly doing pos += 2 + valueLen to avoid skipping past valid data when
valueLen is malformed.
src/views/mainview/index.ts (1)

666-678: Silent clipboard failure could confuse users.

The empty catch block means copy failures are invisible. Consider a brief visual indicator on failure.

Proposed improvement
     navigator.clipboard.writeText(val).then(() => {
       target.textContent = "copied";
       target.classList.add("copied");
       setTimeout(() => {
         target.textContent = "copy";
         target.classList.remove("copied");
       }, 1200);
-    }).catch(() => {});
+    }).catch(() => {
+      target.textContent = "failed";
+      setTimeout(() => {
+        target.textContent = "copy";
+      }, 1200);
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/views/mainview/index.ts` around lines 666 - 678, The silent clipboard
failure occurs in the click handler branch checking
target.classList.contains("json-tree-copy") where
navigator.clipboard.writeText(...).catch(() => {}) swallows errors; update the
catch to both log the error (e.g., console.error) and provide a brief visual
failure state on the target element (set a failure message like "copy failed",
add a distinct class such as "copy-error", and remove/reset the text and class
after the same timeout), so users get immediate feedback when
navigator.clipboard.writeText fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/bun/system/pf-manager.ts`:
- Around line 306-319: The current ipPattern (/^\d{1,3}(\.\d{1,3}){3}$/) in
pf-manager.ts accepts invalid octets (e.g. 999); replace this loose check by
implementing a strict IPv4 validator (e.g. isValidIPv4 that splits on '.' and
ensures there are 4 numeric octets each 0–255 with no leading plus signs) and
use it instead of ipPattern.test inside the loop that populates allIps (refer to
ipPattern, domainIPs, allIps); keep the existing ifacePattern check untouched
and only swap the octet validation to call isValidIPv4(ip) before adding to
allIps.

In `@src/views/mainview/index.ts`:
- Around line 910-920: The call to applyColWidth inside cols.forEach is missing
the third argument, causing a runtime error when the function accesses
current[cls]; update the call applyColWidth(cls, savedWidths[cls]) to pass the
appropriate third parameter (the current map/object referenced as current inside
applyColWidth) so it becomes applyColWidth(cls, savedWidths[cls], current) (or
the actual variable name used in scope), ensuring applyColWidth can safely
read/update current[cls]; verify the variable passed matches the one used by
applyColWidth.

---

Nitpick comments:
In `@src/bun/sse/bedrock-decoder.ts`:
- Around line 63-75: The code is parsing the same JSON twice (first
JSON.parse(decoded) then again when assigning decodedJson); update the Bedrock
decoder to parse decoded only once and reuse the result: remove the initial
discarded JSON.parse(decoded) and instead call JSON.parse(decoded) once into a
variable (e.g., decodedJson) then derive sseEvent = decodedJson.type with a
try/catch or check for valid object, falling back to eventType if parsing fails
or type is missing; adjust references to decodedJson and sseEvent in the
surrounding logic so no duplicate parsing occurs.
- Around line 156-160: In the valueType === 6 (Bytes) branch, add an explicit
bounds check before advancing pos: after reading valueLen via
buffer.readUInt16BE(pos) ensure that pos + 2 + valueLen <= headersEnd (using the
existing pos, headersEnd, and valueLen variables); if the check fails, break (or
return/throw consistent with surrounding logic) instead of blindly doing pos +=
2 + valueLen to avoid skipping past valid data when valueLen is malformed.

In `@src/bun/system/redirect-manager.ts`:
- Around line 18-22: Remove the redundant import of stopRealIpRefresh from
redirect-manager.ts: the function clearRealIpRules already calls
stopRealIpRefresh internally, so delete stopRealIpRefresh from the import list
(leaving applyRealIpRules, clearRealIpRules, startRealIpRefresh). Ensure no
other references to stopRealIpRefresh remain in this module; if there are,
either keep a single needed import or adjust those calls to use
clearRealIpRules/startRealIpRefresh as intended.

In `@src/views/mainview/index.html`:
- Line 75: The trace list container with id "trace-rows" is marked
role="listbox" but each rendered trace row must be given role="option" and a
corresponding aria-selected attribute; update the row-rendering logic in
index.ts (where trace rows are created/updated) to set role="option" on each row
element and toggle aria-selected ("true"/"false") based on the currently
focused/selected item, and ensure keyboard/mouse selection handlers update
aria-selected accordingly.

In `@src/views/mainview/index.ts`:
- Around line 666-678: The silent clipboard failure occurs in the click handler
branch checking target.classList.contains("json-tree-copy") where
navigator.clipboard.writeText(...).catch(() => {}) swallows errors; update the
catch to both log the error (e.g., console.error) and provide a brief visual
failure state on the target element (set a failure message like "copy failed",
add a distinct class such as "copy-error", and remove/reset the text and class
after the same timeout), so users get immediate feedback when
navigator.clipboard.writeText fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dcc79afb-f641-4f13-903e-ee90b1672061

📥 Commits

Reviewing files that changed from the base of the PR and between 3afe9cd and 5f66727.

📒 Files selected for processing (13)
  • native/Helper/HelperTool.swift
  • src/bun/index.ts
  • src/bun/proxy/proxy-server.ts
  • src/bun/sse/bedrock-decoder.test.ts
  • src/bun/sse/bedrock-decoder.ts
  • src/bun/storage/trace-writer.ts
  • src/bun/system/health-monitor.ts
  • src/bun/system/log-manager.ts
  • src/bun/system/pf-manager.ts
  • src/bun/system/redirect-manager.ts
  • src/views/mainview/index.css
  • src/views/mainview/index.html
  • src/views/mainview/index.ts

Comment on lines +306 to +319
// Collect all unique IPs, validating each
const ipPattern = /^\d{1,3}(\.\d{1,3}){3}$/;
const ifacePattern = /^[a-z]+\d+$/;
if (!ifacePattern.test(ifaceName)) {
console.error(`[pf] Invalid interface name: ${ifaceName}`);
return false;
}

const allIps = new Set<string>();
for (const ips of domainIPs.values()) {
for (const ip of ips) {
if (ipPattern.test(ip)) allIps.add(ip);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

IP validation regex allows invalid octets.

The pattern /^\d{1,3}(\.\d{1,3}){3}$/ accepts values like "999.999.999.999". Since IPs come from DoH resolution, this is low risk, but defense-in-depth would validate each octet is ≤255.

🛡️ Proposed stricter validation
-  const ipPattern = /^\d{1,3}(\.\d{1,3}){3}$/;
+  const isValidIPv4 = (ip: string): boolean => {
+    const parts = ip.split(".");
+    if (parts.length !== 4) return false;
+    return parts.every((p) => {
+      const n = parseInt(p, 10);
+      return !isNaN(n) && n >= 0 && n <= 255 && String(n) === p;
+    });
+  };

Then use if (isValidIPv4(ip)) allIps.add(ip); at line 317.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bun/system/pf-manager.ts` around lines 306 - 319, The current ipPattern
(/^\d{1,3}(\.\d{1,3}){3}$/) in pf-manager.ts accepts invalid octets (e.g. 999);
replace this loose check by implementing a strict IPv4 validator (e.g.
isValidIPv4 that splits on '.' and ensures there are 4 numeric octets each 0–255
with no leading plus signs) and use it instead of ipPattern.test inside the loop
that populates allIps (refer to ipPattern, domainIPs, allIps); keep the existing
ifacePattern check untouched and only swap the octet validation to call
isValidIPv4(ip) before adding to allIps.

Comment on lines +910 to +920
// Apply saved widths
cols.forEach((col) => {
const el = col as HTMLElement;
const cls = Array.from(el.classList).find((c) => c.startsWith("col-") && c !== "col");
if (cls && savedWidths[cls]) {
el.style.width = `${savedWidths[cls]}px`;
el.style.flexShrink = "0";
// Apply to trace rows too
applyColWidth(cls, savedWidths[cls]);
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing third argument causes runtime error.

applyColWidth requires 3 parameters but only 2 are passed. This will throw when accessing current[cls] on undefined.

Proposed fix
     if (cls && savedWidths[cls]) {
       el.style.width = `${savedWidths[cls]}px`;
       el.style.flexShrink = "0";
-      applyColWidth(cls, savedWidths[cls]);
+      applyColWidth(cls, savedWidths[cls], savedWidths);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/views/mainview/index.ts` around lines 910 - 920, The call to
applyColWidth inside cols.forEach is missing the third argument, causing a
runtime error when the function accesses current[cls]; update the call
applyColWidth(cls, savedWidths[cls]) to pass the appropriate third parameter
(the current map/object referenced as current inside applyColWidth) so it
becomes applyColWidth(cls, savedWidths[cls], current) (or the actual variable
name used in scope), ensuring applyColWidth can safely read/update current[cls];
verify the variable passed matches the one used by applyColWidth.

@GeiserX
Copy link
Copy Markdown
Owner Author

GeiserX commented Apr 16, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

1 participant