gitshark

Clone repository

git clone https://gitshark.de/git/workaround/Gitshark.git
git clone git@gitshark.de:workaround/Gitshark.git

← Merge requests

✨ (protect): Rate-limit expensive renderings and add a bot check !12

Merged issue-16-bot-captchamain opened by miggi miggi

Closes #16.

## What

Expensive rendered pages are built live from git on every request, which makes them the cheapest way for a crawler to burn an instance's capacity. They are now metered per caller, with an optional captcha so a refused human can continue.

Metered (GET only): `…/commit/{id}`, `…/commits[/{ref}]`, `…/merge-requests/{number}`, `/search`.

| Caller | Key | Default budget |
|---|---|---|
| Anonymous | client IP (proxy-aware) | 60 per minute |
| Logged in | user account | 600 per minute |

Over budget:

- **no captcha configured** → `429` + `Retry-After` (rate limiting never depends on a third party being reachable)
- **captcha configured** → `303` to `/challenge?redirect=…`; a token confirmed by the provider's `siteverify` mints an HMAC-signed, self-expiring `gitshark_human` cookie (`HttpOnly`, `SameSite=Lax`, `Secure` over HTTPS) that lifts the budget for 30 min — no server-side session state, so it survives restarts and works across pods

Both Cloudflare Turnstile and hCaptcha are supported through one code path (they differ only in script URL, widget class, form field and verify endpoint).

Machine surfaces — git transport, `/api/v1`, `runner.v1`, MCP, ActivityPub — are never metered: they authenticate their own callers and cannot solve a challenge.

## Design notes

- Fixed windows, one map entry per caller, pruned past 50 000 keys. Per-pod, not replicated; a global limit belongs at the ingress.
- Refusal redirects to `/challenge` instead of rendering the page inside the request filter (no Qute on the event loop, one renderer).
- `?redirect=` accepts server-relative single-slash paths only, so the challenge can't become an open redirect.
- Verification fails **closed** (timeout / non-200 / unparseable → not verified); an incomplete captcha config fails **open** only in the sense that the challenge disappears — metering stays on.
- No tables, no migrations, no scheduler.

## Configuration

`GITSHARK_PROTECT_ENABLED` (`true`), `_ANONYMOUS_LIMIT` (`60`), `_USER_LIMIT` (`600`), `_WINDOW` (`1m`), `_CAPTCHA_PROVIDER` (`none|turnstile|hcaptcha`), `_CAPTCHA_SITE_KEY`, `_CAPTCHA_SECRET_KEY`, `_CAPTCHA_VERIFY_URL`, `_CAPTCHA_PASS_DURATION` (`30m`).

## Tests

21 new tests (limiter windows/keys/eviction, pass signing/expiry/tampering, metered vs cheap paths, anonymous vs logged-in budgets, the full challenge round trip against a stub `siteverify`, rejected token, open-redirect guard). Full suite: **632 tests, 0 failures**.

## Docs

New `docs/users/bot-check.md`, `docs/admins/bot-protection.md`, `docs/maintainers/bot-protection.md`; index, config-reference table, README feature list, and the commits/search user guides updated.

Assignee

No one assigned

Reviewer

No reviewer

Discussion 0

No comments yet.

Log in to comment.

Changes

No changes to show. The source branch has nothing new over the target.

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog