Files
FabledCurator/SECURITY.md
T
bvandeusenandClaude Opus 5 8a4af589f1
Build images / sign-extension (push) Successful in 4s
Build images / build-agent (push) Successful in 9s
Build images / build-ml (push) Successful in 29s
Build images / build-web (push) Successful in 23s
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 4s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 30s
CI / integration (push) Successful in 3m42s
docs: write the install path for someone who is not the operator (#3271)
FC has no login — no User model, no session auth, nothing. That was a
deliberate call for a single-operator tool and it stays (operator, this
session), but it was nowhere in the docs, and the app stores live Patreon /
SubscribeStar / Pixiv session cookies on accounts that carry a payment
method. Anyone standing this up from the README could reasonably have put it
behind a TLS-terminating proxy and considered it handled.

So the no-auth posture is now stated three times, in the three places someone
decides where to bind the port: README has a "Before you expose it" section
above the install instructions, .env.example explains why there is no auth
variable in it, and the compose header says it before the first service.

SECURITY.md claimed the opposite. It listed "a multi-user sharing ACL —
instances can be shared" among the things worth protecting; there are no
accounts to share between. That was rule 47 applied to a codebase that does
not implement it, and it would have told a researcher FC holds a boundary it
does not. Replaced with the real posture, including that TLS without an
authenticating layer in front changes nothing.

Also corrected, all of it stale rather than wrong-at-the-time:

- EXTENSION_API_KEY was dead config. config.py read it into a field nothing
  consumed; the real key is generated into app_setting on first use and
  managed in the UI. Removed from config.py, compose and .env.example.
- .env.example pointed at docs/superpowers/specs/… — there is no docs/ dir —
  and described the extension key as "lands in FC-3", closed 2026-05-21.
- The /import mount comment described an FC-5 ImageRepo migration run from
  "Settings → Maintenance → Legacy migration", a surface with no frontend.
- README said the extension installs from Settings → Maintenance. It is on
  Subscriptions → Settings.

README is now split: running FC above the line, developing FC below it, with
requirements, first run, the extension, upgrading and troubleshooting on the
running side. First run documents the one real gap it found — a new installer
with a library on disk has no button to import it, only POST
/api/import/trigger, because the manual-scan UI was retired 2026-07-02 when
that stopped mattering for an established install. Filed as #3367.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QHszn9H8VBvx5Ke8x1hvw
2026-09-02 00:16:23 -04:00

3.9 KiB

Security Policy

Reporting a vulnerability

Please do not put vulnerability details in a public issue.

This project has no private disclosure channel yet. Until it does, open an issue on the repository that says only that you have a security report — no reproduction steps, no affected endpoint, no payload — and a maintainer will reply with a private contact to send the details to.

That is a deliberately awkward first step, and it exists because the alternative is worse: an issue tracker is public the moment it is written to, and every self-hosted instance stays vulnerable until its operator has had a chance to update.

Please include, once you have a private channel:

  • what an attacker can do, and what access they need to start
  • the version or commit you tested
  • reproduction steps

Scope — what this software actually handles

FabledCurator is self-hosted and holds things worth stating plainly, because they shape what counts as a serious bug here:

  • Platform credentials. The app captures and stores session cookies for third-party subscription sites (Patreon, SubscribeStar, Pixiv) so it can download on the operator's behalf. These are live credentials for accounts that usually carry a payment method. Anything that discloses them, decrypts them, or lets one user of a shared instance read another's is high severity.
  • An extension API key. The Firefox extension authenticates to the backend with a shared key. Anything that leaks it or lets it be bypassed is a way in.
  • No authentication of its own. This is the most important thing on this page. FabledCurator has no login, no user accounts and no permission model — there is no User table and no session auth anywhere in the backend. Every HTTP client that can reach the port is the administrator, with full read and write access to everything above, including the stored platform credentials. Access control is entirely the operator's job, done at the network layer. Reports that an unauthenticated caller can reach an endpoint are therefore describing the design; reports that something crosses the network boundary the operator drew — an SSRF, a request forgery that rides a browser the operator already has open, a path that leaks state to an origin the operator did not authorise — are in scope and are serious.
  • Arbitrary media from the internet. Downloaded files are decoded, hashed, thumbnailed and fed to ML models. Anything that turns a hostile file into code execution is in scope.

Deployment posture — read this before reporting

FabledCurator is designed to run inside a private network, over plain HTTP, reachable only by its operator. It does not terminate TLS, redirect to HTTPS, or set HSTS; if you want transport security, terminate it at your reverse proxy. It also does not authenticate anyone — see above. These are documented design decisions, not oversights.

Putting this on the public internet, with or without TLS, hands whoever finds it your Patreon, SubscribeStar and Pixiv sessions. A reverse proxy that adds TLS but not an authentication layer does not change that.

Reports that reduce to "the application is served over HTTP", "there is no HSTS header", or "the API needs no credentials" describe those decisions rather than vulnerabilities. Reports that the operator can cause the software to do something destructive are usually also by design — the operator is the administrator of their own instance.

What remains in scope is everything that crosses a boundary the software is actually supposed to hold: between untrusted downloaded content and the host, between a third-party origin and an operator's open browser session, and between the credentials at rest and anything that is not the operator.

Supported versions

Fixes land on the main branch and reach the :latest image. There are no maintained release branches — the supported version is the current one, and the remedy for a security issue is to update.