Skip to content

fix(ratelimit): honour -rl global fallback and -rls duration (#1434)#1764

Merged
Mzack9999 merged 5 commits intoprojectdiscovery:devfrom
CharlesWong:fix/rate-limit-options
Mar 20, 2026
Merged

fix(ratelimit): honour -rl global fallback and -rls duration (#1434)#1764
Mzack9999 merged 5 commits intoprojectdiscovery:devfrom
CharlesWong:fix/rate-limit-options

Conversation

@CharlesWong
Copy link
Copy Markdown
Contributor

@CharlesWong CharlesWong commented Mar 10, 2026

Summary

Fixes #1434 — both -rl (global rate limit) and -rls (per-source rate limit with duration) were silently broken.

Root Cause

Two independent bugs that no single existing approach fully addresses:

  1. -rl ignored for most sources: buildMultiRateLimiter() only checked per-source overrides from -rls. Sources without an explicit -rls entry got rl=0 → unlimited, even when -rl 5 was set.

  2. -rls duration discarded: runner.go converted goflags.RateLimitMapCustomRateLimit but only stored MaxCount, dropping Duration. So -rls hackertarget=2/m silently became 2/s.

Why This PR

I analyzed all existing approaches and found each only partially fixes the issue:

Aspect This PR #1741 #1761 #1762 #1731
Global -rl fallback
-rls duration preserved ✅ stores in CustomDuration ❌ ignored ❌ ignored ❌ wrong struct fields
Duration stored at source runner.go stores from goflags ❌ compiles fail
Nil-safe ✅ guard in buildMultiRateLimiter
Clean separation resolveSourceRateLimit() inline ✅ dedicated func inline inline
Tests ✅ 9 tests 4 tests 3 tests 5 tests 3 tests
Compiles on main ❌ accesses non-existent fields

Key insight: The duration bug lives in runner.go (where -rls is parsed), not just passive.go. Most PRs only patch passive.go and miss the root cause.

Changes

pkg/subscraping/types.go

  • Add CustomDuration field to CustomRateLimit struct

pkg/runner/runner.go

  • Store both MaxCount and Duration from -rls flag parsing
  • This is the actual root cause fix for duration being lost

pkg/passive/passive.go

  • Add resolveSourceRateLimit() with clear priority: per-source → global → unlimited
  • Use per-source duration from -rls when set, default to per-second
  • Guard against nil rateLimit parameter

pkg/passive/ratelimit_test.go (new, 9 tests)

  • Per-source override with custom duration
  • Global -rl fallback for unlisted sources
  • No limits → unlimited
  • Nil/empty CustomRateLimit handling
  • Per-source without explicit duration defaults to per-second
  • Integration tests for buildMultiRateLimiter

How it works now

# Global limit applied to ALL sources (was broken, now works)
subfinder -d example.com -rl 5

# Per-source with duration (was losing the /m, now preserved)
subfinder -d example.com -rls hackertarget=2/m

# Mixed: per-source override + global fallback for others
subfinder -d example.com -rl 10 -rls sitedossier=2/m
# → sitedossier: 2 req/min, all others: 10 req/sec

Testing

$ go test ./pkg/passive/ -run "TestResolveSourceRateLimit|TestBuildMultiRateLimiter" -v
--- PASS: TestResolveSourceRateLimit_PerSourceOverride (0.00s)
--- PASS: TestResolveSourceRateLimit_GlobalFallback (0.00s)
--- PASS: TestResolveSourceRateLimit_NoLimit (0.00s)
--- PASS: TestResolveSourceRateLimit_NilRateLimit (0.00s)
--- PASS: TestResolveSourceRateLimit_PerSourceDefaultDuration (0.00s)
--- PASS: TestBuildMultiRateLimiter_GlobalAppliedToAll (0.00s)
--- PASS: TestBuildMultiRateLimiter_NilRateLimit (0.00s)
--- PASS: TestBuildMultiRateLimiter_PerSourceWithDuration (0.00s)
--- PASS: TestBuildMultiRateLimiter_UnlimitedWhenNoLimits (0.00s)
PASS

Summary by CodeRabbit

  • Bug Fixes

    • Safer handling of missing rate-limit configuration with reliable fallbacks.
    • Per-source custom rate limits now supported with optional per-source durations.
    • Correct behavior for global-rate and unlimited scenarios to avoid misconfiguration.
  • Tests

    • Added comprehensive tests for per-source overrides, duration handling, nil/empty cases, case-insensitive lookups, and multi-source limiter behavior.
  • Chores

    • Extended rate-limit configuration to include per-source duration mappings and initialized duration storage to avoid nil crashes.

…discovery#1434)

Two bugs in rate limit handling:

1. Global -rl flag was ignored for sources without a per-source override.
   Only sources explicitly listed in -rls got rate-limited; all others
   ran unlimited, defeating the purpose of -rl.

2. Per-source duration from -rls (e.g. hackertarget=2/m) was silently
   dropped. runner.go stored MaxCount but discarded Duration, so all
   sources defaulted to per-second regardless of the /m or /s suffix.

Root cause: runner.go converted goflags.RateLimitMap → CustomRateLimit
but only copied MaxCount. buildMultiRateLimiter only checked per-source
limits, never falling back to the global rate.

Changes:
- Add CustomDuration field to CustomRateLimit struct
- Store both MaxCount and Duration from -rls in runner.go
- Add resolveSourceRateLimit() with clear priority:
  per-source > global > unlimited
- Guard against nil rateLimit in buildMultiRateLimiter
- Add 9 unit tests covering all combinations

Fixes projectdiscovery#1434
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 26b864c4-8e55-4a7c-ab4c-e922f7ecdbe1

📥 Commits

Reviewing files that changed from the base of the PR and between cf0b801 and 240305b.

📒 Files selected for processing (1)
  • pkg/passive/ratelimit_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/passive/ratelimit_test.go

Walkthrough

Adds per-source rate-limit durations and resolution logic: a new CustomDuration map in CustomRateLimit, a resolveSourceRateLimit helper, updates to passive rate-limiter construction to use resolved (limit, duration), runner wiring to populate durations, and tests covering nil, per-source, global, default, and unlimited cases.

Changes

Cohort / File(s) Summary
Passive rate-limit logic
pkg/passive/passive.go
Handle nil rateLimit, introduce and use resolveSourceRateLimit to compute effective (limit, duration) per source; use resolved duration when building multi-rate limiters; default unlimited behavior preserved.
Rate-limit tests
pkg/passive/ratelimit_test.go
Add tests and helpers validating per-source overrides, per-source durations, global fallback, nil handling, default durations, multi-rate limiter construction, case-insensitive lookup, and unlimited scenarios.
Types – subscraping
pkg/subscraping/types.go
Add exported field CustomDuration mapsutil.SyncLockMap[string, time.Duration] to CustomRateLimit.
Runner initialization
pkg/runner/runner.go
Initialize CustomDuration in NewRunner and populate per-source durations when sources specify durations alongside per-source limits.

Sequence Diagram(s)

sequenceDiagram
  participant Runner
  participant Subscraping as CustomRateLimit
  participant Passive
  participant RateLimiter as MultiRateLimiter

  Runner->>Subscraping: populate Custom (limits) and CustomDuration (per-source durations)
  Runner->>Passive: provide configured CustomRateLimit
  Passive->>Passive: resolveSourceRateLimit(source) => (limit, duration)
  Passive->>RateLimiter: addRateLimiter(source, limit, duration)
  RateLimiter-->>Passive: success / error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through maps of limits and time,

Each source now gets its own small rhyme.
Nil fooled me not, durations in place,
I bounded through rates with a smiling face.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the two main fixes: honouring global rate limit fallback (-rl) and preserving duration settings (-rls).
Linked Issues check ✅ Passed All code changes directly address the two core bugs reported in issue #1434: global -rl fallback now applies to all sources via resolveSourceRateLimit, and -rls duration suffixes are preserved through CustomDuration in CustomRateLimit.
Out of Scope Changes check ✅ Passed All changes are narrowly scoped to fixing the reported rate-limit issues; no unrelated modifications or refactoring outside the stated objectives detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
pkg/passive/ratelimit_test.go (2)

64-69: Test name is misleading: tests empty struct, not nil pointer.

The test is named TestResolveSourceRateLimit_NilRateLimit but passes &subscraping.CustomRateLimit{} (a zero-valued struct with nil internal maps), not an actual nil pointer. This distinction matters because:

  1. The function doesn't receive a nil pointer here
  2. The zero-valued struct has nil Map fields inside, which may panic

Consider renaming to TestResolveSourceRateLimit_EmptyRateLimit or TestResolveSourceRateLimit_ZeroValuedRateLimit to accurately describe what's being tested.

♻️ Suggested rename
-func TestResolveSourceRateLimit_NilRateLimit(t *testing.T) {
-	// nil CustomRateLimit should not panic
+func TestResolveSourceRateLimit_ZeroValuedRateLimit(t *testing.T) {
+	// Zero-valued CustomRateLimit (with nil internal maps) should not panic
 	rl, dur := resolveSourceRateLimit(5, &subscraping.CustomRateLimit{}, "crtsh")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/passive/ratelimit_test.go` around lines 64 - 69, Rename the test
TestResolveSourceRateLimit_NilRateLimit to accurately reflect that it passes a
zero-valued struct (not a nil pointer); for example
TestResolveSourceRateLimit_EmptyRateLimit or
TestResolveSourceRateLimit_ZeroValuedRateLimit, and update the test name string
accordingly where the function resolveSourceRateLimit is invoked with
&subscraping.CustomRateLimit{} so readers understand it is testing the
empty/zero-valued struct case rather than a nil pointer.

115-125: Remove dead code reference.

Line 124 (_ = math.MaxUint32) serves no purpose—it doesn't assert anything and the comment "referenced for clarity" doesn't clarify the test behavior. Consider removing it or adding a meaningful assertion about the unlimited behavior.

♻️ Proposed cleanup
 func TestBuildMultiRateLimiter_UnlimitedWhenNoLimits(t *testing.T) {
 	// Without any rate limits, sources should get MaxUint32 (effectively unlimited)
 	agent := New([]string{"hackertarget"}, []string{}, false, false)
 	crl := newCustomRateLimit(nil, nil)

 	limiter, err := agent.buildMultiRateLimiter(context.Background(), 0, crl)
 	require.NoError(t, err)
 	require.NotNil(t, limiter)
-	// No assertion on internal state - just verify it doesn't error
-	_ = math.MaxUint32 // referenced for clarity
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/passive/ratelimit_test.go` around lines 115 - 125, The test
TestBuildMultiRateLimiter_UnlimitedWhenNoLimits contains a dead reference "_ =
math.MaxUint32" that should be removed or replaced; locate the test and either
delete the pointless line or add a meaningful assertion that verifies the
returned limiter from agent.buildMultiRateLimiter (created with
newCustomRateLimit(nil, nil)) grants unlimited behavior (e.g., check limiter is
non-nil and/or its per-source limit equals math.MaxUint32) and remove the unused
math.MaxUint32 assignment if you opt to keep the test minimal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/passive/passive.go`:
- Around line 88-90: The nil guard for rateLimit creates a zero-valued
CustomRateLimit whose Custom.Map and CustomDuration.Map remain nil and can panic
when resolveSourceRateLimit calls .Get() (SyncLockMap.Get) on them; modify the
initialization of rateLimit in pkg/passive/passive.go (the block that sets
rateLimit = &subscraping.CustomRateLimit{}) to allocate non-nil SyncLockMap
instances for both Custom.Map and CustomDuration.Map following the pattern used
in runner.go (use mapsutil.NewSyncLockMap() or the project-equivalent
constructor) and add the mapsutil import if missing so resolveSourceRateLimit
and SyncLockMap.Get() never operate on nil maps.

---

Nitpick comments:
In `@pkg/passive/ratelimit_test.go`:
- Around line 64-69: Rename the test TestResolveSourceRateLimit_NilRateLimit to
accurately reflect that it passes a zero-valued struct (not a nil pointer); for
example TestResolveSourceRateLimit_EmptyRateLimit or
TestResolveSourceRateLimit_ZeroValuedRateLimit, and update the test name string
accordingly where the function resolveSourceRateLimit is invoked with
&subscraping.CustomRateLimit{} so readers understand it is testing the
empty/zero-valued struct case rather than a nil pointer.
- Around line 115-125: The test TestBuildMultiRateLimiter_UnlimitedWhenNoLimits
contains a dead reference "_ = math.MaxUint32" that should be removed or
replaced; locate the test and either delete the pointless line or add a
meaningful assertion that verifies the returned limiter from
agent.buildMultiRateLimiter (created with newCustomRateLimit(nil, nil)) grants
unlimited behavior (e.g., check limiter is non-nil and/or its per-source limit
equals math.MaxUint32) and remove the unused math.MaxUint32 assignment if you
opt to keep the test minimal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65c3b523-7e03-4481-b3ad-8e2ac96937ff

📥 Commits

Reviewing files that changed from the base of the PR and between 13e5db7 and c1ce78e.

📒 Files selected for processing (4)
  • pkg/passive/passive.go
  • pkg/passive/ratelimit_test.go
  • pkg/runner/runner.go
  • pkg/subscraping/types.go

@neo-by-projectdiscovery-dev
Copy link
Copy Markdown

neo-by-projectdiscovery-dev bot commented Mar 10, 2026

Neo - PR Security Review

Caution

Review could not be completed

Review could not be completed. Please retry with @pdneo review.

Suggestion: Try again with @pdneo review.

Comment @pdneo help for available commands.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/passive/ratelimit_test.go (1)

115-125: Remove dead code: the math.MaxUint32 reference is a no-op.

Line 124 assigns to the blank identifier and has no effect. The comment says "referenced for clarity" but this doesn't verify any behavior. Either add an actual assertion or remove this line along with the unused math import.

🧹 Proposed cleanup
 import (
 	"context"
-	"math"
 	"testing"
 	"time"
 func TestBuildMultiRateLimiter_UnlimitedWhenNoLimits(t *testing.T) {
 	// Without any rate limits, sources should get MaxUint32 (effectively unlimited)
 	agent := New([]string{"hackertarget"}, []string{}, false, false)
 	crl := newCustomRateLimit(nil, nil)
 
 	limiter, err := agent.buildMultiRateLimiter(context.Background(), 0, crl)
 	require.NoError(t, err)
 	require.NotNil(t, limiter)
-	// No assertion on internal state - just verify it doesn't error
-	_ = math.MaxUint32 // referenced for clarity
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/passive/ratelimit_test.go` around lines 115 - 125, In
TestBuildMultiRateLimiter_UnlimitedWhenNoLimits remove the dead no-op reference
to math.MaxUint32 (the line assigning it to the blank identifier) and delete the
now-unused math import; if you intended to assert behavior instead, replace that
line with a concrete assertion on the limiter (e.g., expected rate/limit) inside
the same test function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/passive/ratelimit_test.go`:
- Around line 115-125: In TestBuildMultiRateLimiter_UnlimitedWhenNoLimits remove
the dead no-op reference to math.MaxUint32 (the line assigning it to the blank
identifier) and delete the now-unused math import; if you intended to assert
behavior instead, replace that line with a concrete assertion on the limiter
(e.g., expected rate/limit) inside the same test function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8da0b4fb-6580-424c-8e6d-c5901e2e56ee

📥 Commits

Reviewing files that changed from the base of the PR and between c1ce78e and 6f0e11e.

📒 Files selected for processing (1)
  • pkg/passive/ratelimit_test.go

@CharlesWong
Copy link
Copy Markdown
Contributor Author

Fixed in latest commit: properly initialize the CustomRateLimit maps to avoid nil map panic.

rateLimit = &subscraping.CustomRateLimit{
    Custom: mapsutil.SyncLockMap[string, uint]{
        Map: make(map[string]uint),
    },
    CustomDuration: mapsutil.SyncLockMap[string, time.Duration]{
        Map: make(map[string]time.Duration),
    },
}

This mirrors the initialization pattern used in runner.go as suggested. Build passes locally.

CodeRabbit identified that the zero-valued CustomRateLimit struct
left Custom.Map and CustomDuration.Map as nil, which could cause
a panic when resolveSourceRateLimit calls .Get() on these maps.

Mirror the initialization pattern from runner.go by explicitly
creating the underlying maps.
Copy link
Copy Markdown
Contributor Author

@CharlesWong CharlesWong left a comment

Choose a reason for hiding this comment

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

Thanks for catching this @coderabbitai! The nil map panic risk was real — I've applied the fix to properly initialize both Custom.Map and CustomDuration.Map using the same pattern from runner.go. Pushed in cca10d8.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
pkg/passive/passive.go (2)

89-98: Centralize CustomRateLimit initialization.

This struct literal now exists here and in pkg/runner/runner.go. Since CustomDuration was just added, multiple hand-written initializers make the next field addition easy to miss again. A small constructor or ensureInitialized() helper in pkg/subscraping would keep that contract in one place.

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

In `@pkg/passive/passive.go` around lines 89 - 98, The code duplicates
construction of subscraping.CustomRateLimit (with fields Custom and
CustomDuration using mapsutil.SyncLockMap) in multiple places; add a single
constructor or ensureInitialized() function in the subscraping package (e.g.,
func NewCustomRateLimit() *CustomRateLimit or func (r *CustomRateLimit)
EnsureInitialized()) that returns/initializes a CustomRateLimit with both Custom
and CustomDuration maps set, then replace the inline struct literal here and in
pkg/runner/runner.go with a call to that constructor/EnsureInitialized to
centralize initialization and avoid future omissions.

121-126: Normalize custom source keys consistently.

The lookup lowercases sourceName, but WithCustomRateLimit callers populate Custom and CustomDuration directly, and pkg/runner/runner.go currently stores parsed keys as-is. That leaves a hidden casing contract where mixed-case entries silently miss and fall back to the global/unlimited path. Normalize keys on insert as well, or keep lookups case-sensitive and document the expected format.

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

In `@pkg/passive/passive.go` around lines 121 - 126, The code lowercases
sourceName for lookups (sourceLower) but callers like WithCustomRateLimit
populate rateLimit.Custom and rateLimit.CustomDuration without normalizing keys,
causing mismatches; update the code path that inserts custom keys (e.g., the
WithCustomRateLimit constructor or the runner code that parses and stores keys)
to normalize keys to strings.ToLower(...) on insert so that
rateLimit.Custom.Get(sourceLower) and rateLimit.CustomDuration.Get(sourceLower)
will match, or alternatively make the lookup use the exact stored key
consistently—ensure the change touches the insertion helpers
(WithCustomRateLimit and the runner's parsing/storing logic) and add a small
comment documenting the lowercase contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/passive/passive.go`:
- Around line 89-98: The code duplicates construction of
subscraping.CustomRateLimit (with fields Custom and CustomDuration using
mapsutil.SyncLockMap) in multiple places; add a single constructor or
ensureInitialized() function in the subscraping package (e.g., func
NewCustomRateLimit() *CustomRateLimit or func (r *CustomRateLimit)
EnsureInitialized()) that returns/initializes a CustomRateLimit with both Custom
and CustomDuration maps set, then replace the inline struct literal here and in
pkg/runner/runner.go with a call to that constructor/EnsureInitialized to
centralize initialization and avoid future omissions.
- Around line 121-126: The code lowercases sourceName for lookups (sourceLower)
but callers like WithCustomRateLimit populate rateLimit.Custom and
rateLimit.CustomDuration without normalizing keys, causing mismatches; update
the code path that inserts custom keys (e.g., the WithCustomRateLimit
constructor or the runner code that parses and stores keys) to normalize keys to
strings.ToLower(...) on insert so that rateLimit.Custom.Get(sourceLower) and
rateLimit.CustomDuration.Get(sourceLower) will match, or alternatively make the
lookup use the exact stored key consistently—ensure the change touches the
insertion helpers (WithCustomRateLimit and the runner's parsing/storing logic)
and add a small comment documenting the lowercase contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49182a5d-6214-4fd2-9f94-2af5c8346c8e

📥 Commits

Reviewing files that changed from the base of the PR and between 6f0e11e and cca10d8.

📒 Files selected for processing (1)
  • pkg/passive/passive.go

Per CodeRabbit nitpick: the blank identifier assignment and math import
in TestBuildMultiRateLimiter_UnlimitedWhenNoLimits served no purpose.
The test already verifies correct behavior via NoError + NotNil assertions.
@CharlesWong
Copy link
Copy Markdown
Contributor Author

Three nitpicks addressed (cf0b801):

  1. Test name: already renamed to TestResolveSourceRateLimit_ZeroValueRateLimit in the previous commit — not the nil pointer case.
  2. Dead math.MaxUint32 reference: removed the blank-identifier no-op and the unused math import from TestBuildMultiRateLimiter_UnlimitedWhenNoLimits.
  3. CustomRateLimit constructor / key casing: noted as architectural improvements — happy to tackle in a follow-up PR to keep this one focused on the original fix.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/passive/ratelimit_test.go (1)

82-90: Consider adding behavioral assertions.

The test verifies that buildMultiRateLimiter completes without error, but doesn't validate that the global rate limit (5) was actually applied to both sources. If the MultiLimiter API allows inspecting configured limits, consider adding assertions to verify the expected behavior.

If the API doesn't expose this, the current smoke test approach is acceptable.

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

In `@pkg/passive/ratelimit_test.go` around lines 82 - 90,
TestBuildMultiRateLimiter_GlobalAppliedToAll currently only verifies no error
from agent.buildMultiRateLimiter but doesn't assert the global limit was
applied; update the test to inspect the returned limiter (from
buildMultiRateLimiter) and assert that each source's configured rate equals 5 by
using the MultiLimiter inspection API (e.g., any
GetLimit/GetRate/GetLimiterForSource or exported per-source limiter methods) or
unwrap the MultiLimiter to its child limiters and assert their limits; if the
MultiLimiter API doesn't expose limits, add a short exercise that sends
known-numbered requests per-source and asserts throttling behavior consistent
with rate=5, otherwise leave the smoke test as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/passive/ratelimit_test.go`:
- Around line 82-90: TestBuildMultiRateLimiter_GlobalAppliedToAll currently only
verifies no error from agent.buildMultiRateLimiter but doesn't assert the global
limit was applied; update the test to inspect the returned limiter (from
buildMultiRateLimiter) and assert that each source's configured rate equals 5 by
using the MultiLimiter inspection API (e.g., any
GetLimit/GetRate/GetLimiterForSource or exported per-source limiter methods) or
unwrap the MultiLimiter to its child limiters and assert their limits; if the
MultiLimiter API doesn't expose limits, add a short exercise that sends
known-numbered requests per-source and asserts throttling behavior consistent
with rate=5, otherwise leave the smoke test as-is.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 328c427b-e45b-44e5-abdf-af5f53859974

📥 Commits

Reviewing files that changed from the base of the PR and between cca10d8 and cf0b801.

📒 Files selected for processing (1)
  • pkg/passive/ratelimit_test.go

- PerSourceExceedsGlobal: per-source limit > global should be honoured
  (verifies sourceRateLimitOrDefault returns source, not global)
- CaseInsensitive: source names stored lowercase must match mixed-case
  lookups (documents and verifies the ToLower normalization contract)
@CharlesWong
Copy link
Copy Markdown
Contributor Author

Senior dev review — two more test cases added (240305b):

  1. PerSourceExceedsGlobal: verifies per-source limit > global is honoured (documents sourceRateLimitOrDefault returns source value, not global).
  2. CaseInsensitive: documents and verifies the ToLower normalization contract — looking up HackerTarget must find the hackertarget key.

@CharlesWong
Copy link
Copy Markdown
Contributor Author

Acknowledged @coderabbitai[bot]'s review.

@CharlesWong
Copy link
Copy Markdown
Contributor Author

Acknowledged @neo-by-projectdiscovery-dev[bot]'s review.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 15, 2026

Rate Limit Exceeded

@CharlesWong have exceeded the limit for the number of chat messages per hour. Please wait 2 minutes and 6 seconds before sending another message.

@CharlesWong
Copy link
Copy Markdown
Contributor Author

@dogancanbakir This PR fixes #1434 (both -rl global fallback and -rls duration handling). Could you please process the bounty reward? Thanks!

@CharlesWong
Copy link
Copy Markdown
Contributor Author

@Mzack9999 Thanks for merging this PR! Could you help process the $75 bounty reward for issue #1434? I've posted /claim #1434 on the issue but haven't received an Algora bot confirmation yet. cc @dogancanbakir

@dogancanbakir
Copy link
Copy Markdown
Member

/tip $75

@algora-pbc
Copy link
Copy Markdown

algora-pbc bot commented Mar 25, 2026

🎉🎈 @CharlesWong has been awarded $75 by ProjectDiscovery! 🎈🎊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Issue] The rl,rls options are not supported

3 participants