Skip to content

hackbrowser worker crashes (exit 1) mid-crawl when a target closes — unguarded request.allHeaders() #104

Description

@badchars

Summary

The hackbrowser crawl worker can crash with exit code 1 in the middle of a crawl. The request-capture handler awaits request.allHeaders() without a guard; when the page/context/browser closes while requests are still in flight, Playwright rejects with Target page, context or browser has been closed. Because this happens inside a fire-and-forget page.on("request", ...) handler, the rejection is unhandled and takes down the entire worker process — so the crawl ends after exploring only a fraction of the target.

Environment

  • Version: 1.1.16
  • Mode: full-auto-headed
  • Runtime: Bun 1.3.14 (macOS arm64)

Symptom

The TUI shows the crawl as Failed:

hackbrowser worker exited unexpectedly (code 1): ...
      const headers = await request.allHeaders();
                            ^
error: allHeaders: Target page, context or browser has been closed
      at .../cyberstrike/bin/hackbrowser-worker.js
Bun v1.3.14 (macOS arm64)

Several endpoints were captured before the crash — i.e. the crawl was still in progress (not finished) when the worker died.

Root cause

packages/hackbrowser/src/agent.ts, the request interceptor:

page.on("request", async (request) => {
  ...
  const method = request.method()
  const headers = await request.allHeaders()   // <-- unguarded; rejects if target closed
  const postData = request.postData() ?? null
  ...
})

In headed mode a page/context can close mid-crawl (a popup/tab closing, or a navigation) while requests are still in flight. allHeaders() then rejects, and because the handler is async and fire-and-forget, the rejection is unhandled and crashes the worker (exit 1). The response branch further down is already wrapped in try/catch; this call was not. The sibling page.on("response", ...) login detector is sync-only and unaffected.

Proposed fix

Guard the allHeaders() call and drop the capture on a closed target instead of crashing:

const method = request.method()
let headers: Record<string, string>
try {
  headers = await request.allHeaders()
} catch {
  // Target/page/browser closed mid-flight — drop this capture rather than
  // letting an unhandled rejection crash the worker (exit 1).
  return
}
const postData = request.postData() ?? null

Optional hardening: add a process-level unhandledRejection handler in the worker as a safety net, so any other stray closed-target rejection degrades gracefully instead of killing the crawl.

Impact

Crawl terminates early — only part of the application is explored/captured. Most likely in headed mode and on apps that open/close popups or navigate during the crawl.

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