Files
SnapOtter/.github/workflows/ci.yml
T
SnapOtter 4e64ee2779 fix(security): comprehensive security audit and hardening
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).
2026-05-13 21:33:50 +08:00

118 lines
3.7 KiB
YAML

name: CI
on:
workflow_dispatch:
push:
branches: [main]
paths-ignore:
- "README.md"
- "CONTRIBUTING.md"
- "branding/**"
- "apps/docs/**"
- "apps/landing/**"
- "docs/**"
pull_request:
branches: [main]
paths-ignore:
- "README.md"
- "CONTRIBUTING.md"
- "branding/**"
- "apps/docs/**"
- "apps/landing/**"
- "docs/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: ./.github/actions/setup
- run: pnpm lint
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: ./.github/actions/setup
- run: pnpm typecheck
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install system dependencies (HEIC + ExifTool + ImageMagick + exotic format tools)
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends libheif-examples libheif-plugin-x265 libheif-plugin-libde265 libimage-exiftool-perl imagemagick ghostscript libjxl-tools libopenjp2-tools
- name: Allow ImageMagick to read EPS/PS via Ghostscript delegate
run: |
POLICY_FILE=$(find /etc/ImageMagick* -name policy.xml 2>/dev/null | head -1)
if [ -n "$POLICY_FILE" ]; then
sudo sed -i 's/<policy domain="coder" rights="none" pattern="EPS"/<policy domain="coder" rights="read" pattern="EPS"/' "$POLICY_FILE"
sudo sed -i 's/<policy domain="coder" rights="none" pattern="PS"/<policy domain="coder" rights="read" pattern="PS"/' "$POLICY_FILE"
fi
- uses: ./.github/actions/setup
- run: pnpm test:ci
pip-audit:
name: Python Dependency Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
- run: pip install pip-audit
- name: Run pip-audit (ignoring CVEs blocked by dependency constraints)
run: >-
pip-audit -r packages/ai/python/requirements.txt
--ignore-vuln CVE-2024-27763
--ignore-vuln CVE-2026-40086
--ignore-vuln CVE-2026-25990
--ignore-vuln CVE-2026-40192
--ignore-vuln GHSA-55v6-g8pm-pw4c
--ignore-vuln CVE-2026-42308
--ignore-vuln CVE-2026-42310
--ignore-vuln CVE-2026-42311
build:
name: Build
runs-on: ubuntu-latest
needs: [lint, typecheck, test]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: ./.github/actions/setup
- run: pnpm build
# docker:
# name: Docker Build Test
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: docker/setup-buildx-action@v3
# - name: Log in to GHCR (for registry cache)
# uses: docker/login-action@v3
# with:
# registry: ghcr.io
# username: ${{ github.repository_owner }}
# password: ${{ secrets.GHCR_TOKEN }}
# - uses: docker/build-push-action@v6
# with:
# context: .
# file: docker/Dockerfile
# push: false
# tags: snapotter:ci
# build-args: SKIP_MODEL_DOWNLOADS=true
# cache-from: type=registry,ref=ghcr.io/snapotter-hq/snapotter:cache-linux-amd64
# cache-to: type=registry,ref=ghcr.io/snapotter-hq/snapotter:cache-ci,mode=max