Skip to content

FeedEcho v1.2.0 — Security Hardening (Critical + High)

Choose a tag to compare

@jcrabapple jcrabapple released this 08 Aug 03:51
· 9 commits to master since this release

Addresses all Critical and High-severity findings from the GPT-5.6 Terra Pro code review.

Critical Fixes

C-1: SSRF bypass via redirects

Replaced follow_redirects=True with a manual redirect loop that validates every Location header via validate_outbound_url(). A public feed that redirects to 169.254.169.254 or 127.0.0.1 is now blocked. Capped at 5 redirects.

C-2: Unauthenticated /oauth/connect SSRF

Removed /oauth/connect from auth-exempt paths. Only /oauth/callback remains exempt (needed for Mastodon redirect). Unauthenticated users can no longer trigger outbound requests to arbitrary instance URLs.

High Fixes

H-1: Session-bound, expiring, single-use OAuth state

New oauth_states table stores nonce, instance, hashed session binding, expiry, and consumed timestamp. State tokens are:

  • Session-bound: OAuth session cookie ties callback to initiating browser
  • Expiring: 10-minute TTL
  • Single-use: Atomically consumed via conditional UPDATE
  • Full HMAC: SHA-256 (was truncated to 64 bits)

H-2: Cursor stops at first failed item

break on first failure instead of continuing past it. Failed items are retried on next poll, never permanently skipped. Cursor only advances across a contiguous successful prefix.

H-3: Atomic pending-row claim

INSERT ... ON CONFLICT DO UPDATE with conditional WHERE clause. claim_token ensures only the owning worker can finalize. Stale pending rows (10+ min) are reclaimable. attempt_count tracks retries.

H-4: Per-feed lease

Atomic lease acquisition prevents concurrent processing. Lease renewed per-item, released in finally block. Cursor updates require lease ownership. Scheduler uses max_instances=1, coalesce=True.

H-5: SSRF validation for all outbound URLs

Renamed validate_feed_url to validate_outbound_url and applied to: feed URLs, Mastodon API calls, OAuth registration/token exchange. Blocks private IPs, embedded credentials, non-http schemes.

Also Fixed (Medium severity)

  • M-1: get_db() now properly closes connections via @contextmanager
  • M-1: busy_timeout=30s for SQLite write contention
  • M-1: WAL mode set once in init_db, not per-connection
  • M-2: Generic error messages to clients; details logged server-side with logger.exception()

Tests

71 tests passing. New tests cover: single-use state consumption, session binding, expired state rejection, full-length signature, redirect-to-private blocked, instance URL SSRF, embedded credentials.

Full changelog: v1.1.0...v1.2.0