Skip to content

[bug-hunter] CrowdStrike OAuth rate-limit retries are cancelled by timeout context #50542

@github-actions

Description

@github-actions

Impact

Filebeat streaming inputs that use CrowdStrike OAuth can fail token acquisition during 429 throttling even though retry logic exists. If timeout is lower than retry backoff, retries never execute and auth fails with context deadline exceeded.

Reproduction Steps

  1. Add this new subtest to x-pack/filebeat/input/streaming/crowdstrike_ratelimit_test.go inside TestRateLimitTransport.
  2. Run:
    cd x-pack/filebeat/input/streaming
    go test -run 'TestRateLimitTransport/timeout_should_not_cancel_retry_during_rate_limit_backoff' -count=1 ./...

Expected vs Actual

Expected: the transport should wait and retry once (429 -> 200), returning success.

Actual: retry never runs; call returns context deadline exceeded during backoff.

Observed output:

--- FAIL: TestRateLimitTransport (0.00s)
    logger.go:146: ... WARN rate limited, backing off {"attempt": 1, "max_retries": 3, "retry_after": "200ms"}
    --- FAIL: TestRateLimitTransport/timeout_should_not_cancel_retry_during_rate_limit_backoff (0.05s)
        crowdstrike_ratelimit_test.go:146: unexpected error: context deadline exceeded
FAIL

Failing Test

t.Run("timeout_should_not_cancel_retry_during_rate_limit_backoff", func(t *testing.T) {
	t.Parallel()
	ft := &fakeTransport{statuses: []int{429, 200}}
	rt := &rateLimitTransport{
		base:     ft,
		timeout:  50 * time.Millisecond,
		maxRetry: 3,
		wait:     200 * time.Millisecond,
		log:      log,
	}

	req, _ := http.NewRequestWithContext(context.Background(), "GET", "(example.com/redacted) nil)
	resp, err := rt.RoundTrip(req)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("got status %d, want %d", resp.StatusCode, http.StatusOK)
	}
	if ft.call != 2 {
		t.Fatalf("got %d calls, want retry to run", ft.call)
	}
})

Evidence

  • x-pack/filebeat/input/streaming/crowdstrike.go:148-153 wires authClient.Timeout into rateLimitTransport.timeout.
  • x-pack/filebeat/input/streaming/crowdstrike_ratelimit.go:37-41 applies one context.WithTimeout to the request context before retry loop.
  • x-pack/filebeat/input/streaming/crowdstrike_ratelimit.go:73-78 waits on timer using the same context; when timeout < backoff, context expires before retry attempt 2.
  • This makes maxRetry ineffective for common Retry-After/fallback waits when timeout is smaller.

Note

🔒 Integrity filter blocked 4 items

The following items were blocked because they don't meet the GitHub integrity level.

  • #49720 search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #49718 search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #49719 search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #49453 search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

What is this? | From workflow: Bug Hunter

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

  • expires on May 14, 2026, 11:49 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs_teamIndicates that the issue/PR needs a Team:* label

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions