fix(qa): harden DB file permissions — was world-readable 644 (QA-B16-02)

docker-entrypoint chmod 700'd the data dir but never the DB file; SQLite created
bills.db/-wal/-shm at umask 644 (world-readable), holding financial data +
encrypted SimpleFIN token/sessions/secrets. Add `umask 077` (files 600, dirs 700)
+ explicit chmod 600 of any pre-existing DB files on upgrade. Found on the live
nebula deploy (BillTracker.db was 644).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-03 10:32:39 -05:00
parent fc2daf2e9e
commit a5671ab3be
2 changed files with 10 additions and 0 deletions

View File

@ -3,6 +3,7 @@
### 🐛 QA Fixes
- **[Security] SQLite DB was created world-readable (644)** — `docker-entrypoint.sh` locked the data *directory* (`chmod 700`) but never the DB *file*, and SQLite created `bills.db`/`-wal`/`-shm` under the default umask (644). On a real deploy the DB (financial data + encrypted SimpleFIN token, sessions, SMTP/OIDC secrets) was world-readable, shielded only by the parent dir's 700. Added `umask 077` to the entrypoint (DB, WAL/SHM, backups, exports now created 600/700) plus an explicit `chmod 600` for pre-existing files on upgrade. (QA-B16-02)
- **[Privacy] The version check is now opt-out-able** — the privacy policy described the external version check as "optional", but there was no way to disable it (it hit a hardcoded upstream host whenever the About/Status/version page loaded). Added an **admin toggle**: an `update_check_enabled` setting gates the request in `services/updateCheckService.js` (default on — when off, **no external request is made**), exposed via `GET`/`PUT /api/about-admin/update-check-setting` and a switch on the admin **System Status** page. Privacy policy updated to state an admin can disable it. Test: `tests/updateCheckOptOut.test.js`. (was QA-B16-01)
- **[Security] Bill name could inject HTML into reminder emails** — `buildEmailHtml` (`services/notificationService.js`) escaped the bill name in the detail table but interpolated it **raw** into the reminder message line (`<strong>${bill.name}</strong> is due…`), so a bill named `<img src=x onerror=…>` landed unescaped in the email HTML. Self-XSS (reminder emails go to the bill's owner), but a clear inconsistent-escaping bug — now escaped everywhere. Covered by `tests/notificationDelivery.test.js`. (was QA-B14-04)
- **[Notifications] "Send test push" was completely broken** — `services/notificationService.js` attached its `_push` export (the ntfy/Gotify/Discord/Telegram helpers) *before* the final `module.exports = {…}`, which clobbered it, so `require('…/notificationService')._push` was `undefined`. `routes/notifications.js` (`const { sendTestPush } = require(…)._push || {}`) therefore always hit `throw 'Push service not initialised'`**`POST /api/notifications/test-push` always returned 500** for every user testing their push channel. Scheduled reminders were unaffected (they call `sendPushToUser` in-scope). Moved the `_push` assignment after the reassignment. Covered by `tests/notificationDelivery.test.js` (per-channel payloads, dispatch, error handling, and a check that the auth token never leaks into the message body). (was QA-B10-01)

View File

@ -1,6 +1,12 @@
#!/bin/sh
set -eu
# Files this app writes (the SQLite DB + WAL/SHM, backups, exports) hold financial
# data and encrypted secrets (SimpleFIN token, sessions, SMTP/OIDC). Create them
# owner-only (600 files / 700 dirs) — not world-readable. Inherited by the exec'd
# node process so SQLite's -wal/-shm are locked too. (QA-B16-02)
umask 077
APP_USER="${APP_USER:-bill}"
APP_GROUP="${APP_GROUP:-bill}"
DATA_DIR="${DATA_DIR:-/data}"
@ -13,6 +19,9 @@ mkdir -p "$DATA_DIR" "$DB_DIR" "$BACKUP_DIR" /app/backups
if [ "$(id -u)" = "0" ]; then
chown -R "$APP_USER:$APP_GROUP" "$DATA_DIR" /app/backups
chmod 700 "$DB_DIR" "$BACKUP_DIR" /app/backups
# Lock any pre-existing DB files that were created world-readable (644) before
# this umask fix — otherwise they keep their old mode across an upgrade.
chmod 600 "$DB_FILE" "$DB_FILE"-wal "$DB_FILE"-shm 2>/dev/null || true
if [ "${RUN_DB_MIGRATIONS:-true}" = "true" ]; then
su-exec "$APP_USER:$APP_GROUP" node scripts/migrate-db.js
fi