Skip to content

Commit 6f705b6

Browse files
committed
fix: use system chromium for httpx/nuclei headless when present
go-rod ships no arm64-linux chrome to download, so the embedded scanners must use the installed browser. Gate UseInstalledChrome/UseChrome on launcher.LookPath so amd64 hosts without a browser still auto-download, and log which browser resolves at startup.
1 parent 832329d commit 6f705b6

6 files changed

Lines changed: 59 additions & 35 deletions

File tree

.gitignore

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,6 @@ cmd/fleetx/fleetx
99
cmd/pd-agent/pd-agent
1010
output/*
1111

12-
# Local runtime and scratch
13-
.claude/
14-
REVIEW.md
15-
chrome-ps-aux.txt
16-
ubisoft.txt
17-
18-
# SQLite runtime files
19-
pd-agent-*.db
20-
pd-agent-*.db-shm
21-
pd-agent-*.db-wal
22-
23-
# Local binaries at repo root
24-
/pd-agent-linux-amd64
25-
/pd-agent-linux-arm64
26-
/pd-agent-windows-amd64.exe
27-
/interactsh-debug
28-
29-
# Scan logs
30-
scan-*.log
31-
32-
# Local helper scripts at repo root
33-
/bench-create.sh
34-
/bench-create-multi.sh
35-
/cidr-ip.sh
36-
/start.sh
37-
/start-mac.sh
38-
/ssh-config
3912

4013
# Local build artifacts
4114
/build/
@@ -48,3 +21,6 @@ scan-*.log
4821
!/docs/configuration.md
4922
/pd-agent-deployment.yaml
5023
/pd-agent-deployment-prod.yaml
24+
25+
local/
26+
.claude/

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/projectdiscovery/pd-agent
33
go 1.25.7
44

55
require (
6+
github.com/go-rod/rod v0.116.2
67
github.com/json-iterator/go v1.1.12
78
github.com/klauspost/compress v1.18.4
89
github.com/miekg/dns v1.1.68
@@ -175,7 +176,6 @@ require (
175176
github.com/go-playground/locales v0.14.1 // indirect
176177
github.com/go-playground/universal-translator v0.18.1 // indirect
177178
github.com/go-playground/validator/v10 v10.26.0 // indirect
178-
github.com/go-rod/rod v0.116.2 // indirect
179179
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
180180
github.com/go-sql-driver/mysql v1.9.3 // indirect
181181
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect

pkg/prereq/prereq.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ func EnsureAll() (failed []string) {
3737
func warmupBrowser() error {
3838
slog.Info("prereq: validating browser (embedded httpx screenshot probe)...")
3939

40+
if path, ok := runtools.SystemChromePath(); ok {
41+
slog.Info("prereq: using system browser", "path", path)
42+
} else {
43+
slog.Info("prereq: no system browser found, go-rod will download Chrome (amd64 only)")
44+
}
45+
4046
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
4147
w.Header().Set("Content-Type", "text/html")
4248
_, _ = w.Write([]byte(`<html><body><h1>pd-agent warmup</h1></body></html>`))
@@ -70,15 +76,24 @@ func warmupBrowser() error {
7076
})
7177
if runErr != nil {
7278
msg := runErr.Error()
73-
if strings.Contains(msg, "cannot open shared object") ||
79+
switch {
80+
case strings.Contains(msg, "cannot open shared object") ||
7481
strings.Contains(msg, "Failed to launch the browser") ||
75-
strings.Contains(msg, "error while loading shared libraries") {
82+
strings.Contains(msg, "error while loading shared libraries"):
7683
slog.Error("prereq: browser validation failed, missing Chrome dependencies",
7784
"error", msg,
7885
"hint", "install Chrome deps: sudo apt-get install -y libatk1.0-0 libatk-bridge2.0-0 libcups2 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 libnspr4 libnss3 libxcomposite1 libxfixes3 libxshmfence1 libxkbcommon0")
7986
return fmt.Errorf("missing Chrome shared libraries")
87+
case strings.Contains(msg, "browser is not installed") ||
88+
strings.Contains(msg, "find a browser binary") ||
89+
strings.Contains(msg, "find a valid URL to download"):
90+
slog.Error("prereq: browser validation failed, no chrome/chromium found",
91+
"error", msg,
92+
"hint", "install a system browser (e.g. sudo apt-get install -y chromium); pd-agent only auto-downloads Chrome on amd64, so arm64 hosts must have one installed")
93+
return fmt.Errorf("no chrome/chromium browser available")
94+
default:
95+
slog.Warn("prereq: browser validation httpx probe had non-fatal error", "error", msg)
8096
}
81-
slog.Warn("prereq: browser validation httpx probe had non-fatal error", "error", msg)
8297
}
8398

8499
if shotPath := readFirstScreenshotPath(tmpPath); shotPath != "" {
@@ -89,7 +104,7 @@ func warmupBrowser() error {
89104
}
90105

91106
slog.Error("prereq: browser validation failed, screenshot was not written",
92-
"hint", "Chrome may need more time on first launch (it downloads ~150MB); rerun, or pre-install Chrome via go-rod's manager")
107+
"hint", "ensure a system chromium/chrome is installed and on PATH (the docker image ships /usr/bin/chromium)")
93108
return fmt.Errorf("screenshot not produced")
94109
}
95110

pkg/runtools/chrome.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package runtools
2+
3+
import (
4+
"sync"
5+
6+
"github.com/go-rod/rod/lib/launcher"
7+
)
8+
9+
type chromeInfo struct {
10+
path string
11+
ok bool
12+
}
13+
14+
// resolveSystemChrome runs go-rod's LookPath once. Matching the check
15+
// httpx/nuclei run internally means we never force "use installed" on a host
16+
// that has none, which would hard-error instead of falling back to download.
17+
var resolveSystemChrome = sync.OnceValue(func() chromeInfo {
18+
path, ok := launcher.LookPath()
19+
return chromeInfo{path: path, ok: ok}
20+
})
21+
22+
// systemChromeAvailable reports whether a chrome/chromium binary is installed
23+
// on this host. httpx and nuclei must be told to use it (go-rod ships no
24+
// arm64-linux chrome to download); when absent, go-rod downloads its own,
25+
// which only works on amd64.
26+
func systemChromeAvailable() bool { return resolveSystemChrome().ok }
27+
28+
// SystemChromePath returns the resolved system browser path and whether one was
29+
// found, so callers can log which browser the embedded scanners will use.
30+
func SystemChromePath() (string, bool) {
31+
c := resolveSystemChrome()
32+
return c.path, c.ok
33+
}

pkg/runtools/httpx.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ type HttpxOptions struct {
1818
Concurrency int
1919
Timeout time.Duration
2020
Screenshot bool
21-
// ScreenshotTimeout bounds each headless screenshot. First-time runs need
22-
// longer because Chrome has to download.
21+
// ScreenshotTimeout bounds each headless screenshot.
2322
ScreenshotTimeout time.Duration
2423
// DisableStdout suppresses httpx's per-result stdout writes.
2524
DisableStdout bool
@@ -56,6 +55,7 @@ func RunHttpx(ctx context.Context, targets []string, opts HttpxOptions) (string,
5655
Timeout: int(opts.Timeout.Seconds()),
5756
Screenshot: opts.Screenshot,
5857
ScreenshotTimeout: opts.ScreenshotTimeout,
58+
UseInstalledChrome: systemChromeAvailable(),
5959
DisableStdout: opts.DisableStdout,
6060
StoreResponseDir: opts.StoreResponseDir,
6161
StatusCode: true,

pkg/runtools/nuclei.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func RunNuclei(ctx context.Context, opts NucleiOptions) (string, error) {
9797
}))
9898
}
9999
if opts.Headless {
100-
sdkOpts = append(sdkOpts, nuclei.EnableHeadlessWithOpts(nil))
100+
sdkOpts = append(sdkOpts, nuclei.EnableHeadlessWithOpts(&nuclei.HeadlessOpts{UseChrome: systemChromeAvailable()}))
101101
}
102102
if opts.MatcherStatus {
103103
sdkOpts = append(sdkOpts, nuclei.EnableMatcherStatus())

0 commit comments

Comments
 (0)