Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
afc47cc
docs: STATE.md hand-off snapshot
SloThdk May 6, 2026
51e078b
fix(deploy): ARM-aware Dockerfiles + 3 stragglers + in-container builds
SloThdk May 7, 2026
14a5e8f
fix(docker): pin pnpm 9.12.3 explicitly to dodge corepack key-verify bug
SloThdk May 7, 2026
cb40e89
fix(docker/web): same corepack pin fix that landed in api-gateway
SloThdk May 7, 2026
213272a
fix(docker): web pnpm re-install + explicit TARGETARCH passing
SloThdk May 7, 2026
ddf0617
fix(ci): float Node to 20.x, prettier-format repo, fix Docker build c…
SloThdk May 7, 2026
15fa608
fix(deploy): drop secrets.* in if + flatten multiline if expression
SloThdk May 7, 2026
c465907
fix(ci): add api-gateway smoke test + format 7 missed files
SloThdk May 7, 2026
6369a2c
fix(ingest): convert libpq URL to Npgsql keyword form
SloThdk May 7, 2026
55670c3
fix(db): bootstrap Supabase-style roles before 0001 RLS policies
SloThdk May 7, 2026
79b4403
fix(db): grant trigger-only functions to service_role, not postgres
SloThdk May 7, 2026
a421e29
docs(state): live deployment hand-off - all 13 services healthy
SloThdk May 7, 2026
6d19701
feat(security): real HTTPS + nonce CSP + brand assets
SloThdk May 7, 2026
93bbbe0
fix(web): force dynamic rendering so nonce reaches emitted scripts
SloThdk May 7, 2026
c0ff690
feat(prod): enterprise hardening overlay + nightly pg backups
SloThdk May 7, 2026
5e0b9bc
fix(prod): valkey caps + alpine-portable pg-backup scheduler
SloThdk May 7, 2026
3ab7ce0
feat(observability): Prometheus alerts + Grafana dashboard
SloThdk May 7, 2026
5d7be89
ci(deploy): rebuild locally + prune cache + default smoke domain
SloThdk May 7, 2026
1d51e9d
fix(api-gateway): wire INGEST_PUBLIC_URL through compose env
SloThdk May 7, 2026
38bddbb
docs(state): final hand-off - everything live on slothbox.philipsloth…
SloThdk May 7, 2026
8c71b7f
fix(security): patch System.Text.Json + bump Go to 1.23 for stdlib CVEs
SloThdk May 7, 2026
6334693
Bump Npgsql from 8.0.5 to 10.0.2
dependabot[bot] May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: "20.18"
# "20" pulls the latest 20.x. We were pinned to 20.18.3 which
# tripped eslint-visitor-keys@5.0.1's engines requirement
# (^20.19.0 || ^22.13.0 || >=24). Floating to 20.x latest keeps
# us on Node 20 LTS while satisfying transitive deps.
node-version: "20"
cache: "pnpm"

- name: Install
Expand Down Expand Up @@ -104,7 +108,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"
cache: true
cache-dependency-path: ${{ matrix.module }}/go.sum

Expand Down Expand Up @@ -134,7 +138,9 @@ jobs:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: "20.18"
# See comment in `node` job — float to 20.x latest for engines
# compat with transitive deps (eslint-visitor-keys@5.0.1+).
node-version: "20"
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm format:check
80 changes: 70 additions & 10 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
name: Deploy

# Two jobs:
# 1. build — multi-arch Docker build for all 5 services, push to GHCR.
# Runs on every master push and every v* tag.
# 2. deploy — SSH to the production Hetzner box and `docker compose pull` +
# `up -d`. Gated behind the repo variable AUTO_DEPLOY=true so it
# only fires when secrets are configured. Manual runs always
# attempt deploy.
#
# Build platforms intentionally include linux/arm64 because the production
# host is an ARM cax11. linux/amd64 is kept so contributors can `docker pull`
# on a normal x86 dev box.

on:
push:
branches: [master]
Expand Down Expand Up @@ -27,19 +39,28 @@ jobs:
strategy:
fail-fast: false
matrix:
# `context` is the build context Docker uploads. `file` is the path to
# the Dockerfile relative to that context. The two TS workspaces use
# the monorepo root as context (their Dockerfiles reference the root
# package.json + pnpm-lock.yaml + workspace packages); the .NET / Go
# services are self-contained so their context can be the service
# directory itself.
include:
- image: web
context: ./apps/web
context: .
file: ./apps/web/Dockerfile
- image: api-gateway
context: ./apps/api-gateway
context: .
file: ./apps/api-gateway/Dockerfile
- image: ingest
context: ./services/ingest
file: ./services/ingest/Dockerfile
- image: receipt
context: ./services/receipt
file: ./services/receipt/Dockerfile
- image: reaper
context: ./services/reaper
outputs:
image-tag: ${{ steps.meta.outputs.version }}
file: ./services/reaper/Dockerfile
steps:
- uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -70,8 +91,12 @@ jobs:
uses: docker/build-push-action@v6
with:
context: ${{ matrix.context }}
file: ${{ matrix.file }}
push: true
platforms: linux/amd64
# Multi-arch. The Hetzner production host is ARM; contributors
# tend to dev on AMD64. Both platforms get pushed under the same
# tag and Docker pulls the right one based on the host's arch.
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
Expand All @@ -82,7 +107,14 @@ jobs:
needs: build
runs-on: ubuntu-24.04
environment: production
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')
# Only attempt SSH deploy when AUTO_DEPLOY is enabled in repo variables
# OR the workflow was triggered manually. Without this gate the job
# would fail on every push for any fork that hasn't configured the
# 3 production secrets (HETZNER_HOST / HETZNER_USER / HETZNER_SSH_KEY).
# Note: GitHub Actions does not allow `secrets.*` in `if:` clauses, so
# gating on a repo *variable* (vars.AUTO_DEPLOY) is the canonical
# workaround. The single-line form avoids YAML block-scalar quirks.
if: github.event_name == 'workflow_dispatch' || (vars.AUTO_DEPLOY == 'true' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -94,20 +126,48 @@ jobs:
host: ${{ secrets.HETZNER_HOST }}
username: ${{ secrets.HETZNER_USER }}
key: ${{ secrets.HETZNER_SSH_KEY }}
# Real install path is /home/slothbox/slothbox (per the slothbox
# user's homedir).
#
# Deploy strategy (current):
# git pull → rebuild changed images locally on the box → up -d
#
# We rebuild locally instead of `docker compose pull` because the
# production overlay leaves `image:` lines commented out — the
# source-of-truth images are still being built from the local
# context (until we flip to GHCR-pinned images). `--build` on
# `up -d` rebuilds any service whose Dockerfile or build context
# changed since the last run; unchanged services are no-ops.
# `--remove-orphans` cleans containers from removed services.
script: |
cd /opt/slothbox
cd /home/slothbox/slothbox
git pull --ff-only origin master
export IMAGE_TAG=${{ github.sha }}
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml build
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --remove-orphans
docker compose -f docker-compose.yml -f docker-compose.prod.yml ps
# Prune build cache + dangling images so /var/lib/docker doesn't
# grow unbounded on a 40 GB volume. -f skips the y/N prompt.
docker image prune -f
docker builder prune -f --keep-storage 2GB

- name: Smoke test
# Hits the production domain (defaults to slothbox.philipsloth.com)
# to verify the new revision is actually serving HTTP 200.
# PRODUCTION_DOMAIN can be overridden as a repo secret if we ever
# move to a different domain.
env:
PROD_DOMAIN: ${{ secrets.PRODUCTION_DOMAIN }}
run: |
DOMAIN="${PROD_DOMAIN:-slothbox.philipsloth.com}"
echo "Smoke-testing https://$DOMAIN"
for i in {1..30}; do
if curl -fsS https://${{ secrets.PRODUCTION_DOMAIN }}/healthz; then
echo "deployment healthy"
CODE=$(curl -fsS -o /dev/null -w "%{http_code}" "https://$DOMAIN/healthz" || echo "000")
if [ "$CODE" = "200" ]; then
echo "deployment healthy (got HTTP $CODE on attempt $i)"
exit 0
fi
echo "attempt $i: got HTTP $CODE, retrying in 2s"
sleep 2
done
echo "deployment did not become healthy in time"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"

- name: Build
working-directory: tools/verify
Expand Down
21 changes: 15 additions & 6 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
pull_request:
branches: [master]
schedule:
- cron: "23 6 * * 1" # weekly Monday 06:23 UTC
- cron: "23 6 * * 1" # weekly Monday 06:23 UTC

permissions:
contents: read
Expand Down Expand Up @@ -45,7 +45,9 @@ jobs:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: "20.18"
# See ci.yml — float to 20.x latest for engines compat with
# transitive deps that require >=20.19 (eslint-visitor-keys, etc.).
node-version: "20"
cache: "pnpm"
- run: pnpm install --frozen-lockfile --ignore-scripts
- name: Audit (high+)
Expand Down Expand Up @@ -88,7 +90,7 @@ jobs:
persist-credentials: false
- uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
- working-directory: ${{ matrix.module }}
run: govulncheck ./...
Expand Down Expand Up @@ -121,24 +123,31 @@ jobs:
strategy:
fail-fast: false
matrix:
# web + api-gateway use monorepo root as build context; .NET / Go
# services are self-contained. See deploy.yml for the same matrix.
include:
- image: web
context: ./apps/web
context: .
file: ./apps/web/Dockerfile
- image: api-gateway
context: ./apps/api-gateway
context: .
file: ./apps/api-gateway/Dockerfile
- image: ingest
context: ./services/ingest
file: ./services/ingest/Dockerfile
- image: receipt
context: ./services/receipt
file: ./services/receipt/Dockerfile
- image: reaper
context: ./services/reaper
file: ./services/reaper/Dockerfile
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Build image
run: docker build -t slothbox-${{ matrix.image }}:scan ${{ matrix.context }}
run: docker build -t slothbox-${{ matrix.image }}:scan -f ${{ matrix.file }} ${{ matrix.context }}
continue-on-error: true # some images may not build until services are fleshed out

- name: Scan
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]

### Planned for v0.5.0

- Lucia v3 / better-auth + Argon2id + magic-link primary
- Account dashboard with share history, manual revoke
- RFC 3161 timestamp receipt issuance
Expand All @@ -16,13 +17,15 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Grafana dashboards published

### Planned for v1.0.0

- Per-recipient asymmetric encryption via `age`
- Verifiable deletion proofs anchored to a public Merkle root
- Standalone offline verifier CLI (full feature)
- External cryptographer review and audit report under `/audits/`
- Public bug bounty program

### Planned for v1.1.0

- WebRTC P2P file transfer
- MitID OIDC for verified senders
- Time-locked / deadman's-switch shares
Expand All @@ -31,6 +34,7 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.0-alpha.1] — 2026-05-07

### Added

- Initial scaffold of the v0.1.0-alpha public repository
- Monorepo with pnpm workspaces:
- `apps/web` — Next.js 15 frontend
Expand Down Expand Up @@ -66,6 +70,7 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- MIT license

### Security

- Server-side cannot decrypt files — encryption key lives in URL fragment
- Audited cryptographic primitives only (no roll-your-own)
- Branch protection requires signed commits + CODEOWNERS review
Expand All @@ -74,6 +79,7 @@ the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Encrypted backups via `age`

### Known limitations (tracked for v0.5 / v1.0)

- No accounts / dashboard yet (anonymous shares only)
- RFC 3161 receipts return 501 Not Implemented
- Per-recipient encryption not yet implemented
Expand Down
15 changes: 8 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ is held to a higher bar.

### Soft rules

- Document the *why* in the PR description, not just the *what*. Cryptographic
- Document the _why_ in the PR description, not just the _what_. Cryptographic
changes need their threat-model justification spelled out.
- Reference the audited reference implementation you're following.
- If the change affects the threat model, update [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md)
Expand Down Expand Up @@ -119,14 +119,15 @@ Pre-commit hook runs the formatters. CI fails on diff.

## Filing issues

| Type | Use |
|---|---|
| Bug | Issue template `bug` |
| Feature request | Issue template `feature` |
| Type | Use |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| Bug | Issue template `bug` |
| Feature request | Issue template `feature` |
| Security vulnerability | **DO NOT** file an issue. Email security@philipsloth.com — see [`SECURITY.md`](SECURITY.md) |
| Question | GitHub Discussions (when enabled) |
| Question | GitHub Discussions (when enabled) |

When filing a bug, please include:

- SlothBox version (commit SHA or tag)
- Browser + OS (for frontend) or Docker version (for backend)
- Steps to reproduce
Expand Down Expand Up @@ -155,4 +156,4 @@ agreement.

Anyone whose PR is merged, or whose vulnerability report leads to a fix, gets
listed in `CONTRIBUTORS.md` (with consent). For security reports, the standard
"Reported by ___" credit is in the release notes.
"Reported by \_\_\_" credit is in the release notes.
Loading
Loading