Skip to content

transactional post writes + safe cache keys

jedmund requested to merge jedmund/arch-audit-tx-cache into main

Created by: jedmund

Summary

Architecture audit A-6 and A-7 — two isolated integrity/security fixes.

A-6 · Transactional post writes

POST /api/posts, PUT /api/posts/[id], and DELETE /api/posts/[id] all did multi-step writes (post row + media-usage bookkeeping) without a transaction, with the media-usage block wrapped in a silent try/catch that warned and continued. The failure mode was exactly what A-6 flags: post updated, media usage left stale — orphaned MediaUsage rows.

Now wrapped in prisma.$transaction:

  • trackMediaUsage() / removeMediaUsage() accept an optional Prisma.TransactionClient
  • Post create/update/delete handlers pass the tx through so all writes commit atomically
  • Silent catch removed — media-usage failures now roll back the post write (this is the intended behavior; the previous catch was the bug)

A-7 · Redis cache key escaping

User-supplied query params were concatenated directly into Redis keys (e.g. `tags:list:${search}`). A search term containing `:` could collide with a structurally different key, poisoning the cache or breaking wildcard invalidation (`redis.keys('tags:list:*')`).

  • New `src/lib/server/cache-keys.ts` with `safeKey()` — base64url-encodes any user-supplied segment
  • Applied to the public `/api/og-metadata` (highest attack surface — unauthenticated, URL param), `/api/tags` search, `/api/tags/suggest` query, and all 6 `/api/admin/garden/search/*` routes
  • Key prefixes/structure preserved so wildcard patterns keep working

Test plan

  • `pnpm check` — baseline 116 errors / 9 warnings, no new ones
  • Edit a post with attachments → verify DB: post row updated AND `MediaUsage` rows match the new attachment set
  • Force a media-usage failure (e.g. temporarily break the upsert call) → post row should NOT update
  • Delete a post → `MediaUsage` rows gone in same transaction
  • Search tags with `?search=foo:bar:baz` → no Redis key collision (inspect `redis-cli KEYS 'tags:list:*'` — final segment is base64url)
  • `/api/og-metadata?url=https://example.com\` still caches and returns consistently

Merge request reports

Loading