Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,16 +275,18 @@ UNCOVER:
-ur, -uncover-ratelimit int override ratelimit of engines with unknown ratelimit (default 60 req/min) (default 60)

RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
-hbs, -headless-bulk-size int maximum number of headless hosts to be analyzed in parallel per template (default 10)
-headc, -headless-concurrency int maximum number of headless templates to be executed in parallel (default 10)
-jsc, -js-concurrency int maximum number of javascript runtimes to be executed in parallel (default 120)
-pc, -payload-concurrency int max payload concurrency for each template (default 25)
-prc, -probe-concurrency int http probe concurrency with httpx (default 50)
-rl, -rate-limit int maximum number of requests to send per second (ignored when -rate-limit-host is set) (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-rlh, -rate-limit-host int maximum number of requests to send per host per rate-limit-host-duration (0 = disabled, takes priority over -rate-limit)
-rlhd, -rate-limit-host-duration value refill interval for the per-host rate limit bucket (default 1s)
Comment on lines +278 to +282
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

-rate-limit-duration is described like a count, not a window.

-rld is a duration-valued flag, so “maximum number of requests to send per second” is misleading here. The wording should describe the rate-limit interval/window, similar to the new per-host duration flag on Line 282.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 278 - 282, Update the description for the -rld,
-rate-limit-duration flag so it describes a time window/interval rather than a
request count; replace the misleading "maximum number of requests to send per
second" with wording like "rate-limit interval/window for -rate-limit (default
1s)" or "refill interval for the global rate limit bucket (default 1s)" to match
the style of -rlhd/-rate-limit-host-duration.

-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
-hbs, -headless-bulk-size int maximum number of headless hosts to be analyzed in parallel per template (default 10)
-headc, -headless-concurrency int maximum number of headless templates to be executed in parallel (default 10)
-jsc, -js-concurrency int maximum number of javascript runtimes to be executed in parallel (default 120)
-pc, -payload-concurrency int max payload concurrency for each template (default 25)
-prc, -probe-concurrency int http probe concurrency with httpx (default 50)
-tlc, -template-loading-concurrency int maximum number of concurrent template loading operations (default 50)

OPTIMIZATIONS:
Expand Down
2 changes: 2 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ UNCOVER引擎:
限速:
-rl, -rate-limit int 每秒最大请求量(默认:150)
-rlm, -rate-limit-minute int 每分钟最大请求量
-rlh, -rate-limit-host int 每个主机每 rate-limit-host-duration 时间窗口内的最大请求数(0 = 禁用)
-rlhd, -rate-limit-host-duration value 每主机限速桶的填充间隔(默认:1秒)
-bs, -bulk-size int 每个模板最大并行检测数(默认:25)
-c, -concurrency int 并行执行的最大模板数量(默认:25)
-hbs, -headless-bulk-size int 每个模板并行运行的无头主机最大数量(默认:10)
Expand Down
2 changes: 2 additions & 0 deletions README_ES.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int número máximo de peticiones a enviar por segundo (por defecto 150)
-rlm, -rate-limit-minute int número máximo de peticiones a enviar por minuto
-rlh, -rate-limit-host int número máximo de peticiones por host por rate-limit-host-duration (0 = desactivado)
-rlhd, -rate-limit-host-duration value intervalo de recarga del bucket de rate-limit por host (por defecto 1s)
Comment on lines +241 to +242
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Document that -rlh overrides -rl.

The implementation gives the per-host limiter priority when both flags are set. Please say that explicitly here too; otherwise this help text reads as if the two limits stack. It would be worth mirroring the same wording in the other localized READMEs updated in this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README_ES.md` around lines 241 - 242, Update the help text for the rate-limit
flags to state that the per-host flag overrides the global flag: explicitly note
that when both -rlh (or -rate-limit-host) and -rl (global rate-limit) are set,
-rlh takes precedence rather than stacking; modify the existing lines for
-rlh/-rate-limit-host and -rl to include a short parenthetical or sentence
indicating this behavior and mirror the same wording in all other localized
README files updated in this PR.

-bs, -bulk-size int número máximo de hosts a ser analizados en paralelo por plantilla (por defecto 25)
-c, -concurrency int número máximo de plantillas a ejecutar en paralelo (por defecto 25)
-hbs, -headless-bulk-size int número máximo de hosts headless a ser analizados en paralelo por plantilla (por defecto 10)
Expand Down
2 changes: 2 additions & 0 deletions README_ID.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rlm, -rate-limit-minute int maximum number of requests to send per minute
-rlh, -rate-limit-host int maximum number of requests to send per host per rate-limit-host-duration (0 = disabled)
-rlhd, -rate-limit-host-duration value refill interval for the per-host rate limit bucket (default 1s)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
-hbs, -headless-bulk-size int maximum number of headless hosts to be analyzed in parallel per template (default 10)
Expand Down
2 changes: 2 additions & 0 deletions README_KR.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int 초당 보낼 최대 요청 수 (기본값 150)
-rlm, -rate-limit-minute int 분당 보낼 최대 요청 수
-rlh, -rate-limit-host int 호스트당 rate-limit-host-duration 동안 보낼 최대 요청 수 (0 = 비활성)
-rlhd, -rate-limit-host-duration value 호스트별 rate-limit 버킷의 리필 간격 (기본값 1s)
Comment on lines +208 to +209
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mention that -rate-limit-host overrides -rate-limit.

The Korean help text adds the new flags but omits the precedence rule now documented in README.md. That can make -rl and -rlh look additive instead of mutually prioritized.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README_KR.md` around lines 208 - 209, Update the Korean help text to state
that the -rate-limit-host (-rlh) flag takes precedence over the global
-rate-limit (-rl) flag; edit the entries for -rate-limit-host / -rlh and/or
-rate-limit / -rl in README_KR.md so the precedence rule is explicit (e.g., add
a short note like "주의: -rate-limit-host가 -rate-limit보다 우선합니다") referencing the
flags by name so readers understand -rlh overrides -rl rather than being
additive.

-bs, -bulk-size int 템플릿당 병렬로 분석할 최대 호스트 수 (기본값 25)
-c, -concurrency int 병렬로 실행할 최대 템플릿 수 (기본값 25)
-hbs, -headless-bulk-size int 템플릿당 병렬로 분석할 최대 headless 호스트 수 (기본값 10)
Expand Down
2 changes: 2 additions & 0 deletions README_PT-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int número máximo de solicitações a serem enviadas por segundo (padrão 150)
-rlm, -rate-limit-minute int número máximo de solicitações a serem enviadas por minuto
-rlh, -rate-limit-host int número máximo de solicitações por host por rate-limit-host-duration (0 = desativado)
-rlhd, -rate-limit-host-duration value intervalo de recarga do bucket de rate-limit por host (padrão 1s)
Comment on lines +241 to +242
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add the -rlh vs -rl precedence note here too.

The PT-BR help text documents the new flags but not that -rate-limit-host takes priority over the global -rate-limit, which is now called out in the main README.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README_PT-BR.md` around lines 241 - 242, Add the same precedence note to the
PT-BR help text: state that the per-host flag (-rlh / -rate-limit-host) takes
precedence over the global flag (-rl / -rate-limit) when both are provided;
update the lines that document -rlh/-rate-limit-host to include a short
parenthetical or sentence noting this precedence and mirror the wording used in
the main README for consistency.

-bs, -bulk-size int número máximo de hosts a serem analisados em paralelo por template (padrão 25)
-c, -concurrency int número máximo de templates a serem executados em paralelo (padrão 25)
-hbs, -headless-bulk-size int número máximo de hosts headless a serem analisados em paralelo por template (padrão 10)
Expand Down
4 changes: 3 additions & 1 deletion cmd/nuclei/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,11 @@ on extensive configurability, massive extensibility and ease of use.`)
)

flagSet.CreateGroup("rate-limit", "Rate-Limit",
flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", 150, "maximum number of requests to send per second"),
flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", 150, "maximum number of requests to send per second (ignored when -rate-limit-host is set)"),
flagSet.DurationVarP(&options.RateLimitDuration, "rate-limit-duration", "rld", time.Second, "maximum number of requests to send per second"),
flagSet.IntVarP(&options.RateLimitMinute, "rate-limit-minute", "rlm", 0, "maximum number of requests to send per minute (DEPRECATED)"),
flagSet.IntVarP(&options.RateLimitHost, "rate-limit-host", "rlh", 0, "maximum number of requests to send per host per rate-limit-host-duration (0 = disabled, takes priority over -rate-limit)"),
flagSet.DurationVarP(&options.RateLimitHostDuration, "rate-limit-host-duration", "rlhd", time.Second, "refill interval for the per-host rate limit bucket"),
flagSet.IntVarP(&options.BulkSize, "bulk-size", "bs", 25, "maximum number of hosts to be analyzed in parallel per template"),
flagSet.IntVarP(&options.TemplateThreads, "concurrency", "c", 25, "maximum number of templates to be executed in parallel"),
flagSet.IntVarP(&options.HeadlessBulkSize, "headless-bulk-size", "hbs", 10, "maximum number of headless hosts to be analyzed in parallel per template"),
Expand Down
16 changes: 16 additions & 0 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hostratelimit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/honeypotdetector"
Expand Down Expand Up @@ -89,6 +90,7 @@ type Runner struct {
issuesClient reporting.Client
browser *engine.Browser
rateLimiter *ratelimit.Limiter
hostRateLimiter *hostratelimit.Pool
hostErrors hosterrorscache.CacheInterface
resumeCfg *types.ResumeCfg
pprofServer *pprofutil.PprofServer
Expand Down Expand Up @@ -406,6 +408,17 @@ func New(options *types.Options) (*Runner, error) {
}
runner.rateLimiter = utils.GetRateLimiter(context.Background(), options.RateLimit, options.RateLimitDuration)

if options.RateLimitHost > 0 {
hostDuration := options.RateLimitHostDuration
if hostDuration == 0 {
hostDuration = time.Second
}
runner.hostRateLimiter = hostratelimit.NewPool(context.Background(), hostratelimit.Options{
MaxCount: uint(options.RateLimitHost),
Duration: hostDuration,
})
}

// Initialization successful, disable cleanup on error
cleanupOnError = false
return runner, nil
Expand Down Expand Up @@ -454,6 +467,7 @@ func (r *Runner) Close() {
if r.rateLimiter != nil {
r.rateLimiter.Stop()
}
r.hostRateLimiter.Stop()
r.progress.Stop()
if r.browser != nil {
r.browser.Close()
Expand Down Expand Up @@ -517,6 +531,7 @@ func (r *Runner) RunEnumeration() error {
Catalog: r.catalog,
IssuesClient: r.issuesClient,
RateLimiter: r.rateLimiter,
HostRateLimiter: r.hostRateLimiter,
Interactsh: r.interactsh,
ProjectFile: r.projectFile,
Browser: r.browser,
Expand Down Expand Up @@ -573,6 +588,7 @@ func (r *Runner) RunEnumeration() error {
Catalog: r.catalog,
IssuesClient: r.issuesClient,
RateLimiter: r.rateLimiter,
HostRateLimiter: r.hostRateLimiter,
Interactsh: r.interactsh,
ProjectFile: r.projectFile,
Browser: r.browser,
Expand Down
3 changes: 3 additions & 0 deletions internal/server/nuclei_sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hostratelimit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
browserEngine "github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
Expand All @@ -51,6 +52,7 @@ type NucleiExecutorOptions struct {
Catalog catalog.Catalog
IssuesClient reporting.Client
RateLimiter *ratelimit.Limiter
HostRateLimiter *hostratelimit.Pool
Interactsh *interactsh.Client
ProjectFile *projectfile.ProjectFile
Browser *browserEngine.Browser
Expand All @@ -74,6 +76,7 @@ func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) {
Catalog: opts.Catalog,
IssuesClient: opts.IssuesClient,
RateLimiter: opts.RateLimiter,
HostRateLimiter: opts.HostRateLimiter,
Interactsh: opts.Interactsh,
ProjectFile: opts.ProjectFile,
Browser: opts.Browser,
Expand Down
24 changes: 24 additions & 0 deletions lib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,30 @@ func WithGlobalRateLimitCtx(ctx context.Context, maxTokens int, duration time.Du
}
}

// WithHostRateLimit configures a per-host rate limit. Each unique target
// host is capped at maxTokens requests per duration. Pass maxTokens=0 to
// disable per-host limiting (the default).
//
// The per-host limiter takes priority over the global rate limit
// (WithGlobalRateLimitCtx): when this option is in effect, requests carrying
// a host scope consult only the per-host bucket, and the global limiter is
// bypassed. This is intentional — the global rate limit defaults to a
// non-zero value, so layering both would silently throttle aggregate
// throughput and defeat the point of opting into a per-host budget.
// Aggregate scan throughput is naturally bounded by num_hosts * maxTokens.
//
// Requests without a host scope (rare, e.g. self-contained templates) fall
// back to the global limiter so they remain paced.
func WithHostRateLimit(ctx context.Context, maxTokens int, duration time.Duration) NucleiSDKOptions {
return func(e *NucleiEngine) error {
e.opts.RateLimitHost = maxTokens
e.opts.RateLimitHostDuration = duration
// Lazily constructed in init() so we honor whichever ctx the
// engine ultimately runs under; this option only records intent.
return nil
}
}

// HeadlessOpts contains options for headless templates
type HeadlessOpts struct {
PageTimeout int // timeout for page load
Expand Down
40 changes: 29 additions & 11 deletions lib/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hostratelimit"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/utils/errkit"
Expand All @@ -29,17 +30,18 @@ type unsafeOptions struct {
func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options) (*unsafeOptions, error) {
u := &unsafeOptions{}
u.executerOpts = &protocols.ExecutorOptions{
Output: base.customWriter,
Options: opts,
Progress: base.customProgress,
Catalog: base.catalog,
IssuesClient: base.rc,
RateLimiter: base.rateLimiter,
Interactsh: base.interactshClient,
Colorizer: aurora.NewAurora(true),
ResumeCfg: types.NewResumeCfg(),
Parser: base.parser,
Browser: base.browserInstance,
Output: base.customWriter,
Options: opts,
Progress: base.customProgress,
Catalog: base.catalog,
IssuesClient: base.rc,
RateLimiter: base.rateLimiter,
HostRateLimiter: base.hostRateLimiter,
Interactsh: base.interactshClient,
Colorizer: aurora.NewAurora(true),
ResumeCfg: types.NewResumeCfg(),
Parser: base.parser,
Browser: base.browserInstance,
}
Comment on lines 32 to 45
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't inherit and then stop the base host limiter.

createEphemeralObjects() seeds HostRateLimiter from base.hostRateLimiter, but closeEphemeralObjects() always stops whatever pointer is there. If a call inherits the base pool, that shuts down shared state for later/concurrent ExecuteNucleiWithOpts* calls. The per-call executor should either own its own pool or avoid stopping inherited ones.

♻️ Proposed fix
 type unsafeOptions struct {
 	executerOpts *protocols.ExecutorOptions
 	engine       *core.Engine
+	ownsHostRateLimiter bool
 }
@@
 	u.executerOpts = &protocols.ExecutorOptions{
 		Output:          base.customWriter,
 		Options:         opts,
 		Progress:        base.customProgress,
 		Catalog:         base.catalog,
 		IssuesClient:    base.rc,
 		RateLimiter:     base.rateLimiter,
-		HostRateLimiter: base.hostRateLimiter,
+		HostRateLimiter: nil,
 		Interactsh:      base.interactshClient,
 		Colorizer:       aurora.NewAurora(true),
 		ResumeCfg:       types.NewResumeCfg(),
 		Parser:          base.parser,
 		Browser:         base.browserInstance,
@@
 	if opts.RateLimitHost > 0 {
 		hostDuration := opts.RateLimitHostDuration
 		if hostDuration == 0 {
 			hostDuration = time.Second
 		}
 		u.executerOpts.HostRateLimiter = hostratelimit.NewPool(ctx, hostratelimit.Options{
 			MaxCount: uint(opts.RateLimitHost),
 			Duration: hostDuration,
 		})
+		u.ownsHostRateLimiter = true
 	}
@@
-	u.executerOpts.HostRateLimiter.Stop()
+	if u.ownsHostRateLimiter && u.executerOpts.HostRateLimiter != nil {
+		u.executerOpts.HostRateLimiter.Stop()
+	}

Also applies to: 58-70, 78-82

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/multi.go` around lines 32 - 45, The HostRateLimiter being assigned into
u.executerOpts from base.hostRateLimiter causes closeEphemeralObjects() to
unconditionally Stop() shared base state; update createEphemeralObjects()/the
executor initialization (where u.executerOpts.HostRateLimiter is set) to ensure
the per-call executor either creates/clones its own HostRateLimiter when
inheriting (so it owns the lifecycle) or marks that it is inherited and then
change closeEphemeralObjects() to only Stop() the limiter if it is owned by this
executor (i.e., not equal to base.hostRateLimiter). Specifically, adjust the
logic around createEphemeralObjects(), closeEphemeralObjects(), HostRateLimiter
and base.hostRateLimiter so inherited pointers are never stopped by per-call
cleanup (or are replaced by a newly allocated limiter owned by u).

if opts.ShouldUseHostError() && base.hostErrCache != nil {
u.executerOpts.HostErrorsCache = base.hostErrCache
Expand All @@ -52,6 +54,21 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
opts.RateLimitDuration = time.Second
}
u.executerOpts.RateLimiter = utils.GetRateLimiter(ctx, opts.RateLimit, opts.RateLimitDuration)

// Per-call ephemeral host rate limiter; the goroutine cost is paid once
// per ExecuteNucleiWithOpts invocation and Stop()-ed in
// closeEphemeralObjects so we do not leak limiters across calls.
if opts.RateLimitHost > 0 {
hostDuration := opts.RateLimitHostDuration
if hostDuration == 0 {
hostDuration = time.Second
}
u.executerOpts.HostRateLimiter = hostratelimit.NewPool(ctx, hostratelimit.Options{
MaxCount: uint(opts.RateLimitHost),
Duration: hostDuration,
})
}

u.engine = core.New(opts)
u.engine.SetExecuterOptions(u.executerOpts)
return u, nil
Expand All @@ -62,6 +79,7 @@ func closeEphemeralObjects(u *unsafeOptions) {
if u.executerOpts.RateLimiter != nil {
u.executerOpts.RateLimiter.Stop()
}
u.executerOpts.HostRateLimiter.Stop()
// dereference all objects that were inherited from base nuclei engine
// since these are meant to be closed globally by base nuclei engine
u.executerOpts.Output = nil
Expand Down
4 changes: 4 additions & 0 deletions lib/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hostratelimit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
Expand Down Expand Up @@ -72,6 +73,7 @@ type NucleiEngine struct {
interactshClient *interactsh.Client
catalog catalog.Catalog
rateLimiter *ratelimit.Limiter
hostRateLimiter *hostratelimit.Pool
store *loader.Store
httpxClient providerTypes.InputLivenessProbe
inputProvider provider.InputProvider
Expand Down Expand Up @@ -228,6 +230,8 @@ func (e *NucleiEngine) closeInternal() {
if e.rateLimiter != nil {
e.rateLimiter.Stop()
}
e.executerOpts.HostRateLimiter.Stop()
e.hostRateLimiter.Stop()
if e.inputProvider != nil {
e.inputProvider.Close()
}
Expand Down
13 changes: 13 additions & 0 deletions lib/sdk_private.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hostratelimit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
Expand Down Expand Up @@ -101,6 +102,17 @@ func (e *NucleiEngine) applyRequiredDefaults(ctx context.Context) {
e.rateLimiter = nucleiUtils.GetRateLimiter(ctx, e.opts.RateLimit, e.opts.RateLimitDuration)
}

if e.hostRateLimiter == nil && e.opts.RateLimitHost > 0 {
hostDuration := e.opts.RateLimitHostDuration
if hostDuration == 0 {
hostDuration = time.Second
}
Comment on lines +105 to +109
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against non-positive per-host duration values.

Line 107 only defaults when duration is 0; negative durations pass through. Normalize <= 0 to avoid invalid limiter configuration.

Suggested patch
-		if hostDuration == 0 {
+		if hostDuration <= 0 {
 			hostDuration = time.Second
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if e.hostRateLimiter == nil && e.opts.RateLimitHost > 0 {
hostDuration := e.opts.RateLimitHostDuration
if hostDuration == 0 {
hostDuration = time.Second
}
if e.hostRateLimiter == nil && e.opts.RateLimitHost > 0 {
hostDuration := e.opts.RateLimitHostDuration
if hostDuration <= 0 {
hostDuration = time.Second
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/sdk_private.go` around lines 105 - 109, The per-host rate limiter setup
uses e.opts.RateLimitHostDuration but only defaults when it equals zero; change
the guard in the host limiter initialization (the block that references
e.hostRateLimiter and e.opts.RateLimitHostDuration) to normalize any
non-positive value to time.Second (i.e., treat <= 0 as default) before using it
to construct the limiter so negative durations cannot be passed into the limiter
configuration.

e.hostRateLimiter = hostratelimit.NewPool(ctx, hostratelimit.Options{
MaxCount: uint(e.opts.RateLimitHost),
Duration: hostDuration,
})
}

if e.opts.ExcludeTags == nil {
e.opts.ExcludeTags = []string{}
}
Expand Down Expand Up @@ -212,6 +224,7 @@ func (e *NucleiEngine) init(ctx context.Context) error {
Catalog: e.catalog,
IssuesClient: e.rc,
RateLimiter: e.rateLimiter,
HostRateLimiter: e.hostRateLimiter,
Interactsh: e.interactshClient,
Colorizer: aurora.NewAurora(true),
ResumeCfg: types.NewResumeCfg(),
Expand Down
Loading
Loading