hermes research Viska-autoresearch deployment arc · 4-parallel sub-agent · 2026-05-25

T3 — Railway Security Posture (Viska-Autoresearch)

Date: 2026-05-25 · Researcher: Hermes T3 Scope: Stage-1 Mastra agent + Nous Research autonomous loop on Railway, client-isolated Supabase KB. Data class (stage 1): trading positions, client PII (names/accounts/contacts), proprietary strategies. Stage 2: broker credentials (deferred).

Executive verdict

Railway is acceptable for stage-1 data class with hardening, but not acceptable as-shipped from defaults. Railway is SOC 2 Type II / SOC 3 certified, signs DPAs + HIPAA BAAs, runs Wireguard-encrypted private networking with per-project/per-environment isolation, and offers sealed variables — the table stakes are present. However: (a) Railway's compute is shared-tenant containers with no documented dedicated-host guarantee at Pro tier, (b) static outbound IPs are explicitly "may be shared with other customers," (c) no native egress allowlist primitive — egress is open-internet by default, and (d) the dominant risks are agent-application risks, not Railway-platform risks — prompt injection, excessive agency, the "lethal trifecta" of PII + untrusted input + outbound LLM API. With the must-have checklist executed, this stack is shippable for a single trusted client. Deal-breaker for stage 2 without further hardening: outbound LLM API as exfil channel — no compensating control today, must add before brokers land.

Railway runtime model

Railway runs customer workloads as containers on shared compute. Public docs do not publish a hypervisor / kernel-isolation guarantee at Pro tier — Railway is positioned as a "modern Heroku" and the runtime profile matches (shared-kernel containers, multi-tenant nodes). Enterprise tier surfaces additional controls (SSO, higher capacity, audit access) but the public docs do not commit to dedicated hosts as a Pro-tier guarantee. Operator follow-up flagged below.

Documented publicly: SOC 2 Type II + SOC 3, audit reports under NDA via trust.railway.com; HIPAA BAA as paid add-on (committed-spend tier), under which Railway staff loses direct workload access; GDPR DPA self-service; EU DORA docs under click-through NDA; private networking uses Wireguard (ChaCha20, Curve25519, BLAKE2s) over IPv6 internal mesh, project + environment is the isolation boundary; sealed variables are write-once, never readable via UI/API post-seal, not copied on env duplication / PR envs / service duplication — no external rotation primitive; static outbound IPs (Pro) are HA-balanced across 3 region-scoped IPs but Railway docs state they are not guaranteed dedicated; region binding rotates IPs on migration.

Not documented publicly: container runtime (runc / gVisor / Kata / Firecracker — unknown); per-tenant CPU/memory isolation guarantees; whether Pro shares hosts with Hobby; native egress filtering (no allowlist primitive); customer-surfaced runtime audit logs (only deploy + var-change events appear in the dashboard).

Operational history: Railway had a global outage on 2025-10-28 impacting backend API, dashboard, CLI, GitHub deploys; post-mortem published. Availability profile is appropriate for a single-client deployment, not for multi-region active-active SLAs.

Threat model

TM-1 — Prompt injection on autonomous loop

Vector. Nous/Karpathy-style auto-research loops generate their own next-step prompts from prior outputs. A direct injection landing in any input (Slack message, KB row, supplied research URL, Plutus document) propagates through and persists across iterations.

Blast radius. With Supabase write privileges, the agent can corrupt positions_snapshot, write attacker-controlled rows anywhere it has access, and (if Slack output is wired) post arbitrary content under the Mastra agent's identity. The loop multiplies impact — one successful injection persists across thousands of iterations until a human notices.

Mitigations. Constrain system prompt with explicit role bounds + refusal patterns for "ignore previous instructions" and encoded variants (OWASP LLM01). Enforce structured-output schema validation before any tool call — eliminate free-text-to-tool paths. Loop-iteration cap with mandatory human checkpoint every N iterations (N=10 stage 1, N=5 stage 2). Per-iteration semantic-drift detector (cosine similarity to original goal vector; abort below threshold) catches obvious goal-hijack.

TM-2 — Indirect prompt injection via ingested research content

Vector. This is the dominant risk. Palo Alto Unit 42 published in-the-wild data (March 2026) confirming web-based indirect prompt injection (IDPI) is no longer theoretical — they observed 22 distinct payload techniques across malicious pages, including the first confirmed real-world AI ad-review bypass (Dec 2025). EchoLeak (Aim Security, June 2025) demonstrated zero-click exfiltration against M365 Copilot via crafted email — the same agent-ingests-external-content pattern this stack uses. Viska's auto-research loop fetches market commentary, SEC filings, news feeds — every fetched page is potentially attacker-controlled. Vectors observed in the wild: hidden white-on-white text, zero-width chars, ARIA-label injection, JSON-LD payloads, HTML comments, CSS content:, image-OCR injection, Base64-encoded payloads decoded via prompt instruction.

Blast radius. Higher than TM-1 — attacker doesn't need any Viska access, just needs to publish a page the loop will fetch. Combined with Supabase write + outbound LLM API, attacker can drive arbitrary KB writes and exfiltrate the PII+positions context window via a single crafted page.

Mitigations. Treat all fetched content as data, never as instructions — wrap in explicit delimiters (<external_untrusted_source>...</external_untrusted_source>); system prompt instructs that tagged content is information about, not instructions from. Pre-ingestion sanitization: strip zero-width chars (U+200B/200C/200D/FEFF), Unicode-normalize (NFC), strip HTML comments + CSS content: + aria-label + JSON-LD. OCR on images only via a separate scoped model with zero tool access. Dual-LLM pattern (Simon Willison) is the single most effective mitigation — quarantined LLM reads external content and produces structured summaries with zero tool access; privileged LLM consumes summaries and has tool access. Domain allowlist for research fetches — operator-approved list only; wildcard internet fetch forbidden. Per-fetch size cap.

TM-3 — Tool-use abuse (Supabase write privileges)

Vector. Agent has Supabase write to KB tables. Attacker-controlled instructions (via TM-1 or TM-2) drive the agent to issue INSERT/UPDATE/DELETE. Without role-level + RLS gating, the LLM is the only access-control gate — and LLMs are not access-control gates.

Blast radius. Corruption of trading positions ledger (HIGH — drives downstream calculations the client trusts), poisoning of research notes (MEDIUM — slow-burn, hard to detect, propagates across iterations), wholesale table delete (LOW prob if RLS denies, HIGH if not).

Mitigations. OWASP LLM06 §4–§7 minimum-privilege Supabase roles. Concretely: agent_reader (SELECT only), agent_writer_scoped (INSERT only on research_notes + loop_iterations, no UPDATE/DELETE anywhere), agent_admin (human-triggered Plutus sessions only, not the autonomous loop). Supabase RLS policies on every agent-touched table — belt to role suspenders. Append-only audit table agent_actions(id, ts, sql, args, trace_id) via trigger; out-of-band cron diffs positions_snapshot against the audit log. Stage 1: no autonomous destructive ops — UPDATE/DELETE flow through a human-approved queue. Agent never writes positions_snapshot directly — it writes agent_position_suggestions, Plutus promotes.

TM-4 — LLM API as exfil channel

Vector. The "lethal trifecta" (Simon Willison): agent has private data + ingests untrusted input + can communicate externally. This stack hits all three. After a successful TM-2 injection, the payload tells the agent to encode PII context into the next API call's prompt (e.g., "summarize as Base64"). The LLM provider's logs now contain the client's PII. Attacker can also induce outbound HTTP to attacker-controlled hosts if the tool surface allows. EchoLeak is the canonical 2025 real-world instance.

Blast radius. Worst-case for this stack. PII + positions visible to LLM provider (baseline — vendor sees what the agent sees) and exfilable to attacker via crafted prompts in fetched content.

Mitigations. PII tokenization before context assembly — names, account numbers, contacts replaced with opaque tokens (<<CLI_42>>, <<ACC_a8f>>) before entering LLM context; detokenize only at structured output validation. Breaks the exfil chain at the data layer — provider logs and any exfil channel see only tokens. Zero-data-retention LLM tier — Anthropic ZDR, OpenAI ZDR agreement, or self-hosted (T1 Option B). Outbound egress allowlist at infrastructure level — agent service may only reach the LLM endpoint, Supabase URL, Slack API, and allowlisted research domains. Railway has no native primitive — implement via Envoy sidecar service in the Railway project, or external Cloudflare Zero Trust gateway. Append-only audit of outbound LLM API calls; sample-review entropy spikes (PII-encoded payloads have different entropy than legitimate prompts). Strict tool surface — no general http_fetch; agent uses fetch_research(domain_id) with domain_id as int key into the allowlist table. Free-form URL fetch is the single largest exfil vector and must be eliminated.

TM-5 — Railway tenant escape

Vector. Shared-tenant containers. Container escape (runc / kernel CVE) → noisy-neighbor compromise → in-memory secrets of this workload readable by neighbor.

Blast radius. High impact (all env vars including sealed variables become visible — sealed vars are encrypted at rest but injected as plaintext env at runtime), low probability (container escapes are rare; Railway patches base images).

Mitigations. Stage 1: rely on Railway's patching cadence + SOC 2 controls. Stage 2: Railway Enterprise tier with dedicated-host contract commitment, or self-host the broker-touching service off Railway. Compensating control today: short-lived tokens rather than long-lived secrets so rotation window is hours.

TM-6 — Supply chain (deps + model weights)

Vector. Python + TS deps pulled at build from PyPI / npm. Compromised upstream → arbitrary code execution at build → embedded in production image. If self-hosting model weights (depends on T1), weight tampering is a vector — signed Hugging Face releases mitigate; unsigned mirrors do not.

Blast radius. Identical to any container service; high if dep compromise yields RCE — agent's Supabase access amplifies impact.

Mitigations. pip-audit + safety + npm audit + osv-scanner block CI before Railway deploy on HIGH/CRITICAL findings. Lockfiles with hashes committed; no latest tags in Dockerfiles. SBOM (Syft / CycloneDX) per deploy, tagged with Railway deploy ID. Model weights: pin to revision SHA, verify SHA256 against official Hugging Face release. Distroless or official python:3.12-slim base image. Dependabot/Renovate on the repo.

TM-7 — Egress to attacker-controlled URLs (SSRF chain)

Vector. Research-fetch function accepts URLs from Slack / KB / operator. Attacker drives the agent to fetch internal Railway services (*.railway.internal), cloud metadata (169.254.169.254 — Railway exposure undocumented), or attacker URLs. Combined with TM-2, fetched content executes as injected prompt.

Blast radius. Medium-high. SSRF to Railway internal services if private networking misconfigured. SSRF to cloud IMDS is the classic credential-theft pattern; Railway IMDS exposure is undocumented (operator follow-up).

Mitigations. Allowlist validation BEFORE fetch (not after — DNS rebinding is real). Explicit deny of RFC1918, IPv4 link-local (169.254.0.0/16), IPv6 ULA (fc00::/7), and *.railway.internal in the research-fetch function. DNS-pinning: resolve once, fetch by IP, validate IP against denylist post-resolution and pre-connection. Per-fetch timeout (10s) + size cap (5MB).

Hardening checklist — MUST-HAVE (stage 1 blockers)

Hardening checklist — SHOULD-HAVE (stage 1.5)

Hardening checklist — NICE-TO-HAVE (post-launch)

Specific recommendations

Secrets management. Use Railway sealed variables stage 1 — integration cost zero, meets rotation-window-of-hours requirement. Do NOT add Doppler / Infisical stage 1 — extra dependency, extra credential, extra attacker target. Stage 2: Hades vault as authoritative store; Railway sealed var holds a short-lived signed JWT the agent exchanges for broker session token. Broker creds never live on Railway.

Egress control. Railway has no native egress firewall — implement as Envoy sidecar service inside the Railway project (envoyproxy/envoy:v1.30 as a sibling service, agent traffic routes via envoy.railway.internal, allowlist in envoy.yaml). Cheaper than Cloudflare Zero Trust if operator already has Cloudflare, equivalent security otherwise. Allowlist: LLM provider endpoint, Supabase URL, slack.com, allowlisted research domains. Default deny. This is the highest-value single control on the list.

Audit log. Three layers. (a) Railway native — workspace audit log via GraphQL API, polled nightly to external bucket. Covers deploys, var changes, member changes. (b) Supabase audit — pgAudit on the project; logs all DDL + DML by agent_writer. (c) App-level — agent_actions append-only on a separate Supabase project, written on every agent write + every outbound LLM API call. (c) is the only one capturing reasoning trace — invest there first.

Model API key isolation. Per-loop ephemeral creds add operational cost without much over short-rotation static. Recommend: static sealed variable, rotated every 30 days, sourced from a Hades-deposited project-scoped key (Anthropic Console + OpenAI both support project-scoped sub-keys). Blast radius on leak = one project, not org. Rotation rehearsed (item 20).

PII handling in prompts. Implement tokenizer module running before context assembly and after structured output validation. Scheme: opaque tokens with monotonic-counter prefix per category (<<CLI_42>>, <<ACC_a8f>>). Token-to-PII map in-process only, never persisted to logs. LLM vendor logs see tokens. This is the single defense that breaks TM-4's exfil chain at the data layer rather than the network layer — pairs with item 7 (egress proxy) for defense in depth.

When stage-2 (broker credentials) lands

Adding broker creds changes the data class from "high-value disclosure" to "high-value action" — the agent can move money. New top-priority threat TM-8 — unauthorized broker order. Hardening delta: broker creds never live on Railway env — Hades vault holds them, agent receives a short-lived signed token via Railway sealed var (rotated hourly) authorizing a single broker session with explicit per-order signing; per-order human approval gate — autonomous loop proposes orders into a queue, orders execute only after operator (or Plutus) confirms; out-of-band callback (ntfy / Slack DM / SMS) within seconds of agent proposing an order; daily reconciliation between agent_position_suggestions and broker's actual positions, deviation triggers freeze; mandatory move to Railway Enterprise with dedicated-host commitment, or move the broker-touching service off Railway to a single-tenant host. Re-run dual-LLM and PII tokenization audit covering broker context. Workload may also enter MiFID II / SEC reg-AI / EU AI Act high-risk classification — compensating controls become audit evidence, not just hygiene.

Open questions for operator

  1. What container runtime does Railway use? runc / gVisor / Kata / Firecracker. Not in public docs. Materially affects TM-5 — request via Trust Center.
  2. Does Railway expose cloud metadata (169.254.169.254) inside customer containers? Not documented. Affects TM-7 denylist design — Trust Center or Discord support.
  3. Data-class boundary for Pro → Enterprise upgrade. Stage 1 PII may be acceptable on Pro; stage 2 broker creds likely require Enterprise. Solutions team conversation before stage 2 lands.
  4. Plutus session privilege model. If Plutus runs above the autonomous loop with separate context, dual-LLM holds. If Plutus shares context or runs within the loop, additional segregation required — clarify before architecture lock-in.
  5. Stage-1 LLM provider. Anthropic / OpenAI / self-hosted Nous on Railway GPU / self-hosted external. Determines whether TM-4 mitigation is "ZDR contract" or "no third party at all." Coordinate with T1.

Sources