Vantage: black-box, unauthenticated, external. Observation only (GET/HEAD). 25 hosts probed · 24 reachable · 4 findings · highest severity P1.
Complementary to the Friday white-box source-and-schema review. Known-accepted items (/api/1099/* ungated by design, NodeDough legacy anon key, monitoring-without-alerting) are not re-reported.
ESCALATE NOW — this one costs money.
test.projects.jbnx.io and projects.jbnx.io are the same Railway service. The test host is publicly reachable with no authentication and reads live production data — and rate limiting, which is the only compensating control for the deliberately-ungated /api/1099/* write API, is counted per host, not per IP.
Evidence — the test lane returns this run's live production claim:
$ curl -s https://test.projects.jbnx.io/api/1099/board | grep -A3 automation
"slug":"automation"
"name":"Automation"
"status":"active"
"claimed_by":"1099:sched-resume-9k2f" <-- claim created on projects.jbnx.io
Evidence — independent rate-limit buckets, measured back to back:
$ for i in $(seq 1 45); do curl -so/dev/null -w '%{http_code} ' https://projects.jbnx.io/api/1099/board; done
30 200 / 15 429 (429 body: {"error":"slow down","retry_after_seconds":54})
# with the production bucket still exhausted:
$ curl -s https://test.projects.jbnx.io/api/1099/board -> 200 (full board)
$ for i in $(seq 1 45); do curl -so/dev/null -w '%{http_code} ' https://test.projects.jbnx.io/api/1099/board; done
29 200 / 16 429 (a separate counter, same ~30/window allowance)
All write routes are present on the test host (/claim, /status, /release, /document, /record-usage, /visit-beacon all resolve; they answer through the same rate limiter rather than 404).
What an outsider can do right now: alternate the two hostnames to get 2× the intended write rate against production, with no credential. Downstream that means forged claims (locking the agent fleet out of every slug), forged billing.work_sessions — which a paying customer is invoiced for at bill.jbnx.io — and forged handover status and published /framework/* documents on the CEO's command center.
Fix: key the limiter on client IP alone rather than host+IP, so both hostnames draw from one bucket. Better, reject non-GET /api/1099/* when Host: test.projects.jbnx.io — the test lane needs the SPA and reads to verify a deploy, not production writes.
The DNS record exists and points at Railway's edge, but no service is bound. Every browser gets a full-page certificate interstitial; behind it there is a 404.
$ curl -sI https://mkt.jbnx.io/
* subject: CN=*.up.railway.app
* subjectAltName does not match mkt.jbnx.io
* SSL: no alternative certificate subject name matches target host name 'mkt.jbnx.io'
-> curl exit 60, no HTTP response
$ curl -k -so/dev/null -w '%{http_code}' https://mkt.jbnx.io/ -> 404
$ curl -so/dev/null -w '%{http_code} %{redirect_url}' http://mkt.jbnx.io/ -> 301 https://mkt.jbnx.io/
Port 80 redirects into the broken HTTPS, so there is no working path to this host at all.
Impact: any campaign or link pointing at mkt.jbnx.io dies at the door, and readers are trained to click through certificate warnings — which is the habit that makes a real MITM work later. Secondary: a hostname pointed at a shared multi-tenant platform edge with no service bound is the standard subdomain-takeover precondition. Railway verifies domain ownership before binding, so takeover risk here is low but not zero.
Fix: complete the Railway custom-domain binding (needs the dashboard-only TXT record — the API returns only the CNAME), or delete the mkt.jbnx.io record until the host is actually wanted. Leaving it half-configured is the worst of the three states.
fedm8.com, www.cipherdeck.com and nguy.in are on GitHub Pages and send none of the four headers that every Railway-hosted surface in the estate sends correctly.
$ curl -sI https://fedm8.com/ | grep -iE 'strict-transport|content-security|x-frame|x-content-type'
(no output)
$ curl -sI https://fedm8.com/ | grep -i access-control
access-control-allow-origin: *
Compare a Railway surface, which is exemplary:
$ curl -sI https://jbnx.io/
content-security-policy: default-src 'self'; ... frame-ancestors 'none'; object-src 'none'
strict-transport-security: max-age=63072000; includeSubDomains
x-frame-options: DENY · x-content-type-options: nosniff
Impact: low but not nil. fedm8.com is the product's front door and is framable, so a hostile page can overlay its sign-in / CTA (clickjacking). No HSTS means a first-visit plain-HTTP request is strippable. The wildcard ACAO is not paired with Allow-Credentials and the site holds no session, so cross-origin read is limited to already-public marketing content.
Fix: GitHub Pages cannot set response headers. Front these three through Cloudflare and add a Transform Rule setting the four headers, or move them onto Railway alongside the rest.
The surface that handles money and sessions sets CSP frame-ancestors, HSTS (max-age=63072000; includeSubDomains), x-content-type-options and referrer-policy, but no Permissions-Policy — while projects.jbnx.io and ai.jbnx.io, which handle less, both set it. Consistency gap rather than an exploitable flaw.
Fix: add permissions-policy: geolocation=(), microphone=(), camera=() to the bill service, matching the portal.
bill.jbnx.io is still DNS-only / grey-cloud. Resolves to 69.46.46.81 (Railway), answers server: railway-hikari with no cf-ray. Unchanged and correct — POST /api/login is not being bot-challenged.
/api/1099/ genuinely is enforced* — ~30 requests/window, then a clean 429 with retry-after. The control works; P1 above is that it is scoped per host instead of per IP.
bill.jbnx.io admin and customer APIs return 401 with aone-line JSON hint and nothing else. No stack traces, database errors, framework version banners or internal hostnames anywhere in the estate.
/static/, /assets/ or /js/ path; no credentials in HTML comments. The JWT in NodeDough's /config.js decodes to "role":"anon" — expected, and not a service-role key.
mkt.jbnx.io. Earliest expiry is tensuite.net, 2026-10-04 — 53 days out, so nothing is inside the 30-day window.
test.projects.jbnx.io and demo.jbnx.io via meta robots; test.fedm8.com via x-robots-tag).
www.jbnx.io's certificate mismatch is resolved. It now presents CN = www.jbnx.io, valid to 2026-10-24, and verifies cleanly. The jbnx-security-posture skill still lists it under §6 known-open — that row can be closed.
Login user-enumeration on bill.jbnx.io (does "wrong password" differ from "no such user") was not probed. Testing it requires deliberate failed-password attempts against a real account, which is brute-force-adjacent and risks locking out the admin account. Recommend the Friday white-box review confirm from source that both paths return an identical response and take the same time.
Black-box external audit, observation only. No DNS, certificate, header, code or configuration was modified.