Security policy
How to report a vulnerability, which versions are supported, and how to verify that a release is genuinely ours.
This is the full policy. The repository keeps a short SECURITY.md because GitLab surfaces that file in its own security tab — it carries the reporting addresses and the signing fingerprint, and points here for the rest.
For how the application defends itself — credentials, sessions, 2FA, end-to-end encryption, SSRF protection — see the Security model.
Reporting a vulnerability
Please do not open a public issue. The Bug template is public the moment you submit it, and so is anything you attach to it.
Report privately, either way:
- Open a confidential issue.This link opens the new-issue form with confidentiality already ticked. Doing it by hand works just as well: start a new issue and tick "This issue is confidential and should only be visible to team members with at least Reporter access" under the description box before submitting. Leave the template dropdown alone — the default is only a comment, so write the report in your own words.
- Email the maintainer (the repository owner's contact is on their GitLab profile), if you would rather not go through the issue tracker at all.
Not the support address
hyper-calendar has a Service Desk inbox for ordinary bug reports and questions. Mail sent there becomes a ticket, and although those tickets are confidential, the address is published and handled as routine support — it is the wrong channel for a vulnerability. Use one of the two routes above instead.
Include a description, reproduction steps, the affected version or commit, and the impact. Say which deployment mode you reproduced on — Docker/self-hosted server, the standalone Android app, or the Android app connected to a server — because the same routes run on very different hosts and a finding may not apply to all three (the note on ssrf.ts in The Android app is an example).
You can expect an initial response within a few days. Please allow reasonable time for a fix before any public disclosure.
Supported versions
Releases are tagged from main — see the merge and release workflow for the dev → main model. Only the latest tagged release is supported: fixes land on main and go out in the next tag, and there are no backport branches for older versions. Please reproduce on a recent version before reporting.
Verifying a release
Release tags are signed with this GPG key:
Lars Kreuz <ticket.grapple368@passmail.net>
ed25519 7616A94E6FEC9EAB682790D0414534E1D89CB2B2The fingerprint is published deliberately, so it can be checked independently of the GitLab UI. A "Verified" badge proves only that GitLab is satisfied; if GitLab itself were compromised that badge could be fabricated, and a fingerprint you obtained elsewhere could not.
To verify a release yourself:
# Import the key from a keyserver, or from the account's GitLab profile
gpg --keyserver keys.openpgp.org --recv-keys 7616A94E6FEC9EAB682790D0414534E1D89CB2B2
# fallback: curl -s https://gitlab.com/lars.kreuz.gpg | gpg --import
git clone https://gitlab.com/kreuz-com-group/hyper-calendar.git
cd hyper-calendar
git verify-tag vX.Y.ZGood signature with the fingerprint above is what you want. Trust the fingerprint you compared out of band, not the name attached to it — anyone can create a key bearing any name and email.
What this does and does not cover:
- Covers the source at that tag: the tag object is signed, and it names the commit, which names the whole tree.
- Does not cover the container image or the APK, which are signed separately — the APK with the Android release key, the F-Droid repository index with its own key. Those chains are independent of this one by design, so that a compromise of one does not silently validate the others.
v2.0.0 and v2.0.1 are unsigned. They predate this policy, and signing them retroactively would mean rewriting tags other people have already fetched. Every tag from the next release onward is signed.
Deployment assumptions
hyper-calendar is built for self-hosting for an owner and a few family members, behind a TLS-terminating reverse proxy. Some behaviours are intentional and are the operator's responsibility to account for:
- Free/busy is shared across all users of an instance. The "find a time" feature exposes other registered users' busy intervals — opaque, no event details — by design. Only register accounts you trust on a shared instance.
- Data at rest is plaintext unless you opt in. By default SQLite stores events in the clear on disk. A calendar can be marked encrypted, and then its events' titles, descriptions, locations and attachments are sealed in the browser under a key derived from your passphrase. Encryption does not cover metadata: start and end times, which calendar an event belongs to, attendees, and reminder times stay plaintext either way, so the shape of your schedule is still visible to anyone holding the file. Use an encrypted filesystem or volume if the host is untrusted, and restrict the database file's ownership and permissions to the app user.
- Losing an encryption passphrase loses the data. There is no recovery path by design — the server cannot decrypt what it was never given the key to.
- Backups are the operator's responsibility. The app does not create off-host backups; see Deployment and operations.
- Run behind HTTPS. Secure cookies and HSTS are enabled only when
NODE_ENV=production. SetTRUST_PROXY=1so rate limiting sees the real client IP behind the proxy. AUTH_DEV_AUTOLOGINmust never be set in production — it bypasses authentication and is for local development only.
Automated scanning
Every release merge request, every push to main, and a weekly scheduled pipeline run Secret Detection, SAST and Dependency Scanning, alongside npm audit on production dependencies. Findings block the merge rather than only being reported, and accepted findings are recorded with a reason in .gitlab/security-baseline.json.
Dependency advisories are scoped to what actually ships. The dev tree is audited weekly as well but does not block a release: those advisories are almost always denial-of-service via input fed to build tooling, and the build's input is our own source.
The weekly schedule exists because these checks can turn red without anyone changing the code, when a new advisory lands upstream — so they are looked at on a fixed day rather than discovered mid-release.
The security gate documents how to accept a finding and how to run the gate locally.