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

Static Content Site on DigitalOcean

Success

Checks passed16/16
Human steps37 min of a person's time
Security & compliance80/100
Build1m 26s
Deploy2m 18s
Tokens646.6K
Agent cost$0.48
Hosting / month$5.00 always on

Deployed to https://adb-nzg8i-static-kk63g.ondigitalocean.app. Every deployment is deleted after it's verified and reviewed.

Technologies

Runtime
Node.js 22
Frameworks
Node.js http module (built in)
Packaging
Docker (node:22-alpine)App spec (app.yaml)
Hosting
App Platform service (apps-s-1vcpu-0.5gb)DigitalOcean Container Registry (starter)

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

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 deployment is a small zero-dependency Node.js server, running as non-root on DigitalOcean App Platform from a private DOCR image. It has a tight route allowlist, so nothing beyond the app is exposed, and no secrets are involved. The main weaknesses are missing security headers (HSTS, CSP, frame protection), no rate limiting or field length caps on the public contact endpoint, user input written unsanitized to logs, and build inputs referenced by mutable tags rather than pinned digests.

CriterionWeightScoreWhy
Secrets handling 25 9/10 The app needs no secrets and none are in the source, image or deployed files. The DO token was only used through the $DIGITALOCEAN_ACCESS_TOKEN env var and `doctl registry login`. One small point: the deploy transcript printed account metadata (owner email, team UUID) into the logs.
Deployment provenance 15 7/10 The image was built from the reviewed source and pushed to a registry the owner controls. The app has no npm dependencies. But the base image `node:22-alpine` isn't pinned to a digest, the app references the mutable tag `v1`, and doctl was downloaded and installed without checksum verification.
Transport security 15 6/10 HTTPS works with a valid Google Trust Services certificate, and HTTP redirects to HTTPS with a 301. X-Content-Type-Options is set, but HSTS, Content-Security-Policy and frame protection (X-Frame-Options or frame-ancestors) are all missing.
Attack surface 15 10/10 The server uses an explicit allowlist of routes, served from memory, so source files, .env, .git and the Dockerfile all return 404 and traversal attempts return 400. There are no directory listings or stack traces, and the only extra endpoint is a benign /healthz.
Application security 20 7/10 Input validation is solid: it checks types, the body is capped at 64KB, and JSON parse errors are handled. The client uses textContent, so there is no XSS. However, the public POST endpoint has no rate limiting and no per-field length limits, and unsanitized names and emails go into logs, which allows log forging.
Least privilege 10 9/10 The container runs as the non-root `node` user from a minimal Alpine image. The app has no data stores, one small instance and a private DOCR registry. Nothing is needlessly public.

Findings

Cloud resources and cost

Resources the agent created

KindNamePlanList price / monthPricing
appadb-nzg8i-staticadb-nzg8i-static-web: apps-s-1vcpu-0.5gb x 1$5.00adb-nzg8i-static-web: apps-s-1vcpu-0.5gb x 1
registryadb-nzg8i-staticstarter$0.00Container Registry starter tier (one per account)

The agent estimated $5.00 a month.

Code

Code
392 lines in 15 files (JavaScript 192, HTML 156, CSS 36, JSON 25, YAML 16, Docker 8, Markdown 7)
Dependencies
0 npm, 0 Python
Deploy config
Dockerfile .dockerignore app.yaml

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted1m 26s7255.8K$0.316
Deploycompleted2m 18s9390.8K$0.178

The agent's deploy summary

The site is live at https://adb-nzg8i-static-kk63g.ondigitalocean.app, and every requirement in TASK.md works through that URL. Nothing needed a person, so human_steps is empty.

What I deployed:

  • Container registry adb-nzg8i-static: a DigitalOcean registry on the free Starter tier, holding the image adb-nzg8i-static-web:v1.
  • App Platform app adb-nzg8i-static: runs that image as one web service on the smallest paid size (apps-s-1vcpu-0.5gb) in New York. It gets HTTPS automatically, and plain HTTP redirects to HTTPS. The app's configuration is in app.yaml.
  • Cost: about $5/month, all of it for the web service; the registry is free.

App Platform's free static-site option needs a connected GitHub repository, and none is linked to this account. That's why I built a Docker image and ran it as a paid service instead.

What I tested on the public URL:

  • /, /about and /contact return 200 as HTML.
  • Unknown paths, including /about.html, return 404.
  • POST /api/contact returns 200 for valid input. A missing name, a bad email or an empty message each return 400 with an error message.
  • In a headless Chrome browser, filling in and sending the contact form showed the confirmation without a page reload.

No credentials were created, so there is no .env file. The details are recorded in deployment.json.

8 shell commands during deploy
  1. 5s which doctl; doctl version 2>/dev/null; curl -s -H "Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN" https://api.digitalocean.com/v2/registry; echo; curl -s -H "Authorization: Bearer $DIGITALOCEAN_AC
  2. 10s cd /tmp && curl -sL https://github.com/digitalocean/doctl/releases/download/v1.146.0/doctl-1.146.0-linux-amd64.tar.gz | tar xz && sudo mv doctl /usr/local/bin/ && doctl version && doctl registry creat
  3. 29s doctl registry login >/dev/null && IMG=registry.digitalocean.com/adb-nzg8i-static/adb-nzg8i-static-web:v1 && docker build -q --platform linux/amd64 -t $IMG . && docker push -q $IMG && cat > app.yaml <
  4. 1m 29s doctl apps list --format ID,Spec.Name,DefaultIngress,ActiveDeployment.Phase
  5. 1m 32s doctl apps list -o json | python3 -c "import json,sys;[print(a['id'],a['spec']['name'],a.get('live_url'),a.get('active_deployment',{}).get('phase')) for a in json.load(sys.stdin)]"
  6. 1m 37s U=https://adb-nzg8i-static-kk63g.ondigitalocean.app
  7. 1m 45s mkdir -p /tmp/pw && cd /tmp/pw && npm init -y >/dev/null && npm i -s playwright >/dev/null 2>&1 && npx playwright install --with-deps chromium >/dev/null 2>&1; cat > t.js <<'EOF'
  8. 2m 12s cat > deployment.json <<'EOF'

Raw data