Skip to content

fix(server): keep the word list private from other accounts - #24

Merged
HackTuah merged 1 commit into
mainfrom
fix/server-file-modes
Oct 6, 2026
Merged

HackTuah merged 1 commit into
mainfrom
fix/server-file-modes

Conversation

@HackTuah

@HackTuah HackTuah commented Oct 6, 2026

Copy link
Copy Markdown
Member

What (Linear SCR-450, input to the slice 54 security review)

The server's word list could be read by other accounts on a shared computer: kotiko.db, its -wal/-shm, migration backups, models-cache.json, and a new data folder all followed the process umask. Now they're private, at the root and in depth:

  • Checked first, empirically, with exqlite: SQLite gives -wal/-shm/-journal the database's mode when it creates them. So the database is created empty at 0600 before SQLite opens it, and the others follow. VACUUM INTO into an existing empty 0600 file keeps 0600.
  • New Kotiko.Private. The BEAM can't create a file with a mode, so the code creates empty 0600 files, writes through a private temp file and renames, and makes new folders 0700. The token, the model cache, the db, the partial copy and the backups all use it.
  • At every start, before the db opens (DataDir.make_private/1):
    • Kotiko's own files are tightened to 0600 and backups/ to 0700, with one warning listing what changed.
    • The folder becomes 0700 only if everything in it is Kotiko's. A folder with other files in it, or a link, may be shared on purpose (KOTIKO_DATA_DIR=~), so it's left alone and a warning gives the chmod. A disk that ignores modes (WSL /mnt/c) gets one warning.
    • The server never refuses to start over this.
  • run.sh sets umask 077, and the systemd unit gets UMask=0077. Re-running install-service.sh picks this up, and the CHANGELOG says so.
  • Existing installs: on the first start after updating, the files are tightened. Nothing moves and nothing is deleted.

Tests (each fails without the fix; verified by breaking it on purpose)

  • private_test.exs covers the helpers, and the SQLite behaviour this relies on.
  • data_dir_test.exs has 6 make_private cases, including the exact warning text.
  • Config, token, catalog and script tests:
    • run.sh gives mix umask 0077 even from a 022 shell;
    • the unit has UMask=0077.
  • The boot test starts from umask 022 and checks 700 600 600 600 600 (folder, db, -wal, -shm, token) while running.

Docs: the assurance case (gap closed, new residual risk: a shared folder stays shared), the security requirements, configuration.md ("Files in the data folder"), the site's server page, the CHANGELOG and slice 29.

Checked

  • Server: mix format, compile --warnings-as-errors and credo --strict pass. mix test --warnings-as-errors --cover: 1106 tests, 23 properties, 0 failures, 91.68 %. mix sobelow: exit 0.
  • Other: shellcheck, check-old-name, bestpractices-links --check, reuse lint, and site npm test / npm run check all pass.

Known and left for later (recorded for slice 54)

  • mix kotiko.export --output writes the export with the shell's umask.
  • When the server isn't started by run.sh or the unit, a new, still-empty file has the umask mode for a moment before its chmod.

🤖 Generated with Claude Code

The database, its -wal and -shm files, the model list cache and a new data
folder were created with the process umask, and the systemd unit set no
UMask, so on a shared computer other accounts could read the word list
(SCR-450). The API token and backups were already private.

Now every file the server keeps in the data folder is readable only by its
own account. The BEAM can't give a mode when it creates a file, so files are
created empty, made 0600, and only then filled: the database before SQLite
first opens it (SQLite gives the -wal, -shm and -journal files the database's
mode; a test checks this), each backup and the copy from the old name before
VACUUM INTO, and the token and model cache through a private temporary file.
A data folder the server makes is 0700.

For existing installs, every start checks the folder before the database
opens: Kotiko's own files that others can open are made 0600 again (with one
warning naming them), and the folder 0700 when it holds nothing but Kotiko's
files. A folder with other files in it, or a link, may be shared on purpose,
so it is left as it is with a warning that gives the chmod to run. Nothing
here stops the start. run.sh now sets umask 077 and the unit from
install-service.sh UMask=0077, so files are private from the moment they
exist.

Docs: the configuration reference, security requirements and assurance case
(the umask gap leaves the residual risks), the site's server page, the
changelog and slice 29.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
@HackTuah
HackTuah requested a review from minitru as a code owner October 6, 2026 22:31
@HackTuah
HackTuah merged commit 137cc8f into main Oct 6, 2026
14 checks passed
@HackTuah
HackTuah deleted the fix/server-file-modes branch October 6, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant