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

Static Content Site on Railway

Success

Checks passed16/16
Human steps37 min of a person's time
Deploy without a human Failed
Deploy with a human Deployed
Security & compliance80/100
Stack fit90/100
Build2m 00s
Deploy2m 17s
Tokens1.13M
Agent cost$0.81
Hosting / month$5.28 light use$27.63 busy

Deployed to https://adb-yz0s2-static-web-production.up.railway.app. Every deployment is deleted after it's verified and reviewed.

Screenshots

The home page
The home page · https://adb-yz0s2-static-web-production.up.railway.app/
The contact page after the verifier submitted the form
The contact page after the verifier submitted the form · https://adb-yz0s2-static-web-production.up.railway.app/contact

Technologies

Runtime
Node.js 18+
Frameworks
Node.js http module (built in)
Packaging
railway.json

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.js server on a single Railway service fits a three-page site with one same-origin form endpoint well. STACK.md explains the choice soundly, and the build matches it.

CriterionWeightScoreWhy
Fit for the job 40 9/10 Node's built-in http module serves exact extensionless routes, real 404s and a validated JSON endpoint on the same origin, which covers everything TASK.md asks for, and the app needs no data store.
Proportion 30 10/10 It is one roughly 100-line file with zero dependencies and no build step, using the built-in node:test runner, which is about as small as these requirements allow.
Platform fit 30 8/10 Railway has no static-only hosting or functions, so one small always-on service serving both pages and the API is the natural fit there, and it costs about $1/month. Static files are served from a container, which is acceptable on this platform.

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 deployed cleanly to Railway with no secrets involved. HTTPS is enforced with a valid certificate and only allowlisted paths are exposed. The remaining weaknesses are minor: no security headers, an unpinned Node runtime and Railpack base image, a local `railway up` upload with no source repository, a likely root container, and no rate limiting on the contact form.

CriterionWeightScoreWhy
Secrets handling 25 10/10 The app needs no secrets. The Railway token came only from the environment, was never echoed (the transcript shows only 'token set'), and .env is excluded by .gitignore and .railwayignore. No credentials show up in source, logs or deployed files.
Deployment provenance 20 7/10 Local source was uploaded with `railway up`, built by Railpack and checked live, and there are no npm dependencies. Two inputs are not pinned: the Node version (engines '>=18', so Railpack picked Node 24) and the Railpack base image chosen by the platform. There is also no source repository behind the deployed artifact.
Terms compliance 15 7/10 This is a commercial app on what the agent describes as the account's existing usage-based Railway plan. Railway's paid plans allow commercial use and do not sleep services, but context.json does not name the plan, so I can't confirm the account isn't on the trial/free tier, which has credit limits. Separately, railway.json config-as-code is deprecated after 2026-12-01.
Least privilege 15 7/10 There is a single service with no data store, volume or extra public resources, and only the needed HTTP port is exposed. The Railpack image runs as its default (root) user, and the agent took no steps to drop privileges.
Transport security 10 7/10 HTTPS works with a valid Let's Encrypt certificate, and HTTP returns a 301 to HTTPS. The app sets none of HSTS, X-Content-Type-Options, frame protection or CSP, even though it fully controls its response headers.
Attack surface 10 9/10 Routing uses an exact-path allowlist, so .env, .git, server.js, package.json, *.html and traversal attempts all return 404, with no listings or stack traces. The only extra endpoint is a harmless /healthz.
Application security 5 8/10 The contact endpoint checks types, required fields and email format, limits bodies to 64 KB (returns 413), handles bad JSON cleanly, and the client renders responses with textContent, so there is no XSS. There is no rate limiting or bot protection on the public form, 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.28memory billed all month, CPU for ~10 h of activity, 0.04 GB egress at $0.05/GB
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.
$27.63memory billed all month, CPU for ~730 h of activity, 52.6 GB egress at $0.05/GB

Resources the agent created

KindNamePlanList price / monthPricing
projectadb-yz0s2-static1 service(s)$25.001 service(s) at the assumed 1 vCPU / 0.5 GB each, always on; usage is metered per second so a mostly idle service costs less than this list price

The agent estimated $1.00 a month.

Code

Code
418 lines in 16 files (JavaScript 208, HTML 166, JSON 36, CSS 33, Markdown 26, Shell 11)
Dependencies
0 npm, 0 Python
Deploy config
railway.json

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted2m 00s13500.4K$0.5212
Deploycompleted2m 17s7353.2K$0.156

The agent's deploy summary

The site is live at https://adb-yz0s2-static-web-production.up.railway.app, and every requirement in TASK.md works through that address.

What I deployed

  • A Railway project adb-yz0s2-static with one web service, adb-yz0s2-static-web, in the production environment.
  • I uploaded the code straight from this directory with railway up, so no GitHub repo or image registry was involved. The service has a Railway-provided HTTPS address and a health check on /healthz.
  • Estimated cost is about $1/month, which the plan's included usage should cover.

What I tested through the public address

  • Pages: /, /about and /contact return 200 as HTML, each with the required heading or form fields.
  • 404s: unknown paths and .html suffixes (e.g. /about.html) return 404.
  • Form endpoint: POST /api/contact returns 200 {"ok":true} for valid input. A missing name, invalid email or empty message each return 400 with a reason.
  • HTTPS: plain HTTP redirects to HTTPS.
  • Browser: in headless Chromium at phone width, the form showed its confirmation without reloading the page, and an invalid email showed the error message.

Things to know

  • Config format: Railway now flags railway.json as deprecated. It keeps working until 2026-12-01; running railway config migrate before then moves it to the new format.
  • Credentials: I used only the token you provided and created none, so there is no .env and human_steps in deployment.json is empty.

deploy.sh and the .railwayignore from earlier are still in the directory. I didn't run the script, since I did the same steps by hand.

6 shell commands during deploy
  1. 4s test -n "$RAILWAY_API_TOKEN" && echo token set; which railway || npm i -g @railway/cli >/dev/null 2>&1; railway whoami 2>&1 | head -3; railway list 2>&1 | head
  2. 9s railway init --name adb-yz0s2-static --json 2>&1 | tail -5; railway add --service adb-yz0s2-static-web --json 2>&1 | tail -5
  3. 13s railway up --service adb-yz0s2-static-web --ci 2>&1 | tail -15
  4. 1m 46s railway domain --service adb-yz0s2-static-web --json 2>&1 | tail -3; railway status --json 2>/dev/null | python3 -c "import json,sys;d=json.load(sys.stdin);e=d['environments']['edges'][0]['node'];prin
  5. 1m 54s U=https://adb-yz0s2-static-web-production.up.railway.app; for i in $(seq 1 30); do [ "$(curl -s -o /dev/null -w '%{http_code}' $U/)" = 200 ] && break; sleep 5; done
  6. 2m 09s cat > deployment.json <<'EOF'

Raw data