Skip to content
tbckrPublic

About

Self-hostable web client for your CalDAV calendars and tasks that actually looks good. Works with Nextcloud, Radicale, iCloud and more.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Lucid

Self-hostable web client for your CalDAV calendars and tasks that actually looks good.
Tested with Nextcloud, made for any CalDAV server.

CI CodeQL License: GPL-3.0

Features · Status · Quick start · Configuration · Security · Contributing

Lucid gives the CalDAV server you already have a calendar you enjoy opening: the views and drag & drop you know from Google Calendar, tasks with checklists, light and dark mode. Your data stays where it is. There is nothing to migrate and no database to run, just a single binary or container in front of your server.

Lucid is also a contribution to DI.DAY, the Digital Independence Day, which invites you to swap one Big Tech service for a privacy-friendly alternative on the first Sunday of every month. Your calendar can be one of them: move it to your own CalDAV server or a provider you trust, and Lucid keeps the comfort you're used to from Google Calendar.

Lucid's month view with the task sidebar

Features

  • Calendar views: month, week, day and agenda, with a color and a show/hide toggle for every calendar.
  • Drag & drop: move events and tasks and resize events right in the calendar, a new one too while you are still naming it; changes show up instantly.
  • Tasks: a task sidebar that groups each list by when its tasks are due, with checklists, start and due dates, priorities, repeats and completion. Rename a task or move it to another day right in the list, and delete done tasks one by one or all at once. Tasks with a date also appear in the calendar views, repeating ones at every repeat, where you can check them off or hide them once they're done. Checklists are saved as plain Markdown task lines, so your other apps can still read them.
  • Recurring events and time zones: recurring series, all-day and timed events, shown in your local time zone in the calendar and the lists, and time zones of their own for the events and tasks that need them, such as a flight, which their editor and details show too.
  • Custom repeats: beyond daily, weekly, monthly and yearly, the repeat editor builds repeats of events and tasks such as "every 2 weeks on Monday and Thursday" or "every month on the last weekday", with an end date or a number of times. It shows the repeat as a sentence and lists its next dates; everywhere else, repeats are shown in words too.
  • Easy login: enter just your domain; Lucid finds the CalDAV endpoint via /.well-known/caldav and DNS SRV records.
  • Fast: repeated requests are answered from a short-lived cache that checks your server for changes. Changed something on your phone? Refresh with a click or r to see it right away.
  • Yours to adjust: light and dark mode, English and German, date and time formats that follow your locale or your own choice in the settings.
  • Keyboard friendly: shortcuts and full keyboard navigation (WCAG 2.1 AA target).
  • Easy to run: download it and double-click on your own computer, or run a single static binary, a distroless container or a NixOS module on a server, with JSON logs, Prometheus metrics and health endpoints. NixOS and Home Manager modules cover both.

Status and limits

Lucid is young: this is its first release series (0.x), made for early adopters. It reads and writes your real calendars, so try it with a test calendar first. Until 1.0, a release may change settings or behavior; its release notes say what changed.

Tested with Nextcloud. Lucid speaks plain CalDAV, so other servers such as Radicale, Baïkal, SOGo or Synology should work, but none has been tried yet. If yours doesn't, please report it: the problem report is made for exactly that. Lucid signs in with a user name and password; iCloud needs an app-specific password, and Google Calendar, which only offers OAuth, doesn't work.

Not there yet:

  • Reminders: Lucid doesn't show or set alarms. Alarms set in other apps stay when you edit an event or task in Lucid.
  • Invitations: attendees aren't shown, and you can't answer an invitation. They stay when you edit, but your server may email them: Nextcloud tells the attendees when the organizer changes an event's time or repeat or deletes it, and the organizer when an attendee deletes it, though not for a new title, place or note, nor for past events.
  • Calendars: no creating, renaming or deleting calendars, no subscriptions to calendar feeds (webcal), no import or export of .ics files, and one account at a time. Calendars shared with you show if your server puts them in your calendar home, as Nextcloud does.
  • Events and tasks: no search, no moving to another calendar, and no subtasks.
  • Touch screens: drag and drop is made for mouse and keyboard and untested on touch screens; the editor works everywhere.
  • Sessions live in memory only, so restarting Lucid signs everybody out.

Screenshots

Week view with tasks in the calendar Editing a task with a checklist
Tasks right in the calendar Tasks with checklists
Week view in dark mode Details of an event
Week view in dark mode Event details
Creating a task from a click in the week view Sign-in page
Create an event or a task with a click Sign in with just your domain

How it works

flowchart LR
    browser["Your browser"] -- "HTTP(S) + JSON" --> lucid["Lucid"]
    lucid -- "CalDAV" --> server["Your CalDAV server"]
Loading

Lucid is a smart proxy: a single Go binary with the web app embedded. It talks JSON to your browser and CalDAV to your server. Your CalDAV server stays the single source of truth; Lucid only keeps sessions and a short-lived cache in memory. After login, your browser holds nothing but a session cookie, never your CalDAV credentials.

Repeating tasks

A repeating task shows up at every repeat in the calendar views. Checking off the current repeat moves the task on to its next one and leaves the done one behind as a completed entry of its own. Repeats are checked off in order. Where there is a choice, a change of a repeat asks which repeats it reaches: only the current one, this one and the following ones, or all of them. Apple Reminders and Tasks.org roll a repeating task forward the same way, so they see what you did in Lucid, and Lucid reads what Thunderbird, KDE and others write. docs/RECURRING-TASKS.md explains why, with a survey of how CalDAV clients handle repeating tasks.

Time zones

The calendar, the task list and the details show every time in your browser's time zone, and the editor and the details also show an event or task in time zones of its own, if it has any (see below). A repeating event or task you create in Lucid gets your browser's time zone, such as Europe/Berlin, as calendar apps usually do: that is what keeps a weekly meeting at 9:00 when daylight saving time begins or ends. A time zone stays with its event or task on your CalDAV server, where whoever runs the server, and whoever you share the calendar with, can see it, so a single event or task gets a time zone only when you choose one; without one, it is stored in UTC. One that another app gave a time zone keeps it when you change it in Lucid.

To keep your time zone out, use a browser that reports UTC instead, such as Firefox with privacy.resistFingerprinting, which reports UTC or Iceland's time (Atlantic/Reykjavik). Lucid then shows every time in that zone, and a repeating event or task you create gets it, as Thunderbird does with its time zone set to UTC, so a weekly meeting at 9:00 stays at 9:00 UTC all year.

An event or task can have time zones of its own, one for its start and one for its end or due date, such as a flight that leaves Berlin at 10:00 and lands in New York at 13:00. Its details then show its times there as well, and the editor shows and accepts each time in its own time zone. "Other time zone" adds them to an event or task in yours, and ✕ goes back to yours. You find a time zone by its city in English, its name or its offset. A new time zone keeps the time you entered, so 10:00 in Berlin becomes 10:00 in New York, and the editor says when that is in your time zone.

A repeating event or task stays in its time zone, also when you change its repeat. A weekly meeting on Tuesdays at 18:00 in New York shows on Wednesdays in Berlin, in the calendar and in its details, while the editor shows it at 18:00 on Tuesdays in New York, where you change its repeat. A new time zone applies to all events of a series or the following ones, at the time you entered: moved to Berlin at 18:00, the meeting is at 18:00 every Tuesday in Berlin.

Quick start

On your own computer

You need nothing but a CalDAV account: no server, no TLS, no configuration.

  1. Download the archive for your system from the releases page and extract it. Its name ends in your system and processor, like lucid_X.Y.Z_darwin_arm64.tar.gz:
    • Windows: windows_amd64.zip for most PCs, windows_arm64.zip for ARM devices such as Snapdragon laptops.
    • macOS (called darwin): darwin_arm64.tar.gz for Apple silicon (M1 and later), darwin_amd64.tar.gz for Intel Macs.
    • Linux: linux_amd64.tar.gz for most PCs, linux_arm64.tar.gz for ARM machines such as a Raspberry Pi 4 or 5 with a 64-bit OS.
  2. Double-click lucid (lucid.exe on Windows).
  3. A window opens and your browser shows Lucid at http://127.0.0.1:3456/. Log in with your CalDAV server's address (a bare domain is enough), your username and your password.

Close the window to stop Lucid. Closing the browser tab does not stop it.

  • macOS: Gatekeeper blocks apps that are not signed by an Apple developer, and Lucid is not. Open System Settings, go to Privacy & Security and click "Open Anyway" next to the message about lucid.
  • Windows: SmartScreen warns about the same. Click "More info", then "Run anyway".
  • Linux: run ./lucid in a terminal. Ctrl+C stops it. With the deb, rpm or Arch package (On a server), start "Lucid" from the app menu instead; it opens a terminal window, and closing it stops Lucid.

This mode is meant for your own computer. Lucid listens on 127.0.0.1 only, so no other device can reach it, and it serves plain HTTP because the traffic never leaves your machine. Do not use it on a computer shared with people you do not trust. CalDAV servers on your own computer or network (a Radicale on your NAS, for example) work without further setup. To use Lucid from other devices, run it on a server (below).

You log in again after each start, because sessions live in memory only. If port 3456 is taken, Lucid says so and exits (on Windows the window stays open until you press Enter). To choose another port, set LUCID_LOCAL_PORT in a terminal, for example LUCID_LOCAL_PORT=4000 ./lucid on Linux and macOS, or $env:LUCID_LOCAL_PORT = "4000"; .\lucid.exe in Windows PowerShell.

On a server

Important

TLS is mandatory on a server. Lucid sets Secure session cookies and handles your CalDAV credentials. Run it behind a TLS-terminating reverse proxy (Caddy, Traefik, nginx, ...) or let it serve HTTPS itself via LUCID_TLS_CERT / LUCID_TLS_KEY.

Setting LUCID_ADDR makes Lucid a server (the container image and compose.yaml set it for you, the Nix modules with mode = "server"); without it, Lucid runs in local mode.

Docker Compose (recommended)

The repository contains a hardened example compose.yaml (read-only root filesystem, all capabilities dropped, non-root user, no-new-privileges), bound to 127.0.0.1:8080 for your reverse proxy:

curl -fsSLO https://raw.githubusercontent.com/tbckr/lucid/stable/compose.yaml
docker compose up -d

Minimal Caddy configuration:

calendar.example.com {
	reverse_proxy 127.0.0.1:8080
}

Then open Lucid and log in with your CalDAV server's address (a bare domain is enough), your username and your password.

Docker
docker run -d --name lucid \
  -p 127.0.0.1:8080:8080 \
  --read-only --cap-drop ALL --security-opt no-new-privileges:true \
  ghcr.io/tbckr/lucid:latest

Images are published for linux/amd64 and linux/arm64, based on gcr.io/distroless/static and running as UID/GID 65532.

Binary

Download the archive for your platform from the releases page (Linux, macOS, Windows; amd64 and arm64), verify it (see Verifying releases) and run:

LUCID_ADDR=127.0.0.1:8080 ./lucid
./lucid --version

Health endpoints: GET /healthz (liveness), GET /readyz (readiness). lucid healthcheck probes the local instance and exits non-zero on failure (used by the container HEALTHCHECK, since distroless has no shell or curl).

Debian, Ubuntu, Fedora, Arch (package)

Each release has Linux packages for amd64 and arm64: a .deb, an .rpm and an Arch .pkg.tar.zst. They install Lucid with a hardened systemd service for server mode, which they do not enable, and an app-menu entry for local mode. They need systemd 247 or later: Debian 12, Ubuntu 22.04, RHEL 9 and its clones, Fedora, Arch, or newer. There is no package repository; to update, install the next release's package the same way.

Download the package from the releases page, verify it against checksums.txt (see Verifying releases) and install it:

sudo apt install ./lucid_X.Y.Z_linux_amd64.deb         # Debian, Ubuntu
sudo dnf install ./lucid_X.Y.Z_linux_amd64.rpm         # Fedora, RHEL
sudo pacman -U ./lucid_X.Y.Z_linux_amd64.pkg.tar.zst   # Arch

The packages carry no signature of their own; the signed checksums.txt covers them, so verify the package before you install it. If dnf refuses the rpm as unsigned (it checks local packages where localpkg_gpgcheck is set), add --nogpgcheck.

Then configure and start the service:

sudoedit /etc/lucid/lucid.env   # LUCID_* settings, see Configuration
sudo systemctl enable --now lucid
journalctl -u lucid             # logs

/etc/lucid/lucid.env runs server mode on 127.0.0.1:8080 behind a reverse proxy on the same machine, like compose.yaml; updates keep your changes.

To let Lucid serve TLS itself, pass the certificate and key in a drop-in (sudo systemctl edit lucid), since the service runs as a dynamic user that cannot read them:

[Service]
LoadCredential=tls-cert:/etc/ssl/lucid/cert.pem
LoadCredential=tls-key:/etc/ssl/lucid/key.pem
Environment=LUCID_TLS_CERT=/run/credentials/lucid.service/tls-cert
Environment=LUCID_TLS_KEY=/run/credentials/lucid.service/tls-key

Then set LUCID_ADDR in /etc/lucid/lucid.env to the public address (for example :8443) and remove LUCID_TRUST_PROXY_HEADERS. A port below 1024 needs one more drop-in; a reverse proxy remains the simpler choice:

[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
PrivateUsers=no

With Nix

The repository is a Nix flake with the package, an overlay, a NixOS module and a Home Manager module. Both modules have the same options under services.lucid, one for each variable in Configuration, and run Lucid in local mode unless you set mode = "server". Setting an option of the other mode is an error when the configuration is evaluated. To try Lucid without installing it: nix run github:tbckr/lucid/stable (local mode).

github:tbckr/lucid/stable is the latest release: the stable branch moves to each release once it is published, pre-releases excepted, so nix flake update brings you the next one. To stay on one release, use its tag (github:tbckr/lucid/v1.2.0); without a ref, github:tbckr/lucid is the development state of main. A flake cannot see tags, so lucid --version of a Nix build shows nix- and the commit instead of the version number (nix-a10d937).

NixOS

services.lucid runs Lucid as a hardened systemd service with a dynamic user, started at boot:

{
  inputs.lucid.url = "github:tbckr/lucid/stable";

  outputs =
    { nixpkgs, lucid, ... }:
    {
      nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          lucid.nixosModules.default
          { services.lucid.enable = true; }
        ];
      };
    };
}

On your own computer (local mode, the default), Lucid listens on 127.0.0.1:3456 (port changes it). Open http://127.0.0.1:3456/ or the "Lucid" entry in your app menu. One instance serves every user of the computer, each with their own CalDAV account.

On a server behind a reverse proxy, any proxy works; Lucid only needs to know that one is in front of it:

services.lucid = {
  enable = true;
  mode = "server"; # listens on 127.0.0.1:8080
  trustProxyHeaders = true;
};

# nginx (with security.acme set up) ...
services.nginx.virtualHosts."calendar.example.com" = {
  forceSSL = true;
  enableACME = true;
  locations."/" = {
    proxyPass = "http://127.0.0.1:8080";
    # Sends X-Forwarded-For; without it all clients share one rate limit.
    recommendedProxySettings = true;
  };
};

# ... or Caddy
services.caddy.virtualHosts."calendar.example.com".extraConfig = ''
  reverse_proxy 127.0.0.1:8080
'';

On a server with its own TLS, the certificate and key can come from any source (ACME, agenix, sops-nix, plain files). systemd reads them and hands them to Lucid as credentials, so the service needs no access to the files, and they never enter the Nix store:

services.lucid = {
  enable = true;
  mode = "server";
  address = "0.0.0.0";
  port = 443;
  openFirewall = true;
  tls.certFile = "/var/lib/acme/calendar.example.com/fullchain.pem";
  tls.keyFile = "/var/lib/acme/calendar.example.com/key.pem";
};
# If security.acme manages the certificate: start Lucid once the files
# exist, and restart it on renewal.
systemd.services.lucid = {
  wants = [ "acme-calendar.example.com.service" ];
  after = [ "acme-calendar.example.com.service" ];
};
security.acme.certs."calendar.example.com".reloadServices = [ "lucid.service" ];

Lucid reads the certificate only when it starts, so a renewal restarts it, and everyone logs in again (sessions live in memory). A port below 1024 gives the service CAP_NET_BIND_SERVICE and turns off PrivateUsers; everything else stays as restricted as with a higher port.

Home Manager (Linux and macOS)

For computers without NixOS or without root: lucid.homeModules.default runs Lucid under your own user, as a systemd user service on Linux and a launchd agent on macOS, started when you log in:

home-manager.lib.homeManagerConfiguration {
  # pkgs = ...;
  modules = [
    lucid.homeModules.default
    { services.lucid.enable = true; }
  ];
}

With Home Manager as a NixOS or nix-darwin module, add it with home-manager.sharedModules = [ lucid.homeModules.default ];. The options are those of the NixOS module, without openFirewall.

  • In local mode, open http://127.0.0.1:3456/; on Linux there is also a "Lucid" entry in the app menu. Two users of one computer need different ports.
  • Logs: journalctl --user -u lucid on Linux, ~/Library/Logs/lucid.log on macOS.
  • A user service on Linux starts at login. To start it at boot, for example for server mode, run loginctl enable-linger <user> once.
  • A Linux user service cannot listen on a port below 1024; use a reverse proxy or a higher port.
  • Your user must be able to read tls.certFile and tls.keyFile: on Linux your own systemd user manager reads them, on macOS Lucid does.

Configuration

Lucid is configured exclusively through environment variables; there are no secrets in the configuration (the session key is random per start, the TLS key is a file).

Lucid runs in local mode when LUCID_ADDR is unset and in server mode when it is set. The Mode column says which mode a variable belongs to. A variable set for the other mode (even to its default value), or one of the variables of earlier versions that no longer exist, stops Lucid at startup with a message that names the variable. For a variable of the other mode it says which mode that is, for a removed one what replaces it or why it is gone.

All environment variables
Variable Mode Default Description
LUCID_ADDR Server (unset) Listen address, e.g. :8080. Setting it selects server mode; unset runs local mode
LUCID_LOCAL_PORT Local 3456 Port on 127.0.0.1. The only port Lucid tries: if it is taken, Lucid exits
LUCID_OPEN_BROWSER Local true Open the browser at Lucid's address once it is listening. false for development, tests and headless machines
LUCID_METRICS_ADDR Server (unset) Address of a separate listener that serves /metrics. Unset: no /metrics at all
LUCID_TLS_CERT, LUCID_TLS_KEY Server (unset) Paths of the certificate and key files, both or neither. Serves HTTPS directly. Otherwise TLS must be terminated by a reverse proxy
LUCID_TRUST_PROXY_HEADERS Server false Use X-Forwarded-For for the client IP (only behind a trusted proxy)
LUCID_PRIVATE_NETWORKS Both local all, server none Internal addresses (loopback, private, link-local, ...) that CalDAV servers may have: all allows every one, none none, and a comma-separated list of CIDRs and addresses (e.g. 192.168.1.10/32) only those. all and none are case-insensitive and cannot be mixed with addresses
LUCID_SESSION_TTL Both 12h Absolute session lifetime
LUCID_SESSION_IDLE_TIMEOUT Both 2h Idle timeout, at most LUCID_SESSION_TTL
LUCID_RATE_LIMIT_RPS Both 20 Token bucket refill per client IP (API)
LUCID_RATE_LIMIT_BURST Both 60 Token bucket size
LUCID_LOGIN_RATE_LIMIT_PER_MIN Both 10 Login attempts per client IP per minute
LUCID_CACHE_SIZE Both 256 Cached entries (LRU), one per user, calendar and component (events or tasks)
LUCID_CACHE_FRESHNESS Both 10s Serve cache without CTag check for this long
LUCID_UPSTREAM_TIMEOUT Both 20s Timeout for requests to CalDAV servers
LUCID_LOG_LEVEL Both info debug, info, warn, error. Logs go to stdout: text in local mode, JSON in server mode; debug adds a line for each API request and each CalDAV call, without contents and with pseudonymised paths, see Reporting problems

Durations use Go syntax (90s, 15m, 12h).

The REST API is documented in docs/API.md.

Reverse proxy notes

  • Terminate TLS at the proxy and forward plain HTTP to Lucid on a private interface (e.g. 127.0.0.1:8080). Never expose the plain-HTTP port publicly.
  • Set LUCID_TRUST_PROXY_HEADERS=true only if Lucid is reachable exclusively through the proxy; otherwise clients can spoof X-Forwarded-For and evade rate limiting.
  • If your CalDAV server runs on your LAN, allow it narrowly with a CIDR in LUCID_PRIVATE_NETWORKS (e.g. 192.168.1.10/32) rather than all.
  • /metrics is served only on the separate address LUCID_METRICS_ADDR, never on the main listener. Keep that address internal; without it there are no metrics.

Reporting problems

Calendar servers differ in small ways, and a problem with yours is easiest to fix if the maintainers can see what your server sent. Lucid helps you share that without sharing your calendar.

If you use Lucid

Choose Report a problem in the account menu. The same dialog opens from an error message that shows a reference such as Reference: 3f9a1c2b (also on the login screen), from the login screen after Lucid signed you out without you asking, from the placeholder of an item Lucid could not read, and from the life-buoy button Add to problem report in the details of any event or task, because the likeliest bugs, a wrong time or a missing repeat, leave no error behind.

The dialog shows the file exactly as it is downloaded. By default it holds:

  • the Lucid version, its mode and the operating system,
  • the product and host of your CalDAV server, the endpoint Lucid found, and the paths of your principal and calendar home, with placeholders for the names in them, as in the CalDAV calls,
  • what each calendar supports and how many items it holds, never its name,
  • your recent CalDAV calls (up to 100, 30 before you sign in): method, path, status, duration and a few response headers, without any request or response body,
  • what Lucid could not use in your server's answers, as structure only: property names and the format of values, with titles, notes, places, names, addresses and IDs replaced (time zone and repeat rules stay, as bugs hide in them),
  • the field names that failed in items Lucid could not read,
  • your browser's time zone, language and user agent, the size of its window, and the settings that change what Lucid shows: language, time format, week start, whether done tasks are hidden in the calendar, and how many of your calendars you hid (not which).

In every path in the file, your login name is replaced by {user} and every other name, of a calendar, an item or an account ID, by a placeholder; only names that make up how CalDAV servers are laid out, such as remote.php or calendars, stay. No title, description, place or attendee is in the file. The host of your CalDAV server is the one name that stays readable: mask it before you post the file if you would rather not publish it. For an event or task you add, the file holds its structure. Its content is added only if you choose Show content, read it, and tick the box Add this content to the report, which starts unticked, for that one item.

To add several events or tasks, choose Add another item (or close the dialog): the report then waits at the bottom of the screen, with the items in it, until you download or discard it. The life buoy of the next item adds it and opens the report again. While it waits, Lucid keeps what you showed and ticked, in your browser only; downloading, discarding or signing out forgets it, and so does closing a report without items.

Lucid sends nothing itself: the report is a file you download. Open issue form opens the issue form in a new tab, with Lucid's version and mode and your server product filled in, and you attach the file there (if GitHub refuses the .json file, rename it to .txt or paste its content). Issues on GitHub are public, so read the file first. Report security problems privately, see SECURITY.md.

If Lucid shows Something broke and reloading doesn't help, open Error details below the button: copy them with Copy details and paste them into the bug form. They hold Lucid's version, your browser's time zone, language and user agent, and the error; read them first, as an error can name an item of your calendar. Lucid keeps the calls and problems in memory only, for as long as your session lasts.

If you run Lucid for others

Lucid never logs calendar contents or passwords, at any level and in either mode, and no setting changes that. With LUCID_LOG_LEVEL=debug it adds a caldav exchange line for every request to a CalDAV server (method, host, path, status, duration, size, content type, the Server and DAV headers and, for a transport failure, a class such as dns, tls or timeout) and an http request line for every API request. A caldav problem line, written at warn and so at the default level too, comes with the structure of the document Lucid could not read.

  • Find a user's request. Every response has an X-Request-Id, and every line logged while serving the request has it as request_id. The reference a user sees in an error message is its first 8 characters, so searching the log for it (for example grep 3f9a1c2b) finds the request. One exception: a caldav problem line for an item Lucid cannot read or show is written only once per Lucid process, with the request ID of the first read that met it, so a reference may not lead to it. The path of such a line is the one in the report's problems, until Lucid restarts.
  • Paths are pseudonymised. The user name is {user}, the calendar home and principal are {home} and {principal}, and the names below the home become placeholders such as cal-1a2b3c4d/, obj-5e6f7a8b.ics and seg-<h> for any other name. They are derived with a key that exists only in RAM: the same name has the same placeholder until Lucid restarts, and none can be turned back into a name. API IDs in URLs are encrypted with a key that exists only in RAM, so your reverse proxy's log shows no names either, and the IDs change on restart. One thing in a URL is no pseudonym: a change to one occurrence of a repeating event or task has the occurrence's start time in its URL (.../occurrences/<time>, .../following/<time>), so your reverse proxy's log and the http request lines show that time, though not the event's or task's name or content. The host of the CalDAV server stays, so you can tell servers apart. So do structural names outside the home and principal, such as remote.php, dav, principals and calendars (a name somebody chose that is one of these words stays as well), so that a discovery path remains readable.
  • Share the CalDAV lines, not all of them. The caldav exchange and caldav problem lines can go into a public issue. security event lines record a login with the user name and server URL entered, for abuse detection, and carry a client IP address; http request lines carry client IP addresses too. Keep both to yourself.

Security

  • Proxy architecture. The browser only talks to Lucid; Lucid talks to the CalDAV server. This avoids CORS problems and keeps CalDAV credentials out of the browser: after login the frontend only holds an opaque session cookie (HttpOnly, SameSite=Strict, and Secure in server mode; local mode serves plain HTTP on the loopback interface only and sets no Secure flag).
  • Encrypted in-memory sessions. Credentials are stored server-side, encrypted with AES-256-GCM under a key that is random per start, and only in RAM. Nothing is written to disk, and no key appears in the environment or the configuration.
  • Local mode. It listens on 127.0.0.1 only and accepts requests only when the Host header is localhost or 127.0.0.1 (any port), which protects against DNS rebinding. It is meant for your own computer: other users of that computer can reach the same address.
  • SSRF protection. Because users choose the server URL, every outgoing request (including auto-discovery and redirects) goes through a safe transport that refuses loopback, private, link-local and other internal address ranges unless LUCID_PRIVATE_NETWORKS allows them. Local mode allows them all by default, because you can reach your own network anyway; server mode allows none.
  • Web hardening. CSRF synchronizer tokens on all state-changing requests, token-bucket rate limiting (stricter for login), strict security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options), XML entity protection, input validation, open-redirect protection, no source maps in production builds.
  • Supply chain. Signed releases and images, SBOMs and daily vulnerability scans; see Supply chain.

To report a vulnerability, see SECURITY.md.

Contributing

Bug reports, feature ideas and pull requests are welcome. Report bugs through the issue forms, and bring ideas and questions to Discussions. CONTRIBUTING.md explains the development setup (just dev starts Lucid with a demo CalDAV server) and the project's conventions.

License

Lucid is free software licensed under the GNU General Public License v3.0.

About

Self-hostable web client for your CalDAV calendars and tasks that actually looks good. Works with Nextcloud, Radicale, iCloud and more.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages