Security¶
What Punchlist does for you¶
- Passwords are hashed (ASP.NET Core Identity's PBKDF2). Five wrong attempts lock an account for 15 minutes; each address gets ten attempts a minute.
- Sessions are HttpOnly, SameSite cookies, signed with keys kept in the database. Changing a password, turning an account off or deleting it ends its sessions at once.
- Single sign-on always uses PKCE, checks required groups at every sign-in, and never takes over
an existing account by username alone. With
LOCAL_LOGIN=disabledno password works at all. - Sign-in settings (
OIDC_*,LOCAL_LOGIN,REGISTRATION,TRUSTED_PROXIES,PUBLIC_URL) and whole-server backups are for super admins only, so an admin cannot widen their own access. - Browser protections come with every answer: a strict Content-Security-Policy (only your server's own scripts run), HSTS on HTTPS, no framing by other sites, no content-type guessing and a same-origin referrer policy. See security headers.
- Logs never contain passwords, cookies, tokens, secrets or task content; the first admin password goes to the console only.
- Admin actions are recorded in the event log, including everything done while viewing the app as someone else.
- The image is a chiseled base with no shell or package manager, running as a non-root user.
- Secrets can come from files (
NAME_FILE) or be encrypted.
Should it be on the internet?¶
It can be: Punchlist is built to sit behind HTTPS on a public name. If only you and your household use it, a VPN (WireGuard, Tailscale, NetBird) in front is still the safer choice: nobody else can even reach the sign-in page, and every item below matters less. The phone app and the web app work the same over a VPN.
The rest of this page is for a server people reach over the internet. Each item says whether it is needed and why.
Needed¶
Do these before the server is reachable from the internet.
- [ ] HTTPS in front, and only the proxy reaches Punchlist. Punchlist's own port is plain HTTP:
passwords and session cookies would cross the internet readable. Put a
reverse proxy in front and do not publish port
8080beyond it. With Docker Compose, leaveports:off the Punchlist service. - [ ]
PUBLIC_URLset to thehttps://address. Invite links, one-time sign-in links and single sign-on callbacks are built from it. Without it they are built from whatever host name the request names. - [ ] A real password for
admin, and a normal account for every day. A generated first password must be changed at the first sign-in; one set withADMIN_PASSWORDis kept as it is, so make it long and take it out of the environment after the first start (it is only read then). Keepadmin(a super admin) for administration and use a plain account for your tasks, so a stolen everyday session cannot change who may sign in. - [ ]
REGISTRATIONatinvite(the default) orclosed.openlets anyone on the internet make an account, and every account can fill the disk with attachments. If it must be open, one address makes at mostSIGNUP_RATE_LIMITaccounts an hour (5 by default); a public trial belongs on a demo server. - [ ] Never
AUTH_MODE=noneon the internet. It turns sign-in off: everyone who reaches the server is its owner. It is only for a proxy that signs people in itself, on a network where everyone may see and change everything. - [ ] Keep up to date. Only the latest release gets fixes. Watch the releases and read Upgrading.
Recommended¶
Not needed for Punchlist to work, but each one closes a real gap for little effort.
- [ ]
TRUSTED_PROXIESset to the proxy alone. The default believes forwarded headers from every private address, so anything on your LAN, VPN or Docker network that can reach port 8080 directly can claim to be any address: in the access log, and to get around the per-address sign-in limit. Not needed when nothing but the proxy can reach Punchlist, as in the Compose example with Caddy. - [ ] With single sign-on,
LOCAL_LOGIN=disabled. Then nobody can guess a password, through the app or straight at the API: there is none to guess. Password sessions end at once; provider sessions go on. Keep a way back in for when the provider is down:punchlist admin login-link adminprints a one-time sign-in link. Not needed when you use passwords only; the attempt limits and lockout above protect long passwords well. - [ ] Single sign-on settings. Use
OIDC_<NAME>_REQUIRED_GROUPSso only the provider's Punchlist group gets in. LeaveAUTO_CREATEoff unless everyone the provider knows may have an account. Turn onLINK_BY_EMAILonly if the provider verifies emails, andLINK_BY_USERNAMEonly while moving people over, and only if people cannot rename themselves at the provider: either would let someone sign into an account that is not theirs. See Single sign-on. - [ ] Attachments limit.
ATTACHMENT_MAX_MB(25 MB by default) caps each file, but there is no total per person yet. Watch the free space of the/filesvolume, or lower the limit, when you do not know everyone with an account. - [ ] Backups are as sensitive as the server. Snapshots and exports hold every password hash and the keys that sign sessions: anyone with a copy could sign in as anyone. Keep them as private as the database, and encrypt off-site copies.
- [ ] Secrets in the environment or in files, not on the settings page, when backups leave the
machine. Secrets saved on the settings page are encrypted with a key kept in the same
database, so a backup holds both. An OIDC client secret given as
NAME_FILEor as an encrypted value is not in the database at all.
Optional¶
- A VPN or a sign-in proxy in front (Pangolin, Authelia, Authentik): strangers never reach
Punchlist's own sign-in. Keep
/api/*and/auth/*reachable for the app, see Others. OIDC_<NAME>_SESSION_HOURSshorter than the default 12 hours, so leaving the provider's Punchlist group takes effect sooner on open sessions.- Logs shipped off the machine (Logs and audit), so they survive the server.
Not needed¶
- Security headers or compression at the proxy. Punchlist sends both itself; adding them
again does no harm (but a
Content-Security-Policyat the proxy must not replace Punchlist's, see security headers). - A web application firewall, fail2ban or Cloudflare's proxy for Punchlist's sake. Sign-in attempts are already limited per address and per account. Cloudflare's proxy can also delay live updates.
- Another port than 8080, or hiding the
Serverheader. Neither makes anything harder to attack; the proxy is what is reachable anyway. X-XSS-ProtectionorExpect-CT. Obsolete headers that current browsers ignore.- HSTS
preload. It puts your domain on a list built into browsers that is slow to get off. PlainStrict-Transport-Securitygives the protection that matters here.
Reporting a vulnerability¶
Please do not open a public issue. See SECURITY.md for how to report privately and what to expect.