Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@

---

## Console Wrapping Removal

- We no longer monkey-patch `console.*` methods; global `error`/`unhandledrejection` handlers remain the only capture points.
- Rationale: console wrapping risks recursion, breaks DevTools expectations, mutates global browser state, and adds per-call overhead.
- Security posture is improved by avoiding interception of developer tooling while still capturing unhandled errors/rejections.

---

## Rule of Thumb: Priority Fixes

**When adversarial analysis identifies issues, categorize and handle as:**
Expand Down Expand Up @@ -3288,4 +3296,3 @@ Hera is a **monitoring tool**, not an **authentication library**. Signature veri
**Date:** 2025-10-30
**Verdict:** ✅ STRONG FOUNDATION, 5 CRITICAL GAPS TO ADDRESS
**Recommendation:** Prioritize P0 (DPoP integration) + P1 (PKCE severity, rotation UI, debugger scoping)

21 changes: 21 additions & 0 deletions docs/runbooks/RUNBOOK-error-collection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Error Collection Runbook

## Overview
- Error collection now relies solely on global `error` and `unhandledrejection` handlers; console method wrapping was removed for security and stability.
- Purpose: capture actionable runtime failures (message, stack, filename/line) and persist recent entries without mutating browser globals.

## What Is Captured
- `UNHANDLED_ERROR`: window-level errors with message, stack, filename, line, column, and timestamp.
- `UNHANDLED_REJECTION`: unhandled promise rejections with message, stack (when available), and timestamp.
- Warnings and info logs from inside the collector itself; no interception of `console.*`.

## Expected Behavior
- Errors are de-duped within a 5s window to prevent bursts.
- Persistence is debounced to minimize storage writes; only the most recent entries are kept.
- Console calls (`console.error|warn|log`) are **not** intercepted—this is intentional to avoid recursion and DevTools breakage.

## Operator Checks
1) Trigger an uncaught error in the extension context; confirm it appears as `UNHANDLED_ERROR`.
2) Trigger an unhandled promise rejection; confirm it appears as `UNHANDLED_REJECTION`.
3) Call `console.error` and verify it is **not** captured (by design).
4) Export errors (JSON or text) from the UI and ensure timestamps and stacks are present.
87 changes: 24 additions & 63 deletions modules/error-collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@ class ErrorCollector {
// Dedupe cache for noisy repeats
this._lastSeen = new Map(); // key -> timestamp

// OFF by default; can be toggled by message or storage
this.captureDebugLogs = false;

// FIX: Debounce timer for persistence (prevent rate limit violations)
this._persistTimer = null;
this._PERSIST_DELAY = 1000; // 1 second

// Intercept console errors
// Periodic cleanup for dedupe cache
if (chrome?.alarms) {
// Register cleanup alarm (runs every 60 seconds)
chrome.alarms.create('error-collector-cleanup', { periodInMinutes: 1 });
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm?.name === 'error-collector-cleanup') {
this._cleanupLastSeen();
}
});
}

// Intercept global errors
this.setupErrorHandlers();
}

Expand All @@ -48,6 +56,18 @@ class ErrorCollector {
return true;
}

/**
* Remove expired dedupe entries
*/
_cleanupLastSeen() {
const now = Date.now();
for (const [key, timestamp] of this._lastSeen.entries()) {
if (now - timestamp > DEDUPE_WINDOW_MS) {
this._lastSeen.delete(key);
}
}
}

/**
* Safely stringify a value with size limit
*/
Expand Down Expand Up @@ -164,65 +184,6 @@ class ErrorCollector {
}
});
}

// Wrap console methods
this.wrapConsole();
}

/**
* Wrap console methods to capture errors
*/
wrapConsole() {
const originalError = console.error;
const originalWarn = console.warn;
const originalLog = console.log;

console.error = (...args) => {
// BUGFIX: Don't log storage quota errors to prevent infinite loop
const message = args.map(a => String(a)).join(' ');
if (!message.includes('Storage rate limit') && !message.includes('QUOTA_BYTES')) {
const entry = {
type: 'CONSOLE_ERROR',
message: message,
args: args,
stack: new Error().stack,
timestamp: new Date().toISOString()
};
if (this._shouldLog(entry)) {
this.logError(entry);
}
}
originalError.apply(console, args);
};

console.warn = (...args) => {
const entry = {
type: 'CONSOLE_WARN',
message: args.map(a => String(a)).join(' '),
args: args,
timestamp: new Date().toISOString()
};
if (this._shouldLog(entry)) {
this.logWarning(entry);
}
originalWarn.apply(console, args);
};

// Optionally capture logs for debugging
if (this.captureDebugLogs) {
console.log = (...args) => {
const entry = {
type: 'CONSOLE_LOG',
message: args.map(a => String(a)).join(' '),
args: args,
timestamp: new Date().toISOString()
};
if (this._shouldLog(entry)) {
this.logInfo(entry);
}
originalLog.apply(console, args);
};
}
}

/**
Expand Down
Loading