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

Static Content Site on Render

Success

Checks passed16/16
Human steps25 min of a person's time
Deploy without a human Failed
Deploy with a human Deployed
Security & compliance72/100
Stack fit81/100
Build1m 34s
Deploy1m 49s
Tokens1.33M
Agent cost$1.00
Hosting / month$0.00 light use$7.13 busy

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

Screenshots

The home page
The home page · https://adb-xbmk6-static-web.onrender.com/
The contact page after the verifier submitted the form
The contact page after the verifier submitted the form · https://adb-xbmk6-static-web.onrender.com/contact

Technologies

Runtime
Node.js 22
Frameworks
Node.js http module (built in)
Packaging
Render Blueprint (render.yaml)
Hosting
Render web service (free, running the prebuilt image node:22-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 / returns HTML page_home
passed GET /about returns HTML page_about
passed GET /contact returns HTML page_contact
passed Home has an <h1> containing 'Northwind Robotics' home_content
passed About has a heading containing 'About' about_content
passed Contact has a form with name, email and message fields contact_form
passed Every page has a <title> and a viewport meta tag head_tags optional
passed Home links to /about and /contact navigation optional
passed POST /api/contact with valid input returns {ok: true} contact_api_valid
passed POST /api/contact rejects a bad email and an empty message with 400 contact_api_validation
passed Unknown paths return 404 not_found
passed Submitting the /contact form in a browser posts to /api/contact without leaving the page browser_contact_form
passed Pages load in a browser without uncaught JavaScript errors browser_no_errors optional

Human steps

Stack fit

The same review rated the stack the agent chose, judged against the app's requirements rather than a preferred stack.

A dependency-free Node web service is a well-reasoned, proportionate choice for three static pages plus a same-origin JSON endpoint. Its platform fit suffers from the Secret Files and image workaround and the free sleeping instance, both departures from the native Node service described in STACK.md.

CriterionWeightScoreWhy
Fit for the job 40 9/10 A single Node server using the built-in http module serves the three static pages and the same-origin /api/contact endpoint exactly as TASK.md requires, with no data store because none is needed.
Proportion 30 9/10 There are zero npm dependencies, one file of server code and plain HTML/CSS/JS. That is about as simple as a static site with one endpoint can be.
Platform fit 30 6/10 STACK.md planned Render's native Node runtime, which is a reasonable fit. The build departed from it: an image-backed service with the code smuggled in as Secret Files, which works against Render's normal Git-based build. It also uses an always-on-style container on a sleeping free tier where static hosting could serve most of the content.

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 Northwind Robotics site is a small, dependency-free Node server with sound input validation and nothing exposed beyond its routes, served over HTTPS with a valid certificate and no leaked secrets. The main weaknesses are in the deployment: the code was pushed into a node:22-alpine container (unpinned tag, running as root) as Render Secret Files rather than built from source control, and this commercial site runs on Render's free tier, which sleeps when idle. The app also sets no security headers.

CriterionWeightScoreWhy
Secrets handling 25 9/10 The app needs no secrets. The Render API key was read from the environment or a .env file that git ignores, and it never shows up in commands, output or deployed files. The only minor leak is the account owner's email address, which was printed in an API response in the deploy transcript.
Deployment provenance 20 5/10 Production runs code that was uploaded as Render Secret Files from the agent's local working tree, not built from a repository the owner controls. The base image is the floating tag node:22-alpine with no digest, so each deploy can pull a different image. Zero npm dependencies limits the supply-chain risk.
Terms compliance 15 5/10 A commercial marketing site runs on Render's free web-service instance. Render positions that tier for hobby and preview use, not production, and it sleeps after 15 idle minutes, which means 30-60 s cold starts for visitors. I'm not aware of Render's terms banning commercial use on the free tier, but it is a poor fit for a business site that should stay up.
Least privilege 15 7/10 There are no data stores and only one public service, which is appropriate. However, the container runs node:22-alpine with its default root user, because the dockerCommand is 'node /etc/secrets/server.js' with no user set.
Transport security 10 7/10 The site is HTTPS-only: HTTP gets a 301 redirect, and the Google Trust Services certificate for onrender.com is valid. The app sets none of HSTS, X-Content-Type-Options, frame protection or CSP, though it could easily have done so in server.js.
Attack surface 10 10/10 Only the explicitly routed pages, assets, /api/contact and a harmless /healthz respond. /.env, /.git/config, /server.js, /package.json and traversal attempts return 404 or 400, with no stack traces.
Application security 5 9/10 The contact endpoint checks types, rejects non-object JSON, validates email, caps the body at 64 KB (returning 413) and stores nothing. The client writes messages with textContent, so there is no XSS path. There is no rate limiting, but abuse has little impact because nothing is stored or sent.

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-xbmk6-static-webfree$0.00free x 1 instance(s)

The agent estimated $0.00 a month.

Code

Code
515 lines in 17 files (JavaScript 211, HTML 157, Shell 116, CSS 31, Markdown 30, JSON 29, YAML 13)
Dependencies
0 npm, 0 Python
Deploy config
render.yaml

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted1m 34s11377.0K$0.379
Deploycompleted1m 49s12666.4K$0.4411

The agent's deploy summary

The site is live at https://adb-xbmk6-static-web.onrender.com and every requirement in TASK.md works through that URL.

What's running: one free Render web service, adb-xbmk6-static-web (srv-db80vqlg1s2s738k8k5g), costing $0 a month. Nothing else was created.

How it's deployed, which is unusual: Render normally builds from a Git repo, but no Git provider was linked to the account and I had no credentials for one. Instead:

  • The service runs the official public node:22-alpine image. It's only pulled from Docker Hub; nothing was pushed to another registry.
  • The app files are uploaded as Render Secret Files and started with node /etc/secrets/server.js.
  • To change the site, edit the files and re-run scripts/deploy-image.sh. It updates the files and redeploys; there's no push-to-deploy.

Checked through the public URL:

  • Pages: /, /about and /contact return 200 text/html with a title, viewport tag and navigation links.
  • 404s: unknown paths and the .html versions of the pages return 404.
  • Contact endpoint: POST /api/contact returns 200 {"ok":true} for valid input, and 400 with an error message for a missing name, bad email or empty message.
  • Form in a browser: in headless Chromium at phone width, the form on /contact showed the confirmation without reloading the page, and the page didn't scroll sideways.

Changes:

  • The server now reads its page directory from a PUBLIC_DIR setting.
  • I renamed 404.html to not-found.html because Secret File names can't start with a digit.
  • The local tests still pass, and the changes are committed.

Limitation: the free plan puts the service to sleep after 15 minutes without traffic, so the first request after that takes about 30–60 seconds.

One optional step is listed in deployment.json: connect a Git repo to Render to get push-to-deploy. render.yaml and scripts/deploy.sh are ready for that, and the site works without it.

11 shell commands during deploy
  1. 7s A="Authorization: Bearer $RENDER_API_KEY"; curl -sS -H "$A" https://api.render.com/v1/owners | jq '[.[].owner|{id,name,type}]'; curl -sS -H "$A" "https://api.render.com/v1/services?limit=50" | jq '[.[
  2. 22s sed -i "s|^const PUBLIC_DIR = path.join(__dirname, 'public');|const PUBLIC_DIR = process.env.PUBLIC_DIR \|\| path.join(__dirname, 'public');|" server.js && grep -n PUBLIC_DIR server.js | head -2 && np
  3. 33s cat > scripts/deploy-image.sh <<'EOF'
  4. 40s sed -i 's|SVC=$(curl -fsS "${auth\[@\]}" -X POST "$API/services" -d "$body" \| jq -r .\.service\.id.)|resp=$(curl -sS "${auth[@]}" -X POST "$API/services" -d "$body"); SVC=$(jq -r ".service.id // empt
  5. 43s A="Authorization: Bearer $RENDER_API_KEY"; curl -sS -H "$A" "https://api.render.com/v1/owners?limit=1" | head -c 200; echo; curl -sS -H "$A" "https://api.render.com/v1/services?name=adb-xbmk6-static-w
  6. 47s A="Authorization: Bearer $RENDER_API_KEY"; S=srv-db80vqlg1s2s738k8k5g; curl -sS -H "$A" "https://api.render.com/v1/services/$S/deploys?limit=3" | jq '[.[].deploy|{id,status,trigger,createdAt}]'; curl
  7. 51s A="Authorization: Bearer $RENDER_API_KEY"; S=srv-db80vqlg1s2s738k8k5g; for f in 404.html styles.css contact.js; do jq -n --rawfile c public/$f '{content:$c}' | curl -sS -H "$A" -H "Content-Type: appli
  8. 56s git mv public/404.html public/not-found.html && sed -i "s|'404.html'|'not-found.html'|" server.js && sed -i 's|--rawfile n public/404.html|--rawfile n public/not-found.html|; s|{name:"404.html",conten
  9. 1m 23s B=https://adb-xbmk6-static-web.onrender.com; scripts/verify.sh $B; for p in /styles.css /contact.js /about.html /index.html; do curl -s -o /dev/null -w "$p %{http_code} %{content_type}\n" $B$p; done;
  10. 1m 29s cd /tmp/pw && sed -i 's|http://localhost:3123|https://adb-xbmk6-static-web.onrender.com|g' t.js && node t.js
  11. 1m 40s cat > deployment.json <<'EOF'

Raw data