v1.2.0 — visibility, two-tier auth, DoS hardening
Highlights
Per-artifact visibility
Each artifact is now public (default, unchanged), private (operator-only), or
password-protected (a shared password you hand out). Gated on every serve path
(/a/:slug, ?raw=1, /source, zip sub-assets) — sub-resources return 404 with no
existence leak. A correct password sets an HttpOnly, slug-scoped, 7-day signed cookie so
shared links aren't re-prompted. Set it from the dashboard, the CLI (artifacts visibility),
or MCP (set_artifact_visibility). Existing artifacts stay public with no migration.
Two-tier auth
The dashboard now has a proper admin login (username + password, scrypt-hashed, signed
HttpOnly session cookie) separate from scoped, revocable API keys (read / publish /
full, optional expiry) for CLI and MCP. The bootstrap ARTIFACTS_API_KEY still works as an
all-scope break-glass bearer. Seed the admin from ARTIFACTS_ADMIN_USERNAME /
ARTIFACTS_ADMIN_PASSWORD or via the first-run setup screen.
Abuse resistance (DoS + rate limiting)
- Password hashing moved off the event loop.
scryptran synchronously on two
unauthenticated routes (login, unlock); a handful of concurrent requests stalled the whole
process. It now runs on the libuv threadpool behind a concurrency semaphore, so a flood
degrades those two routes instead of the server. - Rate limiting on login (10 failures / 15 min per client IP) and unlock (10 / hour per
IP + slug),429withRetry-After. Failures only — a correct password never counts. TRUST_PROXY(none|cloudflare|xff) resolves the real client IP behind a
tunnel or proxy, with IPv6 /64 bucketing. Pair with a CDN/edge limiter for volumetric
protection — see the deploy guide.- Structured auth-failure logging — one JSON line per failed or rate-limited attempt.
Fixes
- Postgres
move/deleteSlugrange under locale-aware collation. - Key/session expiry boundary now matches artifact expiry (
<=).
Upgrade notes
Backward compatible. No migration: artifacts with no visibility field remain public, and
the bootstrap key keeps working. If you deploy behind Cloudflare or a reverse proxy, set
TRUST_PROXY so rate limits key on the real client IP (see
deploying).
Full changelog: v1.1.0...v1.2.0