Skip to content

fix(security): rate-limit share-link password verification (TASK-2055)#913

Merged
xarmian merged 1 commit into
mainfrom
feat/task-2055-sharelink-bruteforce-limiter
Jul 11, 2026
Merged

fix(security): rate-limit share-link password verification (TASK-2055)#913
xarmian merged 1 commit into
mainfrom
feat/task-2055-sharelink-bruteforce-limiter

Conversation

@xarmian

@xarmian xarmian commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

TASK-2055 — Brute-force limiter for share-link password verification

Share-link password verification (GET /api/v1/s/{token} with X-Share-Password) had no dedicated brute-force limiter, so a password-protected link could be ground fast — the resolve handler would bcrypt-compare an unbounded stream of guesses.

Fix

Two limiters in RateLimiters, both charged before the bcrypt compare in handleResolveShareLink:

  • SharePasswordIP — 5 guesses / 10-per-hour, keyed on SHA-256(share ID)+client IP. Caps a single grinder and protects bcrypt CPU. Per-IP, so one caller can't lock out other viewers; checked first so a single address can't drain the link-wide bucket.
  • SharePasswordShare — 60 guesses / 60-per-hour, keyed on SHA-256(share ID). Caps the aggregate guess rate across a botnet that rotates IPs. Charged pre-compare like login's per-email AuthEmail gate, so an exhausted link blocks even a would-be-correct guess (no password oracle). Burst sized so ordinary multi-viewer traffic never trips it; the per-IP gate ahead of it means exhausting it needs a genuine botnet (self-healing) — the same bounded tradeoff AuthEmail accepts for an unauthenticated shared secret.

Both keyed on SHA-256 so no secret hits the limiter map. In-memory limiter — no DB migration.

Tests

internal/server/handlers_share_links_bruteforce_test.go:

  • Per-IP burst throttles repeated wrong guesses (429 after burst) while a correct password works within budget and a second viewer / second link keep their own budget.
  • Aggregate cap engages across rotating IPs and blocks even a correct guess once spent (no oracle).

Gates

go build, go test ./..., golangci-lint on internal/server — all green. Self-Codex review: CLEAN.

https://claude.ai/code/session_015yuBJQYfDj95cgX3DaD8SF

Share-link password verification had no dedicated brute-force limiter, so a
password-protected /s/{token} link could be ground offline-fast — the resolve
handler would bcrypt-compare an unbounded stream of guesses.

Add two limiters, both charged BEFORE the bcrypt compare:

  - SharePasswordIP (5 / 10-per-hour, keyed on SHA-256(share ID)+client IP)
    caps a single grinder and protects bcrypt CPU; per-IP so one caller can't
    lock out other viewers, and it's checked first so a single address can't
    drain the link-wide bucket.
  - SharePasswordShare (60 / 60-per-hour, keyed on SHA-256(share ID)) caps the
    aggregate guess rate across a botnet that rotates IPs. Charged pre-compare
    like login's per-email AuthEmail gate, so an exhausted link blocks even a
    would-be-correct guess (no password oracle). Its burst is sized so ordinary
    multi-viewer traffic never trips it, and the per-IP gate ahead of it means
    exhausting it needs a genuine botnet (self-healing) — the same bounded
    tradeoff AuthEmail accepts for an unauthenticated shared secret.

Both keyed on SHA-256 so no secret hits the limiter map.

Claude-Session: https://claude.ai/code/session_015yuBJQYfDj95cgX3DaD8SF
@xarmian xarmian merged commit 8f7b7d5 into main Jul 11, 2026
4 checks passed
@xarmian xarmian deleted the feat/task-2055-sharelink-bruteforce-limiter branch July 11, 2026 03:49
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