390 Commits
Author SHA1 Message Date
Doğan Can Bakır bc47c52bf5 bump version 2026-10-06 22:11:46 +03:00
Doğan Can Bakır cf55e34ed4 fix(subdomaincenter): isolate state per run 2026-10-06 21:58:47 +03:00
Doğan Can Bakır e12ca4922b Merge remote-tracking branch 'origin/dev' into feat/subdomaincenter
# Conflicts:
#	pkg/passive/sources.go
2026-10-06 21:58:43 +03:00
Doğan Can Bakır dcc066aa48 refactor(runner): keep compiled filters on runner, accept regex files 2026-10-06 21:42:42 +03:00
Doğan Can Bakır aabab03235 Merge remote-tracking branch 'origin/dev' into feat/1955-regex-filters 2026-10-06 21:39:23 +03:00
Dogan Can Bakir 180db5cf08 Merge pull request #1957 from CalvinTjoaquinn/fix/release-response-bodies
fix(sources): release the response body, and bound the github 403 retry
2026-10-06 21:25:23 +03:00
Dogan Can Bakir e850539092 Merge pull request #1863 from krishna28238-arch/fix-normalize-source-subdomains
fix: normalize source-reported subdomains before domain check
2026-10-06 21:14:20 +03:00
Calvin Tjoaquinn 107c3f25e8 fix(github): bound the rate-limit retry per URL, and close on drain error
Two problems the review found in the previous commit.

`rateLimitRetries` lived on the run, so a 403 on a page that then
succeeded still spent budget. With one exhausted token and one healthy
one in the pool, every page answers 403 once and 200 once, and the
third such page ran the counter past the pool size: enumeration ended
early and reported that every token was rate limited while a working
token was still in the pool. The budget is now a `retries` argument
that a new URL starts over at zero, so a page that succeeds cannot eat
into the budget of the pages after it.

`DiscardHTTPResponse` returned before `Close()` when draining failed,
leaking the connection in exactly the case the helper exists to handle,
and every source now reaches it through a defer. It closes in both
paths.

Both tests fail without their fix: the retry one reports page 3 being
requested once rather than twice, the discard one reports the body
never being closed.
2026-10-01 08:20:28 +07:00
Calvin Tjoaquinn bae98a62a2 fix(sources): release the response body, and bound the github 403 retry
httpRequestWrapper returns the response *and* an error for any non-200
status, so a source that returns on `err != nil` before registering its
defer leaves that body undrained. Seven sources did:

  certspotter, digitalyama, driftnet, leakix, merklemap, reconeer,
  securitytrails

For a key-based source this is the common path, not a rare one: 429 once a
free-tier key is exhausted, 403 once it is rejected. Moving the deferred
DiscardHTTPResponse above the error check fixes it, and the helper already
ignores a nil response. securitytrails is different in shape: its 403 retry
overwrote resp without draining the first reply. digitalyama closed its body
without draining, so the connection could not be reused either.

The new sweep also uncovered a crash. github recursed into enumerate on every
403 with X-Ratelimit-Remaining 0, and Tokens.Get hands the pool out round
robin without skipping the tokens already marked exceeded. Once every token is
limited there was nothing to stop the recursion: one stack frame per 403 until
the process died with a stack overflow, which is what the test hit before this
change. Each token now gets one attempt, then the run reports the rate limit
and ends.

TestSourcesCloseResponseBodyOnErrorStatus runs every source against a 429 and
a 403 and requires every body handed out to be closed, so a source added later
cannot quietly reintroduce this.
2026-09-29 13:38:34 +07:00
root-Manas 048692348f fix(runner): isolate compiled filters per runner 2026-09-27 18:31:24 +05:30
root-Manas 6d2d8b9eda fix(runner): preserve filters on failed regex compilation 2026-09-27 11:02:07 +05:30
root-Manas bccaf2a47e feat(runner): add regular expression result filters 2026-09-27 10:45:36 +05:30
dw1 4debdd5fdb refactor(sources): isolate state for concurrent source runs (#1953)
* chore: go mod tidy

Signed-off-by: Dwi Siswanto <[email protected]>

* refactor(sources): isolate state for concurrent source runs

Keep counters and skipped state local to each Run call so overlapping
runs cannot reset each other's accounting or consume each other's result
limits. Protect statistics publication and reads with a mutex, returning
a snapshot of the most recently completed run.

Snapshot API keys at the start of each run and copy supplied key slices
in AddApiKeys. Keep atomic counters scoped to one run for the GitHub,
GitLab, and Driftnet workers.

Move GitLab's deferred wg.Done call before the worker's error paths so
failed file requests do not leave enumeration blocked.

Fixes #1952
Closes #1947

Signed-off-by: Dwi Siswanto <[email protected]>

---------

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-25 11:15:23 +07:00
dw1andMzack9999 cb8efa3faa fix(scanmalware): statistics races between concurrent runs (#1949)
* fix(scanmalware): statistics races between concurrent runs

Concurrent Run calls reset and update counters on the same Source,
causing data races and mixing statistics from separate enumerations.
Statistics also reads those fields without synchronization.

Keep counters local to each run and publish the completed statistics
under a mutex before closing the results channel. Statistics returns
a snapshot of the most recently completed run. Requests remain
concurrent.

Fixes #1948

Signed-off-by: Dwi Siswanto <[email protected]>

* skip submd

* relax timing

---------

Signed-off-by: Dwi Siswanto <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-09-23 18:58:37 +07:00
Doğan Can Bakır 5156637b4a fix(crtname): lint errors and response body cleanup 2026-09-16 14:33:58 +03:00
Doğan Can Bakır bfa7890c73 Merge remote-tracking branch 'origin/dev' into feat/add-crtname
# Conflicts:
#	pkg/passive/sources.go
2026-09-16 14:33:58 +03:00
dw1andMzack9999 eba9c51eaf fix(zoomeyeapi): switch to the v2 search API (#1944)
* fix(zoomeyeapi): switch to the v2 search API

Replace GET /domain/search with POST /v2/search, using a base64-encoded
domain query and reading data[].domain. Select web assets explicitly
and preserve the existing host:key configuration.

Stop pagination at the reported total or an empty page. Report HTTP
403 and API errors, close responses before sending errors, and make
error delivery cancelable.

Fixes #1943

Signed-off-by: Dwi Siswanto <[email protected]>

* add tests

---------

Signed-off-by: Dwi Siswanto <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-09-15 18:38:46 +07:00
Mzack9999 0239836d90 Merge pull request #1873 from projectdiscovery/dwisiswant0/chore/subscraping/restore-HTTP-connection-reuse
chore(subscraping): restore HTTP connection reuse
2026-09-09 23:28:41 +02:00
Dwi Siswanto 0c457c2917 chore(subscraping): restore HTTP connection reuse
Drop the default Connection: close header so follow-up requests to the
same host can reuse a session's connection pool. That skips repeating
TCP setup and TLS handshakes on paginated sources. Explicit Connection
headers from sources still win.

The header landed in b03f0933d1 to stop descriptor exhaustion from
bound how long idle conns stick around. Existing Session.Close cleanup
still drains the pool when enumeration finishes.

Add HTTP and HTTPS regression tests for reuse, explicit close overrides,
and session cleanup. Add a Linux subprocess test that enumerates 128
domains under a 64-descriptor cap, makes two requests per domain, and
checks that each pair shares one connection. Disable enumeration's
session cleanup via a test overlay and the same test dies on the 30th
domain with "too many open files".

Add a benchmark for successive session requests, including connection
counts and allocations. Median of five runs of 500 requests against a
local server:

                         before       after
    HTTP ns/op           197234       82625
    HTTPS ns/op         2871946       88933
    HTTP allocs/op          145          80
    HTTPS allocs/op         929          85
    connections/run         500           1

These numbers are local repeated requests, not full enumeration
throughput.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-09 10:14:43 +07:00
dw1andcoderabbitai[bot] 357385b8e4 Update pkg/runner/runner.go
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-09-09 09:48:14 +07:00
Dwi Siswanto 6d1b124dbd feat: add bounded concurrent domain enumeration
Process domain lists with a worker pool bounded by Options.Threads.
Sources already run concurrently within each domain, but the runner
used to wait for every source to finish before starting the next one.
Reuse -t so those waits can overlap instead of adding another flag.
Default stays at 10; -t 1 keeps the old sequential domain behavior.

Create a fresh built-in source instance per domain. Snapshot source
selection plus provider-file and environment keys when the runner is
constructed, and keep environment-key precedence. That way mutable
source state and stats stay isolated across concurrent enumerations.

Preserve registered custom sources and overrides. Serialize their runs
by source name, drain results on cancel, and capture stats before
releasing the source. Custom sources without explicit keys keep the
registered instance's shared config. Leave the legacy passive
constructor and provider-loading API in place.

Share provider budgets across the whole batch instead of handing every
domain a fresh request burst. Built-in sources get cancellable
fixed-window waits, with no background timers for unlimited entries.
Keep per-source overrides of the global rate setting. Custom sources
share one legacy limiter so direct Session.MultiRateLimiter users still
work, including the existing blocking wait. Sessions borrow shared
budgets and do not stop them when a domain finishes.

Apply -t to the aggregate active DNS workload, including wildcard
probes. Reserve shared capacity before spinning up resolution task
goroutines so we don't create Threads workers for every concurrent
domain. Watch cancellation while waiting for capacity, receiving tasks,
and sending results. Keep wildcard state local to each domain and wait
for passive result processing before merging wildcard metadata.

Serialize callbacks and complete-domain output writes. Validate output
paths before any provider work starts, open shared output once on the
first accepted domain, and serialize file creation for repeated domain
paths. Leave output alone when input is empty. Return scanner errors,
preserve write and close errors, and cancel pending work on the first
failure. Domain output order is unspecified.

Reject nonpositive CLI thread counts, but keep a one-worker fallback
for SDK callers that leave Threads unset. Document the expanded -t
behavior and the built-in vs custom-source config boundaries.

Add a benchmark before touching the scheduler, plus regression tests
for concurrency bounds, cancellation, shared budgets, source and
credential isolation, custom-source compatibility, DNS worker growth,
callbacks, output files, and input errors.

For 40 domains with a simulated 5 ms source wait, median batch times
over five runs on Go 1.26.5 linux/amd64 were:

    Threads       Before       After
          1     225.35 ms    219.20 ms
          4     225.22 ms     55.43 ms
         10     227.32 ms     22.63 ms

All domains completed and peak concurrency matched the requested bound.
At Threads=10, allocated bytes went from about 643 KB to 720 KB per
batch. This benchmark only measures scheduling with a mock source; it
does not measure production source construction or live provider
throughput.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-09 07:57:25 +07:00
Ayush Singh bbcbf6e849 added subdomain center 2026-09-07 20:30:04 +05:30
krishna28238-arch fc4f14e992 fix: normalize source-reported subdomains before domain gate check 2026-09-06 15:17:58 +00:00
daixiheguu 56ff3e1327 fix(github): correct processItems helper name
Signed-off-by: daixiheguu <[email protected]>
2026-08-27 23:44:19 +08:00
Melvin Lammerts 3cac8c8428 feat: add crtname passive source
Query crt.name /v1/search for CT-indexed subdomains. Free tier needs no
key; a Bearer token is optional. Not recursive because the API only
accepts eTLD+1.

Closes projectdiscovery/subfinder#1831
2026-08-24 15:39:05 +02:00
PDTeamX 51e9b9065e version bump + make thc non default 2026-08-23 00:14:39 +05:30
5e30e2d208 fix(crtsh): set statement_timeout via SQL query to avoid PgBouncer handshake rejection (#1828) (#1832)
* fix(crtsh): set statement_timeout via SQL query to avoid PgBouncer handshake rejection (#1828)

* fix(crtsh): pin statement_timeout to single sql.Conn connection (#1828)

* fix(crtsh): explicitly discard deferred conn.Close error to satisfy errcheck

---------

Co-authored-by: BountyGrid <[email protected]>
Co-authored-by: Sandeep Singh <[email protected]>
2026-08-23 00:09:17 +05:30
Jonas LejonandSandeep Singh 85fee23c94 feat: add scanmalware passive source (#1834)
* feat: add scanmalware passive source

ScanMalware is a free sandboxed URL scanner with a public archive. The source
needs no API key: the API is anonymous, and a key only raises the rate limit.

Two endpoints are queried and merged. /api/v1/ct/dns/{domain} returns subdomains
observed in Certificate Transparency DNS records and carries most of the answer.
/api/v1/search/smql?q=domain:*.{domain} returns hosts present in the public scan
archive, which is not redundant: a host that was scanned but never held a
certificate appears only there.

The API caps its subdomain list and reports subdomains_truncated when it does;
that is surfaced as an error result rather than silently returning a partial list
as if it were complete.

Requests carry an explicit User-Agent so the traffic is attributable. The session
sets a random browser User-Agent by default, which headers passed to session.Get
override.

* feat(scanmalware): also read browser-observed hosts

Adds a third query, to /api/v1/hosts/{domain}, returning the hosts a browser
resolved and requested while rendering pages on the domain. That is a third
population: dev and staging hosts holding no certificate and never scanned
directly appear only there.

The default source set is used deliberately. The endpoint can also return hosts
named in a page's CSP or extracted statically from script source, but only about
a third of the statically extracted ones were ever contacted, and subfinder has
no way to carry that distinction through to the user.

Truncation is reported per source and surfaced rather than silently returning a
partial list.

* fix(scanmalware): check the error from closing response bodies

golangci-lint errcheck flagged three unchecked resp.Body.Close() calls. Close
errors are now reported as an error result, matching how the other sources
handle them.

* fix(scanmalware): do not skip the other queries when one endpoint fails

The three queries return independent populations, so a failure in the first was
silently skipping the archive and observed-host queries for the whole run. Only
a cancelled context stops the run now; an endpoint error is reported and the
remaining queries still execute.

Reported in review.

* fix(scanmalware): report archive pagination truncation

The archive query stops after a fixed number of pages. When the last allowed
page came back full there were more results than were read, and the source
returned the shortened set without saying so.

The other two queries already report truncation, so this was the one path where
a partial answer could be mistaken for a complete one.

Reported in review.

---------

Co-authored-by: Sandeep Singh <[email protected]>
2026-08-22 23:57:42 +05:30
Jonas Lejon 534188ad22 fix(subscraping): use DialContext instead of the deprecated Dial (#1835)
golangci-lint reports SA1019 for net/http.Transport.Dial, deprecated since
Go 1.7. Because Test Builds declares needs: [lint], that single finding fails
the lint job and skips the build, test, race and example steps on every pull
request.

DialContext is the documented replacement and takes priority when both are set,
so behaviour is unchanged. golangci-lint goes from 1 issue to 0 across the tree.
2026-08-22 23:43:09 +05:30
Mzack9999 717f59d8e7 Merge pull request #1809 from adamsjack711-ux/security/cap-response-body-size
fix(subscraping): cap untrusted source response bodies with a size limit
2026-08-10 14:45:54 +04:00
Mzack9999 7946c6edd6 optional limit 2026-08-10 14:32:32 +04:00
Mzack9999 3f5641b2b0 Merge pull request #1811 from Gerijacki/fix/extractor-domain-anchoring
fix(extractor): match the target domain literally and reject partial hosts
2026-08-05 22:34:12 +04:00
Mzack9999 d426b12589 Merge pull request #1810 from Gerijacki/feat/max-results-paginating-sources
feat(sources): honor -max-results across paginating sources
2026-08-05 21:56:40 +04:00
Doğan Can Bakır edd7132723 fix: replace deprecated golang.org/x/exp with stdlib maps and slices
golangci-lint v2.12.2 enables govet's inline analyzer, which reports
"cannot inline: type parameter inference is not yet supported" on calls
into golang.org/x/exp/maps and golang.org/x/exp/slices, since those are
marked //go:fix inline upstream.

Switch to the stdlib maps/slices packages:
- maps.Copy, slices.Contains are drop-in
- maps.Keys/maps.Values now return iterators, so wrap in
  slices.Collect (or slices.Sorted where the result was already sorted)

golang.org/x/exp drops to an indirect dependency.
2026-08-05 08:26:59 -07:00
Dogan Can Bakir cee010b1af Merge pull request #1812 from Gerijacki/feat/list-sources-json
feat(runner): machine-readable source listing with -ls -oJ
2026-07-30 14:55:54 +03:00
Doğan Can Bakır 55590bd09c refactor(runner): inline -ls -oJ JSON output
Fold the source metadata emitter back into listSources instead of a
separate file: the exported SourceInfo type had no consumer, and sorting
AllSources (a compile-time array, already alphabetical) only made the
JSON order differ from the human-readable listing.

Drops the accompanying tests, which re-derived the struct from the same
interface calls and asserted that json.Encoder emits JSON.
2026-07-30 14:43:42 +03:00
Jatinder Mahajan bb1a83558c fix(anubis): switch subdomain API to anubisdb.com
jonlu.ca/anubis now returns Cloudflare 403, so the source finds
nothing. anubisdb.com serves the same API and returns results.

Closes #1808
2026-07-25 23:09:43 +05:30
Gerijacki a23ed259f3 fix(sources/onyphe): observe ctx cancellation while emitting results 2026-07-21 08:17:54 +02:00
Gerijacki 5bf5db42d5 perf(extractor): clone extracted match to release the response backing array 2026-07-21 08:17:03 +02:00
Gerijacki 6c5fb9a5fc feat(runner): machine-readable source listing with -ls -oJ
The -ls listing only prints source names with "*"/"~" markers, which is
awkward for automation that wants to pick sources by property (key-less,
recursive, default). When -ls is combined with -oJ, emit one JSON object
per source with its name, default, recursive and keyRequirement fields.
The default human-readable listing is unchanged.

Add tests covering the metadata builder and the JSONL writer.
2026-07-21 01:39:12 +02:00
Gerijacki 13fda40dc7 fix(extractor): match the target domain literally and reject partial hosts
NewSubdomainExtractor interpolated the raw domain into the regex, so the
dots in the domain were treated as "any character" (e.g. "example.com"
also matched "exampleXcom"). The pattern was also unbounded on the right,
so text such as "foo.example.com.evil.org" yielded the bogus subdomain
"foo.example.com", which passes the runner's HasSuffix check and reaches
the output. This affects every source that extracts hosts from free text
(crtsh, waybackarchive, commoncrawl, hackertarget, digitorus, rapiddns,
urlscan, sitedossier, ...).

Escape the domain with regexp.QuoteMeta and drop matches that are only a
prefix of a longer hostname, while still accepting the trailing-dot FQDN
form. Add table-driven tests for the extractor.
2026-07-21 01:36:39 +02:00
Gerijacki 46d0dc1270 feat(sources): honor -max-results across paginating sources
The -max-results flag was only honored by the virustotal source; every
other paginating source kept walking its cursor/pages and burning API
quota even when a cap was requested.

Apply the same early-stop pattern to the remaining paginating sources
(censys, certspotter, shodan, securitytrails, quake, dnsdb, onyphe,
rsecloud, redhuntlabs, urlscan, merklemap, commoncrawl): once
session.MaxResults results have been emitted the source returns instead
of requesting the next page. Single-request sources are unaffected.

Add httptest-based coverage for censys and certspotter proving the cap
stops pagination and bounds the request count, and update the flag help
text and docs to reflect the broader support.
2026-07-21 01:33:49 +02:00
Jack Adams-LovellandClaude Fable 5 b3d955c1e3 fix(subscraping): cap untrusted source response bodies with a size limit
Passive sources read third-party (and, since TLS verification is skipped,
potentially MITM'd) HTTP response bodies with unbounded io.ReadAll /
json.Decoder / bufio.Scanner. The client Timeout bounds time, not size, so a
malicious or compromised upstream could stream an effectively unbounded body
and OOM the process.

Cap the body centrally at the session layer (httpRequestWrapper) so every
source inherits the limit regardless of how it consumes the body. The
threatcrowd source issues a raw session.Client.Do, bypassing the wrapper, so it
opts in explicitly via the exported LimitResponseBody helper. The wrapper
preserves the original body's Close, so no body/connection leak.

50MB cap is generous enough that legitimate large subdomain lists are
unaffected.

Co-Authored-By: Claude Fable 5 <[email protected]>

Signed-off-by: Jack Adams-Lovell <[email protected]>
2026-07-13 17:13:29 -07:00
nandaanomiandMzack9999 d0ea1029cf normalize entries (#1791)
Co-authored-by: Mzack9999 <[email protected]>
2026-07-02 23:31:46 +02:00
48ce2e0b2d fix(sources/virustotal): cap pagination + clearer 429 message (#1788)
* Add Option: Limit Results With Virustotal Source

* Update: README

* Update pkg/runner/enumerate.go

Co-authored-by: Pepijn van der Stap  <[email protected]>

* Update pkg/subscraping/sources/virustotal/virustotal.go

Co-authored-by: Pepijn van der Stap  <[email protected]>

* Update pkg/subscraping/sources/virustotal/virustotal.go

Co-authored-by: Pepijn van der Stap  <[email protected]>

* fix virustotal

* clarify quota

* lint errcheck

---------

Co-authored-by: Bundy01 <[email protected]>
Co-authored-by: Pepijn van der Stap <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-07-02 23:10:39 +02:00
cdb3279355 Add Option -vR, -virustotal-res For Virustotal Source (#1719)
* Add Option: Limit Results With Virustotal Source

* Update: README

* Update pkg/runner/enumerate.go

Co-authored-by: Pepijn van der Stap  <[email protected]>

* Update pkg/subscraping/sources/virustotal/virustotal.go

Co-authored-by: Pepijn van der Stap  <[email protected]>

* Update pkg/subscraping/sources/virustotal/virustotal.go

Co-authored-by: Pepijn van der Stap  <[email protected]>

* fix virustotal

---------

Co-authored-by: Pepijn van der Stap <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-07-02 22:21:16 +02:00
Dogan Can Bakir 44bf9d8984 Merge pull request #1799 from projectdiscovery/drain-sources
drain sources
2026-07-01 11:56:19 +03:00
Dogan Can Bakir c3127872fd Merge pull request #1801 from projectdiscovery/source-leaks
fix source leaks
2026-07-01 11:53:25 +03:00
Doğan Can Bakır 8ef44ee912 bump version 2026-07-01 11:49:44 +03:00
Mzack9999 6cd012b76f stop fallthrough 2026-06-25 22:23:59 +02:00