DEV Community

WayforthOfficial
WayforthOfficial

Posted on

How We Hardened the Wayforth Gateway - Complete Security Audit

Wayforth_Demo

We shipped Wayforth — a search and payment rail
for AI agents — and before expanding the managed
services catalog we ran a full security audit.

Here's how we fixed it.

The Stack

FastAPI · PostgreSQL · Railway · Base blockchain

Supabase Auth · Stripe · Fernet AES-128 · BSL 1.1

uvx wayforth-mcp
Enter fullscreen mode Exit fullscreen mode

Critical Findings (5)

C1 — JWT not cryptographically verified

Fix: JWKS-based ES256 verification via Supabase's
public endpoint — no shared secret needed.

def verify_supabase_jwt(token: str) -> dict:
    jwks = get_jwks()  # cached 1hr
    header = jwt.get_unverified_header(token)
    key = next(k for k in jwks if k["kid"] == header["kid"])
    public_key = ECAlgorithm.from_jwk(key)
    return jwt.decode(token, public_key,
        algorithms=["ES256"], audience="authenticated")
Enter fullscreen mode Exit fullscreen mode

C2 — CORS wildcard + credentials

Fix: Explicit origins only.

allow_origins=[
    "https://wayforth.io",
    "https://www.wayforth.io",
    "http://localhost:3000",
    "http://localhost:5173",
]
Enter fullscreen mode Exit fullscreen mode

C3 — Admin key timing oracle

Fix: secrets.compare_digest() in all 11 places.

C4 — BYOK silent plaintext fallback

Fix: Both now raise HTTP 500 instead of
silently degrading security.

C5 — Webhook IDOR

Fix: Ownership verified before delete.

High Findings (5)

  • H1 — Fernet key entropy: invalid keys now raise ValueError at call time
  • H2/H3 — BYOK encrypt/decrypt fail silently: both raise HTTP 500
  • H4 — No security headers: added X-Frame-Options, X-Content-Type-Options, HSTS, Referrer-Policy, Permissions-Policy
  • H5supabase_id echoed in 401 body: removed (account enumeration vector)

Frontend Findings

  • API key written to localStorage on signup → moved to sessionStorage
  • Admin token in localStoragesessionStorage
  • No expired session handler → global 401 interceptor now redirects to /login
  • innerHTML with API responses → escaped

What Was Already Clean

SQL injection — parameterized everywhere, zero
string concatenation. No eval() or exec().

API keys — SHA-256 hashed, never logged.

Admin passwords — bcrypt.

Key generation — secrets.token_urlsafe().

.env in .gitignore.

Result

20 findings. All resolved.
Zero open findings before expanding the catalog.


Try it: uvx wayforth-mcp

GitHub: github.com/WayforthOfficial/wayforth

Docs: wayforth.io

Top comments (0)