Skip to main content

Yabanin Security Audit

Dependency CVE Scan​

Tool: govulncheck (Go vulnerability scanner) Date: 2026-07-23 Result: 0 vulnerabilities in reachable code.

VulnerabilityPackageStatusAction
GO-2026-4316go-chi/chi/v5 v5.2.3FixedUpgraded to v5.2.4
GO-2026-5932golang.org/x/crypto/openpgpNot calledPackage not imported in codebase; transitive only

Scan Command​

govulncheck ./...

Threat Model​

Attack Surface​

ComponentExposureAttack Vectors
Gateway (:8082)Public internetAPI key brute-force, prompt injection, SSRF via upstream URLs
Admin (:8086)Internal onlySession hijacking, CSRF, privilege escalation
Dashboard (:3000)Internal onlyXSS, auth bypass
PostgreSQL (:5432)Internal networkSQL injection, connection exhaustion
Redis (:6379)Internal networkCache poisoning, key enumeration
Classifier (:8080)Internal onlyModel abuse, information leakage
Admin APIInternal onlyIDOR, mass assignment, audit log tampering

Security Controls​

Authentication & Authorization​

  • API Keys: SHA-256 hashed, stored in api_keys table, resolved via Redis cache
  • Admin sessions: HMAC-signed cookies with rotation support
  • Root-only routes: Admin operations (org management, cache flush, pricing) require is_root=true
  • Team isolation: API keys bound to teams; whitelist enforcement per key/team

Input Validation​

  • Request validation: pkg/validation/openai/ validates all incoming requests
  • SQL injection: All queries use parameterized pgx (no string concatenation)
  • SSRF protection: pkg/ssrf/ validates upstream URLs against allowlist
  • Prompt injection: inherent to LLM traffic; since the OUTPLAY program (2026-10) mitigable with opt-in controls — deterministic injection detectors, guard modes, and policy-pipeline steps (see security-owasp-llm.md, LLM01). Without those controls enabled the original posture ("not mitigated") still applies.

Data Protection​

  • PII redaction: Redis publisher redacts API keys, tokens, secrets from event payloads (redactPII)
  • URL credential stripping: redactSensitiveURL removes credentials from logged upstream URLs
  • Encryption at rest: Provider API keys encrypted with AES-256-GCM (pkg/encryption/)
  • Secrets management: Docker secrets (/run/secrets/), not environment variables

Network Security​

  • Read-only containers: All services run with read_only: true filesystem
  • No new privileges: security_opt: no-new-privileges:true on all containers
  • Internal network: Services communicate via app-internal bridge network
  • tmpfs only: Write paths limited to /tmp in read-only containers

Rate Limiting​

  • Gateway: Per-IP rate limiting via token bucket
  • Admin login: 5 attempts per 15 minutes (loginLimiter)
  • Admin API: 1000 requests per minute (rateLimiter)

Audit Trail​

  • Audit logs: All admin mutations logged with actor, IP, action, resource
  • Request logs: Every API request persisted with org/team/cost/model metadata
  • Stream persistence: Redis stream provides durable event trail before DB write

Budget Enforcement (agent budgets)​

  • Hard limit: Requests return HTTP 429 when the team (monthly) or agent (daily) budget is exceeded
  • Soft limit: Logged but allowed
  • Fail-closed: A budget-store error denies the request (503) — spend is never allowed while the budget store is unavailable

Key Revocation Propagation​

Guarantee (updated): with Redis configured (the admin service shares the gateway's Redis), a revoked api_keys / agent_keys credential is rejected immediately — every revocation path (deactivate, delete, rotate, agent revoke, budget removal) purges org:resolve:<sha256(key_hash)> from the shared cache, so the next resolve hits the DB and denies. Without Redis the bound degrades to YABANIN_ORG_RESOLVER_TTL seconds (default 300s).

  • The gateway's org resolver (pkg/auth) caches successful resolve results in Redis, keyed by sha256(plaintext), for OrgResolverTTL seconds. The admin service now shares that Redis and purges the cached entry on every revocation path (fail-soft: a purge failure logs a warning and the TTL bound applies). When Redis is absent the TTL is the propagation bound.
  • The bound is therefore exact: stale window ≤ OrgResolverTTL. The fake-clock tests in pkg/auth/resolver_revocation_test.go pin both edges — a revoked key still resolves at TTL−1s and is denied at TTL, for both the api-key and agent-key paths, and the TTL passed to Redis equals the configured bound (tightening the env var tightens the bound).
  • Mitigation for a faster kill: revoke and flush the matching org:resolve:<sha256> Redis key (requires shared Redis access); otherwise plan incident response around the configured TTL. Set YABANIN_ORG_RESOLVER_TTL low (e.g. 30) for deployments with high key churn or strict revocation SLAs, at the cost of one extra DB pair-read per key per window.
  • Cache entries are HMAC-signed (CacheSigner), so an attacker with Redis access cannot forge a valid resolve result for a key that was never issued; they can only keep an already-issued identity alive within the same bound.
  • Note: entries written before the key-revocation fix used a 3-field agent encoding whose cached reads misattributed the agent id to the team slot; they age out within one TTL (≤300s) and are rewritten in the correct 4-field format.

Pre-Launch Security Checklist​

  • Dependency scan: 0 reachable vulnerabilities
  • All containers run read-only with no-new-privileges
  • Secrets stored as Docker secrets (not env vars)
  • All SQL queries use parameterized statements
  • Upstream URLs validated against SSRF patterns
  • PII redacted from event stream and logs
  • Provider API keys encrypted at rest
  • Admin routes protected with root-only middleware
  • Rate limiting on gateway and admin login
  • Audit logging on all admin mutations
  • TLS termination at load balancer (configure before launch)
  • CORS policy for dashboard (restrict to known origins)
  • Secret rotation procedure documented (see runbook)

Recommendations​

  1. TLS: Terminate TLS at the load balancer (nginx/ALB). Gateway and admin listen on plain HTTP behind the LB.
  2. CORS: Add Access-Control-Allow-Origin header restricted to the dashboard domain.
  3. Secret rotation: Rotate session_key, cache_hmac, and pg_uri quarterly.
  4. WAF: Consider Cloudflare/AWS WAF for DDoS protection before the gateway.
  5. Pen test: Schedule external penetration test before SOC2 compliance work.

2026-10 OUTPLAY Program — Threat-Model Addendum​

Date: 2026-10-08 (lane OP-24). Scope: the Policy Pipeline (YPP) and the MCP credential/aggregator surfaces delivered by the OUTPLAY program (docs/plans/OUTPLAY-PLAN-2026-10-06.md, internal). This addendum records the new threat models and compensating controls; the sections above remain valid. The full per-risk mapping (OWASP Top-10 for LLM Applications → controls, with honest coverage boundaries) lives in security-owasp-llm.md.

Pipeline threat model (pkg/pipeline, pkg/policystore)​

The pipeline is a programmable inspection/mutation conveyor over gateway traffic (pipeline.md). Its own attack surface, and the controls:

SurfaceThreatControl
Condition engines (pkg/pipeline/script: expr default, lua, starlark)a policy condition becomes a DoS/escape vectorconditions are compiled once at policy load (invalid source = load error, never a silent skip); expr is loop-free by design; lua runs sandboxed with an instruction budget; per-step timeout bounds every engine; condition errors follow on_error (default fail-closed). Policies are admin-authored and git-reconciled — not client input
Callout steps (pkg/pipeline/callout)SSRF via scanner URLs; data exfiltration through projectionssteps compile only with YABANIN_POLICY_CALLOUT_ALLOW_HOSTS set (empty = fail-closed); SSRF guard pins DNS answers and rejects reserved addresses at dial time (rebinding window closed); no redirects, no retries, per-URL circuit breaker, per-step timeout; expose projections bound what leaves (metadata never leaves); max_response_bytes caps replies
LLM judge (pkg/pipeline/llmjudge)pipeline recursion (judge calls the gateway, re-entering the pipeline, amplifying cost)hard exclusion: gateway-made judge calls carry judge=true meta and X-Yabanin-Judge: true; internal calls are exempt, while any inbound request presenting that header (any value, empty or duplicated included) is rejected with 400 judge_reentry_unsupported after auth (pipeline.md §6.2); judge prompts are versioned resources; temperature pinned to 0; omitted confidence counts as 1.0 (fail-closed)
Verdict compositiona buggy step smuggles an allow past a denyfixed strictness scale deny > mask > flag > allow; union across attached policies with most-restrictive verdict; unknown actions rank as deny; first deny short-circuits
Streaming mutationtorn writes leak pre-substitution values across chunk boundariesprefix gate (64 tokens / 500 ms) denies before release; chunk-carry holds partial matches; transform runs before the cache writer (pipeline.md §7)
Decision logsensitive content persisting via findingsevidence stored hashed; quoted user content persists only in shadow/debug retention; 90-day retention (migration 0009)

MCP credentials threat model (pkg/mcpagg, migration 0007)​

Summarized from mcp-gateway.md §6, §8 (R4) — the R4 posture as shipped: secrets exist only as enc:-encrypted envelopes (fail-closed writes), under org-isolation RLS; reads return masked rows only (secret_last4/has_secret); every CRUD/status step lands in the hash-chained audit journal; plaintext bounded (8 KiB) and never logged. A credential can never widen egress: the SSRF allow-list check on the server URL precedes credential resolution on every invoke. Per-user credential flows (submit/rotate, OAuth begin/callback) require an enabled grant — the grant is the ceiling for every flow step.

Posture change vs. the 2026-07 audit​

The July audit recorded "prompt injection: not mitigated". The program adds opt-in mitigations (guard modes, injection detectors, policy steps, tool-result post-checks) while keeping the default posture unchanged: every new surface ships off (YABANIN_POLICY_MODE, YABANIN_MCP_GATEWAY, guard mode all default off/unmounted), and egress knobs default to fail-closed. An unconfigured deployment behaves as before.