Thanks for helping keep this project and its users safe.
Please do not open a public GitHub issue for security reports.
Instead, use GitHub's private vulnerability reporting: go to the repository's Security tab and click "Report a vulnerability". This opens a private advisory that only the maintainers can see.
Include:
- A clear description of the issue and impact
- Steps to reproduce (or a proof-of-concept)
- Affected versions / commit SHA if known
- Any suggested fix/mitigation (optional)
This is a small, fast-moving project. Security fixes are provided on a best effort basis for the latest version on the default branch.
We aim to acknowledge reports within 7 days, but cannot guarantee a specific SLA.
Deckyard is designed to run self-hosted behind a reverse proxy (e.g. Caddy),
with authentication enabled. In that setup the defaults are safe. The
following controls are built in; the env vars are documented in .env.example.
- Authentication fails closed. If
AUTH_SECRETis missing the server refuses to start, rather than falling back to anonymous admin access. To run intentionally without auth, setAUTH_ENABLED=false(sandbox/demo modes also boot without auth). See the breaking-change note below. - Dev auth bypass is gated on
NODE_ENV.AUTH_DEV_BYPASS=trueonly takes effect whenNODE_ENV=development; a leftover flag in staging or production (or withNODE_ENVunset) can never grant passwordless admin. - Login brute-force throttle. Password logins are rate-limited per IP and
per email. Behind a proxy, set
TRUST_PROXY=trueso the throttle keys on the real client IP instead of the proxy address. - CSRF protection. Cookie-authenticated state-changing requests must be
same-origin (the
Origin/Refererhost must match the appHost,APP_URLorDOMAIN). Token / API-key clients are unaffected. Add trusted partner origins withCSRF_ALLOWED_ORIGINS. - SSRF guard on export/render. Remote images pulled into server-side PDF/PNG rendering are resolved and blocked if they point at loopback, private, link-local or cloud-metadata addresses (IPv4 and IPv6, including IPv4-mapped/compatible forms), so a slide cannot make the server fetch internal endpoints.
- Uploaded SVGs are served inert. User-uploaded SVGs are served with
Content-Disposition: attachment,nosniffand a script-blocking CSP, so a malicious SVG cannot run scripts in the app's origin. - Request-body size cap. JSON bodies are bounded (
MAX_REQUEST_BODY_BYTES, default 25 MB) to prevent memory exhaustion from oversized requests. - Non-root container + optional Chromium sandbox. The Docker image runs the
app (and headless Chromium) as a non-root user, so a renderer compromise is
contained without root. Chromium's own sandbox is off by default because the
namespace sandbox needs syscalls Docker's default seccomp profile blocks; if
you harden the runtime, set
PUPPETEER_SANDBOX=trueto re-enable it. - Media keys are confined to the uploads directory, preventing path traversal out of the storage root.
Deployments that previously ran without AUTH_SECRET relied on the old
fail-open behavior (anonymous admin). After upgrading, such a deployment will
refuse to start until you either set AUTH_SECRET (recommended) or explicitly
set AUTH_ENABLED=false to keep running without auth.
Because the container now runs as a non-root user (uid 1000), bind-mounted
server/data and server/uploads directories created by an older, root-running
image may be owned by root and cause EACCES on first write. Fix once with:
chown -R 1000:1000 ./server/data ./server/uploads