Cold review of PR #331:
- Move the ZAP-container reap from an inline post-scan line into cleanup() (the
EXIT trap) so a SIGINT/timeout kill mid-scan can't leave it running.
- semgrep absent now prints a stderr WARN instead of silently skipping the SAST
pass (no false confidence for a release gate).
refs #314
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scripts/security-scan.sh: boots a THROWAWAY container from the image under test
(fresh config volume; never the deployed prod/test container — the active scan
attacks write endpoints), reads its machine key, and runs an authenticated OWASP
ZAP api-scan that imports /openapi/v1.json (all 160 /api/v1 ops) and injects
X-Api-Key on every request via a replacer rule so it reaches the
[RequiresAuthentication] + RequireKeyForReads surface — then a semgrep SAST
cross-check. Wrapped in `timeout` because zap-api-scan can hang in post-scan
cleanup after the report is already written.
docs/ci-cd.md: new 'Security scanning' section (out-of-ecosystem black-box gate,
run on the docker host per-release like migration-smoke, not a per-PR CI job) +
the Microsoft.OpenApi 2.7.5 pin note in dependency management.
refs #314#197
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new api-docs gate (#303 H4/H5) runs update-openapi.sh after only `dotnet
restore`. The script's `dotnet build -t:GenerateOpenApiDocuments` does not compile
the project (OpenApiGenerateDocumentsOnBuild=false), so in a clean tree getdocument
fails with 'ErsatzTV.deps.json does not exist' (exit 129). This PR is the first to
change the /api surface and thus the first to exercise the gate's regen path,
exposing the latent bug. Add a full `dotnet build` before the doc-gen target so the
assembly + deps.json exist. Verified: clean-tree regen now succeeds with zero diff.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review (MERGEABLE-WITH-NITS) findings:
- Remove the broad FAIL_RE log-scan (matched benign ErsatzTV startup noise — library
scans against absent media mounts, EF connection retries — risking a false-FAIL that
blocks a good release). It was also redundant: a failed migration faults the
BackgroundService -> default StopHost -> container exit, which the early-exit check
already catches reliably (per the reviewer's own analysis). Migration failure is now
detected by early container exit + timeout + the post-boot serve probe.
- Validate --timeout is a positive integer (was: '--timeout abc' -> 0 -> instant false-FAIL).
- Log the resolved image id after (attempted) pull, so a pull-failure that rehearses a
stale local :latest is visible to the operator.
Re-validated live on bumblebee: :latest vs the 283MB prod-copy -> migrations clean, PASS,
image digest logged, no leftover temp dir/container. shellcheck + bash -n clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI's migrations job only proves a migration is well-formed against a fresh,
empty DB. It never exercises the migration — or ErsatzTV's startup data steps
(DatabaseMigratorService -> DbInitializer + PopulatePathHashes over the real
MediaFile table) — against the accumulated prod SQLite, so a migration green on
a fresh DB can still fail/corrupt on prod, found only mid-deploy.
scripts/migration-smoke.sh rehearses it on a THROWAWAY copy of the latest prod
backup: boots the new image against the copy, gates PASS on the "Done applying
database migrations" log line (the migrator is a BackgroundService running
concurrently with Kestrel, so HTTP readiness alone doesn't prove migrations
finished), FAILs on early container exit / migration exception / timeout / not
serving afterwards. Always operates on a copy, never the live DB; tears down its
container + temp dir (incl. the container's root-owned config files) on exit.
Validated live 2026-07-12: :latest vs a copy of the 283MB prod backup ->
migrations applied cleanly, app booted+served, temp dir removed.
Home split: this repo owns the script + docs; wiring it into the Komodo
pre-deploy step is server-management#589 (cross-repo). Docs: docs/ci-cd.md
(Migration integrity), docs/decisions.md (new entry, pure insertion).
fixes#315
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scripts/issue-qualification-audit.sh: lists OPEN issues missing a `priority:` label
(the #237 ranking keys off priority:/gate labels, so an unlabeled issue is invisible
to it). "Fully qualified" = has a priority: label; gate-vs-backlog derives from the
review label / milestone, and a milestone is NOT required (backlog is unmilestoned).
Advisory (exit 1 if any unqualified); fail-open without Gitea creds. Wired into the
kickoff session-end protocol + a lore bullet. Tested live (flagged 2) + no-creds no-op.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Only run generate-endpoint-index.py if the GenerateOpenApiDocuments build
succeeded, so a failed build can't render docs/endpoint-index.md from a
stale/partial v1.json.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds docs/testing.md as the authoritative testing map (consolidated
from docs/contributing.md §8, now shrunk to a pointer), and a
generated docs/endpoint-index.md via scripts/generate-endpoint-index.py
(hooked into scripts/update-openapi.sh). Updates docs/README.md's
reading order and removes the "still to come" placeholder.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* generate openapi definitions as separate build task
* first defns
* install etv-client module in docker
* include python entrypoint in docker
* update changelog