Codebase audit
This is a contributor-facing risk register produced while validating the documentation against the entire repository on 2026-07-23. It records observed behavior, not a claim that every item is exploitable in production. None of the code risks below was silently changed as part of the documentation rewrite.
Executive summary
Section titled “Executive summary”The architecture is coherent and unusually well-commented for its size: provider adapters share a small contract, tombstones make offline deletion reliable, secrets are encrypted, request IDs cross subsystem boundaries, and backup/restore scripts validate their artifacts.
The calendar-detail P0, database-integrity, quality-gate, single-replica contract, exact-account OAuth cleanup, and federation token-lifecycle findings are resolved, as is release/toolchain reproducibility. The largest remaining risks are at boundaries:
- provider event delivery is best-effort without a durable retry queue;
- first-contact federation home-server claims are still unverified metadata.
Priority model
Section titled “Priority model”| Priority | Meaning |
|---|---|
| P0 | Address before treating the current API as hardened for untrusted multi-user deployment |
| P1 | High-value reliability/delivery work; plan explicitly |
| P2 | Important hardening or maintainability work |
| P3 | Cleanup, consistency, and developer-experience debt |
P0 — authorize calendar detail reads
Section titled “P0 — authorize calendar detail reads”Status: resolved. getCalendarDetailsForUser() now checks the caller’s
calendar role before loading either the calendar or its members. The
calendars.test.ts regression self-check proves a non-member receives
Forbidden and neither protected database read runs.
Originally observed: GET /api/v1/calendars/:id used requireAuth, but
handlerGetCalendar read the calendar and its members without verifying that
req.user belongs to it. The returned member shape includes names and email
addresses.
Original impact: an authenticated user who learned a calendar UUID could read metadata and member contact information for a calendar they did not belong to.
Source: apps/api/src/index.ts and
apps/api/src/handlers/calendars.ts.
Remaining follow-up: expand the same non-member coverage into a route-level authorization matrix for every endpoint that accepts a resource ID.
P1 — make multi-row invariants atomic
Section titled “P1 — make multi-row invariants atomic”Status: resolved for local database writes. The query layer now commits each of these invariants in one Drizzle transaction:
- a calendar row and its owner membership;
- an event, its creator-attendee row, and all initial calendar links;
- an event edit, link reconciliation, and removed provider mappings;
- unlinking an event and tombstoning it when the final link disappears;
- calendar deletion, origin tombstones, cascaded links, and orphan cleanup; and
- external-mirror disablement or whole-account disconnect plus all local mirror cleanup (including CalDAV credentials).
Ownership transfer also locks the calendar row before changing both member roles
and creatorID, so concurrent transfers cannot both proceed from the same owner
snapshot. Provider mirrors reject ownership transfer because their credentials
remain scoped to the original connected account.
Originally observed: these writes ran as independent statements. A database
error or process exit between them could leave ownerless calendars, partially
linked events, stale mappings, or roles that disagreed with creatorID.
Provider boundary: a PostgreSQL transaction cannot roll back Google, Microsoft, or CalDAV. Calendar writes retain their documented provider-first ordering, while event delivery remains best-effort. Durable recovery for that cross-system boundary is tracked separately under make provider event delivery recoverable.
P1 — add database constraints for assumed uniqueness
Section titled “P1 — add database constraints for assumed uniqueness”Status: resolved. Migration 0033_busy_thunderball.sql removes historical
duplicates before adding:
- a primary key on
user_settings.id; and - a unique constraint on
calendar_events(event_id, calendar_id).
Settings creation now absorbs a concurrent insert, settings saves use one
upsert, and event-link inserts use ON CONFLICT DO NOTHING. The schema
self-check asserts both constraints are present in Drizzle metadata.
Originally observed: neither invariant was enforced by PostgreSQL. Lazy settings creation and event-link retries could create duplicate rows, leaving reads and updates ambiguous or multiplied.
Remaining follow-up: exercise the migration and concurrent writes against disposable PostgreSQL in CI as part of provider and database integration coverage.
P1 — make provider event delivery recoverable
Section titled “P1 — make provider event delivery recoverable”Observed: event create/update/delete persists locally, then
pushEventToCalendars catches and logs individual provider failures. The API can
therefore succeed while Google, Microsoft, or CalDAV remains stale. There is no
durable retry queue. Calendar-level operations generally use provider-first
semantics, so the two write classes behave differently.
Impact: temporary provider outages create divergence that may not automatically reconcile in the expected direction.
Recommendation: persist an idempotent outbox job in the same transaction as
the local event change, retry with backoff, expose pending/failed delivery state,
and document conflict resolution. Until then, alert on stage="push" and
confirm convergence manually.
P1 — restore an enforceable quality gate
Section titled “P1 — restore an enforceable quality gate”Status: remediated on 2026-07-23.
Observed on 2026-07-23:
- there is no pull-request CI workflow;
- the Docker release workflow builds/publishes images without a preceding repository check;
- root
buildexercises only workspaces that declare a build script; - the existing tests are twelve direct
tsxassertion programs with no runner, coverage, database integration, or authorization matrix; and - client lint reports 82 errors and 55 warnings.
Resolution: the genuine lint errors and ordinary hygiene warnings were
fixed. React Compiler diagnostics that currently reject Reanimated mutable
shared values, gesture live refs, and legacy state-reset effects remain visible
as warnings. expo lint --max-warnings 108 locks that debt so it can only move
down, and pnpm check now includes lint.
.github/workflows/quality.yml runs a frozen install, typechecks, the twelve
self-checks, lint, docs build, Drizzle generation drift detection, and a
production API Docker build for every pull request and push to main. The
manual Docker publication workflow calls the same reusable gate before it can
push an image or create a release.
Follow-up: migrate Reanimated .value access to compiler-compatible
get()/set() where supported, resolve effect dependency findings, and lower
the warning budget with every cleanup. Expand the twelve assertion programs into
route/database integration coverage as tracked below.
P1 — make horizontal scaling explicit or coordinated
Section titled “P1 — make horizontal scaling explicit or coordinated”Status: remediated as an enforced single-replica contract on 2026-07-23.
Observed: scheduled provider sync, cleanup timers, SSE clients, rate limits, and metrics are process-local. Each API replica starts the timers and sees only its own connections/requests.
Impact: multiple replicas can duplicate scheduled work, miss cross-replica SSE broadcasts, and apply per-instance rather than global rate limits.
Resolution: the API now holds a dedicated PostgreSQL session advisory lock
and refuses to start a second process against the same database. Both Compose
files explicitly declare one API replica. Cleanup and external sync use a
tested single-flight wrapper; an overlong run skips the next tick and emits a
structured warning plus musubi_scheduled_task_skips_total{task}. The shipped
Prometheus rules alert on any skip.
Follow-up: horizontal scaling remains intentionally unsupported. Replacing the singleton contract still requires job leadership/distributed locking, shared pub/sub for SSE, a shared rate limiter, and per-instance metric aggregation.
P1 — disconnect exactly one OAuth account
Section titled “P1 — disconnect exactly one OAuth account”Status: remediated on 2026-07-23.
Observed: the connection handler asks Better Auth to unlink one account. If
that fails, its fallback calls cleanUsersOAuthTokens(userID, provider), which
is provider-wide rather than account-specific.
Impact: disconnecting one of several Google or Microsoft accounts can clear tokens for sibling accounts in the fallback path.
Resolution: the modern disconnect fallback now calls
cleanOAuthAccountTokens(userID, provider, accountID), whose update predicate
contains all three identifiers. Unsupported providers are rejected before any
cleanup. A disposable-PostgreSQL integration test proves the target row is
cleared while a sibling account for the same provider, another provider, and
the same account ID owned by another user remain unchanged.
The old Google revoke endpoint has no account ID and remains explicitly
provider-wide for backward compatibility; its helper is named
cleanProviderOAuthTokens so it cannot be mistaken for exact-account cleanup.
P2 — normalize malformed-input responses
Section titled “P2 — normalize malformed-input responses”Status: remediated for the observed event paths on 2026-07-23.
Observed:
- event delete parses
EventSchemaoutside the handler’s validation catch; and GET /events?since=constructs aDatewithout rejecting an invalid value.
Impact: malformed client input may surface as 500 instead of a stable
400, polluting error alerts and hiding the actual caller defect.
Resolution: event create/update/delete share one body parser that converts
shape failures to BadRequestError and validates every event/calendar UUID
before a query. Event route parameters use the same UUID guard.
GET /events?since= now rejects empty, invalid, array, and object values before
calling the database. The global error mapper also treats uncaught ZodError
as 400 rather than 500.
A direct self-check covers malformed UUIDs, timestamps and Zod bodies plus the
error mapper’s 400/500 boundary.
Follow-up: extend the same route-boundary matrix to calendars, invites, federation and user routes, including invalid enum and cursor cases.
P2 — review federation identity and token lifecycle
Section titled “P2 — review federation identity and token lifecycle”Status: remediated on 2026-07-23, with the first-contact trust boundary documented explicitly.
Observed: invite acceptance trusts the remote profile supplied by the bearer of the invite; it does not prove control of the claimed home server. Member tokens have no expiry, and the exported revocation helper is not part of a clear lifecycle. Kicking a member removes calendar authorization, but stale authentication tokens can remain stored.
The public invite-preview endpoint also returns live calendar context, including events and member email data, to anyone possessing a valid invite token. That may be intended capability behavior, but it is a meaningful privacy surface.
Recommendation: document the trust model, minimize preview data, add expiry and revocation/rotation, and authenticate remote identity if it is used for more than display. Include invite leakage and replay in threat modeling.
Resolution: unverified homeServer + email claims are no longer used to
find an existing shadow user. First contact always creates an isolated identity;
reusing a shadow across later invites requires its current member token as
proof. This closes the cross-calendar privilege escalation where a holder of
one invite could claim another shadow’s profile and inherit all of its
memberships.
Member tokens now expire after 90 days. Versioned token values carry a client-readable issue time, the client rotates in the final 14 days, and the origin performs compare-and-swap rotation so one credential cannot be exchanged twice. Expired hashes are purged hourly. Removing a shadow’s final membership revokes its tokens in the same database transaction.
The invite preview now returns only calendar identity, member names/avatars, and minimal event fields for the 30-day UI window. It omits member email, event description, location, URL, attribution and unrelated historical one-off events.
A pure security self-check covers token format/timing, origin validation and preview minimization. A disposable-PostgreSQL integration test covers expiry, single-use rotation, unproved profile isolation, proved identity reuse and last-membership revocation.
Remaining boundary: v1 still does not prove that the first-contact person controls the claimed home server. Those claims are display metadata and cannot bind to existing access. Add a signed or callback-based server-to-server assertion before treating them as globally verified identity.
P2 — improve provider and database integration coverage
Section titled “P2 — improve provider and database integration coverage”Status: provider coverage expanded on 2026-07-23; route/offline coverage remains open.
Remaining coverage gaps include:
- PostgreSQL constraints, migrations, transactions, or concurrent writes;
- route authentication/authorization beyond the calendar-detail regression and error mapping;
- signed/callback-based federation identity proof and live transport failures; or
- offline-client behavior beyond the tombstone retention window.
Use disposable PostgreSQL in CI, fake provider servers at adapter boundaries, and a small route-level test matrix. Keep live sandbox smoke tests as a separate release check.
Resolution so far: always-on self-checks now run local fake Google and
Microsoft Graph HTTP servers. They cover pagination, partial-page discard after
410, full-set reset cursors, Graph series-master cache reset/reuse and a safe
retry after a transient provider response. The new reset assertion exposed and
fixed a stale-master cache surviving from the discarded delta pages.
Provider/account orchestration is isolated in a dependency-free boundary test: one discovery/account failure does not block healthy siblings, retryable accounts run again in the next cycle, strict connect flows still throw, and provider/account filters stay scoped.
pnpm test:provider-db adds a disposable-PostgreSQL OAuth lifecycle test with a
fake token endpoint. It proves encrypted access/refresh-token rotation,
account-scoped invalid_grant reconnect state, sibling preservation, and a
transient 503 remaining active and succeeding on retry.
Remaining: make disposable PostgreSQL mandatory in CI; add the complete route authentication/authorization/error matrix; cover signed federation identity proof and live transport failures; and define/test client behavior beyond the tombstone retention window. Live provider credentials remain a separate release smoke test.
Resolved P3 — align versions and build reproducibility
Section titled “Resolved P3 — align versions and build reproducibility”Observed before remediation:
- root pins
[email protected], while some package metadata/Dockerfiles still declare pnpm 10; the API image build prepares pnpm 10 but root metadata still makes Corepack download pnpm 11.8.0 duringpnpm install; - root, workspace, docs, and native app versions do not express one clear release version;
- the docs image installs an unpinned global pnpm and the full workspace without a frozen lockfile;
- API image installation also relaxes lockfile reproducibility; and
- the iOS forced-update URL still contains an
<APP_ID>placeholder.
Resolved on 2026-07-23: root package.json is now the only source for the
product version and exact pnpm version. Expo reads that product version
directly; private workspace manifests no longer advertise unrelated versions or
toolchains. Both images use Corepack, filtered production installs, frozen
lockfiles, and the same digest-pinned Node base. pnpm release:verify protects
these invariants, the Docker release job requires its requested version to match
the manifest, and production EAS config rejects a missing/malformed iOS store
URL.
Strengths worth preserving
Section titled “Strengths worth preserving”- Provider-neutral sync contract with provider-specific behavior isolated in adapters.
- Soft tombstones and client delta reconciliation for offline correctness.
- AES-GCM encryption for stored CalDAV credentials and roaming member tokens; only member-token hashes authenticate at origin.
- Registered-route metric labels and structured request correlation.
- Provider cursor reset/full-set sweep semantics are documented in code.
- Backup scripts use restrictive permissions, validate partial dumps, and keep restore isolated.
- Source comments explain several load-bearing client state and recurrence decisions.
Suggested remediation sequence
Section titled “Suggested remediation sequence”- Completed: protect calendar-detail reads and add a non-member regression self-check.
- Completed: make core writes transactional and enforce the remaining uniqueness constraints.
- Completed: establish PR CI and make lint green/enforceable.
- Add a durable provider outbox plus visible delivery health.
- Completed: enforce and document the supported single-replica deployment.
- Completed: scope modern OAuth disconnect cleanup to one account.
- Partially completed: isolate unproved federation identities, add token expiry/rotation/revocation, and minimize previews; first-contact server proof remains.
- Completed: consolidate toolchain, image, and release versioning. Keep
pnpm release:verifyin the required release path.