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 & compliance91/100
Build1m 45s
Deploy1m 54s
Tokens732.6K
Agent cost$0.51
Hosting / month$5.00 light use$5.00 busy

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

Screenshots

The home page
The home page · https://adb-fe53v-static-rvzm5.ondigitalocean.app/
The contact page after the verifier submitted the form
The contact page after the verifier submitted the form · https://adb-fe53v-static-rvzm5.ondigitalocean.app/contact

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 Northwind Robotics site is a dependency-free Node.js container running as non-root on a paid, always-on DigitalOcean App Platform service, which fits commercial use. It handles no secrets, serves only allow-listed paths over valid HTTPS with an HTTP-to-HTTPS redirect, and validates contact input with a body size cap. The weaknesses are minor: unpinned build inputs (floating base image tag, a mutable `v1` deploy tag, doctl installed without checksum checks), no security headers set by the app, and no rate limiting on the public contact endpoint.

CriterionWeightScoreWhy
Secrets handling 25 10/10 The app needs no secrets and none appear in the source, image or deployed files. The DigitalOcean token was only used through the $DIGITALOCEAN_ACCESS_TOKEN environment variable and doctl's own login, and it was never echoed.
Deployment provenance 20 7/10 The image was built from the reviewed source with no npm dependencies and pushed to the owner's private DOCR registry. But the base image is an unpinned floating tag, the app runs a mutable `v1` tag rather than a digest, and doctl was downloaded without checksum verification.
Terms compliance 15 10/10 A commercial marketing site runs on a paid, always-on App Platform service ($5/mo basic instance) and the DOCR Starter tier. As far as I know, DigitalOcean's standard terms allow commercial use on both, and neither sleeps or expires.
Least privilege 15 10/10 There is a single public web service with no data store. The container runs as the non-root `node` user, and the image registry is private. Nothing unnecessary is exposed.
Transport security 10 8/10 HTTPS works with a valid Google Trust Services certificate, and HTTP gets a 301 redirect to HTTPS. The app sets no HSTS, X-Content-Type-Options, frame-protection or CSP headers, though it easily could have.
Attack surface 10 10/10 Probes for .env, .git, server.js, package.json, Dockerfile, app.yaml, .html variants and path traversal all returned 404 or 400. Only the allow-listed pages and assets are served, and errors return clean JSON or HTML with no stack traces.
Application security 5 8/10 Contact input is checked for type and format, bodies are capped at 64 KB (413), and the client shows responses with textContent, so there is no XSS. The public endpoint has no rate limiting or bot protection, and submitter emails are written to the logs.

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.
$5.00fixed monthly prices; 0.0373 GiB out of a 50 GiB allowance
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.
$5.00fixed monthly prices; 49 GiB out of a 50 GiB allowance

Resources the agent created

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

The agent estimated $5.00 a month.

Code

Code
391 lines in 15 files (JavaScript 181, HTML 165, CSS 36, JSON 24, YAML 15, Docker 9, Markdown 8)
Dependencies
0 npm, 0 Python
Deploy config
Dockerfile .dockerignore app.yaml

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted1m 45s9335.2K$0.338
Deploycompleted1m 54s9397.5K$0.188

The agent's deploy summary

The site is live over HTTPS at https://adb-fe53v-static-rvzm5.ondigitalocean.app, and every requirement in TASK.md passed when I tested it through that URL.

What's deployed:

  • App Platform app adb-fe53v-static: one web service, adb-fe53v-static-web, on the smallest plan (apps-s-1vcpu-0.5gb, about $5/month). It runs the Docker image and uses the platform's own ondigitalocean.app domain and HTTPS certificate.
  • Container Registry adb-fe53v-static: free Starter tier, holding the image adb-fe53v-static-web:v1. I deployed from an image because no Git account is connected.
  • Estimated cost: about $5/month. A free App Platform static site can't run the /api/contact endpoint, so the $5 service is the cheapest setup that fits.

What I checked on the public URL:

  • /, /about and /contact return 200 with text/html, and have the required title, viewport tag, headings and form fields.
  • /missing and /about.html return 404.
  • POST /api/contact returns 200 {"ok":true} for valid input, and 400 with a reason for a missing name, a bad email or an empty message.
  • In headless Chrome, submitting the form on /contact showed the confirmation message without reloading the page.

Files added: app.yaml is the App Platform spec, and deployment.json has the resource IDs and redeploy steps.

No steps need a person. I didn't create any credentials, so there is no .env. The app went into the account's default DigitalOcean project, since I didn't create a named one. Contact submissions are only logged, not stored.

8 shell commands during deploy
  1. 7s which doctl; curl -s -H "Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN" https://api.digitalocean.com/v2/registry; echo; curl -s -H "Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN" https://api.dig
  2. 12s 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 2>&1 && docker build -q --platform linux/amd64 -t registry.digitalocean.com/adb-fe53v-static/adb-fe53v-static-web:v1 . && docker push -q registry.digitalocean.com/adb-f
  4. 1m 10s doctl apps list --format ID,Spec.Name,DefaultIngress,ActiveDeployment.Phase
  5. 1m 13s doctl apps list -o json | python3 -c "import json,sys;[print(a['id'],a['spec']['name'],a.get('default_ingress'),a.get('active_deployment',{}).get('phase'),a.get('project_id')) for a in json.load(sys.s
  6. 1m 18s U=https://adb-fe53v-static-rvzm5.ondigitalocean.app
  7. 1m 28s mkdir -p /tmp/pw && cd /tmp/pw && npm init -y >/dev/null && npm i -s playwright-core >/dev/null 2>&1 && cat > t.js <<'EOF'
  8. 1m 48s cat > deployment.json <<'EOF'

Raw data