Skip to content

#563 Cleanup Orphaned Blob Is An Unauthenticated Server Action - #595

Open
b-at-neu wants to merge 1 commit into
devfrom
563-cleanup-orphaned-blob-unauthenticated-action
Open

#563 Cleanup Orphaned Blob Is An Unauthenticated Server Action#595
b-at-neu wants to merge 1 commit into
devfrom
563-cleanup-orphaned-blob-unauthenticated-action

Conversation

@b-at-neu

Copy link
Copy Markdown
Collaborator

Closes #563

Summary

cleanupOrphanedBlob was 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 — new import 'server-only' module (no 'use server'), sitting next to lib/prisma.ts and lib/email/resend.ts. Exports countAnswerFileReferences(url) (split out for testability) and cleanupOrphanedBlob(url), same reference-count-then-del logic as before, same best-effort error swallow.
  • prisma/actions/question-files.ts — removed cleanupOrphanedBlob; imports it from @/lib/blobs instead. The del import for the upload rollback path is unchanged.
  • prisma/actions/applications.ts — repointed the cleanupOrphanedBlob import from @/prisma/actions/question-files to @/lib/blobs; call site in deleteDraftApplication is unchanged.
  • eslint.config.mjs — added lib/blobs.ts to the no-restricted-imports ignore list for @/lib/prisma, alongside lib/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. Covers countAnswerFileReferences against 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 the cleanupOrphanedBlob row 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

  • On the preview deploy, upload a PDF to a file_upload question 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.
  • Upload a file on /positions/[id]/apply, then Remove it — toast "File removed", field empty, blob deleted.
  • Answer the same global file_upload question 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).
  • Delete a draft application that has a file answer — the draft disappears from /my-applications and its blob is gone.
  • Open DevTools → Network on any file upload and confirm the request still succeeds (the moved helper didn't break the action's module graph).
  • Confirm cleanupOrphanedBlob is no longer reachable as a Server Action (not exported from any 'use server' module) — grep -rn "cleanupOrphanedBlob" prisma/actions/ returns no matches.
  • Confirm every remaining export of prisma/actions/question-files.ts (uploadQuestionFileAnswer, removeQuestionFileAnswer, downloadQuestionFileAnswer) still opens with getCurrentUser().

Automated checks

  • npm run prettier:check — pass
  • npm run eslint:check — pass
  • npm run tsc:check — pass
  • npm run test:unit — 227 passed (local Postgres unavailable in this worktree; tests/db/blob-cleanup.test.ts will run in CI against the db project)

Notes

  • Added lib/blobs.ts to the no-restricted-imports ignore list in eslint.config.mjs — this wasn't spelled out in the plan, but it's a direct consequence of the plan's own design (a new import 'server-only' module in lib/ reading Prisma directly, same category as lib/prisma.ts/lib/email/resend.ts, which were already exempted from that rule).
  • docs/WORKFLOWS.md AP-8 still names cleanupOrphanedBlob by function name only (no path reference), so it stays accurate and was left untouched, per the plan.

@b-at-neu b-at-neu added the claude Will be worked on by Claude label Aug 24, 2026
@b-at-neu b-at-neu self-assigned this Aug 24, 2026
@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aplio Ready Ready Preview Aug 24, 2026 9:52pm

@b-at-neu b-at-neu added ready for review PR ready for review agent reviewing Review agent working (in-flight) and removed ready for review PR ready for review agent labels Aug 24, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Cycle 1 · approved

0 open — clean

@b-at-neu b-at-neu added approved Review passed, ready to merge needs revision Review found issues that need fixing revising Revise agent working (in-flight) and removed reviewing Review agent working (in-flight) approved Review passed, ready to merge needs revision Review found issues that need fixing labels Aug 24, 2026
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>
@b-at-neu
b-at-neu force-pushed the 563-cleanup-orphaned-blob-unauthenticated-action branch from 0f5f01a to 936fc26 Compare August 24, 2026 22:10
@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deployment failed for project aplio with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/sga-operational-affairs-projects?upgradeToPro=build-rate-limit

@b-at-neu

Copy link
Copy Markdown
Collaborator Author

Revision — Cycle 1

no unresolved findings at dispatch (last review: Cycle 1 · approved · 0 open, no inline threads) · rebased onto dev only · 936fc26
· rebase: docs/PERMISSIONS.md (non-overlapping deviation-table rows — kept dev's new #567 row, kept this PR's removal of the #563 row)

@b-at-neu b-at-neu added ready for review PR ready for review agent reviewing Review agent working (in-flight) and removed revising Revise agent working (in-flight) ready for review PR ready for review agent labels Aug 24, 2026

@b-at-neu b-at-neu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@b-at-neu b-at-neu added needs revision Review found issues that need fixing and removed reviewing Review agent working (in-flight) labels Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude Will be worked on by Claude needs revision Review found issues that need fixing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cleanupOrphanedBlob is an unauthenticated server action

1 participant