Skip to content

Commit 6546c53

Browse files
authored
Merge pull request #158 from projectdiscovery/shubh/headless-system-chrome
fix: use system chromium for httpx/nuclei headless when present
2 parents 832329d + 3a412d1 commit 6546c53

11 files changed

Lines changed: 122 additions & 176 deletions

File tree

.github/workflows/build-test.yml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
paths:
66
- '**.go'
77
- '**.mod'
8+
- 'Dockerfile'
89
workflow_dispatch:
910

1011
permissions:
@@ -46,4 +47,28 @@ jobs:
4647

4748
- name: Race Condition Tests
4849
run: go build -race .
49-
working-directory: cmd/pd-agent/
50+
working-directory: cmd/pd-agent/
51+
52+
build-image:
53+
name: Build Image
54+
needs: [lint]
55+
runs-on: ubuntu-latest-16-cores
56+
steps:
57+
- uses: actions/checkout@v4
58+
59+
- name: Set up QEMU
60+
uses: docker/setup-qemu-action@v3
61+
62+
- name: Set up Docker Buildx
63+
uses: docker/setup-buildx-action@v3
64+
65+
- name: Build image (no push)
66+
uses: docker/build-push-action@v6
67+
with:
68+
context: .
69+
platforms: linux/amd64,linux/arm64
70+
push: false
71+
build-args: |
72+
VERSION=pr-${{ github.sha }}
73+
cache-from: type=gha
74+
cache-to: type=gha,mode=max

.github/workflows/codeql-analysis.yml.old

Lines changed: 0 additions & 41 deletions
This file was deleted.

.github/workflows/ghcr-push.yml

Lines changed: 0 additions & 47 deletions
This file was deleted.

.github/workflows/release-test.yml

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
name: 🎉 Release Binary
1+
name: 🎉 Release
22

33
on:
44
push:
55
tags:
66
- v*
7-
workflow_dispatch:
7+
8+
permissions:
9+
contents: write
810

911
jobs:
1012
build-mac:
@@ -81,4 +83,36 @@ jobs:
8183
version: latest
8284
args: release -f .goreleaser/windows.yml --clean
8385
env:
84-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
86+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87+
88+
publish-image:
89+
runs-on: ubuntu-latest-16-cores
90+
permissions:
91+
contents: read
92+
packages: write
93+
steps:
94+
- name: Checkout
95+
uses: actions/checkout@v5
96+
97+
- name: Set up QEMU
98+
uses: docker/setup-qemu-action@v3
99+
100+
- name: Set up Docker Buildx
101+
uses: docker/setup-buildx-action@v3
102+
103+
- name: Login to GHCR
104+
uses: docker/login-action@v3
105+
with:
106+
registry: ghcr.io
107+
username: ${{ github.actor }}
108+
password: ${{ secrets.GITHUB_TOKEN }}
109+
110+
- name: Build and push
111+
uses: docker/build-push-action@v6
112+
with:
113+
context: .
114+
platforms: linux/amd64,linux/arm64
115+
push: true
116+
build-args: |
117+
VERSION=${{ github.ref_name }}
118+
tags: ghcr.io/projectdiscovery/pd-agent:latest,ghcr.io/projectdiscovery/pd-agent:${{ github.ref_name }}

.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,

0 commit comments

Comments
 (0)