Skip to content

Close what a security review and the scanners found, and give CI's provider keys an environment of their own - #25

Merged
Dim145 merged 3 commits into
mainfrom
security/2026-11-03
Nov 3, 2026
Merged

Dim145 merged 3 commits into
mainfrom
security/2026-11-03

Conversation

@Dim145

@Dim145 Dim145 commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Two commits on top of main: what a security review of every area of the server, and the scanners, found — and the providers' keys moved to an environment of their own.

Nothing let a caller past a guard. What follows let one cost the server too much, see what was not theirs, or keep what they had lost: six findings of medium weight and some twenty-five small ones, each checked against the code before it was changed.

What changes

  • Signing in. Failures are counted per account and address, before the password is hashed, with one atomic count on the cache server; a bar five times higher across addresses never applies to an address the account signed in from in the last thirty days, so a guesser can no longer lock the administrator out. A run of failed sign-ins, or of stray returns from the identity provider, is one audit row with its number.
  • Accounts and the identity provider. An administrator's pause of a member's key stands on the member, not on the key they could delete. Tying a provider login asks for the password (or a fresh sign-in at the provider); the owner can untie it, and an administrator's password reset does. The provider's client refuses internal addresses at connect time and judges every endpoint before asking it. A trusted proxy that names no client leaves the client unknown, refused by every allowlist.
  • Load. No network holds more than 64 connections of a door (trusted proxies and this machine excepted); a connection with nothing in flight for two minutes is closed, an unread response given up on after a minute. Searches, facets, lists and figures no cache holds are read three at a time (503 with Retry-After past that). Link previews and /ready count against the caller and are kept a few seconds. /tmdb-images/ has a quota of its own and hands on 16 pictures at a time. Search terms are cut at 200 characters at every door.
  • Pictures. What a decode will hold is counted from the header before anything is decoded, against 256 MB the decodes share — one 100 KB grey PNG took some 700 MB before. Copies made on demand wait their turn before reading the original, 32 at once, counted against the caller. Uploads are cut off as they are read; a writer keeps 1 GiB of them and is told only that a picture could not be fetched.
  • Relays. TheTVDB's own ids are found in maps read once per catalogue state, and seasons and orders' episodes are indexed by TheTVDB's id (migration 0053), where a made-up id read whole tables. Reference lists are filed by path, language and region, 512 at most per relay. TheTVDB's and AniList's answers are keyed on the headers they are asked with.
  • Outbound requests send no Referer, and the providers a key travels to follow a redirect only within their origin. A series' IMDb id is checked before it goes into TMDB's /find path.
  • Logs mask a sign-in's code and state, TMDB's session ids and an auth parameter; an import rule's error and a database address no longer carry their keys or passwords.
  • Backups are written 0600 in 0700 directories and their downloads audited; a restore quotes the archive's column names — a crafted archive could run statements of its own. transfer reads URLs that carry a password from AMS_TRANSFER_FROM and AMS_TRANSFER_TO. The trailer's frame is sandboxed; trust-ca.sh installs exactly one certificate.
  • CI's keys. TMDB_API_KEY, TVDB_API_KEY and FANARTTV_API_KEY are the secrets of the environment provider-keys, read by the end-to-end tests and the parity check alone. They are already there; the repository's copy of TMDB_API_KEY is deleted, the two others go once this is merged.
  • Scanners' false positives are named: .gitleaksignore for three lines of history, nosemgrep for the counting allocator and the watch script.

Behaviour to tell operators about is in the changelog's Unreleased section: transfer's variables, an upstream that redirects http:// to https:// given as https://, the password asked to tie an account.

Checked locally

  • cargo fmt, cargo clippy --all-targets -D warnings, cargo test: 1998 tests (136 new).
  • Front end: typecheck, lint, vitest (275 tests), build; the docs site builds with its 2384 links whole.
  • Playwright, desktop, mobile, server-wide and visual: 565 passed, none failed; the media specs on a server with a store: 3 of 3.
  • scripts/e2e-stack.sh: 149 checks against Sonarr 4, Sonarr 5, Radarr and Plex; e2e-relays.sh, e2e-yamtrack.sh and e2e-sonarr-parity.sh all good; e2e-skyhook-v2.sh answers line for line as before, its one failure the real service's 500 for The Office by TMDB id.
  • A throwaway PostgreSQL with works imported through the real providers, now with the audit's gathered failures and the relay's filing: 40 checks.
  • semgrep (19 packs), CodeQL security-extended (JS/TS, Actions, Python, Rust), gitleaks over the whole history, zizmor in both personas, actionlint, shellcheck: nothing new; CodeQL's seven Rust alerts are the ones already dismissed. trivy finds 44 Debian vulnerabilities in the image's base, none with a fix.

🤖 Generated with Claude Code

Dim145 and others added 2 commits November 3, 2026 21:02
… load limits, decode memory, relay caches, backups and logs

A review of every area of the server, and of what semgrep, CodeQL,
gitleaks, trivy and zizmor find in it. Nothing let a caller past a guard;
these let one cost the server too much, see what was not theirs, or keep
what they had lost.

- Signing in: failures are counted per account and address, before the
  password is hashed, with one atomic count on the cache server; a bar five
  times higher across addresses never applies to an address the account
  signed in from in the last thirty days, so a guesser cannot shut the
  administrator out. A run of failed sign-ins or stray provider returns
  from one address is one audit row with its number.
- Accounts: an administrator's pause of a key stands on its owner; tying an
  identity-provider login asks for the password (or a fresh sign-in at the
  provider), the owner can untie it, and an administrator's password reset
  does. The provider's client refuses internal addresses at connect time
  and judges every endpoint before asking it. A trusted proxy that names no
  client leaves the client unknown.
- Load: no network holds more than 64 connections of a door (trusted proxies
  and this machine excepted), a connection with nothing in flight for two
  minutes is closed and an unread response given up on after a minute;
  searches, facets, lists and figures no cache holds are read three at a
  time, answered 503 with Retry-After past that; link previews and /ready
  are counted against the caller and kept a few seconds; /tmdb-images has a
  quota of its own and hands on 16 pictures at a time; search terms are cut
  at 200 characters at every door.
- Pictures: what a decode will hold is counted from the header before
  anything is decoded, against 256 MB the decodes share; copies made on
  demand wait their turn before reading the original, 32 at once, counted
  against the caller; uploads are cut off as they are read, and a writer
  keeps 1 GiB of them; a writer is told only that a picture could not be
  fetched.
- Relays: TheTVDB's own ids are found in maps read once per catalogue state,
  an id none could be answered at once, and seasons and orders' episodes are
  indexed by TheTVDB's id (migration 0053); reference lists are filed by
  path, language and region, 512 at most per relay; TheTVDB's and AniList's
  answers are keyed on the headers they are asked with.
- Outbound requests send no Referer, and the providers a key travels to
  follow a redirect only within their origin; a series' IMDb id is checked
  before it goes into TMDB's /find path.
- Logs mask a sign-in's code and state, TMDB's session ids and an auth
  parameter; an import rule's error and a database address no longer carry
  their keys or passwords.
- Backups are written 0600 in 0700 directories, their downloads audited, and
  a restore quotes the archive's column names. transfer reads URLs that
  carry a password from AMS_TRANSFER_FROM and AMS_TRANSFER_TO. The trailer's
  frame is sandboxed, and trust-ca.sh installs exactly one certificate.
- The scanners' false positives are named: .gitleaksignore for three lines
  of history, nosemgrep for the counting allocator and the watch script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-to-end tests and the parity check alone

zizmor's auditor persona found TMDB_API_KEY, TVDB_API_KEY and
FANARTTV_API_KEY read as repository secrets, which every job of the
repository can reach. The two jobs that run against the real services now
take them from the GitHub environment provider-keys; once the secrets are
moved there and the repository's copies deleted, no other job can read them.
Forgejo, which runs both workflows too, has no environments and keeps its
own secrets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gitguardian

gitguardian Bot commented Nov 3, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 3 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
39527350 Triggered Generic Password 0a2b693 src/db/mod.rs View secret
39527348 Triggered Generic Password 0a2b693 src/db/mod.rs View secret
39527349 Triggered Generic Password 0a2b693 src/db/mod.rs View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

GitGuardian read three of the addresses the test writes — a password as a
parameter, and one holding an `@` — as passwords left in the code. They are
made up; they are now put in as the test runs, so that no line reads as an
address and its password.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Dim145
Dim145 deployed to provider-keys November 3, 2026 20:48 — with GitHub Actions Active
@Dim145
Dim145 merged commit b3bb811 into main Nov 3, 2026
17 checks passed
@Dim145
Dim145 deleted the security/2026-11-03 branch November 3, 2026 21:08

This branch was successfully deployed

1 active deployment
provider-keys — 24b681a0 Deployed Nov 3, 2026 by Dim145 via End-to-end tests #76
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