Docs / Security model

[ REFERENCE ]

Security model

The most useful security property of Oddysey is not a control it implements — it is a capability it does not have. Nothing in this system can move money. Everything below is about the smaller risks that remain.

Start from the blast radius

Before the mechanisms, the honest question: what is the worst thing an attacker gets?

IF THIS LEAKSTHEY CANTHEY CANNOT
A session cookieRead one operator's deck, arm and pause watches, approve or reject that operator's proposals.Place an order, reach another deck, or read the cookie's signing key.
A deck tokenCall the nine MCP tools on one operator's data, including reporting a false execution.Approve anything, spend anything, or reach another deck.
The whole databaseRead every operator's watches, proposals, and ledger.Replay any deck token (only SHA-256 digests are stored), forge a session (the key is a Worker secret), or decrypt broker tokens (the key is outside the database).
The cron secretTrigger the evaluation sweep early. It costs you model spend.Read or change anything.

Sessions

A session is an HMAC-SHA256-signed payload in an httpOnly cookie carrying an address, a mode, and an expiry twelve hours out. No database row backs it, so there is no session table to steal and nothing to enumerate.

  • Tampering fails signature verification and drops you at the login screen.
  • Rotating SESSION_SECRET invalidates every session immediately — that is the whole incident response for a suspected session compromise.
  • With no secret configured in development, a random per-boot secret is generated instead of falling back to a hardcoded one. Sessions reset on restart; they are never signed with a value an attacker could know.

Sign-In with Ethereum

A wallet session proves control of an address by signing an EIP-4361 message. Verification checks three things independently, and any mismatch is a 401: the nonce in the signed message matches the single-use nonce cookie, the address line matches the claimed address, and the signature verifies against that address.

The message states plainly that it triggers no transaction and costs nothing. Signing it grants a login and nothing else — Oddysey never asks for a transaction signature, because it has nothing to transact.

Deck tokens

A deck token is odsy_ plus 32 random bytes. Only its SHA-256 digest is stored; the plaintext exists exactly once, in the response that created it.

Why a plain digest and not bcrypt or Argon2.

These are high-entropy random values, not passwords. Key stretching defends against guessing a low-entropy secret — against 256 bits of randomness it buys nothing and costs latency on every request.

Why bearer tokens and not OAuth.

The MCP specification permits both. This server hands out no money and holds no broker credentials, so a revocable bearer token is proportionate to what it actually grants.

Last-used tracking.

Every successful call stamps last_used_at, so an operator can spot a token being used when the client that owns it should be idle.

Input validation and injection

  • Every request body is parsed with a schema at the boundary. Nothing downstream sees unvalidated input.
  • All database access uses bound parameters. No query is assembled by string concatenation.
  • Model output is treated as untrusted input: it is re-validated against a schema, and a proposed allocation that invents a symbol, duplicates one, or does not sum to 100% is rejected outright.
  • Secret comparison for the cron endpoint is constant-time, so timing cannot be used to recover it byte by byte.
  • Error responses are written for humans and carry no internal detail; the diagnostics stay in the server logs.

The brokerage layer that is switched off

A complete brokerage connection layer exists in the codebase: OAuth with PKCE (S256), RFC 7591 dynamic client registration, and refresh tokens encrypted with AES-256-GCM under a key held outside the database. It is tested, and it is unmounted.

What Oddysey does not defend against

Stated plainly, because a security page that claims completeness is lying:

  • A false execution report. Oddysey records that a fill was reported; it cannot verify one. Reconcile against your venue’s own statements.
  • A bad approval. If you approve a poor proposal, the system did its job — the decision was yours to make and you made it.
  • A compromised executing agent. Your agent’s access to your venue is entirely outside Oddysey’s reach, which is the point, but also means Oddysey cannot protect it.
  • Model error. A rationale can be confidently wrong. That is why a human reads it, and why the numbers beside it are computed rather than generated.