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

Data / Visualization App on Render

Success

Checks passed12/12
Human steps25 min of a person's time
Security & compliance74/100
Build2m 16s
Deploy2m 21s
Tokens1.50M
Agent cost$0.93
Hosting / month$0.00 light use$7.13 busy

Deployed to https://adb-j6usb-dataviz-web.onrender.com. Every deployment is deleted after it's verified and reviewed.

Screenshots

The dashboard
The dashboard · https://adb-j6usb-dataviz-web.onrender.com/

Technologies

Runtime
Node.js 24
Frameworks
Express
Packaging
Render Blueprint (render.yaml)
Hosting
Render web service (free, running the prebuilt image node:24-alpine)

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 dashboard as HTML dashboard
passed GET /api/summary (no filters) matches the dataset summary_all
passed GET /api/summary (region=West) matches the dataset summary_region
passed GET /api/summary (category=Electronics) matches the dataset summary_category
passed GET /api/summary (region=East, category=Furniture, start=2025-03-01, end=2025-06-30) matches the dataset summary_combined
passed GET /api/summary (region=Atlantis) matches the dataset summary_empty
passed The dashboard renders a chart (SVG or canvas) in a browser browser_chart
passed The dashboard shows at least two charts browser_two_charts optional
passed The dashboard has filter controls browser_filters 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 sales dashboard is a small, read-only Express app with clean input handling. No secrets leaked and nothing beyond the app is exposed: HTTPS is enforced, and source, .env and .git paths return 404. The main weaknesses are in how it was deployed: the code was shipped as a base64 tarball in a Render secret file on a floating node:24-alpine tag, with npm ci at every start and the container running as root. The business app also sits on Render's free plan, which sleeps when idle and isn't meant for production; security headers and rate limiting are missing too.

CriterionWeightScoreWhy
Secrets handling 25 10/10 The Render API key was only ever used as $RENDER_API_KEY in curl headers and never printed. The app needs no secrets, and the bundle, transcripts and live site show no credentials.
Deployment provenance 20 5/10 The running code is the bundle the agent built, but it was shipped as a base64 tarball in a Render 'secret file', not from a version-controlled repo. It runs on the floating node:24-alpine tag, and npm ci pulls dependencies from the public registry on every container start.
Terms compliance 15 5/10 A business's dashboard (use=commercial) is hosted on Render's free web-service plan. To my knowledge Render doesn't explicitly forbid commercial use on that plan, but it markets free instances for testing and hobby work, not production. They also spin down after about 15 minutes idle, causing 30-60 s cold starts that the agent itself acknowledged.
Least privilege 15 7/10 There is only one public web service and no data stores or extra resources. However, the container runs the stock node image as root, and the start script never drops to the built-in 'node' user.
Transport security 10 8/10 HTTPS works with a valid certificate, and HTTP gets a 301 redirect to HTTPS. Responses carry no HSTS, X-Content-Type-Options, frame-protection or CSP headers, which the Express app could easily have set.
Attack surface 10 9/10 Only the public/ directory is served statically. Source files, package files, .env, .git, the CSV and the secret files all return 404, and error responses contain no stack traces. The only extras are a benign /healthz and /api/meta.
Application security 5 8/10 Date parameters are validated (400 on a bad format), non-string query values are ignored, page size is capped at 500, and table cells are rendered with textContent. There is no rate limiting on the public API, and the tooltip uses innerHTML (with trusted CSV data only).

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.
$0.00fixed monthly prices; 0.04 GB out of the Hobby workspace's 5 GB; free instances awake 10 h of 750 free hours
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.
$7.13fixed monthly prices; 52.6 GB out of the Hobby workspace's 5 GB, 47.6 GB billed (with a payment method on file; without one Render suspends the services instead); free instances awake 730 h of 750 free hours

Resources the agent created

KindNamePlanList price / monthPricing
serviceadb-j6usb-dataviz-webfree$0.00free x 1 instance(s)

The agent estimated $0.00 a month.

Code

Code
439 lines in 11 files (JavaScript 320, CSS 62, HTML 57, JSON 34, Markdown 13, YAML 8)
Dependencies
1 npm, 0 Python
Deploy config
render.yaml

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted2m 16s15582.8K$0.5311
Deploycompleted2m 21s15921.8K$0.4012

The agent's deploy summary

The sales dashboard is live at https://adb-j6usb-dataviz-web.onrender.com on Render's free plan, so it costs $0 a month. I tested every TASK.md requirement through that URL and they all work.

What I checked on the live site:

  • Dashboard: / loads, and a browser screenshot with filters applied showed the KPIs, both charts and the orders table updating to match.
  • /api/summary: with no filters it returns all 2,400 orders and $1,839,455.69. With all four filters combined, the result exactly matches the same calculation run locally. A filter that matches nothing returns zeros and empty arrays.

How it's deployed: there were no GitHub or other Git credentials, so I couldn't deploy from a repository the normal way.

  • I created one web service, adb-j6usb-dataviz-web (id srv-db7eqru7bikc73akg0og), that runs the public Node.js image (node:24-alpine).
  • The app code is packed into a small archive (about 65 KB, no secrets) and stored as a Render "secret file". A second secret file, start.sh, unpacks it, installs dependencies and starts the server.

What didn't work at first:

  • The API rejected my first service request because I used the wrong field name for the image.
  • The first deploy then failed because Render didn't run the quoted startup command as written. Putting the startup steps in start.sh fixed it.

Limitations:

  • Updating the code: you have to re-upload the archive through the Render API and start a new deploy. Pushing a commit won't redeploy anything.
  • Cold starts: the free plan sleeps after about 15 minutes without traffic. The first request after that takes around 30–60 seconds.

deployment.json: it's written. Its one human_steps entry is optional, not blocking: push the project to GitHub or GitLab and connect it to Render, to get normal builds and auto-deploys. I didn't create any credentials, so there's nothing in .env. I added .env to .gitignore.

Sources:

12 shell commands during deploy
  1. 5s cd /home/runner/work/_temp/adb-j6usb-dataviz; env | cut -d= -f1 | grep -iE 'git|render|token' ; git status --short | head; git remote -v; curl -s -H "Authorization: Bearer $RENDER_API_KEY" https://api
  2. 28s cd /home/runner/work/_temp/adb-j6usb-dataviz; rm -rf /tmp/bundle && mkdir /tmp/bundle; npm ci --omit=dev >/dev/null 2>&1; tar czf /tmp/bundle/app.tgz app.js data.js package.json package-lock.json data
  3. 32s cd /home/runner/work/_temp/adb-j6usb-dataviz; tar czf /tmp/bundle/app.tgz app.js data.js package.json package-lock.json data public; base64 -w0 /tmp/bundle/app.tgz > /tmp/bundle/app.tgz.b64; ls -la /t
  4. 39s cd /tmp/bundle; python3 - <<'EOF' > /tmp/bundle/create.json
  5. 43s cd /tmp/bundle; for img in "docker.io/library/node:24-alpine" "node:24-alpine"; do sed -i "s#\"imageUrl\": \"[^\"]*\"#\"imageUrl\": \"$img\"#" create.json; grep -o '"imageUrl": "[^"]*"' create.json; c
  6. 48s cd /tmp/bundle; sed -i 's#"imageUrl": "[^"]*"#"imagePath": "docker.io/library/node:24-alpine"#' create.json; curl -s -X POST -H "Authorization: Bearer $RENDER_API_KEY" -H "Content-Type: application/js
  7. 53s for i in $(seq 1 40); do s=$(curl -s -H "Authorization: Bearer $RENDER_API_KEY" https://api.render.com/v1/services/srv-db7eqru7bikc73akg0og/deploys/dep-db7eqs67bikc73akg1j0 | python3 -c "import json,s
  8. 1m 11s curl -s -H "Authorization: Bearer $RENDER_API_KEY" "https://api.render.com/v1/logs?ownerId=tea-db6fig0473hc73eb9aeg&resource=srv-db7eqru7bikc73akg0og&limit=40&direction=backward" | python3 -c "import
  9. 1m 20s cat > /tmp/bundle/start.sh <<'EOF'
  10. 1m 57s U=https://adb-j6usb-dataviz-web.onrender.com
  11. 2m 01s cd /tmp; chromium --headless --no-sandbox --disable-gpu --window-size=1280,900 --virtual-time-budget=5000 --screenshot=/tmp/live.png 'https://adb-j6usb-dataviz-web.onrender.com/?category=Furniture&sta
  12. 2m 13s cd /home/runner/work/_temp/adb-j6usb-dataviz; printf 'node_modules\n.env\n' > .gitignore

Raw data