Stop pushing SLSA attestations to the registries as OCI referrer tags (attest.yml: push-to-registry: false) and disable buildx's default provenance sidecars (release.yml: provenance: false). GHCR rendered both as package versions / unknown-unknown architecture entries on the package page. The release/publish flow (publish-images gate, RELEASE_TOKEN, web Sentry DSN) is unchanged.
Point semantic-release's version-bump push and the docs-changelog push at a RELEASE_TOKEN admin PAT so they clear the 16 required checks (default token is rejected). Falls back to the default token, so no change until the secret is set. Documents creating/rotating the token in RELEASE.md.
Attach the manifest job to a publish-images GitHub Environment so Docker Hub + GHCR tags only go live after a maintainer approves the release run. Build + Trivy scan still run automatically; approval is the last step. Adds RELEASE.md runbook.
Removes Sentry tracing entirely (BullMQ idle polling burned 4.8M transactions in 2 days at the baked 0.1 rate), decouples PostHog sampling, and replaces the type-only error scrub with a vetted-field sanitizer plus SafeError/ToolInputError contracts. One classified capture path with per-signature throttles and a per-process ceiling makes storms impossible (NODE-1E was 4,541 events from one 30s loop). Browser errors move to a dedicated web Sentry project with their own source maps. Adds the SNAPOTTER_TELEMETRY runtime kill switch and silences test fleets.
Crash fixes: remote 204/304 SSRF process kill (NODE-20), conversion-preset boot crash loop (NODE-21), Redis version preflight + unhandled subscribe rejection (NODE-1T), Sign PDF on plain-http origins (NODE-1K/1M), wavesurfer/pdf.js teardown rejections (NODE-1P/1N), bundle-import ZlibError to 400 (NODE-1Z), chart-maker input errors declassified (NODE-1H/1J), asset requests skip the session DB lookup (NODE-1D).
* feat(analytics): upload web source maps to Sentry + tie release to build
Web crash reports were unusable: the bundle ships minified with no source
maps uploaded, and every build reported as the frozen APP_VERSION, so a
Sentry error showed an unreadable stack under a single release.
- Add @sentry/vite-plugin: emit hidden source maps and upload them by debug
id when SENTRY_AUTH_TOKEN is present (published Docker build only), then
delete the maps so they never ship. No-op for dev and the source archive.
- Set the Sentry release from SENTRY_RELEASE / VITE_SENTRY_RELEASE (the Docker
build passes the release version), falling back to APP_VERSION.
- Relax beforeSend so app bundle frames keep a host-stripped path (Sentry needs
it to match the uploaded map) while the instance hostname, error message, and
PII stay stripped. Filesystem paths still collapse to the basename.
- Wire the Dockerfile (sentry_auth_token build secret + SENTRY_RELEASE arg/env)
and the release docker job.
* fix(analytics): point source map upload at the snapotter org (project node)
* fix(analytics): bake real Sentry DSN and lower trace sampling
The bake script emitted a placeholder Sentry DSN even in on mode, so every
published image initialized Sentry against a dead endpoint and no events ever
reached the project. Point it at the real snapotter project DSN.
Also drop tracesSampleRate from 1 to 0.1. It governs only performance
transactions (errors are always captured), so 100% fleet-wide tracing would
drain Sentry quota for no benefit.
* fix(analytics): point baked Sentry DSN at the snapotter org
* refactor(analytics): inject Sentry DSN + PostHog key from build env
#336 replaced the analytics creds with placeholders but never added a way to
put real values back at build time, so any image built from the repo since then
ships dead analytics (the live fleet only still reports because publishing is
paused and it runs a pre-placeholder image).
Restore the pipeline the clean way: bake-analytics.mjs reads SNAPOTTER_SENTRY_DSN
and SNAPOTTER_POSTHOG_KEY from the environment; the official image's CI supplies
them from repo secrets via build args. A build with neither stays disabled, so
building from source never phones home. Both values are public (they ship in the
browser bundle), so this is about not making source builds report, not secrecy.
Supersedes the hardcoded DSN: real creds are no longer committed to the repo.
The Release workflow failed at startup ("workflow file issue", no jobs
created) whenever dispatched. Root cause: release.yml sets top-level
`permissions: {}`, so the ai-bundles reusable-workflow call inherited an
empty scope set. ai-bundles.yml's jobs declare `contents: read` /
`packages: read`, and GitHub rejects a called workflow requesting scopes
the caller never granted -- validated at startup, before any job runs, so
the whole run fails immediately regardless of the job's `if:`.
Grant the two scopes on the call job. Confirmed on a throwaway branch: a
neutralized dispatch went from startup_failure to a clean run with
ai-bundles correctly skipped.
This unblocks every real release; main's release.yml could not start.
The Trivy scan finds HIGH CVEs in pnpm's own transitive dependencies
(glob, minimatch, tar, picomatch) which are build-time only and not
in the runtime image. These block manifest creation unnecessarily.
Scan results still upload to GitHub Security tab via SARIF.
- Add v1.17.2 entry to docs/changelog.md
- Add "Update docs changelog" step to release workflow that
auto-prepends .release-notes.md to the docs changelog on
each release, then commits and pushes to trigger docs deploy
Deleting .github breaks the Post Run cleanup for composite actions,
causing the entire job to fail. Use tar --exclude instead so the
working directory stays intact for GitHub Actions cleanup.
The archive step removes .git before creating the tarball, but
gh release upload needs git context to resolve the repo. Pass
--repo explicitly to avoid "not a git repository" errors.
Generate .sha256 checksum files alongside each prebuilt tarball for
download integrity verification. Stop stripping the LICENSE file from
archives to comply with AGPL-3.0 distribution requirements.
Add pre-built release archives (Linux amd64/arm64) to the release
workflow, published as GitHub Release assets. Each archive is a
self-contained tar.gz (~240MB) with built frontend, API source,
and production node_modules. Users extract and run without needing
pnpm build.
Also includes AI install manifest fixes for Proxmox/bare-metal users:
- Pin setuptools<75 for Python 3.13 basicsr compatibility
- Pre-install basicsr with --no-build-isolation before realesrgan
- Loosen mediapipe pins from == to >= for Python 3.13 wheels
- Add retry logic to HuggingFace model downloads
Token-Permissions (0 -> 10): Set permissions: {} at workflow top level
across all 7 workflows, moved write scopes to per-job minimum.
SAST (0 -> 10): Added CodeQL workflow for JavaScript/TypeScript and
Python analysis on push, PR, and weekly schedule.
Vulnerabilities (0 -> ~8): Added 13 pnpm overrides to patch transitive
dependency vulnerabilities (38 -> 2 remaining, both in dev-only tools).
Pinned-Dependencies (5 -> 8-9): Pinned all Docker FROM images to SHA
digests, pinned pip-audit version in CI, pinned pip version in
Dockerfile.
Auth: login rate limit 30/min (was 500), global rate limit 1000/min (was
unlimited), password/username max lengths on all Zod schemas, session
invalidation on role change, API key legacy scan bounded to 100 keys.
SVG: hardened regex sanitizer with CDATA stripping, XML entity decoding,
set/animate/iframe/embed blocking, comprehensive data: URI blocking,
use element external href blocking. 11 attack payload fixtures added.
SSRF: fixed DNS rebinding TOCTOU by pinning resolved IPs via custom
HTTP/HTTPS agents. Added 6to4 and NAT64 to blocked IPv6 ranges.
Docker: capability dropping (cap_drop ALL + minimal cap_add), resource
limits (4g/8g mem, 512/1024 pids), healthcheck timeout, password
removed from startup banner, default password warning comments.
Network: CSP and HSTS applied in all environments (not just production),
stack traces removed from all error responses, internal paths stripped
from error details, per-route rate limits on uploads (60/min) and URL
fetches (200/hour).
Files: exclusive temp file creation (O_EXCL), disk space circuit
breaker, per-user storage quotas, settings payload 64KB size guard.
Python sidecar: script name allowlist in dispatcher, minimal environment
for subprocess spawns.
Dependencies: fixed 6 production CVEs (drizzle-orm, fastify, fast-uri,
@fastify/static, next, archiver/lodash). Pinned all GitHub Actions to
SHA hashes.
114 security tests added. Full OWASP Top 10 penetration test matrix
verified against production Docker container (30/30 pass after
hardening).
Scans the amd64 Docker image for CRITICAL/HIGH CVEs with available fixes
before publishing multi-arch manifests. Results upload to GitHub Security tab
as SARIF. Blocks release if fixable vulnerabilities are found.
Add .vscode/ with Biome formatter, Tailwind, Vitest, Playwright, and
Python debug configs. Extract shared pnpm/Node setup into a composite
GitHub Action and add Dependabot and dependency-review workflows.
- Add Cloudflare Pages deployment for landing page (snapotter.com) and
docs (docs.snapotter.com)
- Create deploy-landing.yml and update deploy-docs.yml workflows
- Update CI to ignore apps/landing/** paths
- Fix logo transparency (remove white background) across all apps
- Recreate social-preview.png with SnapOtter branding
- Update all docs URLs from GitHub Pages to docs.snapotter.com
- Update VitePress config: light theme default, fix llms.txt paths
- Add .vitepress/cache/ and .env.* to gitignore
- Parallelize all 14 model downloads using ThreadPoolExecutor (6 workers)
Downloads were sequential (~30 min), now concurrent (~5-10 min)
- Switch Docker cache from type=gha to type=registry (GHCR)
GHA cache has 10 GB limit causing blob eviction and corrupted builds
Registry cache has no size limit and persists across runner instances
- Add pip download cache mounts to all pip install layers
Prevents re-downloading packages when layers rebuild
Allows re-triggering the release workflow after a Docker push failure
without needing new commits. If semantic-release produces no new version
the workflow now uses the latest existing git tag for the Docker build.
- Remove all lite/full variant logic from frontend, API, shared constants,
docs, and tests (single unified Docker image only)
- Replace single QEMU multi-arch Docker build with per-architecture native
builds (amd64 + arm64) and manifest merge to fix disk space exhaustion
- Add disk cleanup step and per-platform build cache scopes
- Switch release trigger from push to workflow_dispatch
- Add GitHub issue templates and PR template
Merge CPU, CUDA, and lite Docker images into a single unified image.
One tag (latest) works on all platforms: amd64 (NVIDIA CUDA) and arm64 (CPU).
GPU auto-detected at runtime. All ML models and packages baked in.
Key changes:
- Platform-conditional Dockerfile (nvidia/cuda on amd64, node on arm64)
- tini as PID 1 for proper signal handling
- Fix FILES_STORAGE_PATH data loss bug
- Fix RealESRGAN upscaler (was broken, always fell back to Lanczos)
- Fix PaddleOCR language codes and stdout corruption
- Simplified CI/CD (single build, single tag)
- Expanded model pre-download with verification
- Shutdown timeout, improved health endpoint
- Remove unused lama-cleaner
Add a :cuda Docker image tag that auto-detects NVIDIA GPU at runtime
and falls back gracefully to CPU. Same pattern as Immich.
- New gpu.py shared utility for cached CUDA detection
- Background removal (rembg): pass CUDAExecutionProvider to ONNX Runtime
- Upscaling (Real-ESRGAN): use CUDA device + FP16 when GPU available
- OCR (PaddleOCR): enable use_gpu when CUDA detected
- Dispatcher reports GPU status at startup via readiness signal
- Admin health endpoint exposes GPU availability
- Dockerfile uses ARG GPU=false with conditional NVIDIA CUDA base image
- docker-compose.gpu.yml override for GPU users
- CI/CD workflows build and publish :cuda tag (amd64 only)
Three tags: :latest (CPU), :lite (no AI), :cuda (GPU with CPU fallback)
Update all references across docs, workflows, UI components, and config
to point to the new GitHub org (stirling-image/stirling-image) and Docker
Hub account (stirlingimage/stirling-image) ahead of repo transfer.
- Set up semantic-release with zero-touch CI pipeline on push to main
- Add version sync script to keep all package.json files and APP_VERSION
constant in sync automatically
- Consolidate Docker publishing into single tag-triggered workflow that
pushes to both Docker Hub and ghcr.io with semver tags
- Add help dialog with keyboard shortcuts, getting started guide, and
resource links
- Sync all versions to 0.2.1 to match Docker Hub latest