security: backport v0.8.3 hardening to main#111
Merged
Conversation
Feed/article HTML is rendered via {@html}, with the CSP as the only
defense against stored XSS. Add server-side sanitization so the CSP
and sanitization are defense-in-depth rather than a single control.
- feed.SanitizeHTML (bluemonday UGCPolicy): strips <script>, inline
event handlers, <style>, and javascript:/data: URLs.
- feed.SafeHTTPURL: promoted from the TT-RSS importer so every ingest
path shares one http(s)-only URL guard.
- Apply at all ingest points: feed parse (body + article href),
readability extraction, TT-RSS import, and the LLM cleaned_html.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A missing username returned in ~1ms while a real one took ~100ms (argon2), leaking account existence via a timing side channel. Run a throwaway argon2id derivation with the live cost params on the user-not-found path so both paths cost the same. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
handleListUsers projected away secrets in Go but ListUsers still SELECTed password_hash and fever_token into memory. Add ListUsersPublic (id, username, email, is_admin, created_at) so those columns never leave the database for a request that only needs identity and role. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The TT-RSS API import returned raw err.Error(), exposing the resolved endpoint, internal hostnames, and DNS/TLS detail. Log the full error server-side and return a generic message, mirroring the SMTP-test handler. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Discover() returned the alternate <link> URL without running it through the SSRF validator, while DiscoverAll already validates each discovered link. Validate before returning; on reject/unresolvable, fall through to the fallback probes (which validate each) rather than handing back an unchecked URL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
apiClient built its redirect SSRF guard with context.Background(), so the per-redirect validation ignored the import request's cancellation and deadline. Pass the call's ctx through instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A syntactically-invalid search (unbalanced quote, bare boolean operator, bad column filter) surfaced as a 500. Detect the FTS5 MATCH-syntax error classes, return store.ErrInvalidQuery, and map it to 400 — it's client input, not a server fault. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BootstrapAdmin accepted any non-empty EMBER_ADMIN_PASSWORD while the create-user and change-password paths enforce an 8-char floor. Apply the same minimum so the first-run admin can't be seeded weak. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NewFetcher built an unguarded client and relied on every caller to set CheckRedirect afterward. Make validate a constructor parameter and wire RedirectGuard in at construction so the safe default is the only option — a caller can't accidentally build a fetcher that follows redirects to private addresses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Promotes microcosm-cc/bluemonday (+ douceur, gorilla/css) to direct deps for the feed.SanitizeHTML sanitizer introduced in this backport. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fuzzing surfaced that a scheme-only value like "HTTP:" (or an opaque "https:foo") passed the guard and was returned as a "safe" URL despite having no host to link/fetch. Not a javascript:/data: bypass, but a sloppy result for a value rendered as an href/src. Require u.Host != "". Relative links are resolved to absolute before this call, so legitimate URLs are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The admin LLM endpoints passed the model name verbatim to the Ollama API. Constrain it to the documented [registry/][namespace/]name[:tag] shape so a rogue/compromised admin can't coax Ollama into pulling from an arbitrary registry or dereferencing path-traversal components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The nil-client fallback built an unguarded client (no SSRF redirect/dial guard). It's never used in production (the poller passes a guarded client), so make nil an explicit error rather than a silent footgun. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ding urlcheck.Check validated the hostname, but the HTTP stack resolved DNS again at dial time — an attacker controlling DNS could answer public to the check and private to the dial (TOCTOU rebind). Add urlcheck.DialContext / GuardedTransport, which re-resolves, rejects any private resolved IP, and dials the pinned address. Wire it into the feed fetcher and the poller's readability client (alongside the existing redirect guards). (Backport of develop 57a9db1; the push-notifier wiring is omitted — that feature isn't on main.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each filter's regex is compiled and cached for the process lifetime (reCache, no eviction), so an authenticated user creating unbounded filters/patterns is a slow-burn memory DoS. Cap at 200 filters per user and 512 chars per regex. RE2 already rules out backtracking ReDoS. (main-targeted equivalent of develop cad70d0, which doesn't cherry-pick cleanly because develop's filters is the enhanced PR #78 version.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security hardening backport for the v0.8.3 release. All changes target code that ships on
main; develop-only features (email inbox, web push) are excluded.High
SafeHTTPURLalso requires a host.Medium
/api/usersno longer fetches password_hash/fever_token into memory.Discover()validates the discovered feed link (SSRF).Low / hardening
NewFetcher(safe default is the only option).ExtractFromURLrequires a guarded HTTP client.Verified: build, vet, full test suite, gofmt all green on main's base.