Skip to content

Security: srkn0/synclet

Security

docs/security.md

Security

This document describes the boilerplate's controls and the deployment responsibilities it cannot solve generically.

Secrets and configuration

  • No real credentials are committed. .env and variant files are ignored; .env.example contains development-only placeholders.
  • With DJANGO_DEBUG=false, Django refuses to start with the built-in development secret.
  • Production secrets must come from a managed secret store or runtime environment, not an image, Compose file, frontend variable, or CI log.
  • Allowed hosts, CORS origins, and CSRF trusted origins are explicit lists. Do not use wildcard origins with credentialed requests.

Browser authentication, CSRF, and CORS

  • Django session authentication is used because the production SPA and API are same-origin.
  • The session cookie is HTTP-only and SameSite=Lax. Secure defaults on when debug is disabled; local HTTP explicitly disables it.
  • JavaScript obtains a CSRF token through /api/v1/auth/csrf/; every unsafe client operation sends X-CSRFToken. Registration and login are explicitly CSRF-protected as well.
  • Production should terminate TLS at a trusted proxy, preserve X-Forwarded-Proto, enable SSL redirect, secure cookies, and HSTS only after the domain is fully HTTPS-ready.
  • The Nginx image adds nosniff, frame, referrer, and content-security headers. Its CSP permits only the explicitly configured Google Fonts stylesheet/font hosts in addition to same-origin assets. Google Fonts causes clients to contact a third party; self-host the font files when deployment privacy requirements prohibit that request.

Authorization and tenant isolation

  • Contact and sync viewsets always begin with filter(user=request.user); an object owned by another user resolves as 404.
  • Creation never accepts an owner from client input.
  • The synchronization service receives the authenticated user explicitly and matches contacts only inside that owner.
  • Database uniqueness is scoped to (user, lower(email)), so one user's data neither conflicts with nor reveals another user's contact.
  • Any future provider or bulk operation must preserve both queryset- and service-level ownership checks. Tests should attempt cross-user list, retrieve, mutate, and import behavior.

Input and upload handling

  • DRF serializers validate all API input; passwords use Django's configured validators.
  • Contact email addresses are Unicode-normalized, support SMTPUTF8/EAI local parts, and are constrained case-insensitively in PostgreSQL. A future mail sender must itself support SMTPUTF8; this application only stores contact addresses.
  • CSV uploads require an approved MIME type and .csv extension, are limited to 5 MiB and 10,000 rows, reject null bytes and non-UTF-8 content, and cap individual field lengths.
  • CSV values are parsed with Python's CSV library and written through Django ORM; there is no SQL string concatenation.
  • Spreadsheet-formula content is stored as text and never evaluated. A future CSV export must escape formula prefixes to prevent spreadsheet injection.

Transactions and errors

  • A file with any invalid data row creates no contacts. Database failures roll back the entire contact write set.
  • Failed synchronization metadata is persisted after rollback without exposing exception text.
  • API errors use {error: {code, message, fields?, details?}}. Unexpected exceptions return a fixed generic message.
  • Logs are valid JSON lines with timestamp, severity, logger, request ID, route, status, duration, and authenticated user ID. Code intentionally avoids logging passwords, uploaded rows, session/CSRF values, database URLs, query strings, or request bodies.
  • /metrics/technical and /metrics/business contain aggregate data without user or email labels. Expose them only to a trusted Prometheus network or protect them at the ingress; application-level authentication is intentionally omitted so standard scrapers can use them.

Containers and CI

  • API and web runtime containers use non-root users. Dependency installation comes from lockfiles.
  • pnpm build scripts are allow-listed only for esbuild and vue-demi.
  • Healthchecks cover PostgreSQL, Django plus database connectivity, and Nginx.
  • CI has read-only repository permissions, validates generated contracts, and builds images without pushing them.
  • For a real deployment, pin base images and GitHub Actions to reviewed immutable digests/SHAs, scan images and dependencies, produce an SBOM, sign images, and define a patch cadence.

Production checklist

  • Set a high-entropy secret, DEBUG=false, exact hosts/origins, secure cookies, TLS redirect, and appropriate HSTS.
  • Use a least-privilege PostgreSQL role, encrypted connections where required, automated backups, restore tests, connection limits, and separate databases per environment.
  • Configure reverse-proxy request/body/time limits consistently with Django's 5 MiB file policy.
  • Add rate limiting for auth and upload endpoints, email verification, password reset, Prometheus alert rules, an observability backend, audit events, and retention rules before treating this as a complete product.
  • Review privacy, data retention, export, and account-deletion obligations for the deployment region.

There aren't any published security advisories