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

Static Content Site on Railway

Success

Checks passed16/16
Human steps25 min of a person's time
Security & compliance82/100
Stack fit90/100
Build1m 49s
Deploy1m 36s
Tokens897.3K
Agent cost$0.62
Hosting / month$0.00 light use$0.00 busy

Deployed to https://adb-ytghj-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-ytghj-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-ytghj-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.

The agent chose a zero-dependency Node HTTP server as one Railway service, a minimal and well-reasoned fit for three static pages plus one validation endpoint, and the build matches the reasoning in STACK.md.

CriterionWeightScoreWhy
Fit for the job 40 9/10 Plain node:http with an in-memory allowlist meets the requirements exactly: extensionless paths, real 404s and a same-origin JSON endpoint. No storage is needed, and none is used.
Proportion 30 10/10 The whole app is one ~80-line file with zero dependencies, one service and no build step, which is as small as these requirements allow. STACK.md weighs Express and Caddy plus a function, and rejects them for sound reasons.
Platform fit 30 8/10 Railway is a container platform, and a single always-on service fits it: it deploys with zero configuration through Railpack, uses $PORT and a health check, and costs about $1/month. Serving mostly static content from an always-on process is a small mismatch, but it is justified by the same-origin API.

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).

A small, dependency-free Node server on Railway serves the three pages and a well-validated contact endpoint over HTTPS, with no secrets involved and nothing exposed beyond the app. The weaknesses are minor: no HSTS, CSP or frame headers, no rate limiting, raw personal data in the logs, a likely root container, a local upload with no committed revision and an unpinned Node version, and an account plan nobody checked for commercial use.

CriterionWeightScoreWhy
Secrets handling 25 10/10 The app needs no secrets. The Railway token stayed in the session environment, deploy.sh reads it from env/.env, and .env is excluded by both .gitignore and .railwayignore. No secret appears in the source, the transcripts or the deployed files.
Deployment provenance 20 7/10 Railway built the image with Railpack from the agent's local upload (`railway up`), and that build is what runs. But no commit or repo ties the upload to a reviewed revision, and the Node version is left floating (`engines: >=18`, which resolved to Node 24) while the Railpack base images are platform defaults that aren't pinned.
Terms compliance 15 7/10 Railway's paid usage-based plans allow commercial use, and an always-on service fits a marketing site. But process/context.json does not show the account plan, and the agent never checked it. If the account is on the Trial/free tier, its credit limits could stop the service, so I can't fully confirm this fits a commercial app.
Least privilege 15 8/10 There is one public web service, with no data stores, extra services or extra variables. The Railpack image almost certainly runs the Node process as root, because the agent didn't configure a non-root user.
Transport security 10 7/10 The site is HTTPS-only with a valid Let's Encrypt certificate, and HTTP redirects to HTTPS with a 301. The app sets X-Content-Type-Options, but not HSTS, CSP or frame protection, even though it controls every response header.
Attack surface 10 10/10 The server serves only an allowlist of files held in memory. /.env, /.git/config, /server.js, /package.json, /railway.json, path traversal and *.html paths all return the custom 404. There is no directory listing and no stack trace, and the only extra endpoint is a trivial /health.
Application security 5 8/10 Input validation is strict: types are checked, lengths are capped, request bodies are limited to 64 KB, bad JSON gets a 400, and the client writes messages with textContent, so there is no XSS. There is no rate limiting on the public form endpoint, and the server logs submitters' raw names and emails, which allows log injection and keeps personal data in 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.
$0.00nothing deployed
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.
$0.00nothing deployed

The benchmark found no cloud resources for this cell.

The agent estimated $1.00 a month.

Code

Code
372 lines in 16 files (JavaScript 168, HTML 161, JSON 34, CSS 32, Markdown 24, Shell 11)
Dependencies
0 npm, 0 Python
Deploy config
railway.json

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted1m 49s10380.9K$0.369
Deploycompleted1m 36s7340.4K$0.156

The agent's deploy summary

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

What's on Railway:

  • Project: adb-ytghj-static (b35149d1-38b2-4bed-8220-7bb444c4738c), with its production environment.
  • Service: adb-ytghj-static-web (2825be50-3d2b-41bc-8920-1e0eb705e58c), one copy of the Node server, which serves the pages and /api/contact from the same address.
  • Address: the free HTTPS address Railway generated for the service.

I uploaded the code straight from this directory with railway up; no GitHub repo is connected.

Tested through the public URL:

  • /, /about and /contact return 200 as text/html, each with a title, the viewport tag, navigation and the right <h1>.
  • /about.html and an unknown path return 404.
  • POST /api/contact returns 200 {"ok":true} for valid input, and 400 with an error for a missing name, an invalid email or an empty message.
  • In headless Chrome at phone width, the contact form showed its confirmation without reloading the page, and nothing overflowed sideways.

Cost: about $1 a month or less of Railway's usage-based billing for one small always-running service. I estimated that rather than reading it from Railway. The account plan was not changed, and the plan's included credit should cover it.

Nothing needed a person, so human_steps is empty, and I created no new credentials, so there is no .env. The Railway CLI warns that railway.json is deprecated; it keeps working until 2026-12-01. deployment.json has the IDs and notes. The files are still staged in git but not committed.

6 shell commands during deploy
  1. 4s cd /home/runner/work/_temp/adb-ytghj-static; railway --version; railway whoami 2>&1; railway list 2>&1 | head
  2. 8s cd /home/runner/work/_temp/adb-ytghj-static; railway init --name adb-ytghj-static 2>&1 | tail -5; railway add --service adb-ytghj-static-web 2>&1 | tail -5
  3. 19s cd /home/runner/work/_temp/adb-ytghj-static; railway up --service adb-ytghj-static-web --ci 2>&1 | grep -v -i "config as code\|migrate\|keep working" | tail -15
  4. 1m 01s cd /home/runner/work/_temp/adb-ytghj-static; railway domain --service adb-ytghj-static-web 2>&1 | grep -i http; railway status --json 2>/dev/null | python3 -c "
  5. 1m 08s U=https://adb-ytghj-static-web-production.up.railway.app; for i in $(seq 1 20); do [ "$(curl -s -o /dev/null -w '%{http_code}' $U/)" = 200 ] && break; sleep 5; done
  6. 1m 25s cd /home/runner/work/_temp/adb-ytghj-static; cat > deployment.json <<'EOF'

Raw data