#563 Cleanup Orphaned Blob Is An Unauthenticated Server Action - #595
#563 Cleanup Orphaned Blob Is An Unauthenticated Server Action#595b-at-neu wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · approved
0 open — clean
It had no auth guard and was reachable as a Server Action by anyone holding its action id. Moves it to lib/blobs.ts (import 'server-only', no 'use server'), leaving the security boundary at the two callers, which already authenticate. Splits out countAnswerFileReferences so the reference count is testable without @vercel/blob. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0f5f01a to
936fc26
Compare
|
Deployment failed for project aplio with the following error: Learn More: https://vercel.com/sga-operational-affairs-projects?upgradeToPro=build-rate-limit |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 2 · needs revision
1 open — 1 🟡 Low
R2-L1 🟡 Low — Vercel deployment is red while run-neon-check is green, so quota doesn't explain it; per policy that's a finding, not a dismissal. This diff (import move + new internal helper module, no config/build changes) doesn't plausibly cause it, and run-build-check/run-tsc-check/run-linting-check are all green, so this looks like Vercel's own plan-level build-rate-limit (see the check's link: upgradeToPro=build-rate-limit) rather than something this PR broke. Fix: confirm the preview deploys once Vercel capacity frees up before merge; no code change expected.
Closes #563
Summary
cleanupOrphanedBlobwas exported from a'use server'module (prisma/actions/question-files.ts) with no auth guard, making it a callable Server Action reachable by anyone holding its action id. It moves to a new internal helper module,lib/blobs.ts, which is not'use server'— the security boundary is now the two authenticated actions that already call it.Changes
lib/blobs.ts— newimport 'server-only'module (no'use server'), sitting next tolib/prisma.tsandlib/email/resend.ts. ExportscountAnswerFileReferences(url)(split out for testability) andcleanupOrphanedBlob(url), same reference-count-then-dellogic as before, same best-effort error swallow.prisma/actions/question-files.ts— removedcleanupOrphanedBlob; imports it from@/lib/blobsinstead. Thedelimport for the upload rollback path is unchanged.prisma/actions/applications.ts— repointed thecleanupOrphanedBlobimport from@/prisma/actions/question-filesto@/lib/blobs; call site indeleteDraftApplicationis unchanged.eslint.config.mjs— addedlib/blobs.tsto theno-restricted-importsignore list for@/lib/prisma, alongsidelib/prisma.ts,lib/auth/**,lib/email/**(the existing allowlist of server-only lib modules permitted to touch Prisma directly).tests/db/blob-cleanup.test.ts— new. CoverscountAnswerFileReferencesagainst real Postgres: a single profile-answer reference counts 1, a URL referenced from both a global application answer and a position application answer counts 2 (proves all three tables are summed), and an unreferenced URL counts 0.docs/PERMISSIONS.md— dropped thecleanupOrphanedBlobrow from the server-action table and the cleanupOrphanedBlob is an unauthenticated server action #563 row from Known-open deviations (it's no longer an action).No Prisma schema change. No behavior change for end users — same reference-count-then-delete logic, same silent swallow on failure.
Testing plan
file_uploadquestion on/profile, then upload a second file to the same question — toast "File uploaded", the field shows the new file, and the first blob is gone from Vercel Blob storage./positions/[id]/apply, then Remove it — toast "File removed", field empty, blob deleted.file_uploadquestion on/profile, start an application that inherits it, then remove it from the application only — the profile's file still downloads (the shared blob survives because a reference remains)./my-applicationsand its blob is gone.cleanupOrphanedBlobis no longer reachable as a Server Action (not exported from any'use server'module) —grep -rn "cleanupOrphanedBlob" prisma/actions/returns no matches.prisma/actions/question-files.ts(uploadQuestionFileAnswer,removeQuestionFileAnswer,downloadQuestionFileAnswer) still opens withgetCurrentUser().Automated checks
npm run prettier:check— passnpm run eslint:check— passnpm run tsc:check— passnpm run test:unit— 227 passed (local Postgres unavailable in this worktree;tests/db/blob-cleanup.test.tswill run in CI against thedbproject)Notes
lib/blobs.tsto theno-restricted-importsignore list ineslint.config.mjs— this wasn't spelled out in the plan, but it's a direct consequence of the plan's own design (a newimport 'server-only'module inlib/reading Prisma directly, same category aslib/prisma.ts/lib/email/resend.ts, which were already exempted from that rule).docs/WORKFLOWS.mdAP-8 still namescleanupOrphanedBlobby function name only (no path reference), so it stays accurate and was left untouched, per the plan.