nuclei icon indicating copy to clipboard operation
nuclei copied to clipboard

feat(headless): add `cdp-endpoint` option

Open dwisiswant0 opened this issue 1 year ago • 10 comments

Proposed changes

Close #5692

How has this been tested?

# tty1
/path/to/chrome --headless --remote-debugging-port=9222 --ignore-certificate-errors --ignore-ssl-errors
# tty2
go run cmd/nuclei/main.go -headless -t headless-template-1.yaml -u http://scanme.sh -cdp-endpoint "CHROME_DEVTOOLS_ENDPOINT_URL"

Checklist

  • [x] Pull request is created against the dev branch
  • [ ] All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • [ ] I have added tests that prove my fix is effective or that my feature works
  • [ ] I have added necessary documentation (if appropriate)

Summary by CodeRabbit

  • New Features

    • Added a CLI option to connect to a remote browser via a Chrome DevTools Protocol (CDP) endpoint.
  • Refactor

    • Initialization and shutdown now distinguish local vs. remote browser usage, skipping local launch and cleanup when a remote CDP endpoint is used.
  • Documentation

    • Updated HEADLESS docs across locales to document the new CDP endpoint option.

dwisiswant0 avatar Oct 30 '24 13:10 dwisiswant0

But I’m unable to connect to the CDP URL provided by finic:

[FTL] Could not create runner: websocket bad handshake: 400 Bad Request. Failed to open a WebSocket connection: invalid Sec-WebSocket-Key header: nil; Incorrect padding.

Not sure if this is an issue with go-rod or with finic. It needs further investigation, or maybe even a hacky workaround. I’ll try testing it via Playwright - if the issue can’t be reproduced there, I’ll go ahead and raise it as an issue in go-rod.

dwisiswant0 avatar Oct 30 '24 13:10 dwisiswant0

Works with browserless.

image

Seems like the issue is within finic.

dwisiswant0 avatar Nov 03 '24 15:11 dwisiswant0

@ehsandeep - If we look at the trace, it doesn't seem related to our source code - the underlying issue is in go-rod. Also, this panic wouldn't happen if we were using the system CDP.

$ go run cmd/nuclei/main.go -u http://scanme.sh -cdp-endpoint "ws://*********:9222/devtools/browser/************************************" -headless -pt headless

                     __     _
   ____  __  _______/ /__  (_)
  / __ \/ / / / ___/ / _ \/ /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/\__,_/\___/_/\___/_/   v3.3.5

		projectdiscovery.io

[INF] Current nuclei version: v3.3.5 (latest)
[INF] Current nuclei-templates version: v10.0.3 (latest)
[WRN] Scan results upload to cloud is disabled.
[INF] New templates added in latest release: 116
[INF] Templates loaded for current scan: 18
[INF] Executing 18 signed templates from projectdiscovery/nuclei-templates
[INF] Targets loaded for current scan: 1
[piratebay] [headless] [info] https://thepiratebay.org/search.php?q=user:{{user}}
[extract-urls] [headless] [info] http://scanme.sh [""]

dwisiswant0 avatar Nov 03 '24 20:11 dwisiswant0

We either need to tweak the Browserless container or there's something on our end that needs to be handled - but we need to figure out the root cause (template) that's triggering that panic.

dwisiswant0 avatar Nov 03 '24 20:11 dwisiswant0

This pull request has been automatically marked as stale due to inactivity. It will be closed in 7 days if no further activity occurs. Please update if you wish to keep it open.

github-actions[bot] avatar Jul 20 '25 00:07 github-actions[bot]

Walkthrough

A new cdp-endpoint CLI flag and CDPEndpoint option were added. The headless browser engine now conditionally uses the provided CDP WebSocket URL instead of launching a local Chrome instance; initialization and cleanup are skipped when connecting to a remote CDP endpoint.

Changes

Cohort / File(s) Change Summary
CLI flag
cmd/nuclei/main.go
Added cdp-endpoint (cdpe) CLI flag bound to options.CDPEndpoint.
Options struct
pkg/types/types.go
Added CDPEndpoint string field to Options.
Headless engine
pkg/protocols/headless/engine/engine.go
Refactored New and Close to: when CDPEndpoint is set, use it as the launcher URL and skip local Chrome launch, temp dir creation, musl detection, launcher setup, and local cleanup; otherwise preserve existing local launch and cleanup logic. Proxy and extra args apply only to local launches.
Documentation
README.md, README_CN.md, README_ES.md, README_ID.md, README_KR.md, README_PT-BR.md
Added or adjusted HEADLESS section entries to document -cdpe, -cdp-endpoint flag (formatting/content updates across locales).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant Engine
    participant Browser

    User->>CLI: nuclei --cdp-endpoint ws://host:port
    CLI->>Engine: Pass Options (CDPEndpoint set)
    alt CDP endpoint provided
        Engine->>Browser: Connect to provided CDP WebSocket URL
        Note right of Engine #f9f0c1: Skip temp dir creation\nSkip launching local Chrome
        User->>Engine: Perform headless actions
        Engine->>Browser: Send CDP commands over WebSocket
        User->>Engine: Close()
        Engine-->>Browser: Do not terminate remote browser (leave running)
    else No CDP endpoint
        Engine->>Engine: Create temp dir & configure launcher
        Engine->>Browser: Launch local Chrome process
        User->>Engine: Perform headless actions
        Engine->>Browser: Send CDP commands
        User->>Engine: Close()
        Engine->>Browser: Terminate Chrome process and cleanup
    end

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective (issue #5692) Addressed Explanation
Add cdp-endpoint flag to accept a WebSocket/CDP URL for headless mode Flag and Options field added.
Use provided CDP WebSocket URL to connect to existing/remote browser Engine uses provided URL instead of launching Chrome.
Allow users to keep remote browser lifecycle external (skip terminating remote) Close() skips terminating remote browser when CDP endpoint used.
Remove chromium dependency / make base image scratch (optional) Local launch path and related launcher code remain; chromium removal not addressed.

Poem

I’m a rabbit by the CDP shore,
I hop to ws:// and ask for more. 🐇
No local launch, just websockets spun,
Commands go out — the tabs have fun.
I close my paws, the remote stays on.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat(headless): add cdp-endpoint option" succinctly and accurately describes the main change of adding the CDP endpoint flag to the headless mode, following conventional commit conventions and clearly indicating the feature scope.
Linked Issues Check ✅ Passed The changes add the CDPEndpoint field to the Options struct, bind it as a CLI flag, conditionally use the provided WebSocket URL in the headless engine initialization and cleanup logic, and update documentation accordingly, thereby fulfilling the objectives of issue #5692 to enable remote CDP endpoint connections in headless mode.
Out of Scope Changes Check ✅ Passed All code and documentation changes directly relate to introducing and supporting the cdp-endpoint option and its conditional handling in the headless engine, with no modifications detected outside the scope of the linked feature request.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment
  • [ ] Commit unit tests in branch dwisiswant0/feat/headless/cdp-endpoint-option

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fef94adc52f1c536eada11efa042367a3597eed and 0c8fb50b7d1ab5b50c6f3add2ea6491eae321de9.

📒 Files selected for processing (6)
  • README.md (1 hunks)
  • README_CN.md (1 hunks)
  • README_ES.md (1 hunks)
  • README_ID.md (1 hunks)
  • README_KR.md (1 hunks)
  • README_PT-BR.md (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • README_PT-BR.md
  • README.md

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

coderabbitai[bot] avatar Jul 20 '25 00:07 coderabbitai[bot]

https://github.com/orgs/projectdiscovery/discussions/6453

dwisiswant0 avatar Sep 18 '25 14:09 dwisiswant0

Very cool and useful functionality, I hope it'll become part of upstream. The only missing thing here is no incognito mode which could be highly useful. Currently upstream has the following from (called from executeRequestWithPayloads)

func (b *Browser) NewInstance() (*Instance, error) { browser, err := b.engine.Incognito() if err != nil { return nil, err }

and it's not configurable. If we switch to something like

func (b *Browser) NewInstance() (*Instance, error) { var browser *rod.Browser if b.options.HeadlessNoIncognito { // Use the existing browser directly instead of creating an incognito instance browser = b.engine } else { var err error browser, err = b.engine.Incognito() if err != nil { return nil, err } }

we could get much more flexible result. Of course it can be added separetely. Correct me if I'm wrong :-)

Deamhan avatar Nov 01 '25 13:11 Deamhan

[...] The only missing thing here is no incognito mode which could be highly useful. [...]

Since we’re connecting via Chrome DevTools Proto URL (browser’s already live), flags don’t take effect after launch [and are not controllable from the source], right? They’re static, hence any flag changes won’t apply.

I’m not 100% certain. AFK rn, will PoC later to confirm.

dwisiswant0 avatar Nov 01 '25 14:11 dwisiswant0

Since we’re connecting via Chrome DevTools Proto URL (browser’s already live), flags don’t take effect after launch [and are not controllable from the source], right? They’re static, hence any flag changes won’t apply.

I’m not 100% certain. AFK rn, will PoC later to confirm.

I was talking about request.go#L115 - as I see for such requests we create a new instance of browser based on existing one instance.go#L30 no matter if it's provided CDP or not - correct me if I'm wrong. If we want to use the same instance a minor modification is required like I mentioned before - something like avoid of browser, err = b.engine.Incognito(). For production usecase I added this modification and the result works like a charm :-)

Deamhan avatar Nov 06 '25 11:11 Deamhan