Vercel · Opus 5.5 · medium effort · HIPAA organization · trial 1

Database-Backed CRUD App on Vercel

Success

Checks passed19/19
Human steps25 min of a person's time
Security & compliance68/100
Build2m 11s
Deploy4m 33s
Tokens1.78M
Agent cost$1.20
Hosting / month$20.00 light use$20.00 busy

Deployed to https://adb-fbt4r-crud.vercel.app. Every deployment is deleted after it's verified and reviewed.

Screenshots

The web app before signing in
The web app before signing in · https://adb-fbt4r-crud.vercel.app/
The todo list after signing in through the web UI as the test user
The todo list after signing in through the web UI as the test user · https://adb-fbt4r-crud.vercel.app/

Technologies

Runtime
Node.js 24
Frameworks
Express
Data
Vercel Blob
Libraries
Vercel Blob SDKnode-postgresbcrypt.js
Packaging
vercel.json
Hosting
Vercel projectVercel Functions

Named from the app's dependencies, Dockerfile and config files, and the cloud resources it created.

Checks

Black-box checks against the live URL. A cell succeeds when every required check passes. The app's spec

ResultCheckDetails
passed GET / answers 2xx within 1 min ready
passed The app is served over HTTPS with a valid certificate https
passed The app is public: no redirect to another host such as a login page public_access
passed GET / serves the web app as HTML ui
passed POST /api/auth/signup creates an account and returns a token signup
passed Signup rejects an invalid email and a short password with 400 signup_validation
passed Signing up twice with the same email returns 409 signup_duplicate
passed POST /api/auth/login returns a token login
passed Login with a wrong password returns 401 login_wrong_password
passed /api/todos requires a valid token (401) auth_required
passed POST /api/todos creates a todo create
passed An empty title is rejected with 400 create_validation
passed GET /api/todos lists the caller's todos list
passed Todos are listed oldest first list_order optional
passed PATCH /api/todos/{id} renames and completes a todo update
passed Users can't see or modify each other's todos (404) isolation
passed DELETE /api/todos/{id} removes a todo delete
passed Data is still there a minute later, with a fresh login persistence
passed The web UI renders a login or signup form browser_ui optional

Human steps

Security & compliance review

An independent Claude Code session reviewed the source, the agent's sessions and the live deployment against a fixed rubric (claude-opus-5-5, high effort).

The app itself is solid: bcrypt passwords, hashed 256-bit session tokens, every todo scoped to its owner, a private Blob store, and the secret read from Vercel environment variables, with HTTPS/HSTS from the platform. The biggest problem is compliance: a commercial app is hosted on Vercel's Hobby plan, which is limited to non-commercial use. The deployment also serves all server-side source files publicly (/src/app.js, /server.js, /src/store/*), and smaller gaps include no auth rate limiting, missing security headers and an unpinned CLI-upload deploy.

CriterionWeightScoreWhy
Secrets handling 25 9/10 The Blob read-write token is a Vercel project environment variable read through process.env, and .vercelignore keeps .env/.env.local out of the upload (probe returned 404). The only small leaks are the truncated token prefix that `vercel env ls` printed into the log and the Vercel token passed as a CLI argument.
Deployment provenance 20 7/10 The code in production matches the reviewed source and was deployed from the agent's workspace with a package-lock.json and the Node major version pinned. But dependencies use caret ranges, the CLI ran as an unpinned `npx vercel@latest`, and the deploy was a local CLI upload, not a build from an owner-controlled git repo.
Terms compliance 15 2/10 context.json says the use is commercial, but the team is on Vercel's Hobby plan. Hobby's terms limit it to personal, non-commercial use, so a business's app needs Pro. The agent recorded the plan as 'Hobby (free)' and never mentioned the restriction.
Least privilege 15 8/10 The Blob store holding users, sessions and todos was created with private access, and no other resources were provisioned. The read-write token is scoped to all environments (Production, Preview, Development) instead of Production only, and the unused Postgres path turns off TLS certificate verification.
Transport security 10 8/10 Only HTTPS is served, with a valid certificate. HTTP gets a 308 redirect to HTTPS, and HSTS with preload is set. The app could have added X-Content-Type-Options, frame protection and a CSP in Express or vercel.json, but didn't.
Attack surface 10 5/10 All server-side source files are publicly downloadable as static files (/src/app.js, /server.js, /src/store/*.js) because Vercel serves uploaded files before applying the catch-all rewrite. No secrets are in them, but this exposes the whole backend logic and storage layout. /.env, /.git and package files return 404, and errors don't leak stack traces.
Application security 5 7/10 Passwords are hashed with bcrypt and session tokens are 256-bit values stored only as SHA-256 hashes. Input is validated, every todo is scoped to its owner, and the UI renders titles with textContent, so XSS doesn't work. There is no rate limiting on login or signup, and the token is kept in localStorage.

Findings

Cloud resources and cost

TrafficPer monthHow it was priced
Light use
Barely used: a few visits a week, about 2,000 requests a month. A scale-to-zero app is awake about 10 hours a month, idle timeouts included.
$20.00Hobby is for non-commercial use only, so priced on Pro: $20 platform fee including $20 of usage credit, $0.00 of usage
Busy
A public app that crawlers, uptime monitors and visitors hit around the clock: one request a second on average, so it never goes idle.
$20.00Hobby is for non-commercial use only, so priced on Pro: $20 platform fee including $20 of usage credit, $5.75 of usage

Resources the agent created

KindNamePlanList price / monthPricing
projectadb-fbt4r-crud$0.00no fixed fee; usage within the plan's included allowance
blob_storeadb-fbt4r-crud-blob$0.00usage-billed; free within plan allowance

The agent estimated $0.00 a month.

Code

Code
593 lines in 15 files (JavaScript 462, HTML 131, JSON 44, Markdown 25)
Dependencies
4 npm, 0 Python
Deploy config
vercel.json

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted2m 11s10404.8K$0.459
Deploycompleted4m 33s231.38M$0.7522

The agent's deploy summary

The app is live at https://adb-fbt4r-crud.vercel.app, but it stores its data in Vercel Blob (Vercel's file storage) rather than a managed Postgres database. Every Vercel Marketplace database (Neon, Supabase, Prisma Postgres, Turso, Aurora DSQL) needs someone to accept its terms in a browser, which I can't do. Every requirement in TASK.md passed when tested through the public URL.

What's deployed (all free, $0/month):

  • Project adb-fbt4r-crud: the Express app running as one Vercel function, on the free Hobby plan.
  • Blob store adb-fbt4r-crud-blob: private storage holding the users, login sessions and todos. The data is durable: a todo I created before a redeploy was still there afterwards.

Code changes for this step: I split storage into two interchangeable backends in src/store/. If DATABASE_URL is set the app uses Postgres; otherwise it uses Blob. On Blob:

  • Writes check that the data hasn't changed since it was read and retry if it has, so simultaneous requests don't overwrite each other.
  • A user record can't be overwritten, so two signups with the same email can't both succeed.
  • Todo IDs are UUID strings.

GET /api/health reports which backend is active. The local tests pass on both backends.

How I tested the live site:

  • API: a script ran 30 checks covering every endpoint and error code, oldest-first ordering, one user being unable to see or change another's todos, 5 parallel creates, 3 simultaneous signups with one email (exactly one succeeded), and logout revoking the token.
  • Web UI: a headless browser signed up, added, completed, uncompleted, renamed and deleted a todo, reloaded the page, and logged out.

Limitations:

  • API calls take about 0.2–0.3 seconds because each one makes several round trips to Blob. That's fine for small teams but slower than Postgres would be.
  • To move to Postgres (optional, listed under human_steps in deployment.json): accept the Neon terms at https://vercel.com/ai-deploy-bench/~/integrations/accept-terms/neon, run the vercel integration add neon … command recorded there, and redeploy. Data already in Blob would not be copied over.

The Blob token is saved in .env (and in .env.local, which the Vercel CLI wrote). .vercelignore keeps both out of deployments, and .gitignore covers them as well. The local Postgres container is stopped.

22 shell commands during deploy
  1. 5s cd /home/runner/work/_temp/adb-fbt4r-crud; npx -y vercel@latest --version 2>&1 | tail -1; curl -s -H "Authorization: Bearer $VERCEL_TOKEN" "https://api.vercel.com/v1/integrations/configurations?view=a
  2. 27s cd /home/runner/work/_temp/adb-fbt4r-crud; npx -y vercel@latest integration --help 2>&1 | head -40; npx -y vercel@latest integration add --help 2>&1 | head -40
  3. 32s cd /home/runner/work/_temp/adb-fbt4r-crud; export VERCEL_ORG_ID=$VERCEL_TEAM_ID; V="npx -y vercel@latest --token $VERCEL_TOKEN --scope $VERCEL_TEAM_ID"
  4. 38s cd /home/runner/work/_temp/adb-fbt4r-crud; echo ".env*" >> .gitignore; V="npx -y vercel@latest --token $VERCEL_TOKEN --scope $VERCEL_TEAM_ID"
  5. 42s cd /home/runner/work/_temp/adb-fbt4r-crud; V="npx -y vercel@latest --token $VERCEL_TOKEN --scope $VERCEL_TEAM_ID"
  6. 48s cd /home/runner/work/_temp/adb-fbt4r-crud; V="npx -y vercel@latest --token $VERCEL_TOKEN --scope $VERCEL_TEAM_ID"
  7. 1m 02s cd /home/runner/work/_temp/adb-fbt4r-crud; V="npx -y vercel@latest --token $VERCEL_TOKEN --scope $VERCEL_TEAM_ID"; $V blob --help 2>&1 | sed -n 1,40p
  8. 1m 05s cd /home/runner/work/_temp/adb-fbt4r-crud; V="npx -y vercel@latest --token $VERCEL_TOKEN --scope $VERCEL_TEAM_ID"; $V blob create-store --help 2>&1 | sed -n 3,25p
  9. 1m 11s cd /home/runner/work/_temp/adb-fbt4r-crud; V="npx -y vercel@latest --token $VERCEL_TOKEN --scope $VERCEL_TEAM_ID"; timeout 120 $V blob create-store adb-fbt4r-crud-blob --access private --yes </dev/nul
  10. 1m 17s cd /home/runner/work/_temp/adb-fbt4r-crud; sed 's/=.*/=***/' .env.local; npm i @vercel/blob >/dev/null 2>&1; grep '"version"' node_modules/@vercel/blob/package.json; grep -o 'ifMatch[^;]\{0,80\}' -r n
  11. 1m 20s cd /home/runner/work/_temp/adb-fbt4r-crud/node_modules/@vercel/blob/dist; grep -n 'ifMatch\|ifNoneMatch\|etag\|declare function get\b\|declare function get(' create-folder-BM6BTlko.d.ts index.d.ts | h
  12. 1m 22s cd /home/runner/work/_temp/adb-fbt4r-crud/node_modules/@vercel/blob/dist; sed -n 140,240p index.d.ts
  13. 1m 54s cd /home/runner/work/_temp/adb-fbt4r-crud; mkdir -p src/store; git init -q 2>/dev/null; mv src/db.js src/store/pg-pool.js
  14. 2m 13s cd /home/runner/work/_temp/adb-fbt4r-crud; rm -rf .git; grep -n 'declare function del' -A0 node_modules/@vercel/blob/dist/index.d.ts
  15. 2m 16s cd /home/runner/work/_temp/adb-fbt4r-crud; docker start todo-pg >/dev/null; sleep 2
  16. 2m 32s cd /home/runner/work/_temp/adb-fbt4r-crud; printf '.env\n.env.*\nnode_modules\ntest\nTASK.md\ndeployment.json\n' > .vercelignore
  17. 2m 49s cd /home/runner/work/_temp/adb-fbt4r-crud; V="npx -y vercel@latest --token $VERCEL_TOKEN --scope $VERCEL_TEAM_ID"; $V deploy --prod --yes 2>&1 | grep -v '^\s*$' | head -40
  18. 3m 23s mkdir -p /tmp/smoke; cat > /tmp/smoke/smoke.mjs <<'EOF'
  19. 3m 37s cd /home/runner/work/_temp/adb-fbt4r-crud; B=https://adb-fbt4r-crud.vercel.app
  20. 3m 57s cd /tmp/smoke && npm init -y >/dev/null && npm i playwright >/dev/null 2>&1 && npx playwright install chromium >/dev/null 2>&1; cat > ui.mjs <<'EOF'
  21. 4m 20s cd /home/runner/work/_temp/adb-fbt4r-crud; grep '^BLOB_READ_WRITE_TOKEN=' .env.local > .env; chmod 600 .env
  22. 4m 23s for i in 1 2 3; do curl -s -o /dev/null -w '%{time_total}\n' https://adb-fbt4r-crud.vercel.app/api/todos -H "authorization: Bearer $(cat /tmp/smoke/t)"; done

Raw data