Skip to content

docs: prepare v0.2.0 release#19

Merged
hidetzu merged 1 commit into
mainfrom
docs/v0.2.0-release
Apr 12, 2026
Merged

docs: prepare v0.2.0 release#19
hidetzu merged 1 commit into
mainfrom
docs/v0.2.0-release

Conversation

@hidetzu
Copy link
Copy Markdown
Owner

@hidetzu hidetzu commented Apr 12, 2026

Summary

Final PR before `v0.2.0` tag. Bumps `apiVersion` to `"0.2.0"`, rewrites the README, and fixes a rate-limit bypass vulnerability discovered during the release review.

Security fix: Fly-Client-IP priority for rate limiting

Before: `clientIP()` keyed the per-IP rate limiter by the first `X-Forwarded-For` entry. An attacker could rotate `X-Forwarded-For: fake-ip-N` per request to get unlimited fresh rate-limit buckets, completely bypassing the limiter.

After: `clientIP()` now prefers `Fly-Client-IP` — a header set by Fly.io's edge proxy with the real client address. The client cannot forge this header because Fly strips any client-supplied value. `X-Forwarded-For` remains the fallback for non-Fly environments.

Tests:

  • 4 new `clientIP` unit tests: Fly-Client-IP preferred over XFF/RemoteAddr, whitespace handling, IPv6
  • 1 new E2E chain test: `TestChain_RateLimitResistantToXFFSpoofing` — same `Fly-Client-IP` + rotated spoofed XFF → 429 (previously would have been 200)

README rewrite

Section Change
Status Phase 1 (v0.1.0-dev) → Phase 2 (v0.2.0)
Who is this for? New — 3 bullets: multi-language CI/CD, bots, distribution layer
Endpoints +2 rows: `POST /v1/analyze`, `POST /v1/prompt`
/v1/analyze New — request/response JSON + curl example
/v1/prompt New — request/response JSON + curl example (incl. mode/language)
Error format New — envelope shape + 9 error codes table
Configuration +4 vars: `MAX_REQUEST_BYTES`, `RATE_LIMIT_RPM`, `RATE_LIMIT_BURST`, `MAX_CONCURRENT_REQUESTS`
Known constraints New — 6 items (no auth, public only, GitHub 60/hr, related_files heuristic, no cache, rate limit map)
Provider Support Removed (folded into Known constraints)

apiVersion bump

`internal/httpapi/handler/health.go:12`: `"0.1.0-dev"` → `"0.2.0"`

`/version` now returns `{"api_version":"0.2.0","go_version":"go1.26.2"}`.

Local checks

  • `go vet ./...` clean
  • `go test ./... -race -count=1` all pass (215+ test runs including subtests)
  • `golangci-lint run` 0 issues

After merge

  1. `fly deploy` — ship the new image with v0.2.0
  2. Smoke test: `/version` returns `0.2.0`, `/v1/analyze` + `/v1/prompt` still work
  3. `git tag v0.2.0` on the merge commit
  4. Phase 2 complete

Intended to be squash-merged.

Bump apiVersion from "0.1.0-dev" to "0.2.0" and rewrite the
README for the Phase 2 release.

Security fix (bundled because it must ship with v0.2.0):

  clientIP now prefers the Fly-Client-IP header over
  X-Forwarded-For. Fly.io's edge proxy sets Fly-Client-IP with
  the real client address and strips any client-supplied value,
  so it cannot be forged. The previous implementation keyed the
  per-IP rate limiter by the first X-Forwarded-For entry, which
  an attacker could rotate to get a fresh rate-limit bucket per
  request — completely bypassing the limiter. With this fix the
  rate limiter is resistant to XFF spoofing when deployed on
  Fly.io. X-Forwarded-For remains the fallback for non-Fly
  environments (local dev, other trusted reverse proxies).

  New unit tests verify that Fly-Client-IP is preferred over XFF
  and RemoteAddr (4 cases). A new E2E chain test
  (TestChain_RateLimitResistantToXFFSpoofing) simulates the
  attack scenario: same Fly-Client-IP + rotated spoofed XFF
  values. The second request is correctly rate-limited (429)
  because the limiter keys by Fly-Client-IP, not XFF.

README changes:

  - Status: Phase 2 (v0.2.0)
  - "Who is this for?" section (multi-language CI/CD, bots,
    distribution layer)
  - Endpoints table: +2 (POST /v1/analyze, POST /v1/prompt)
  - Full request/response examples with curl commands
  - Error format section with all 9 error codes
  - Configuration table: +4 env vars (MAX_REQUEST_BYTES,
    RATE_LIMIT_RPM, RATE_LIMIT_BURST, MAX_CONCURRENT_REQUESTS)
  - "Known constraints (v0.2.0)" section: no auth, public repos
    only, GitHub 60/hr limit, related_files heuristic, no cache,
    rate limit map unbounded
  - Removed standalone "Provider Support" section (folded into
    known constraints)
@hidetzu hidetzu merged commit 023c3bc into main Apr 12, 2026
2 checks passed
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