Skip to content

fix: rate-limit failed /v1/* bearer-token auth#227

Open
ejames-dev wants to merge 1 commit into
steipete:mainfrom
ejames-dev:fix/v1-auth-rate-limit
Open

fix: rate-limit failed /v1/* bearer-token auth#227
ejames-dev wants to merge 1 commit into
steipete:mainfrom
ejames-dev:fix/v1-auth-rate-limit

Conversation

@ejames-dev
Copy link
Copy Markdown

Summary

/v1/* routes accept an unbounded number of failed authentication attempts. The daemon binds 127.0.0.1 in the common case, so the practical reach is local processes that don't already hold a paired token — but on a 0.0.0.0 bind (Windows containers, per resolveDaemonListenHost) or from any co-resident process, a guesser had no cost to retry. Adding a small lockout is cheap defense-in-depth and combines naturally with the timing-safe compare in PR #226.

Design

New src/daemon/auth-rate-limit.ts exports AuthRateLimiter:

  • Keyed on req.socket.remoteAddress. Null/empty keys are not tracked (always allowed).
  • After 20 consecutive failures within a 60s sliding window, the IP is locked out for 60s. Response is 429 with a Retry-After header.
  • A successful auth clears that client's counter, so legitimate users with a typo on their first try aren't penalized.
  • The IP table is capped (1024 entries) with FIFO eviction so it can't grow unbounded.
  • Configurable via constructor options; injectable now() for tests.

src/daemon/server.ts consults the limiter inside the existing /v1/* gate.

Test plan

  • tests/daemon.auth-rate-limit.test.ts covers: under-threshold allow, exact-threshold trip + retry-after value, client isolation, success clearing the counter, failure-window reset, null-key handling, LRU eviction.
  • CI runs pnpm -s check.

Notes

Third of a small series from a security-oriented review. Sibling PRs: #225 (attachment path traversal) and #226 (timing-safe token compare). All three are independent — feel free to take any subset.

The daemon's `/v1/*` routes accepted unlimited authentication attempts.
Local-only binding bounds the attack surface, but on a 0.0.0.0 bind
(Windows containers) or from a co-resident process, a guesser had no
cost to retry. Add a small per-IP failure counter: after 20 consecutive
failures within 60s the client is locked out for 60s with a 429 +
Retry-After response. A successful auth clears the counter, so legitimate
clients are unaffected. Bounded LRU eviction keeps the table small.

Includes unit tests for the limiter (no integration test since it would
require standing up the full daemon).
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 843cbd6327

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/daemon/server.ts
Comment on lines +190 to +194
const preCheck = authLimiter.check(clientKey);
if (!preCheck.allowed) {
json(
res,
429,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate the bearer token before applying lockout

Because this pre-check runs before accepting a valid bearer token, 20 bad /v1/* requests from the same remoteAddress cause all later requests from that address to return 429, even when they include the configured token. In the normal loopback setup all local processes share 127.0.0.1/::1, so any unpaired local process can lock out the paired extension or CLI for the full timeout; the limiter can still count failed attempts, but valid tokens should bypass or clear the lockout.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant