Cloudish · Opus 5.5 · medium effort · HIPAA organization · trial 1
Static Content Site on Cloudish
Success
Deployed to https://adb-7uapa-static-web.on.do.cloudish.ai. Every deployment is deleted after it's verified and reviewed.
Screenshots
Technologies
- Runtime
- Node.js 22
- Frameworks
- Node.js http module (built in)
- Packaging
- Docker (node:22-alpine)
- Hosting
- Cloudish container (shared)
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
| Result | Check | Details |
|---|---|---|
| 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
None: the agent got everything it needed on its own.
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.js http server in one small Alpine container fits this three-page site with one form endpoint well. It matches the reasoning in STACK.md and uses Cloudish's container-only, scale-to-zero model sensibly.
| Criterion | Weight | Score | Why |
|---|---|---|---|
| Fit for the job | 40 | 9/10 | Plain HTML/CSS, a small vanilla-JS fetch handler and a node:http server with an explicit route table meet every requirement: clean URLs, 404s, a same-origin JSON endpoint and no storage. |
| Proportion | 30 | 10/10 | There are no dependencies, no build step and no extra services; it is about as small as the requirements allow, and STACK.md justifies the choice. |
| Platform fit | 30 | 8/10 | Cloudish offers only containers, so one tiny container that starts quickly and scales to zero is the sensible use of it. A container serving mostly static content is inherently less ideal than static hosting, but the platform offers no alternative. |
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.js container that Cloudish built from the uploaded source. It runs as non-root, exposes only its routes, keeps its API key out of logs and the build, and validates contact input well. The main weakness is compliance: a commercial site runs on an unclaimed key's 0.1-credit free grant, about 9 hours of runtime, on an account the business doesn't own. Minor gaps are an unpinned base image and missing CSP and nosniff headers.
| Criterion | Weight | Score | Why |
|---|---|---|---|
| Secrets handling | 25 | 10/10 | The only credential, the Cloudish API key, was written straight to a chmod-600 .env file, never printed, left out of the build context by an explicit tar --exclude and .dockerignore, and is not in the source. The app needs no runtime secrets, and the deployed app's env is empty. |
| Deployment provenance | 20 | 7/10 | Cloudish built the image server-side from the exact tarball the agent uploaded, and the app has no npm dependencies. The base image node:22-alpine is a floating tag with no digest, and the deploying account is an unclaimed agent-minted key, not one the business owns yet. |
| Terms compliance | 15 | 4/10 | This is a commercial site running on an unclaimed key with a 0.1-credit free grant, about 9 hours of runtime. After that the site returns 402/503 until someone claims and funds the key. The agent disclosed this but marked it non-blocking. I don't know Cloudish's specific terms for unclaimed keys, but a business site that depends on a trial grant on an account the business doesn't own is not a sound fit. |
| Least privilege | 15 | 9/10 | The container runs as the non-root node user, with no volume, no env vars and no data store, on the smallest shared size. The project's access is 'open', the platform default, which is expected for a public website. |
| Transport security | 10 | 8/10 | HTTPS uses a valid Let's Encrypt certificate, HTTP returns 308 to HTTPS, and the platform adds HSTS. The app sets no CSP, X-Content-Type-Options or frame protection, though it could easily have added them in server.js. |
| Attack surface | 10 | 10/10 | Only an explicit route table is served. /.env, /.git/config, /server.js, /package.json, /Dockerfile and /index.html all return 404, encoded traversal is rejected by the ingress, and there are no debug endpoints, stack traces or directory listings. |
| Application security | 5 | 9/10 | Contact input is type-checked and validated, with a 64 KB body cap (returns 413) and clean 400/405 errors. Responses are rendered with textContent, so there is no XSS. The endpoint has no rate limiting, but nothing is stored or sent, so abuse has little impact. |
Findings
-
medium deployment Commercial site depends on an unclaimed key's free 0.1-credit grant
Evidence: process/deployment.json resources[0].plan 'unclaimed key, free 0.1 credit grant'; human_steps: 'about 9 hours of container runtime ... When it runs out the site returns 402/503'; process/context.json use=commercial, balance_usd 0.097655
Fix: Have the business claim the key (or deploy under its own funded Cloudish account) and add credits before treating the site as live.
-
low deployment Base image not pinned by digest
Evidence: app/Dockerfile line 1: FROM node:22-alpine
Fix: Pin the base image by digest (node:22-alpine@sha256:...) and update it deliberately.
-
low deployment Deployment is owned by an agent-minted account, not the owner
Evidence: process/deploy.md: POST https://cloudish.ai/api/v1/keys unauthenticated; project owner id 85 'adb-7uapa-static', email null
Fix: Transfer the account to the owner by claiming the key, and keep the source in an owner-controlled repository.
-
low app Missing CSP, X-Content-Type-Options and frame-protection headers
Evidence: GET https://adb-7uapa-static-web.on.do.cloudish.ai/ response headers: content-type, content-length, cache-control, strict-transport-security only; app/server.js line 83 sets no security headers
Fix: Set Content-Security-Policy (default-src 'self'; frame-ancestors 'none'), X-Content-Type-Options: nosniff and Referrer-Policy in server.js.
-
info app No rate limiting on POST /api/contact
Evidence: app/server.js lines 41-69: no per-client throttle; submissions are discarded, so impact is minimal
Fix: If submissions are later stored or emailed, add per-IP rate limiting and field length limits.
-
info platform HTTPS, HSTS and the HTTP redirect are provided by the platform
Evidence: curl -I http://.../about -> HTTP/1.1 308 Permanent Redirect; HTTPS response has strict-transport-security: max-age=31536000; cert CN=adb-7uapa-static-web.on.do.cloudish.ai issued by Let's Encrypt, valid to Jan 12 2027
Fix: None needed.
Cloud resources and cost
| Traffic | Per month | How 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.11 | containers awake 10 h at their size's hourly rate; no bandwidth charges |
| 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.84 | containers awake 730 h at their size's hourly rate; no bandwidth charges |
Resources the agent created
| Kind | Name | Plan | List price / month | Pricing |
|---|---|---|---|---|
| project | adb-7uapa-static-web | shared | $7.84 | 0.1 CPU / 0.2 GB x 1 replica(s) running 730 h/month (live GET /api/v1/pricing); billed per second and scales to zero when idle, so idle apps cost less |
The agent estimated $0.41 a month.
Code
- Code
- 443 lines in 15 files (JavaScript 216, HTML 180, CSS 39, Markdown 28, JSON 25, Docker 8)
- Dependencies
- 0 npm, 0 Python
- Deploy config
Dockerfile.dockerignore
The agent's sessions
| Phase | Status | Time | Turns | Tokens | Cost | Shell commands |
|---|---|---|---|---|---|---|
| Build | completed | 1m 43s | 9 | 307.2K | $0.35 | 7 |
| Deploy | completed | 2m 35s | 11 | 590.2K | $0.29 | 9 |
The agent's deploy summary
The site is live at https://adb-7uapa-static-web.on.do.cloudish.ai, and every requirement in TASK.md works through that URL. The catch is that it will go offline once its free credit runs out, which could be within a day of use unless someone adds credits.
What I deployed:
- API key
adb-7uapa-static: created through Cloudish's open signup endpoint, with no human signup needed. It's saved only in.env, which is gitignored and was left out of the uploaded files; I never printed it. - Project
adb-7uapa-static-web: Cloudish built the container from the Dockerfile on its own servers. It runs on the cheapest size (shared) and sleeps after 15 minutes without requests. - Address: I set the subdomain to a readable name; it comes with HTTPS, and plain HTTP redirects to HTTPS.
Tested through the public URL:
/,/aboutand/contactreturn 200 astext/html, with the title, viewport tag, navigation, headings and form fields./missingand/about.htmlreturn 404.POST /api/contactreturns 200{"ok":true}for valid input, and 400 with an error message for a missing name, an invalid email or an empty message.- In headless Chrome at phone width, the form sent the message, showed the confirmation without reloading the page, and the layout didn't scroll sideways.
Cost: about $0.41 a month if the site is active about an hour a day; running it around the clock would be about $7.84 a month. The first visit after it has been asleep takes a moment longer while the container starts.
Needs a person: the free grant is 0.1 credits, about 9 hours of running time. When it runs out, visitors get an error page instead of the site. Someone needs to claim the key through a Cloudish claim link (the command to create one is in deployment.json) and add credits. This is listed as a non-blocking step in deployment.json.
9 shell commands during deploy
- 14s
cd /tmp && curl -s https://www.cloudish.ai/skill.md -o cloudish-skill.md; wc -l cloudish-skill.md; cat cloudish-skill.md - 19s
cd /home/runner/work/_temp/adb-7uapa-static - 23s
cd /home/runner/work/_temp/adb-7uapa-static - 27s
cd /home/runner/work/_temp/adb-7uapa-static; set -a; . ./.env; set +a - 1m 30s
jq 'del(.project.owner)' /tmp/proj.json | head -80 - 1m 33s
cd /home/runner/work/_temp/adb-7uapa-static; set -a; . ./.env; set +a - 1m 41s
U=https://adb-7uapa-static-web.on.do.cloudish.ai - 2m 11s
mkdir -p /tmp/e2e && cd /tmp/e2e && npm init -y >/dev/null && npm i -s playwright-core >/dev/null 2>&1; which google-chrome - 2m 28s
cd /home/runner/work/_temp/adb-7uapa-static
Raw data
Report on GitHub Full record (JSON) Workflow run, with transcripts

